Bochs VBE Extensions

From OSDev.wiki
Jump to navigation Jump to search

The Bochs VGA BIOS supports, to an extent, the VBE specification. Since Bochs only emulates a VGA card down to the hardware level (and a Cirrus graphics card if you enable it, but that is not tied in with the Bochs VBE extensions), it emulates very simple graphics hardware that the VBE BIOS can drive. The advantage of this is that if you are running your OS in Bochs (or QEMU, which uses the Bochs VGA BIOS, or even VirtualBox), you can use this emulated hardware to directly set video modes without using VBE (which would require real mode or v86).

Overview

The Bochs emulated graphics hardware (henceforth called BGA for Bochs Graphics Adaptor) is accessed via two 16-bit IO-ports. The first one is an index port, the second one a data port (comparable to how the VGA handles its sets of registers). Via these ports it is possible to enable or disable the VBE extensions, change the screen resolution and bit depth, and manage a larger virtual screen. There are six versions of the BGA (0xB0C0 through 0xB0C5), but if you use the latest version of Bochs you only need to concern yourself with the latest one (0xB0C5). QEMU (with the -std-vga command line argument) also uses the latest version.

The Bochs sources define in vga.h, located in the subdirectory iodev/, a number of defines that are useful for programming the BGA. The names of these defines all start with VBE_DISPI. They are used in the sections below, with their numerical value between parentheses.

BGA versions

As Bochs has evolved, so has the BGA. Six versions of the BGA have existed, of which 0xB0C5 is the current version (as of 2009, Bochs version 2.4). The main features of each version:

  • 0xB0C0 - setting X and Y resolution and bit depth (8 BPP only), banked mode
  • 0xB0C1 - virtual width and height, X and Y offset
  • 0xB0C2 - 15, 16, 24 and 32 BPP modes, support for linear frame buffer, support for retaining memory contents on mode switching
  • 0xB0C3 - support for getting capabilities, support for using 8 bit DAC
  • 0xB0C4 - VRAM increased to 8 MB
  • 0xB0C5 - VRAM increased to 16 MB? [TODO: verify and check for other changes]

[TODO: if and when 4BPP modes are supported] [TODO: if and when VGA attribute controller (AC) is supported]

Programming the BGA

Writing registers

To write an index/data pair to one of the BGA registers, first write its index value to the 16-bit IO-port VBE_DISPI_IOPORT_INDEX (0x01CE), followed by writing the data value to the 16-bit IO-port VBE_DISPI_IOPORT_DATA (0x01CF). The BGA supports 10 different index values (0 through 9):

  • VBE_DISPI_INDEX_ID (0)
  • VBE_DISPI_INDEX_XRES (1)
  • VBE_DISPI_INDEX_YRES (2)
  • VBE_DISPI_INDEX_BPP (3)
  • VBE_DISPI_INDEX_ENABLE (4)
  • VBE_DISPI_INDEX_BANK (5)
  • VBE_DISPI_INDEX_VIRT_WIDTH (6)
  • VBE_DISPI_INDEX_VIRT_HEIGHT (7)
  • VBE_DISPI_INDEX_X_OFFSET (8)
  • VBE_DISPI_INDEX_Y_OFFSET (9)

In order to change the contents of registers 1-3 (VBE_DISPI_INDEX_XRES, VBE_DISPI_INDEX_YRES, VBE_DISPI_INDEX_BPP) the VBE extensions must be disabled first. To do so, write the value VBE_DISPI_DISABLED (0x00) to VBE_DISPI_INDEX_ENABLE (4). The changes are not visible until the VBE extensions are enabled again. To do so, write the value VBE_DISPI_ENABLED (0x01) to the same register (see also note below on enabling the LFB).

Reading registers

To read a register, first write the index value to VBE_DISPI_IOPORT_INDEX (0x01CE), then read the 16-bit value from VBE_DISPI_IOPORT_DATA (0x01CF). The value returned depends on the specific register that is queried. [TODO: check and describe, especially _ID and _ENABLE, others seem trivial]

Checking availability

To check whether the BGA is available, read the value from VBE_DISPI_INDEX_ID (0). If it equals VBE_DISPI_ID5 (0xB0C5) the latest version of the BGA is present. If it returns a value of 0xB0C0 through 0xB0C3, you have an old version of Bochs and/or the Bochs VGA BIOS.

