PHASE I · IGNITION / MODULE 01 · FREE

Boot to Pixels

You'll go from an empty file to your own code drawing your own logo on your real screen — no BIOS, no bootloader you didn't write, nothing underneath you but the firmware. This is the whole toolchain and the first real win.

Module 01 — walkthrough
video walkthroughcoming soon

Here's the promise, stated plainly: by the end of this module you'll hold a USB stick you made, plug it into a laptop, and watch your code take over the screen. Not in an emulator — on the metal. Everything in this module is free. Let's build it.

Lesson 1.1

1.1 Why UEFI, and why everything you've read is about BIOS

no code — the one mental model that makes the rest easy
1.1 — the boot chain, explained
~12 mincoming soon

Almost every OS-dev tutorial you've found starts in 16-bit real mode, fighting the A20 line and writing a bootsector in assembly. That's not because it's the right way to start in 2026 — it's because those tutorials were written for legacy BIOS, and everyone copied everyone.

Modern machines don't boot like that. Here's what actually happens when you press power:

So the "hard 16-bit bootsector" everyone starts with is a history lesson, not a prerequisite. We skip it entirely. Your OS is the program the firmware loads. That's the whole idea, and it's why this path is both more modern and — genuinely — easier to get running.

The one model to keep

firmware → your BOOTX64.EFI → your OS. There is no GRUB, no bootsector, no real mode. Everything in this module is about making that one arrow work, then drawing to the screen the firmware handed you.

Lesson 1.2

1.2 The smallest program the firmware will run

your "it's alive" moment — one line of text from your own binary
1.2 — first boot, live-coded
~18 mincoming soon

A UEFI application is a PE/COFF binary with an entry point you name yourself. No main, no C runtime, no libc. The firmware calls your efi_main and hands you two things: a handle representing your own image, and a pointer to the system table — the root of every service UEFI offers.

Here's the entire first program. It disarms the watchdog (critical — see the warning), prints one line, and parks:

01_bootstrap.ccopy
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
    SysTable  = SystemTable;   // stash it before anything can fail
    SelfImage = ImageHandle;   // this is "us" as far as firmware is concerned

    // KILL THE WATCHDOG. FIRST. BEFORE ANYTHING ELSE.
    // UEFI arms a 5-minute reset before calling you, assuming you're a
    // bootloader that hands off quickly. You're an OS that doesn't — so
    // if you're still alive at 5:00, the firmware reboots the machine.
    SysTable->BootServices->SetWatchdogTimer(0, 0, 0, 0);  // 0 = disable

    print(L"ForgeOS-style bootstrap alive.\r\n");   // \r\n — firmware wants both
    print(L"Firmware vendor: ");
    print(SysTable->FirmwareVendor);              // proves we're reading real firmware data

    for (;;) { }   // park forever — "it worked" must look different from "it crashed"
    return EFI_SUCCESS;
}
The watchdog will waste your evening if you skip it

The symptom is maddening: your OS runs fine for five minutes, then black-screens and reboots with no panic, no log, nothing. That's the firmware watchdog, not your code. Disable it on the very first line and never think about it again.

The build is two commands — clang targeting PE, then lld-link. The flags matter, and this is exactly the kind of thing the tutorials leave out:

build.sh — the flags that actually mattercopy
clang -target x86_64-windows \    # UEFI binaries are PE/COFF, not ELF
      -ffreestanding \           # no libc, no crt0, no malloc
      -fshort-wchar \            # UEFI strings are UTF-16 — without this, L"x" is garbage
      -mgeneral-regs-only \      # no SSE: firmware hasn't set up its state yet
      -c 01_bootstrap.c -o boot.obj

lld-link -subsystem:efi_application -entry:efi_main \
         -nodefaultlib -dll boot.obj -out:BOOTX64.EFI

That -fshort-wchar flag is a classic trap: leave it off and every string you pass to the firmware is silently the wrong width, and nothing prints. -mgeneral-regs-only is the other one — a stray SSE register in a memcpy will fault before you've printed a word.

Lesson 1.3

1.3 A USB stick that boots

the single worst-covered topic in free OS-dev material
1.3 — partition, format, boot on real hardware
~15 mincoming soon

The firmware looks for a specific file at a specific path on a FAT-formatted partition. Put your BOOTX64.EFI there and any UEFI machine will boot it, no configuration, no NVRAM entry:

the magic path
USB stick (FAT32)
└── EFI
    └── BOOT
        └── BOOTX64.EFI   ← the fallback path firmware runs with no boot entry

That exact name — BOOTX64.EFI in /EFI/BOOT/ — is the "removable media" fallback every UEFI firmware checks. It's what makes the stick portable between machines. Format the stick as FAT32, drop the file in that path, and it's bootable.

This is the lesson people pay for

"How do I get my thing off the emulator and onto a real machine" is the question free tutorials answer worst. It's three files in a folder with the right name. Now you know.

Lesson 1.4

1.4 QEMU: your fast feedback loop

develop in the emulator, trust only the hardware
1.4 — the QEMU + OVMF loop
~14 mincoming soon

