Advanced OS Customizations & Bootloaders

Interacting with Your x86 Bootloader: Implementing Keyboard & VGA Text Output

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction: Bringing Your Bootloader to Life

The journey of writing a custom operating system often begins at the most fundamental level: the bootloader. After the BIOS transfers control to your boot sector, you’re operating in 16-bit real mode, with direct access to hardware. While previous steps might have focused on simply displaying a static message, a truly interactive system requires two critical components: the ability to display dynamic information (VGA text output) and the capacity to receive user input (keyboard interaction). This expert-level guide will walk you through implementing these essential features from scratch, giving your minimal x86 bootloader the power to communicate with the user.

The Foundation: Setting Up Your Toolkit

Before diving into code, ensure you have the necessary development tools installed:

  • NASM (Netwide Assembler): For assembling our x86 assembly code into a raw binary.
  • QEMU: A powerful emulator that allows us to run and test our bootloader without needing physical hardware.

On most Linux distributions, you can install them using your package manager (e.g., sudo apt install nasm qemu-system-x86).

Illuminating the Screen: VGA Text Mode Output

VGA Memory Map and Attributes

In 16-bit real mode, the Video Graphics Array (VGA) adapter operates in text mode by default, typically an 80-column, 25-row grid. The video memory for this mode is located at physical address 0xB8000. Each character on the screen occupies two bytes in this memory:

  1. The ASCII code of the character.
  2. The attribute byte, defining its foreground and background colors.

An attribute byte is structured as follows (bits from MSB to LSB):

  • Bit 7: Blink enable (0) / Background intensity (1)
  • Bits 6-4: Background color (Red, Green, Blue)
  • Bit 3: Foreground intensity
  • Bits 2-0: Foreground color (Red, Green, Blue)

For example, a bright white character on a black background would have an attribute byte of 00001111b (0Fh).

Implementing `print_char` and `print_string`

Let’s create functions to print characters and strings directly to VGA memory. We’ll maintain a simple cursor position.

; --- Constants for VGA ---
VGA_TEXT_MODE_MEMORY equ 0xB8000
VGA_WIDTH equ 80

; --- Variables for Cursor Position ---
section .bss
current_row resb 1
current_col resb 1

; --- Function: print_char (AL = char, BH = attribute) ---
; Prints a character to the current cursor position
; and advances the cursor. Handles newlines and scrolling.
print_char:
pusha

; Calculate VGA memory offset
mov bl, [current_row]
mov cl, [current_col]
xor ch, ch ; Clear CH
mul cl ; AX = CL * 80 (col * VGA_WIDTH)
add al, bl ; AX = (row * VGA_WIDTH) + col
shl ax, 1 ; AX = AX * 2 (each char is 2 bytes)

mov di, VGA_TEXT_MODE_MEMORY
add di, ax ; DI points to the character's memory location

cmp al, 0x0A ; Is it a newline character?
je .handle_newline

; Write character and attribute
mov [di], al ; Character
mov [di+1], bh ; Attribute

; Advance cursor
inc byte [current_col]
cmp byte [current_col], VGA_WIDTH
jl .no_wrap
; Wrap to next line
mov byte [current_col], 0
inc byte [current_row]
.no_wrap:

; Check for scrolling
cmp byte [current_row], 25
jl .end_print_char

; --- Scroll up one line ---
; Copy rows 1-24 to rows 0-23
push es
mov ax, 0xB800
mov es, ax
mov si, VGA_WIDTH * 2 ; Source: second row
xor di, di ; Destination: first row
mov cx, VGA_WIDTH * 2 * 24 / 2 ; Number of words to move (24 rows)
rep movsw ; Move 24 rows
pop es

; Clear the last row
mov di, VGA_TEXT_MODE_MEMORY + (VGA_WIDTH * 2 * 24)
mov cx, VGA_WIDTH * 2 / 2
mov ax, 0x0720 ; Space char (0x20) with attribute (0x07)
rep stosw

; Adjust cursor to last row
mov byte [current_row], 24
jmp .end_print_char

.handle_newline:
mov byte [current_col], 0
inc byte [current_row]
jmp .end_print_char

.end_print_char:
popa
ret

; --- Function: print_string (SI = address of string, BH = attribute) ---
print_string:
pusha
.loop:
lodsb ; Load byte from [SI] into AL, then SI++
cmp al, 0 ; Check for null terminator
je .end_print_string
call print_char
jmp .loop
.end_print_string:
popa
ret

This code defines `print_char` to write a character at the current cursor position, handle newlines, and implement basic scrolling when the screen fills. `print_string` iteratively calls `print_char` until a null terminator is found.

Listening to Keystrokes: Keyboard Input

Keyboard Controller Basics

The keyboard controller on x86 systems primarily interacts through I/O ports. We’ll use two main ports:

  • 0x64 (Status Port): Used to check the keyboard’s status. Bit 0 (Output Buffer Full) indicates if data is available.
  • 0x60 (Data Port): Used to read scan codes from the keyboard buffer.

In real mode, we typically poll these ports, meaning we repeatedly check the status port until data is available.

Understanding Scan Codes

When you press or release a key, the keyboard generates a

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner