lets start with some history ...

The original VGA

It was introduced by IBM in 1987. It had a bunch of new features, and also included old ones which where already present in the predecessor devices CGA and EGA, including:

  • text modes (80x25, also 80x50 using a smaller font)
  • 16 color mode (640x480, 4 bit per color, one plane per bit)
  • 256 color mode (320x240, 8 bit per color)
  • various tweaks you can do, like enabling double scan or split screen.

The VGA has 256k of video memory and it is accessed using a memory window at 0xa0000. It is not possible to access all video memory at the same time, you have to set bank registers to map the piece of memory you want access into the window.

All vga devices emulated by qemu support this.

Super VGA

In the early 90ies various enhanced VGA cards, typically named "Super VGA" (abbreviated SVGA) became available from various vendors. The cirrus vga emulated by qemu is a typical SVGA card which was quite popular back then. They add various new features:

  • more video memory, which in turn allows for:
  • higher resolutions for 256 color modes.
  • more colors (64k, 16 bit per pixel).
  • even more colors (16M, 24 or 32 bit per pixel).
  • linear framebuffer, so you can access all video memory at the same time, without having to bank-switch the video memory into the rather small window at 0xa0000.
  • 2D acceleration (cirrus blitter for example).

SVGA in qemu

All SVGA devices in qemu (except cirrus) have support for the bochs display interface. That interface was implemented by the bochs emulator first (this is where the name comes from). It was implemented in qemu too. For the qemu standard vga it is the primary interface. qxl-vga, virtio-vga and vmsvga support the bochs dispi interface when they are in vga compatibility mode, which is typically the case at boot, before the guest os loads the native display driver.

The bochs display interface is a paravirtual interface, with just the bare essentials to set video modes on a virtual display device. There are no registers for clock rate and other timing stuff for example.

Traditionally the bochs display interface uses I/O ports 0x1ce (bochs register index) and 0x1cf (bochs register data), As both registers are 16bit the data registers is unaligned, which does not work on non-x86 archs, so 0x1d0 is supported as data port too.

Graphics usage by modern guests

Lets have a look at what modern guests are doing in the graphics field:

  • Everything but 32 bit true color modes is pretty much unused. The only exception is 16 bit true color modes which are still used sometimes in resource-constrained environments (raspberry pi for example).
  • 2D acceleration is dead. It's either software rendering into a dumb framebuffer, or using the 3D engine for 2D rendering.
  • text mode is used only with BIOS firmware, and even then only at boot (bootloader, vgacon until the kms driver loads). UEFI goes straight to graphics mode.
  • Banked video memory access is dead. Text mode still uses the 0xa0000 window, but the text buffer is small enough that there is no bank switching needed.

So, we have a lot of rather complex code to emulate features not used at all by modern guests. There have been security bugs in the past in that complex but largely unused code ...

So, can we simplify things?

Turns out: yes, we can. First step already happened in qemu 1.3. The qemu stdvga got a MMIO bar. The MMIO bar can be used as alternative way to access the vga registers and also the bochs dispi interface registers.

OVMF (UEFI implementation for qemu) uses the MMIO bar. The bochs-drm.ko linux kms driver uses the MMIO bar too. In fact, both use the bochs display interface registers only, except for setting the unblank bit so the screen will not stay black.

So, the guest code already ignores the vga emulation. Cool. We can build on that.

Introducing -device bochs-display

New display device. Merged in qemu 3.0. Featues:

  • No VGA compatibility. PCI class is display/other instead of display/vga.
  • It has a stdvga-style MMIO bar. The vga registers are not available of course. Otherwise the register interface is identical to the stdvga though.
  • Implemented from scratch, no code sharing with vga. Code size is an order of magnitude smaller when compared to vga.
  • No I/O ports needed. You can plug it into an PCIe slot.
  • OVMF supports it.
  • bochs-drm.ko supports it too.

So, all set for UEFI guests. You can switch from stdvga to bochs-display, and everything continues to work fine.

But what about BIOS and text mode?

Accessing the vga hardware directly for text mode is rare these days. Typically seabios and linux boot loaders call vgabios functions to render text on the display. So, we can hook in there and support text rendering without the hardware actually having text mode support. A very simliar approach is taken by sgabios, to redirect vga text output to the serial line.

Luckily we are not the first ones facing that problem. coreboot can initialize the graphics hardware and setup a framebuffer with the native display resolution. Having to switch back to text mode when running seabios as coreboot payload is not exactly nice. So, there is a vgabios variant for coreboot which renders text to a framebuffer.

So, take that, tweak the initialization code to program the bochs dispi interface instead of looking for a framebuffer setup by coreboot, and we are ready to go. Seabios boot messages show up on the bochs-display framebuffer. Yay!

This will work out-of-the-box in qemu 3.1. The vgabios is already present in qemu 3.0, but due to a bug it is not installed by default, it must be copyed over manually to get things going.

There are some drawbacks, which may or may not be a problem depending on your use case:

  • linux vgacon does not work due to direct vga hardware access. So you have to use vesafb or just live with not having early boot messages. Once bochs-drm.ko loads fbcon will be functional.
  • The vgabios uses a fixed 1024x768 resolution and does not support switching modes after initialization. Reason is that the initialization code runs in big real mode, so accessing the mmio bar is easy then. That is not the case for vgabios function calls though. Resolutions smaller than 1024x768 are allowed by vgabios and will simply use the upper left corner of the display.

That's it. Enjoy the new legacy-free display device.