Graphics. Video! How do you get even basic graphics running on a PC? One way is to use the expansion ROM on the graphics card. Loko Scheme 0.3.4 is out now and comes with a rudimentary VBE driver that uses this method.
Video BIOS & VESA SuperVGA BIOS
The original IBM PC line of computers had support for switchable graphics cards. It originally came with CGA and MCGA, but there were quickly third-party graphics cards and more advanced standards were developed. Something that contributed to making this market possible was the fact that the cards came with their own built-in drivers.
The IBM PC was based around BIOS and DOS. The BIOS provided an abstraction around the hardware based on a variety of system calls via the interrupt vector mechanism in the CPU. One of the tasks of the BIOS was to find option ROMs and call their initialization vectors. The graphics card had one of those option ROMs and it hooked interrupt number 10h to provide graphics and basic teletype services.
Whichever card was in the computer at the time, that was the ROM that was used. This allowed hardware developers a lot of freedom in how to design their devices. The option ROM would hook interrupt 10h, so DOS would simply call the right service as always.
Graphics hardware also had some of the very basic registers in common, so a VGA graphics card would provide the VGA registers. But that only worked up to a point. The things you can do via the VGA registers is very limited, even for the hardware available at the time.
At the ultimate peak of the Video BIOS, VESA had developed standard BIOS extensions for setting higher resolution modes. These provided true color and a linear frame buffer. These are called either the VESA SuperVGA BIOS or the VESA BIOS Extension. Loko now has a driver for this extension.
16-bit code
Loko Scheme runs in 64-bit mode, which makes it harder to run 16-bit code. So the VBE driver in Loko uses Zabavno, my real-mode 386 emulator, which I have written about previously.
The VBE driver makes a new Zabavno machine and copies the ROM to where it expects to be:
(define (video-bios-init dev img)
(let ((M (make-machine)))
…
(machine-CS-set! M 0)
(machine-IP-set! M #x7c00)
…
(copy-to-memory M #xc0000 (pcirom-image img))
(copy-to-memory M #x7c00 #vu8(#x9a #x03 #x00 #x00 #xc0
#xf1))
(enable-interrupt-hooks M)
(machine-hook-interrupt! M 1 (lambda (M int chain) 'stop))
(machine-run M)
M))
The code at 7c00h in the above is a far call to the ROM’s initialization vector, followed by an instruction that will call interrupt 1 and stop the emulator.
The ROM also needs access to some device memory and I/O ports. This is done through Zabavno’s memory and I/O hooks. When I was developing this driver in Loko’s repl through QEMU, one of the first things that SeaVGABIOS tried to do was to write to QEMU’s debug port and access the Bochs graphics hardware. That was easy to handle:
(define vga-raw-i/o-hook
(case-lambda
((port size)
(case size
((8) (get-i/o-u8 port))
((16) (get-i/o-u16 port))
(else (get-i/o-u32 port))))
((port size value)
(case size
((8) (put-i/o-u8 port value))
((16) (put-i/o-u16 port value))
(else (put-i/o-u32 port value))))))
(define (video-bios-init dev img)
…
;; VBE DISPI
(machine-hook-i/o-port! M #x1ce 16 vga-raw-i/o-hook)
(machine-hook-i/o-port! M #x1cf 16 vga-raw-i/o-hook)
;; QEMU debug console
(machine-hook-i/o-port! M #x402 16 vga-raw-i/o-hook)
…)
The get-i/o-u*
and put-i/o-u*
procedures in Loko provide access to
the real I/O bus, so the above just relays these accesses on behalf of
the emulated machine. Elsewhere, the ROM is also given access to the
legacy VGA memory, the legacy VGA registers and its own I/O BARs
from PCI.
The machine-run
procedure starts the ROM, which hooks the emulated
machine’s interrupt 10h. The machine is returned to the caller and is
ready to use for VBE calls.
The mess
The VBE driver in Loko currently gets the BIOS image by taking it from the expansion ROM. Unfortunately things are not always that simple.
The graphics card that the driver finds might not be the primary graphics card. But the legacy VGA registers are routed to the primary graphics card. The Video BIOS has access to the graphics card’s I/O BARs, but often needs access to the legacy VGA registers as well. This means that it unfortunately can’t set graphics modes on secondary cards.
Fixing this routing requires messing with the system’s PCI bridges. Some kernels, such as Linux, redo the whole work the BIOS did when it tried to assign addresses to the PCI devices and the bridges.
One of my test machines has a buggy BIOS that configures the PCI bridges and expansion ROM address wrong. There is a copy of the primary Video BIOS in memory at address C0000h. This ROM also has an entry point called the Protected Mode Entry Point, which is callable from 16-bit and 32-bit protected mode. This might be a more reliable way to get the ROM.
Basically, it’s messy.
Where next
Loko needs some video drivers and the VBE driver is a good first step. There should probably be a fallback driver that uses the VGA registers as well. More modern systems can get graphics working via EFI. There is also AtomBIOS, which is used on AMD graphics cards.
The most promising next step is however the Multiboot2 specification. It has framebuffer support, and perhaps it even works in GRUB 2. It wouldn’t provide switchable graphics modes, but most people have LCDs and just want the native resolution.