Hardware Devices

Every emulated peripheral — how it works, what ports it owns, and how it fits into the PC hardware stack.

All devices are JavaScript

Device controllers live entirely in JavaScript (src/*.js). They register handlers on the I/O bus and respond to WASM callbacks. Device state (registers, FIFOs, buffers) is stored in JS objects. Interrupt signals are sent back to the WASM CPU via cpu.device_raise_irq(n).

Device Cards

Click any card to expand details.

🔔
PIC — 8259A Interrupt Controller
src/rust/cpu/pic.rs
0x20-3F (M) 0xA0-BF (S)

Two cascaded 8259A PICs manage the 16 hardware interrupt lines (IRQ0–15). Master PIC at 0x20 handles IRQ0–7; Slave PIC at 0xA0 handles IRQ8–15 (cascaded via IRQ2).

Flow: Device calls cpu.device_raise_irq(n) → PIC checks mask register → if not masked, signals CPU → CPU checks IF flag → dispatches to IDT[vector].

Software sends EOI (End of Interrupt, 0x20) to dismiss the IRQ after handling. Non-specific EOI clears the highest-priority ISR bit.

⏱
PIT — 8254 Programmable Timer
src/pit.js
0x40–0x43 IRQ 0

Three independent countdown channels clocked at 1.193182 MHz. Channel 0 drives IRQ0 (system tick); Channel 2 drives the PC speaker pitch.

The BIOS programs channel 0 to mode 3 (square wave) at ~18.2 Hz. Linux reprograms it to a higher rate for better timer resolution. Modern OSes use HPET/APIC timer instead.

Speaker: Port 0x61 bit 0 gates channel 2 output to the speaker. The speaker adapter listens for "pcspeaker-update" bus messages.

🕐
RTC — Real-Time Clock / CMOS
src/rtc.js
0x70–0x71 IRQ 8

Indexed CMOS register set accessed via port 0x70 (index) and 0x71 (data). Stores time, date, and machine configuration that persists across reboots.

Key CMOS fields v86 pre-fills: base memory size, extended memory size, boot order (disk/CDROM), floppy drive type, hard disk geometry, equipment flags.

Status register C contains IRQ8 interrupt flags: periodic interrupt (PIE), alarm interrupt (AIE), update-ended interrupt (UIE). Linux uses periodic interrupts for scheduling.

💟
DMA — 8237A Controller
src/dma.js
0x00–0x1F 0x80–0x8F 0xC0–0xDF

Two cascaded 8237A DMA controllers (8 channels total). DMA transfers data between device FIFOs and guest RAM without CPU involvement.

Channel allocation: Ch0=free, Ch1=SB16 8-bit, Ch2=Floppy, Ch3=SB16 16-bit alt, Ch4=cascade, Ch5–7=16-bit ISA.

Page registers (0x80–0x8F) extend the 16-bit DMA address to 20 bits. Device calls dma.do_read() which copies data from guest RAM into the device buffer, then triggers the device callback.

🖥
VGA / SVGA — Graphics Card
src/vga.js (83 KB)
0x3C0–0x3DF PCI screen-fill-buffer

The largest device — emulates a Bochs VBE SVGA-compatible card. Supports VGA text mode (80×25, 25 colors), CGA graphics mode (320×200 or 640×480), and SVGA modes up to 1280×1024 via Bochs VBE extensions (ports 0x1CE/0x1CF).

Text mode: Reads character+attribute bytes from MMIO at 0xB8000, renders font glyphs into a pixel buffer, sends to screen adapter.

SVGA mode: Guest writes pixels directly to linear framebuffer at 0xE0000000. VGA marks dirty rows; next tick flushes changed scanlines via bus message.

MMIO regions: 0xA0000–0xBFFFF (planar), 0xE0000000+ (linear framebuffer), PCI BAR for VBE registers.

⌚
PS/2 Controller — 8042
src/ps2.js
0x60, 0x64 IRQ 1 (KB) IRQ 12 (Mouse) keyboard-code

Emulates the Intel 8042 microcontroller that bridges the CPU to keyboard and mouse. Maintains separate output FIFOs for keyboard and mouse data.

Keyboard scancodes arrive via bus message "keyboard-code". The PS/2 device enqueues the scancode and raises IRQ1. Guest reads the byte from port 0x60.

Mouse packets are 3 bytes (button state + X delta + Y delta), delivered one byte at a time. IRQ12 fires after each byte. Mouse data arrives via bus messages "mouse-delta" and "mouse-click".

Command 0xA9 (test aux port), 0xAD/0xAE (disable/enable keyboard), 0xD4 (write to mouse) are implemented for OS compatibility.

📻
UART — Serial Ports (16550A)
src/uart.js
0x3F8 COM1 0x2F8 COM2 IRQ 4/3 serial-write

Up to 4 UART 16550A serial controllers. Each has a 16-byte RX/TX FIFO. The divisor latch sets baud rate (typical: 115200 baud = divisor 1).

Linux commonly uses COM1 as a serial console. Output bytes go to the serial adapter via bus and appear in a terminal. Input from the terminal is queued in the RX FIFO and triggers UART interrupts.

UART0 (COM1) is always enabled. UART1–3 are created only if enabled in v86 settings. Virtio console can replace UART for better throughput.

💿
IDE Controller
src/ide.js (97 KB)
0x1F0 Primary 0x170 Secondary IRQ 14/15 PCI

PCI IDE controller with two channels (primary/secondary), each supporting master and slave devices. Primary master = hard disk (hda), secondary master = CD-ROM.

Hard disk: ATA PIO transfers for small reads, DMA for large blocks. Disk images are JS ArrayBuffers. Async I/O reads in chunks and signals completion via IRQ14.

CD-ROM: ATAPI protocol (ATA Packet Interface). PACKET command (0xA0) sends a 12-byte CDB. Supports READ(10), REQUEST SENSE, INQUIRY, MODE SENSE, START/STOP UNIT. ISO 9660 image support built-in.

Disk state (dirty sectors) can be persisted to IndexedDB via the buffer abstraction layer (src/buffer.js).

💟
Floppy Controller — 8272A
src/floppy.js (53 KB)
0x3F0–0x3F7 IRQ 6 DMA 2

Intel 8272A Floppy Disk Controller emulation. Supports two drives (fda/fdb), standard 1.44 MB geometry: 80 tracks, 2 heads, 18 sectors/track, 512 bytes/sector.

Command sequence: write command bytes to port 0x3F5, wait for interrupt, read result bytes. BIOS uses INT 13h to abstract floppy access.

DMA channel 2 transfers data. The BIOS typically reads the boot sector (track 0, head 0, sector 1) first during the boot sequence.

🔊
SoundBlaster 16
src/sb16.js (51 KB)
0x220–0x233 IRQ 5 DMA 1/5 speaker

Creative SoundBlaster 16 ISA audio card. DSP version 4.x. Supports 8-bit and 16-bit PCM playback and recording via DMA transfer. Mixer chip at 0x224 controls volume, channel balance, and source selection.

Playback flow: guest programs DMA address/length, writes PLAY DMA command to DSP → DMA reads audio samples from guest RAM → samples sent to Web Audio API via speaker bus adapter → IRQ5 fires when buffer exhausted.

Also emulates OPL2/OPL3 FM synthesis chip (Yamaha YM3812) at 0x388 for MIDI-quality music synthesis used by DOS games.

🌐
NE2000 Ethernet Card
src/ne2k.js
0x280–0x29F IRQ 9 network-send/receive

ISA NE2000-compatible Ethernet adapter using the NS DP8390 controller. Implements register pages 0 and 1 for command, transmit, receive, and interrupt management.

Outgoing packets: guest writes frame to NIC ring buffer → network-send bus message → network adapter sends to relay or fetch backend.

Incoming packets: network adapter fires network-receive → NIC copies frame into ring buffer → raises IRQ9 → guest's driver reads the frame.

MAC address is configurable. Can be replaced by Virtio-net for better performance and simpler guest driver requirements.

⚡
Virtio Devices
src/virtio.js, virtio_net.js, virtio_9p.js

PCI network / 9P / console

Paravirtualized devices using the Virtio standard (PCI transport). Much more efficient than emulating legacy hardware — the guest driver knows it's in a VM and cooperates.

virtio-net: Network card replacement. Virtqueue-based packet I/O. Requires virtio-net driver (standard in Linux since 2.6.25).

virtio-9p: Filesystem passthrough via the 9P protocol. Maps a JS filesystem (backed by lib/9p.js) into the guest. Linux mounts it with mount -t 9p.

virtio-console: High-performance serial console replacement. Shares virtqueues with the serial adapter.

virtio-balloon: Memory pressure signaling — allows the host to reclaim guest memory pages.

🔧
PCI Bus
src/pci.js
0xCF8–0xCFF

PCI configuration space — 256 "slots" (bus 0, devices 0–31, functions 0–7). Each device has a 256-byte config space with standard header: vendor ID (0x0), device ID (0x2), command (0x4), status (0x6), class code (0x8), BARs (0x10–0x24), IRQ line (0x3C), IRQ pin (0x3D).

Access via CONFIG_ADDRESS (0xCF8) and CONFIG_DATA (0xCFC). Writing a 1 to a BAR causes the device to return its required size on the next read — used by BIOS to assign addresses.

v86 devices that use PCI: VGA, IDE, NE2000 (optional), all Virtio devices.

⚙
ACPI
src/acpi.js
PCI 0xB000+ (PM)

Basic ACPI power management support. Implements the ACPI timer (PMTMR at 0xB008) — a 24-bit counter running at 3.579545 MHz, used by Linux for high-resolution timing when HPET is unavailable.

ACPI tables (RSDT, FADT, MADT, HPET) are provided by SeaBIOS in the BIOS ROM and describe the hardware topology to the OS.

Shutdown via ACPI: guest writes 0x2000 to PM1a_CNT register (SLP_TYP=5, SLP_EN=1) → v86 stops the main loop.

Initialization Order

Order matters

Devices are created in a specific order in cpu.init() because some depend on others already existing (e.g. DMA must exist before Floppy, PCI before Virtio).

1
PCI Bus
must exist before any PCI device
2
RTC + fill CMOS
BIOS reads CMOS early in POST
3
DMA Controller
floppy and SB16 need DMA channels
4
VGA
BIOS needs VGA for early video output
5
PS/2 Controller
keyboard before anything tries to read input
6
UART / Serial
serial console available from first BIOS output
7
Floppy Controller
needs DMA (step 3) already registered
8
IDE Controller
disk required for boot sector read
9
PIT Timer
started late to avoid premature IRQ0
10
SoundBlaster 16
optional; needs DMA
11
Network (NE2000 or Virtio)
needs PCI if Virtio
12
Virtio 9P, Console, Balloon
all need PCI; 9P needs bus connected
13
ACPI
PCI device; provides PMTMR for timing

IRQ Routing

IRQVectorDeviceTrigger condition
IRQ 00x08PIT Channel 0Countdown reaches zero (~18.2 Hz or programmed rate)
IRQ 10x09PS/2 KeyboardScancode enqueued from keyboard FIFO
IRQ 20x0APIC cascadeCascade line from slave PIC (not maskable directly)
IRQ 30x0BCOM2 UARTTX empty, RX data ready, or line status change
IRQ 40x0CCOM1 UARTTX empty, RX data ready, or line status change
IRQ 50x0DSoundBlaster 16DMA buffer half/full, or DSP command completion
IRQ 60x0EFloppy FDCCommand completion (seek done, transfer done)
IRQ 70x0FLPT1 (unused)Parallel port — not used in v86
IRQ 80x70RTCPeriodic interrupt (configurable rate) or alarm
IRQ 90x71NE2000 / freePacket received or transmitted
IRQ 100x72free / VirtioPCI device interrupt (shared)
IRQ 110x73free / VirtioPCI device interrupt (shared)
IRQ 120x74PS/2 MouseMouse packet byte ready
IRQ 130x75FPU / coprocessorFPU error (rarely used with x87 exceptions)
IRQ 140x76IDE PrimaryDisk read/write or ATAPI command complete
IRQ 150x77IDE SecondaryCD-ROM read or ATAPI command complete