If for some reason you want Bochs to emulate an older version of the BGA, you can write the desired version to VBE_DISPI_INDEX_ID (0). If succesful, reading the register again will return the value just set. This is used by the Bochs VGA BIOS to ensure it is run with the right version of Bochs. If done from an application (or your OS), this will break compatibility with the Bochs VBE BIOS, which expects the latest version.

Setting display resolution and bit depth

Most likely, setting the display resolution and bit depth is all you need. To do so, disable the VBE extensions (see above), write the X resolution, Y resolution and BPP to their respective registers (VBE_DISPI_INDEX_XRES (1), VBE_DISPI_INDEX_YRES (2) and VBE_DISPI_INDEX_BPP (3)) and enable the VBE extensions. Since the BGA is not real hardware, X and Y resolutions can be set at will up to a maximum horizontal resolution (depending on your version of Bochs) of either 1024 or 1600 (VBE_DISPI_MAX_XRES) and a maximum vertical resolution of either 768 or 1200 (VBE_DISPI_MAX_YRES). It seems that both Bochs and QEMU can use about any resolutions with a few restrictions. In QEMU the X resolution must be divisible by 8. [TODO: Describe how Bochs handle different X resolutions. It can crash with segmentation fault on various X resolutions, so it is difficult to test]. The Y resolution can be any number from 1 to maximum in both Bochs and QEMU. The bit depth needs to be one of the following:

  • VBE_DISPI_BPP_4 (0x04)
  • VBE_DISPI_BPP_8 (0x08)
  • VBE_DISPI_BPP_15 (0x0F)
  • VBE_DISPI_BPP_16 (0x10)
  • VBE_DISPI_BPP_24 (0x18)
  • VBE_DISPI_BPP_32 (0x20)

If you try to set an invalid resolution (greater than the maximum or an irregular X resolution) or bit depth, usually nothing happens. The current video mode is preserved. In some cases Bochs will crash with a segmentation fault if you set an invalid X resolution, so only use standard ones. [TODO: Check latest CVS version of Bochs and search their bug tracker]. You can always read the current resolution and bit depth from their respective registers. When you have set a resolution it is a good idea to read it back and check whether your new resolution is really set. You should do that before you enable the BGA to minimize the chance of your emulator crashing. [TODO: Check what happens when the first resolution you set is invalid.]

Memory layout of video modes

In all modes the first byte represents the top left of the screen. A formula to calculate the video memory offset from a pixel coordinate is: offset = (Y * X-resolution + X) * <some-factor>. The factor varies using the different bit depths. The 4 and 8 BPP modes are palette modes. Read more about programming the attribute controller and DAC palettes in VGA documents. The other modes are using the colour value directly.

In 4 BPP you have 16 colours. The pixel colour is used an index to the attribute controller, which again points to the DAC, which in turn gives an 18 bit colour (6 bits each for red, green and blue) that is shown. The memory is layout like this: The first byte is the colour bit 0 of pixels 0-7. The second byte is the colour bit 1 of pixels 0-7, and so forth. You can access 8 pixels at a time using longwords (32 bits).

In 8 BPP you have 256 colours. The pixel colour is sent to the DAC, which gives an 18 bit colour that is shown. The memory layout is very simple. Each pixel is exactly one byte.

In 15 BPP each pixel is easiest accessed as words (16 bits). There are 5 bits each colour component and the last bit is ignored.

In 16 BPP each pixel is easiest accessed as words (16 bits). There are 5 bits for the red and blue components and 6 bits for green. This makes sense, because the human eye is more sensitive to green colours.

In 24 BPP each pixel is 3 bytes. There is one byte for each component. The colour components is blue first, then green, then red.

In 32 BPP each pixel is 4 bytes and easiest accessed as longwords (32 bits). The fourth byte is ignored. The colour components is layout like in 24 BPP. Accessing pixels as longwords the colour should be defined as 0x00RRGGBB.

Using banked mode

When using banked mode, the BGA uses a 64Kb bank size (VBE_DISPI_BANK_SIZE_KB) starting at address 0xA0000 (VBE_DISPI_BANK_ADDRESS). Banked mode is the default mode, so when enabling the VBE extensions without explicitly telling the BGA to use a linear frame buffer, the BGA enables banked mode. To set the bank to use, write the bank number to the bank register (VBE_DISPI_INDEX_BANK (5)).

Using a linear frame buffer (LFB)

When using a linear framebuffer, the BGA exposes all of the video memory in a single linearly addressable section of memory. The address of the framebuffer is not fixed, and must be read from the first PCI base address register (BAR 0 of device 0x1234:0x1111). To enable the linear framebuffer, use the VBE_DISPI_LFB_ENABLED flag (0x40) when enabling the BGA in conjunction with the VBE_DISPI_ENABLED flag.

