diff options
Diffstat (limited to 'mubios/services')
| -rw-r--r-- | mubios/services/int10.c | 184 | ||||
| -rw-r--r-- | mubios/services/vbe.c | 320 |
2 files changed, 432 insertions, 72 deletions
diff --git a/mubios/services/int10.c b/mubios/services/int10.c index 4f6aef1..17959be 100644 --- a/mubios/services/int10.c +++ b/mubios/services/int10.c @@ -19,136 +19,176 @@ <http://www.gnu.org/licenses/>. */ -#include "bios.h" #include "regs.h" +#include "vbe.h" +#include "vga.h" -#define VGA_TEXT_BASE 0x000B8000u -#define BDA_CURSOR 0x00000450u -#define BDA_CURSOR_SHAPE 0x00000460u -#define VGA_PAGE_SIZE 0x00001000u - -static volatile u16 * +static u16 int10_cursor_for_page (u8 page) { - return (volatile u16 *)(BDA_CURSOR + ((u32)page * 2u)); + return *(volatile u16 *)(MUBIOS_BDA_BASE + BDA_CURSOR_BASE + page * 2u); } static volatile u16 * int10_video_for_page (u8 page) { - return (volatile u16 *)(VGA_TEXT_BASE + ((u32)page * VGA_PAGE_SIZE)); + return (volatile u16 *)(VGA_TEXT_BASE + (u32)page * VGA_PAGE_SIZE); } static void -int10_newline (volatile u16 *cursor) +int10_teletype (u8 character) { - u16 row; + volatile u8 *bda = (volatile u8 *)MUBIOS_BDA_BASE; + u8 page = bda[BDA_VIDEO_PAGE]; + volatile u16 *video = int10_video_for_page (page); + u16 cursor = int10_cursor_for_page (page); + u16 row = cursor >> 8; + u16 column = cursor & 0xFFu; + u16 position; + u16 cell; + u16 blank; + + /* AH=0Eh uses the active page, as in SeaVGABIOS. BEL has no text effect. */ + if (character == 0x07u) + return; + if (character == 0x0Du) + column = 0u; + else if (character == 0x0Au) + ++row; + else if (character == 0x08u) + { + /* A text-mode backspace moves left without erasing or crossing a row. */ + if (column != 0u) + --column; + } + else + { + position = (u16)(row * VGA_COLUMNS + column); + /* Mode 03h has 2000 visible cells per page. */ + if (position >= VGA_TEXT_CELLS) + return; + video[position] = (u16)((video[position] & 0xFF00u) | character); + ++column; + if (column >= VGA_COLUMNS) + { + column = 0u; + ++row; + } + } - row = (u16)(*cursor / 80u); - *cursor = (u16)((row + 1u) * 80u); + /* Passing the last row scrolls the active page, retaining text attributes. + */ + if (row >= VGA_ROWS) + { + blank = (u16)((video[(VGA_ROWS - 1u) * VGA_COLUMNS] & 0xFF00u) | 0x20u); + for (cell = 0; cell < (VGA_ROWS - 1u) * VGA_COLUMNS; ++cell) + video[cell] = video[cell + VGA_COLUMNS]; + for (; cell < VGA_TEXT_CELLS; ++cell) + video[cell] = blank; + row = VGA_ROWS - 1u; + } + vga_set_cursor_position (page, (u16)((row << 8) | column)); } void int10_service (struct bios_regs *regs) { - volatile u16 *cursor; + volatile u8 *bda = (volatile u8 *)MUBIOS_BDA_BASE; volatile u16 *video; - u8 function; - u8 page; - u8 character; - u8 attribute; + u8 function = (u8)(regs->eax >> 8); + u8 page = (u8)(regs->ebx >> 8); + u16 cursor; u16 position; u16 count; + u32 font_address; - function = (u8)(regs->eax >> 8); - page = (u8)(regs->ebx >> 8); + if (function == 0x4Fu) + { + vbe_service (regs); + return; + } if (function == 0x00u) { - if ((u8)regs->eax == 0x03u) - vga_text_mode_init (); + /* Bit 7 preserves video memory; the remaining bits select mode 03h. */ + if (((u8)regs->eax & 0x7Fu) == 0x03u) + vga_text_mode_set ((u8)regs->eax & 0x80u); return; } - if (function == 0x0Fu) + if (function == 0x01u) { - regs->eax = (regs->eax & 0xFFFF0000u) | 0x5003u; - regs->ebx &= 0xFFFF00FFu; + vga_set_cursor_shape ((u16)regs->ecx); return; } - if (function == 0x02u) + if (function == 0x05u) { - cursor = int10_cursor_for_page (page); - position = (u16)(((u8)(regs->edx >> 8) * 80u) + (u8)regs->edx); - *cursor = position; + vga_set_active_page ((u8)regs->eax); return; } - if (function == 0x03u) + if (function == 0x0Fu) { - cursor = int10_cursor_for_page (page); - position = *cursor; - regs->edx = (regs->edx & 0xFFFF0000u) | ((u32)(position / 80u) << 8) - | (u32)(position % 80u); - regs->ecx = (regs->ecx & 0xFFFF0000u) | 0x0607u; + regs->eax = (regs->eax & 0xFFFF0000u) + | ((u32)bda[BDA_VIDEO_COLUMNS] << 8) | bda[BDA_VIDEO_MODE] + | (bda[BDA_VIDEO_CTL] & 0x80u); + regs->ebx = (regs->ebx & 0xFFFF00FFu) | ((u32)bda[BDA_VIDEO_PAGE] << 8); return; } - if (function != 0x09u && function != 0x0Eu) - return; - - cursor = int10_cursor_for_page (page); - video = int10_video_for_page (page); - - character = (u8)regs->eax; - attribute = (u8)regs->ebx; - position = *cursor; - - if (function == 0x09u) + /* AH=11h/AL=30h reports the current font (BH=1) or ROM 8x16 font (BH=6). */ + if (function == 0x11u && (u8)regs->eax == 0x30u) { - count = (u16)regs->ecx; - - while (count != 0u) + if (page == 0x01u || page == 0x06u) { - if (position >= 2000u) - position = 0; - - video[position] = (u16)character | ((u16)attribute << 8); - position++; - count--; + font_address = (u32)vga_font_8x16; + regs->es = (u16)(font_address >> 4); + regs->ebp = (regs->ebp & 0xFFFF0000u) | (font_address & 0x0Fu); } - + regs->ecx = (regs->ecx & 0xFFFF0000u) + | *(volatile u16 *)(bda + BDA_VIDEO_HEIGHT); + regs->edx = (regs->edx & 0xFFFFFF00u) | bda[BDA_VIDEO_ROWS]; return; } - if (character == 0x0Du) + if (function == 0x0Eu) { - *cursor = (u16)((position / 80u) * 80u); + int10_teletype ((u8)regs->eax); return; } - if (character == 0x0Au) + /* The mode 03h INT 10h interface provides page numbers 0 through 7. */ + if (page >= VGA_PAGE_COUNT) + return; + + if (function == 0x02u) { - int10_newline (cursor); + vga_set_cursor_position (page, (u16)regs->edx); return; } - if (character == 0x08u) + if (function == 0x03u) { - if (position != 0u) - { - position--; - video[position] = (u16)(0x20u | ((u16)attribute << 8)); - *cursor = position; - } - + regs->edx = (regs->edx & 0xFFFF0000u) | int10_cursor_for_page (page); + regs->ecx = (regs->ecx & 0xFFFF0000u) + | *(volatile u16 *)(bda + BDA_CURSOR_SHAPE); return; } - if (position >= 2000u) - position = 0; + if (function != 0x09u) + return; - video[position] = (u16)character | ((u16)attribute << 8); - *cursor = (u16)(position + 1u); + cursor = int10_cursor_for_page (page); + position = (u16)((cursor >> 8) * VGA_COLUMNS + (cursor & 0xFFu)); + video = int10_video_for_page (page); + count = (u16)regs->ecx; + /* Repeated character/attribute output stays within the visible page and + does not change either the BDA or hardware cursor. */ + while (count != 0u && position < VGA_TEXT_CELLS) + { + video[position] = (u16)((u8)regs->eax | ((u16)(u8)regs->ebx << 8)); + ++position; + --count; + } } 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; +} |
