-
-
Notifications
You must be signed in to change notification settings - Fork 5
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
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.
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.
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.
| 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.
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.
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).
| 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.
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.
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 |
| 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 = ((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 |
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.
| 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) |
4-level: PML4 → PDPT → PD → PT → 4 KB page.
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) |
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 |
| 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.
kernel/drivers/misc/apic.h. Base LAPIC_BASE = 0xFEE00000.
| 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 |
| Constant | Value |
|---|---|
SVR_ENABLE |
1 << 8 |
SVR_FOCUS |
1 << 9 |
The spurious vector is set to 0x0F.
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 |
| 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.
Base IOAPIC_BASE = 0xFEC00000, 24 redirection entries.
| 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 |
| 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.
| Port | Device | Use |
|---|---|---|
0x20, 0x21
|
Master PIC | Command, data + mask |
0xA0, 0xA1
|
Slave PIC | Command, data + mask |
0x40–0x43
|
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 |
0x1F0–0x1F7
|
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 |
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.
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.
| 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.
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.
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.
| 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.
- Architecture — the address-space map these constants define
- Drivers — how each device is driven
- Memory-Management — the paging mechanisms
- SMP — per-core state and IPIs
- Debugging — decoding an exception chain
- Intel SDM Vol. 3A, Ch. 3 — Protected-Mode Memory Management — GDT and TSS layout
- Intel SDM Vol. 3A, Ch. 4 — Paging — PTE bit definitions
- Intel SDM Vol. 3A, Ch. 6 — Interrupt and Exception Handling — exception vectors and error codes
- Intel SDM Vol. 3A, Ch. 11 — APIC — LAPIC and I/O APIC registers
- Intel SDM Vol. 4 — Model-Specific Registers — the MSR numbers above
- OSDev Wiki — Global Descriptor Table — OSDev
- OSDev Wiki — 8259 PIC — OSDev
NyxOS v6.4.363 · GPL v2 · GitHub · uselessalter on Discord · nyxos@inbox.lv
NyxOS Wiki
Getting started
Kernel
Storage & network
Graphics & apps
Userspace
HOWTO
- HOWTO-Add-a-system-call
- HOWTO-Write-a-userspace-program
- HOWTO-Add-a-shell-command
- HOWTO-Add-a-GUI-application
Reference
- Syscall-Reference
- Command-Reference
- Hardware-Reference
- Format-Reference
- Kernel-Data-Structures
- Source-Tree-Reference
Project