VGA Device Driver

Why This Matters

Every character you see on a terminal got there because an OS kernel wrote bytes to hardware at a specific memory address. Device drivers are the thin layer that translates high-level OS actions ("print a character") into the exact register writes and memory stores the hardware expects. Understanding how xv6 drives CGA and VGA gives you a concrete, complete example of the interrupt → handler → driver pipeline — and the ioctl() mechanism that lets user programs control hardware behavior without bypassing the kernel.


The Interrupt-to-Driver Pipeline (Recap)

When a hardware event occurs (a key press, a serial byte arriving), the CPU saves state, looks up the interrupt vector, and jumps to a handler. In xv6, all interrupt entry points converge at alltraps in trapasm.S, which builds a trap frame on the stack and calls trap() in trap.c. Inside trap(), a switch statement dispatches to the right device handler:

case T_IRQ0 + IRQ_KBD:
    kbdintr();
    lapiceoi();
    break;

The keyboard and COM1 handlers both eventually call consoleintr(), which echoes characters to the screen by calling consputc(). That is where the display driver starts.


CGA: The Display Hardware in xv6

What CGA Is

The Color Graphics Adapter is an early IBM PC video standard that xv6 emulates. It supports an 80×25 character text mode (80 columns, 25 rows = 2,000 character cells).

Memory-Mapped Display

CGA is controlled by writing directly to physical memory starting at 0xB8000. The kernel maps this into its virtual address space:

// console.c
static ushort *crt = (ushort*)P2V(0xb8000);

No I/O port instructions are needed — a write to this memory region goes straight to the display.

Character Cell Format

Each cell is 2 bytes (a ushort):

Bit:  15        8  7        0
      +----------+----------+
      | attr byte | char byte|
      +----------+----------+
Field Bits Meaning
char byte 7–0 ASCII code of the character
attr byte 10–8 foreground color (low 4 bits used)
attr byte 15–11 background color (high 3 bits)

xv6 defaults to gray text on black:

crt[pos++] = (c & 0xff) | 0x0700;  // 0x07 = gray on black

The 0x0700 attribute places 0x07 in the high byte: foreground = light gray (7), background = black (0).

cgaputc() in Context

consputc() calls cgaputc() to place a character at the current cursor position, advance the cursor, and scroll the screen when the last row is reached. Because this happens at interrupt time (inside consoleintr()), no sleeping or blocking is allowed.


ioctl(): Device Control Beyond Read/Write

The Problem

read() and write() move data. But hardware often needs control commands — set baud rate, change color, switch video mode. That is what ioctl() is for.

The "Everything Is a File" Model

In Unix (and xv6), devices appear as files. A user program opens /dev/console, gets a file descriptor, and can call ioctl() on it:

ioctl(1, 0, 3);               // fd=1 (stdout), param=0, value=3
ioctl(2, 0, (color%0xe)+1);  // pass a color value to the console

The Call Chain

sys_ioctl()         [syscall dispatch, kernel/sysfile.c]
  └─ fileioctl()    [looks up device from file descriptor, file.c]
       └─ devsw[major].ioctl(f, param, value)
            └─ consoleioctl()   [or displayioctl() for VGA]

The devsw Table

devsw is an array of function pointers indexed by major device number. Each entry holds the three operations a device can support:

// file.h
struct devsw {
    int (*read) (struct file*, char*, int);
    int (*write)(struct file*, char*, int);
    int (*ioctl)(struct file*, int, int);
};

// file.c
struct devsw devsw[NDEV];

At boot, consoleinit() registers the console driver:

devsw[CONSOLE].write = consolewrite;
devsw[CONSOLE].read  = consoleread;
devsw[CONSOLE].ioctl = consoleioctl;

fileioctl() dispatches to the correct slot using the file's major number (f->ip->major), so the kernel never hard-codes which driver handles which device.


VGA: A More Capable Display Standard

Overview

Video Graphics Array was introduced with IBM's PS/2 in 1987. It is backward compatible with CGA and adds higher-resolution and color modes. Its framebuffer starts at physical address 0xA0000 (different from CGA's 0xB8000).

Key Modes

Mode Name Description
Mode 3 Text mode Compatible with CGA; 80×25 text
Mode 13h Linear 256-color 320×200 pixels, 1 byte per pixel

xv6 exposes these via helper functions: vgaMode3() and vgaMode13().

Mode 13h Pixel Layout

In Mode 13h each pixel is one byte — its value is an index into a palette of 256 colors. To change which color an index maps to, you call vgaSetPalette(). This is how a "fade" effect works: gradually shift the palette entries toward black without touching framebuffer data.


HW4: Building Your Own Device Drivers

Part 1 — Console Color via ioctl()

prettyprint.c uses ioctl() to tell the console to print in a chosen color. Your task is to implement consoleioctl() so that when a color parameter arrives, it stores it and subsequent calls to cgaputc() use it instead of the hardcoded 0x0700.

The full path user code takes:

prettyprint → write(fd, ...) → ioctl(fd, 0, color)
                                   ↓
sys_ioctl → fileioctl → consoleioctl  (your code)

Part 2 — VGA Display Driver

vga.c is provided (the low-level VGA register programming). You implement display.c, modeled on console.c, and register it in devsw. The ioctl() in this driver switches between Mode 3 and Mode 13h. For the optional fade effect, manipulate the palette via vgaSetPalette() using the third ioctl() parameter.


Key Takeaways

Practice

  1. At what physical memory address does the CGA framebuffer begin in xv6?
  2. How many bytes does each character cell occupy in CGA text mode, and what do the two bytes represent?
  3. What is the purpose of the devsw[] array in xv6?
  4. A user program calls ioctl(fd, 0, 5) on an open console file descriptor. Which sequence correctly describes the path this call takes through the xv6 kernel?
  5. VGA Mode 13h differs from Mode 3 (text mode) primarily because:
  6. When consoleinit() runs at boot, which statement best explains why registering function pointers in devsw[CONSOLE] is preferable to hard-coding console calls inside fileioctl()?
  7. Explain the CGA character cell format. If you want to display the letter 'A' (ASCII 65, 0x41) with bright white text (foreground color 0xF) on a blue background (background color 0x1), what 16-bit value would you write to the CGA framebuffer?
  8. A classmate writes a device driver and registers it in devsw[7]. Their ioctl function pointer is NULL (they haven't implemented it yet). What happens when a user program calls ioctl() on a file whose major number is 7? Walk through the relevant xv6 code.
  9. In HW4 part 2, you are implementing a 'fade to black' effect using VGA Mode 13h. Describe, at the level of kernel driver code, how you would implement this effect. What data structure or hardware mechanism do you manipulate, and why doesn't this require rewriting every pixel in the framebuffer?