Unlike Bochs, QEMU does not necessarily pay attention to the VBE_DISPI_LFB_ENABLED flag with respect to banked memory access, allowing both the linear framebuffer and banked memory to be used at all times. Bochs will not honour requests to change the memory bank when the linear framebuffer is enabled, and it will similarly ignore any writes made to the memory bank.

Note: In older versions of Bochs and QEMU, the framebuffer was fixed at 0xE0000000, and modern versions will use that address when emulating ISA-only systems. It is highly inadvisable to make assumptions about the address of the linear framebuffer. It should always be read from the BGA's PCI BAR0.

Clearing display memory

When enabling the VBE extensions, Bochs clears the video memory (i.e. sets all bytes to 0). To prevent this from happening, use the VBE_DISPI_NOCLEARMEM flag (0x80) when enabling the VBE extensions (so write a value of VBE_DISPI_ENABLED | VBE_DISPI_NOCLEARMEM (0x81) for banked mode and VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED | VBE_DISPI_NOCLEARMEM (0xC1) for LFB).

Finding out capabilities

Based on source code examination for Bochs (iodev/vga.cc) setting VBE_DISPI_GETCAPS in VBE_DISPI_INDEX_ENABLE makes the VBE_DISPI_INDEX_ (XRES / YRES / BPP) fields return their maximum values when read instead of the current values.

8-bit DAC

The default palette DAC is a 3x6 bit dac; it returns a value between 0 and 63 for each color. Setting the VBE_DISPI_8BIT_DAC bit in VBE_DISPI_INDEX_ENABLE changes this to a 3x8 bit dac and converts the palette to the appropriate values. Resetting the bit moves them back again.

Virtual display

The Bochs adapter allows a virtual display that is larger than the physical one. The video memory is normally your screen width wide and at least as high as your vertical screen size, the height. The actual video memory is larger and the remainder is seen as a vertical extension.

Having this as a horizontal extension can be very useful. You can use this to make horizontal and vertical scrolling effects very cheap. You can also make a virtual display, double buffering and lots of other ideas.

The mechanism is used that the memory is a virtual display starting at location (0,0) with a size specified by the virtual width. The height is implicitly as large as it can be within the video memory. Suppose you set the virtual width to be 1024 in a 32-bpp mode (4 bytes per pixel) on a 16-meg Bochs card. That makes the vertical height 4096.

You can then specify where the video card should start reading the memory using the X and Y offset.

The variables taking care of this:

  • VBE_DISPI_INDEX_VIRT_WIDTH is the virtual width.
  • VBE_DISPI_INDEX_VIRT_HEIGHT is the virtual height, currently not implemented. Reasoning is above.
  • VBE_DISPI_INDEX_X_OFFSET is the X offset for displaying.
  • VBE_DISPI_INDEX_Y_OFFSET is the Y offset for displaying.

Example code

void BgaWriteRegister(unsigned short IndexValue, unsigned short DataValue)
{
    outpw(VBE_DISPI_IOPORT_INDEX, IndexValue);
    outpw(VBE_DISPI_IOPORT_DATA, DataValue);
}

unsigned short BgaReadRegister(unsigned short IndexValue)
{
    outpw(VBE_DISPI_IOPORT_INDEX, IndexValue);
    return inpw(VBE_DISPI_IOPORT_DATA);
}

int BgaIsAvailable(void)
{
    return (BgaReadRegister(VBE_DISPI_INDEX_ID) == VBE_DISPI_ID4);
}

void BgaSetVideoMode(unsigned int Width, unsigned int Height, unsigned int BitDepth, int UseLinearFrameBuffer, int ClearVideoMemory)
{
    BgaWriteRegister(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED);
    BgaWriteRegister(VBE_DISPI_INDEX_XRES, Width);
    BgaWriteRegister(VBE_DISPI_INDEX_YRES, Height);
    BgaWriteRegister(VBE_DISPI_INDEX_BPP, BitDepth);
    BgaWriteRegister(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_ENABLED |
        (UseLinearFrameBuffer ? VBE_DISPI_LFB_ENABLED : 0) |
        (ClearVideoMemory ? 0 : VBE_DISPI_NOCLEARMEM));
}

void BgaSetBank(unsigned short BankNumber)
{
    BgaWriteRegister(VBE_DISPI_INDEX_BANK, BankNumber);
}

External Links

Specification