Skip to content

Hardware Reference

kazah-png edited this page Jul 28, 2026 · 3 revisions

Hardware reference

Bit-level reference for the CPU structures, control registers, MSRs, interrupt vectors and I/O ports NyxOS programs. Every value is quoted from the source file named beside it.

See also: Architecture, Drivers, Memory-Management, SMP, Boot-Process

Segment selectors

kernel/core/kernel.h

Selector Value Index RPL Descriptor
KERNEL_CS 0x08 1 0 Kernel code, 64-bit
KERNEL_DS 0x10 2 0 Kernel data
USER_CS 0x1B 3 3 User code, 64-bit
USER_DS 0x23 4 3 User data

USER_CS is 0x18 | 3 and USER_DS is 0x20 | 3 — the low two bits are the requested privilege level.

GDT

kernel/core/gdt.c. Size is GDT_ENTRIES = 5 + 2 * MAX_CPUS = 21 entries.

Index Bytes Access Flags Contents
0 8 Null descriptor
1 8 0x9A 0x2 Kernel code 64-bit (L-bit set)
2 8 0x92 0x0 Kernel data
3 8 0xFA 0x2 User code 64-bit
4 8 0xF2 0x0 User data
5 + 2·c 16 0x89 TSS descriptor for CPU c

The TSS selector for a core is TSS_SEL_FOR(cpu) = (5 + 2*cpu) << 3, so CPU 0 uses 0x28, CPU 1 0x38, and so on.

Descriptor construction. A flat descriptor is (access << 40) | (flags << 52). The 16-byte TSS descriptor packs base and limit across both qwords:

Field Bits of gdt[5+2c]
limit[15:0] 0–15
base[23:0] 16–39
access 0x89 40–47
limit[19:16] 48–51
base[31:24] 56–63
base[63:32] gdt[6+2c], bits 0–31

Important

The TSS base is stored as a higher-half address (&tss[c] + KERNEL_BASE). The TSS must be readable under a user CR3, where the kernel exists only through the top PML4 entry.

Note

One TSS descriptor per core is what lets an application processor run ring-3 code. A TSS may be busy on only one core at a time — ltr sets the busy bit in its descriptor — so cores cannot share one. This is why APs used to skip ltr entirely.

The GDT itself is read-only once built, so every core loads the same table (gdt_load_on_ap). The IDT's gates name selector 0x08, and every CPU must resolve that identically.

TSS

kernel/core/kernel.h, tss_entry_t, 104 bytes packed.