Reflashing a USB stick for every change is slow. QEMU with the OVMF UEFI firmware image gives you a full UEFI environment in a window that boots in a second. You'll do 95% of development here and only flash the stick to confirm on real hardware.

run.shcopy
qemu-system-x86_64 \
  -bios OVMF.fd \                 # the UEFI firmware — NOT SeaBIOS
  -drive file=usb.img,format=raw \ # your FAT image with BOOTX64.EFI
  -net none
The thesis of this whole course

QEMU is fast and forgiving. Real hardware is slow and honest. Things that work in the emulator and die on metal: uncached framebuffers, timing assumptions, DMA alignment, firmware quirks. Use QEMU to iterate. Use the laptop to find out what's actually true. You'll feel this for real in the very next lesson.

Lesson 1.5

1.5 Graphics Output Protocol — getting the framebuffer

ask the firmware for a screen you can write to directly
1.5 — locating GOP, picking a mode
~16 mincoming soon

UEFI is built entirely out of protocols — structs of function pointers you request by a 128-bit GUID. Graphics is the Graphics Output Protocol (GOP). You ask the firmware to locate it, pick the best video mode, and it hands you the physical address of a linear framebuffer: raw pixels you write directly.

02_framebuffer.c — cache what matterscopy
status = LocateProtocol(&gEfiGraphicsOutputProtocolGuid, 0, (void**)&Gop);

// cache AFTER SetMode — it replaces Info and FrameBufferBase.
FrameBuffer = (UINT32*)(UINTN)Gop->Mode->FrameBufferBase;  // raw pixels
ScreenW     = Gop->Mode->Info->HorizontalResolution;         # what you SEE
ScreenH     = Gop->Mode->Info->VerticalResolution;
Pitch       = Gop->Mode->Info->PixelsPerScanLine;             // ← the one that bites you
The pitch trap — this shears half of all beginners' first framebuffers

The obvious way to find a pixel is buf[y * ScreenW + x]. It works in QEMU, and on about half of real machines, and then on someone's laptop the entire image comes out sheared diagonally.

Hardware scanlines are often wider than the visible area — a 1366-wide panel might have 1408-pixel scanlines for alignment. PixelsPerScanLine is the true stride; HorizontalResolution is only what you can see. They're equal often enough to fool you and different often enough to ruin your week. Always index with pitch.

Lesson 1.6

1.6 Pixels, rectangles, and your logo on real glass

the payoff — your code, your screen, your hardware
1.6 — first frame on the laptop
~16 mincoming soon

With the framebuffer cached, a pixel is one write. Build draw_rect on top, clip once up front (never per-pixel — that's the whole frame budget at 1440p), and draw your first frame:

02_framebuffer.ccopy
void put_pixel(int x, int y, UINT32 color) {
    if (x < 0 || x >= ScreenW || y < 0 || y >= ScreenH) return;  // no MMU to save you
    FrameBuffer[y * Pitch + x] = color;   // Pitch, never ScreenW
}

void draw_rect(int x, int y, int w, int h, UINT32 color) {
    // clip ONCE, up front — then the inner loop is a bare store
    if (x < 0) { w += x; x = 0; }
    if (y < 0) { h += y; y = 0; }
    if (x + w > ScreenW) w = ScreenW - x;
    if (y + h > ScreenH) h = ScreenH - y;
    for (int r = 0; r < h; r++) {
        UINT32 *line = FrameBuffer + (UINT64)(y+r) * Pitch + x;
        for (int c = 0; c < w; c++) line[c] = color;  // no branch, no bounds check
    }
}

// the payoff frame — your brand colours, dead centre
draw_rect(0, 0, ScreenW, ScreenH, 0x00070B12);          // dark navy fill
draw_rect(cx-120, cy-40, 240, 80, 0x0035C6D8);        // cyan slab
draw_rect(cx-100, cy-20, 200, 40, 0x00E8873F);        // ember inset

Boot that in QEMU — instant. Then flash it to the stick and boot the laptop. On real hardware that full-screen fill might be visibly slow, while QEMU did it instantly. Nothing is wrong with your code:

Your next-module hook — the uncached framebuffer

Firmware sometimes leaves the framebuffer mapped uncached. Every 4-byte pixel write then goes straight over the bus with no batching — a 1440p fill becomes 3.7 million individual transactions. Brutal on metal, invisible in QEMU. The fix (marking the range write-combining) is the single biggest performance lever in the whole OS — and it's the first thing you'll do in Module 02.

Module 01 checkpoint — you should now have
  • A toolchain that builds a BOOTX64.EFI from C
  • A USB stick that boots your code on a real UEFI machine
  • A QEMU + OVMF loop for fast iteration
  • The framebuffer located, with pitch handled correctly
  • Your own logo drawn on your own screen — in an emulator and on real hardware

That's the hardest part behind you — not because the code was long, but because "get my own code onto real hardware under UEFI" is the wall most people never get over. You're past it. From here it's building up: memory, interrupts, a compositor, windows, files, the network — and eventually DOOM.

End of the free module

Module 02 — Memory & Interrupts

Build your own physical memory manager from the firmware's map, install an IDT, and wire up the timer and keyboard so the machine finally responds to you. The rest of the course is where the OS comes to life.

See pricing — from $9/mo or $149 once →