Offset Size Field Use
0 4 reserved0
4 8 rsp0 Ring-0 stack; the scheduler repoints it per task, per core (see Process-Management#Context switching)
12 8 rsp1 Unused
20 8 rsp2 Unused
28 8 reserved1 Exactly eight bytes
36 8 ist1 Double fault
44 8 ist2 NMI
52 8 ist3 Unused
60 8 ist4 Unused
68 8 ist5 Unused
76 8 ist6 Unused
84 8 ist7 Unused
92 8 reserved2
100 2 reserved3
102 2 iomap_base Set to sizeof(tss_entry_t)

Warning

Intel SDM Vol. 3, Fig. 7-11 specifies exactly eight reserved bytes after rsp2. Carrying three dwords there — a leftover of the 32-bit SS0/SS1/SS2 fields — shifts every IST slot four bytes early: tss_set_ist() writes IST1 at offset 40 while the CPU reads it from 36. The double-fault handler then runs on a garbage stack and triple-faults. The IST mechanism never worked until this was found.

IST stacks

Constant Value
IST_STACK_SIZE 8192 bytes
IST_DOUBLE_FAULT 1

Allocated per core at boot: one double-fault stack and one NMI stack each, so a fault on one core cannot corrupt another core's exception stack. Stacks grow down, so the value written is the top of each region. tss_set_ist_cpu(cpu, idx, top) sets one slot on one core.

Interrupt vectors

CPU exceptions, 0–31

kernel/core/isr.c, exception_names[].

# Name Error code NyxOS handling
0 Division by Zero No Ring 3 → SIGFPE
1 Debug No Panic
2 NMI No IST2
3 Breakpoint No Panic
4 Overflow No Panic
5 Bound Range Exceeded No Panic
6 Invalid Opcode No Ring 3 → SIGILL
7 Device Not Available No Panic
8 Double Fault Yes IST1
9 Coprocessor Segment Overrun No Panic
10 Invalid TSS Yes Panic
11 Segment Not Present Yes Panic
12 Stack-Segment Fault Yes Panic
13 General Protection Fault Yes Ring 3 → SIGSEGV
14 Page Fault Yes vm_handle_fault, else ring 3 → SIGSEGV
15 Reserved
16 x87 FPU Error No Panic
17 Alignment Check Yes Panic
18 Machine Check No Panic
19 SIMD FPU Exception No Panic
20 Virtualization Exception No Panic
21 Control Protection Exception Yes Panic
22–29 Reserved
30 Security Exception Yes Panic
31 Reserved

Gates are installed as idt_set_gate(i, isr_stub + KERNEL_BASE, 0x08, 0x8E) — present, DPL 0, 64-bit interrupt gate. Vector 8 uses idt_set_gate_ist(…, IST_DOUBLE_FAULT).

Ring-3 fault reporting. A fault taken in ring 3 prints and then kills only that process:

[fault] pid 7 (myprog): Page Fault (#14) at RIP 0x10049 err 0x6 fault-addr 0x0 -> killed (signal 11)

With diagnostics enabled it also reports cpu=, cr3=, pd=, cs=, and flags <ON-KERNEL-CR3> or <CR3-MISMATCH> when those disagree.

Page-fault error code (vector 14, in err):

Bit Set means
0 Protection violation (clear = page not present)
1 Write access (clear = read)
2 User mode (clear = supervisor)
3 Reserved bit set in a page-table entry
4 Instruction fetch

CR2 holds the faulting linear address.

Device interrupts

ioapic_redirect_irq(i, 32 + i, apic_id) — ISA IRQ i maps to vector 32 + i.

ISA IRQ I/O APIC pin Vector Device
0 2 32 PIT timer
1 1 33 PS/2 keyboard
5 5 37 Sound Blaster 16
12 12 44 PS/2 mouse
14 14 46 ATA primary (polled in practice)

Important

The PIT arrives on I/O APIC pin 2, not pin 0. QEMU's ACPI interrupt-source override remaps ISA IRQ 0, so irq_unmask(0) is a no-op and tick_count stays at zero. Boot code calls ioapic_redirect_irq(2, 32, apic_id) then ioapic_unmask_irq(2).

Other vectors

Vector Use
0x0F Local APIC spurious interrupt
0x41 TLB shootdown IPI
per-CPU AP LAPIC timer, AP spurious

The legacy PIC is fully masked once the APIC is active — irq_unmask only touches the PIC when apic_initialized is false.

Control registers

kernel/core/kernel.h

Register Bit Constant Meaning
CR0 16 WP — supervisor writes to read-only pages fault. Required for COW
CR0 31 PG — paging enable
CR4 5 CR4_PAE Physical Address Extension
CR4 20 CR4_SMEP Supervisor Mode Execution Prevention
CR4 21 SMAP — Supervisor Mode Access Prevention
CR2 Faulting linear address after a #PF
CR3 Physical address of the active PML4

enable_smep_smap() runs at boot and reports:

[CPU] SMEP=unavailable SMAP=unavailable

under a default QEMU qemu64 model, which does not expose them. That message is informational, not an error.

MSRs

kernel/core/kernel.h

MSR Address Purpose
MSR_EFER 0xC0000080 Extended Feature Enable
MSR_STAR 0xC0000081 Segment selectors for syscall/sysret
MSR_LSTAR 0xC0000082 syscall entry RIP
MSR_CSTAR 0xC0000083 Compatibility-mode entry (unused)
MSR_SF_MASK 0xC0000084 RFLAGS mask on syscall
IA32_APIC_BASE_MSR 0x1B Local APIC base and enable

EFER bits

Bit Constant Meaning
0 EFER_SCE System Call Extensions — enables syscall/sysret
8 EFER_LME Long Mode Enable
10 EFER_LMA Long Mode Active (read-only status)
11 EFER_NXE No-Execute Enable

Caution

Without EFER_SCE, the syscall instruction raises #UD in ring 3. This was the reason user processes crashed on entry for several releases.

STAR layout

star = ((uint64_t)USER_CS << 48) | ((uint64_t)KERNEL_CS << 32);
Bits Contents
63:48 sysret CS for ring 3
47:32 syscall CS for ring 0
31:0 Legacy 32-bit SYSCALL EIP, unused

LSTAR

Important

LSTAR must hold the higher-half alias of syscall_entry. A ring-3 syscall runs under the user CR3, which maps kernel code only through the top PML4 entry — not at its low link address. Pointing LSTAR at the low address faults on the first syscall.

IA32_APIC_BASE bits

Bit Constant Meaning
8 APIC_BASE_BSP This core is the bootstrap processor
11 APIC_BASE_ENABLE Local APIC enable
12+ APIC_BASE_ADDR_MASK Base address (~0xFFF)

Page tables

4-level: PML4 → PDPT → PD → PT → 4 KB page.

Architectural PTE bits

kernel/core/kernel.h

Bit Constant Meaning
0 PAGE_PRESENT Entry is valid
1 PAGE_WRITABLE Writable
2 PAGE_USER Accessible from ring 3
7 PAGE_HUGE 2 MB page at PD level
63 PAGE_NX Non-executable (requires EFER_NXE)

Software PTE bits

Bits 9–11 are available to the OS. NyxOS uses them to record why a page is absent or restricted.

Bit Constant Meaning
9 PTE_DEMAND Not yet allocated; supply a zeroed frame on first touch
10 PTE_COW Shared read-only; make a private copy on write

Address-space constants

Constant Value Meaning
PAGE_SIZE 4096
PAGE_ALIGN(a) ((a) + 4095) & ~4095 Round up
KERNEL_BASE 0xFFFFFF8000000000 Higher-half alias of all physical RAM
__pa(v) v - KERNEL_BASE Virtual → physical
__va(p) p + KERNEL_BASE Physical → virtual
KERNEL_HEAP_START 0xFFFFFFFF90000000
KERNEL_HEAP_SIZE 16 MB
MMAP_BASE 0x100000000 Base of user mmap region (4 GiB)
SHARED_LIBC_BASE 0x30000000 Where libc.so is mapped in every process
USER_SPACE_MIN 0x1000 Page 0 left unmapped to trap NULL
USER_SPACE_END 0x800000000000
USER_STACK_TOP 0x00007FFFFFFFE000 Base of the top stack page
USER_STACK_INIT_PAGES 4 16 KB committed at load
USER_STACK_MAX_PAGES 128 512 KB demand-grow ceiling

ELF programs link at 0x10000.

Local APIC

kernel/drivers/misc/apic.h. Base LAPIC_BASE = 0xFEE00000.

LVT flags

Constant Value Meaning
LVT_MASKED 1 << 16 Entry masked
LVT_TIMER_PERIODIC 1 << 17 Reload from INITCNT forever
LVT_TRIGGER 1 << 15 Level-triggered
LVT_LEVEL 1 << 14 Assert
LVT_FIXED 0 Fixed delivery
LVT_NMI 0x400 NMI delivery
LVT_EXTINT 0x700 External interrupt

Spurious Vector Register

Constant Value
SVR_ENABLE 1 << 8
SVR_FOCUS 1 << 9

The spurious vector is set to 0x0F.

ICR delivery modes

Used for AP bringup and shootdown IPIs.

Constant Value Meaning
ICR_INIT 5 << 8 INIT IPI
ICR_STARTUP 6 << 8 SIPI
ICR_PHYSICAL 0 Physical destination
ICR_LOGICAL 1 << 11 Logical destination
ICR_ASSERT 1 << 14 Assert level

AP timer

Constant Value
AP_TIMER_INITCNT 62500, divide-by-16

Deliberately uncalibrated — calibration needs a reference clock the APs do not have, since the PIT belongs to the BSP and is not programmed until after smp_init. Under QEMU this gives roughly 500 Hz per AP; real hardware differs.

I/O APIC

Base IOAPIC_BASE = 0xFEC00000, 24 redirection entries.

Register access

Register Offset Use
IOAPIC_IOREGSEL 0x00 Write the register index here
IOAPIC_IOWIN 0x10 Then read/write the value here
Index Constant Contents
0x00 IOAPIC_ID APIC ID
0x01 IOAPIC_VERSION Version and max redirection entry
0x02 IOAPIC_ARB Arbitration
0x10 + 2·n IOAPIC_REDTBL Redirection entry n, 64 bits over two registers

Redirection-entry flags

Constant Value Meaning
IOAPIC_INT_MASKED 1 << 16 Masked
IOAPIC_INT_PHYSICAL 0 Physical destination mode
IOAPIC_INT_LOGICAL 1 << 11 Logical destination mode
IOAPIC_INT_EDGE 0 Edge triggered
IOAPIC_INT_LEVEL 1 << 15 Level triggered
IOAPIC_INT_ACTIVE_HI 0 Active high
IOAPIC_INT_ACTIVE_LO 1 << 13 Active low

Observed under QEMU: I/O APIC ID: 0, Version: 0x20, Max IRQs: 24.

I/O ports

Port Device Use
0x20, 0x21 Master PIC Command, data + mask
0xA0, 0xA1 Slave PIC Command, data + mask
0x400x43 PIT Channel 0 timer, channel 2 speaker, command
0x60 PS/2 Data — keyboard and mouse
0x64 PS/2 Status and command
0x70, 0x71 CMOS/RTC Index, data
0x80 POST Written for a short I/O delay
0x1F00x1F7 ATA primary Data, error, sector count, LBA, drive, status
0x220+ Sound Blaster 16 See below
0xC000+ RTL8139 PCI-assigned I/O base (QEMU value)
0xCF8, 0xCFC PCI Configuration address, data

RTC registers

kernel/drivers/misc/rtc.h, accessed through 0x70/0x71, BCD-encoded.

Register Index
RTC_SECONDS 0x00
RTC_MINUTES 0x02
RTC_HOURS 0x04
RTC_WEEKDAY 0x06
RTC_DAY 0x07
RTC_MONTH 0x08
RTC_YEAR 0x09
RTC_STATUS_A 0x0A
RTC_STATUS_B 0x0B

RTC_UIP is 0x80 in Status A — update in progress; poll it clear before reading.

Sound Blaster 16

kernel/drivers/audio/sb16.h. Base SB16_BASE_PORT = 0x220, IRQ 5, DMA channel 1.

Port Constant Use
0x224 SB16_MIXER_ADDR Mixer register select
0x225 SB16_MIXER_DATA Mixer data
0x226 SB16_DSP_RESET Write SB16_DSP_RESET_VAL (0x01)
0x22A SB16_DSP_READ DSP read data
0x22C SB16_DSP_WRITE DSP write command/data
0x22C SB16_DSP_WRITE_STAT Write status
0x22E SB16_DSP_READ_STAT Read status
Command Value Meaning
SB16_DSP_ACK 0xAA Expected reset acknowledgement
SB16_CMD_ENABLE_SPEAKER 0xD1
SB16_CMD_DISABLE_SPEAKER 0xD3
SB16_CMD_SET_TIME_CONST 0x40
SB16_CMD_SET_OUTPUT_RATE 0x41
SB16_CMD_SET_INPUT_RATE 0x42
SB16_CMD_8BIT_AUTO_INIT 0xC0 Auto-init playback
SB16_CMD_8BIT_SINGLE 0xC6 Single-cycle playback

[SB16] DSP reset failed at boot simply means no SB16 device is attached.

VBE framebuffer

Property Value
Interface Bochs VBE extensions
Default mode 1024 × 768 × 32
LFB virtual address 0xE0000000
LFB physical address From PCI BAR0 — 0xFD000000 under QEMU
Mapping size 768 pages at the default mode
Pixel format 0x00RRGGBB, i.e. BGRX in memory

Warning

vbe_set_mode validates nothing. setres and mode pass values straight through, so an unsupported mode leaves an unusable display.

Spinlocks

kernel/core/spinlock.h

typedef struct { volatile uint32_t locked; } spinlock_t;   /* 0 = free, 1 = held */
#define SPINLOCK_INIT { 0 }

A plain test-and-set lock, not a ticket lock: NyxOS has at most 8 cores and these critical sections are a few dozen instructions, so a ticket lock's fairness is not worth the extra state.

xchg on memory is atomic on x86 with no LOCK prefix — the bus lock is implied. The contended path re-reads with plain loads plus pause, which yields the core's pipeline and, under QEMU/TCG, its execution slice, instead of hammering the cache line with atomic read-modify-writes.

Caution

Always take these with interrupts disabled on the local CPU (spin_lock_irqsave). A spinlock protects against other cores; it does nothing about the same core re-entering through an interrupt handler, which simply deadlocks against itself. Disabling interrupts is what closes that.

Protected by spinlocks: the physical page allocator, kernel heap, slab caches, VFS node pool, mount table, keyboard input path, network stack, EXT2 driver.

Boot memory layout

kernel/core/boot.asm, in .bss:

Region Size Note
Kernel stack 131072 bytes (128 KB) Placed before the page tables
pml4_table 4096 bytes
pdpt_table 4096 bytes
pd_table 8192 bytes 2 PDs covering 128 MB with 2 MB pages

Warning

The stack was 16 KB and sat directly below the page tables. A deep compositor redraw chain could overflow into the live page tables and corrupt them, which presented as crashes at unrelated addresses. Both the size and the ordering matter.

Multiboot headers

Header Magic Note
Multiboot 1 0x1BADB002 Kept for compatibility
Multiboot 2 0xE85250D6 Primary — what the generated grub.cfg uses

The AP trampoline is loaded at physical 0x8000.

See also

External resources

Clone this wiki locally