include: one folder per subsystem, and includes that name it

Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-23 15:05:12 +02:00
commit a1c965da1e
267 changed files with 1560 additions and 1440 deletions

21
include/arch/asm.h Normal file
View file

@ -0,0 +1,21 @@
//
// assembler macros to create x86 segments
//
#define SEG_NULLASM \
.word 0, 0; \
.byte 0, 0, 0, 0
// The 0xC0 means the limit is in 4096-byte units
// and (for executable segments) 32-bit mode.
#define SEG_ASM(type,base,lim) \
.word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \
.byte (((base) >> 16) & 0xff), (0x90 | (type)), \
(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
#define STA_X 0x8 // Executable segment
#define STA_E 0x4 // Expand down (non-executable segments)
#define STA_C 0x4 // Conforming code segment (executable only)
#define STA_W 0x2 // Writeable (non-executable segments)
#define STA_R 0x2 // Readable (executable segments)
#define STA_A 0x1 // Accessed

61
include/arch/ioperm.h Normal file
View file

@ -0,0 +1,61 @@
#pragma once
// Port I/O, from a process.
//
// A Linux driver does not always ask. `ioread32` is a function call LDLS can
// implement, but `outl` is an *instruction*, compiled into the module, and the
// first foreign driver to need one found this the hard way:
//
// e1000_io_write+0xd: out %eax,(%dx) -> trap 13 err 0, rdx=0xc000
//
// That is not a driver doing something exotic. e1000's own reset path picks it
// deliberately -- "these controllers can't ack the 64-bit write when issuing
// the reset, so use IO-mapping as a workaround" -- so on QEMU's 82540EM there
// is no route to a working NIC that avoids it. Any layer that runs somebody
// else's drivers has to let a process reach the ports of the device it holds.
//
// **Which ports, and no others.** x86 offers two ways to grant this and they
// are very far apart. IOPL=3 is one flag and hands over all 65536 ports -- the
// interrupt controller, the timer, the disk. The TSS I/O permission bitmap is
// per port, and the ports a process gets are exactly the I/O BARs of the PCI
// devices it has claimed. The second costs a few hundred lines and is the only
// one worth having: a foreign driver already has DMA with no IOMMU, and the
// answer to "this is not a security boundary" is not to move it further.
//
// It is the same bargain as kpi_io_ok() in ldls/kpi/io.c, one level down and
// enforced by hardware rather than by the driver's host being polite.
#include "core/types.h"
struct proc;
// Ranges per process. A PCI device has at most six BARs and a process may hold
// several devices; eight is past anything seen and small enough to scan.
#define NIOPORT 8
struct ioport_range {
uint16 base;
uint16 len;
};
// Grant / revoke. Called from /dev/pci when a process claims or releases a
// device, and from exit() by way of pciuser_release_all().
//
// Granting a range twice is not an error -- a driver may claim, release and
// claim again -- and revoking one that was never granted is not either.
void ioperm_grant(struct proc *p, uint16 base, uint16 len);
void ioperm_revoke_all(struct proc *p);
// Install this process's ranges in the running CPU's TSS. Called from
// switchuvm() with interrupts off.
void ioperm_switch(struct proc *p);
// The per-CPU TSS, which now has a bitmap attached to it. seginit() asks for
// this CPU's and points the GDT descriptor at it.
void *ioperm_tss(int cpu, uint64 *limit);
// The same block, for everyone after seginit().
void *ioperm_tss_of(int cpu);
// Where RSP0 lives, so switchuvm() does not have to know the layout.
void ioperm_set_rsp0(void *tss, addr_t stktop);

98
include/arch/memlayout.h Normal file
View file

@ -0,0 +1,98 @@
#pragma once
// Memory layout
#define EXTMEM 0x100000 // Start of extended memory
// Top of usable physical memory. A *variable*, read from the firmware's E820
// map at boot -- see kernel/arch/x86_64/e820.c. It used to be the constant
// below, which was not a limit on what the kernel used but an assertion that
// exactly 224 MB existed: less and the allocator walked off the end of RAM,
// more and it was invisible.
//
// The macro spelling is kept so that the twenty-odd call sites read the same as
// they always did. It must not be used before e820_init(), which runs first
// thing in main() for that reason.
#ifndef __ASSEMBLER__
extern uint64 phystop;
#define PHYSTOP phystop
#endif
// PHYSTOP alone describes memory as one run ending at a top address, which is
// what a BIOS map usually looks like and what UEFI never does: OVMF drops an
// ACPI NVS region at 0x800000, so the run containing the kernel ends at 8 MB
// and everything above the hole was invisible. The usable regions are
// therefore kept as a list as well, and kinit1() frees each one rather than
// walking a single range from the kernel's end to the top.
//
// PHYSTOP still means what it always did -- the top of what kalloc may hand
// out, and the bound kfree() rejects pages above -- so every existing call
// site is unaffected.
#define MEMREGION_MAX 64
#ifndef __ASSEMBLER__
struct memregion {
uint64 base; // first byte, page aligned
uint64 end; // one past the last byte, page aligned
};
extern struct memregion mem_usable[MEMREGION_MAX];
extern int nmem_usable;
// The top of the low region: where the DMA arena is carved from, and the
// ceiling for kinit1(). Always below DIRECTMAP_BOOT, and separate from
// `phystop` because a device is not obliged to be able to reach the top of
// memory -- an RTL8139's buffer address register is thirty-two bits wide.
extern uint64 lowtop;
#endif
// What to assume when the firmware gives us nothing usable: the old constant,
// so that path behaves exactly as the kernel always did.
#define PHYSTOP_FALLBACK 0xE000000ULL
// The most RAM the kernel will use.
//
// This was 0xC0000000 -- three gigabytes -- because kvmalloc() mapped exactly
// four 1 GB pages and the fourth was marked uncached for the PCI hole. On a
// laptop with sixteen gigabytes that threw away thirteen of them, silently.
//
// kvmalloc() now maps as many 1 GB pages as the machine has RAM, so the limit
// is the page table rather than a constant: the kernel PDPT has 512 entries
// and ioremap takes what the direct map does not, so 256 GB of RAM still
// leaves 248 GB of device window. A machine with more than this gets the rest
// ignored, with a line saying so.
//
// It was 64 GB while handing memory to the allocator meant writing every page
// of it at boot. kalloc.c keeps never-used memory as ranges now, so a bigger
// machine costs nothing until its memory is used.
#define PHYSTOP_MAX (256ULL << 30)
// What the *loader's* page tables cover, and therefore the ceiling on anything
// that runs before kvmalloc() builds the kernel's own.
//
// kinit1() frees pages by writing a free-list pointer into each one, so every
// page it touches has to be mapped at the time. Before kvmalloc that means the
// low four gigabytes and nothing else, which is why freeing memory happens in
// two passes -- see kinit_high().
#define DIRECTMAP_BOOT (4ULL << 30)
// Below this the kernel image, its page tables and the DMA arena do not fit.
#define PHYSTOP_MIN (24ULL * 1024 * 1024)
// Key addresses for address space layout
#define KERNBASE 0xFFFF800000000000 // First kernel virtual address
#define KERNLINK (KERNBASE+EXTMEM) // Address where kernel is linked
#ifndef __ASSEMBLER__
static inline addr_t v2p(void *a) {
return ((addr_t)(a)) - ((addr_t)KERNBASE);
}
static inline void *p2v(addr_t a) {
return (void *)((a) + ((addr_t)KERNBASE));
}
#endif
#define V2P(a) (((addr_t) (a)) - KERNBASE)
#define P2V(a) (((void *) (a)) + KERNBASE)
#define V2P_WO(x) ((x) - KERNBASE) // same as V2P, but without casts
#define P2V_WO(x) ((x) + KERNBASE) // same as P2V, but without casts

130
include/arch/mmu.h Normal file
View file

@ -0,0 +1,130 @@
#pragma once
// Eflags register
#define FL_CF 0x00000001 // Carry Flag
#define FL_PF 0x00000004 // Parity Flag
#define FL_AF 0x00000010 // Auxiliary carry Flag
#define FL_ZF 0x00000040 // Zero Flag
#define FL_SF 0x00000080 // Sign Flag
#define FL_TF 0x00000100 // Trap Flag
#define FL_IF 0x00000200 // Interrupt Enable
#define FL_DF 0x00000400 // Direction Flag
#define FL_OF 0x00000800 // Overflow Flag
#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask
#define FL_NT 0x00004000 // Nested Task
#define FL_RF 0x00010000 // Resume Flag
#define FL_VM 0x00020000 // Virtual 8086 mode
#define FL_AC 0x00040000 // Alignment Check
#define FL_VIF 0x00080000 // Virtual Interrupt Flag
#define FL_VIP 0x00100000 // Virtual Interrupt Pending
#define FL_ID 0x00200000 // ID flag
// Control Register flags
#define CR0_PE 0x00000001 // Protection Enable
#define CR0_MP 0x00000002 // Monitor coProcessor
#define CR0_EM 0x00000004 // Emulation
#define CR0_TS 0x00000008 // Task Switched
#define CR0_ET 0x00000010 // Extension Type
#define CR0_NE 0x00000020 // Numeric Errror
#define CR0_WP 0x00010000 // Write Protect
#define CR0_AM 0x00040000 // Alignment Mask
#define CR0_NW 0x20000000 // Not Writethrough
#define CR0_CD 0x40000000 // Cache Disable
#define CR0_PG 0x80000000 // Paging
#define CR4_PSE 0x00000010 // Page size extension
#define CR4_PAE 0x00000020 // Physical address extensions
#define CR4_OSXFSR 0x00000200 // OS supports FXSAVE and FXRSTOR
#define CR4_OSXMMEXCPT 0x00000400 // OS supports SSE exceptions
// Model specific registers
#define MSR_EFER 0xC0000080 // Extended Feature Enable Register
#define MSR_STAR 0xC0000081 // Legacy Mode SYSCALL Target
#define MSR_LSTAR 0xC0000082 // Long Mode SYSCALL Target
#define MSR_CSTAR 0xC0000083 // Compatibility Mode SYSCALL Target
#define MSR_SFMASK 0xC0000084 // SYSCALL Flag Mask
// Segment selectors
#define SEG_KCODE 1
#define SEG_KDATA 2
#define SEG_UCODE32 3
#define SEG_UDATA 4
#define SEG_UCODE 5
#define SEG_KCPU 6
#define SEG_TSS 7 // TSS takes 2 slots in 64-bit GDT
#define NSEGS 9
#define DPL_USER 0x3
#define USER_CS ((SEG_UCODE<<3)|DPL_USER)
#define USER_DS ((SEG_UDATA<<3)|DPL_USER)
#define KERNEL_CS (SEG_KCODE<<3)
#ifndef __ASSEMBLER__
// Segment Descriptor
struct segdesc {
uint32 lim_15_0 : 16; // Low bits of segment limit
uint32 base_15_0 : 16; // Low bits of segment base address
uint32 base_23_16 : 8; // Middle bits of segment base address
uint32 type : 4; // Segment type
uint32 s : 1; // 0 = system, 1 = application
uint32 dpl : 2; // Descriptor Privilege Level
uint32 p : 1; // Present
uint32 lim_19_16 : 4; // High bits of segment limit
uint32 avl : 1; // Available for software use
uint32 l : 1; // 1 = 64-bit segment (Long mode)
uint32 db : 1; // 0 = 16-bit, 1 = 32-bit
uint32 g : 1; // Granularity
uint32 base_31_24 : 8; // High bits of segment base address
};
// 64-bit System Segment Descriptor (e.g., TSS)
struct desclong {
struct segdesc sd;
uint32 base_63_32; // High bits of 64-bit base address
uint32 res; // Reserved
};
#define SEG(type, base, lim, dpl) (struct segdesc) \
{ ((lim) >> 12) & 0xffff, (uint32)(base) & 0xffff, \
((uint32)(base) >> 16) & 0xff, type, 1, dpl, 1, \
(uint32)(lim) >> 28, 0, 1, 0, 1, (uint32)(base) >> 24 }
#endif
// Application segment type bits
#define STA_X 0x8 // Executable segment
#define STA_W 0x2 // Writeable (non-executable)
#define STA_R 0x2 // Readable (executable)
// System segment type bits
#define STS_T64A 0x9 // Available 64-bit TSS
#define STS_T64B 0xB // Busy 64-bit TSS
#define STS_IG64 0xE // 64-bit Interrupt Gate
#define STS_TG64 0xF // 64-bit Trap Gate
// Page table structure: 4 levels
#define PMX(va) (((addr_t)(va) >> 39) & 0x1FF)
#define PDPX(va) (((addr_t)(va) >> 30) & 0x1FF)
#define PDX(va) (((addr_t)(va) >> 21) & 0x1FF)
#define PTX(va) (((addr_t)(va) >> 12) & 0x1FF)
#define PGSIZE 4096
#define PXMASK 0x1FF
#define PGROUNDUP(sz) (((sz)+((addr_t)PGSIZE-1)) & ~((addr_t)(PGSIZE-1)))
#define PGROUNDDOWN(a) (((a)) & ~((addr_t)(PGSIZE-1)))
// Page table/directory entry flags
#define PTE_P 0x001 // Present
#define PTE_W 0x002 // Writeable
#define PTE_U 0x004 // User
#define PTE_PWT 0x008 // Write-Through
#define PTE_PCD 0x010 // Cache-Disable
#define PTE_A 0x020 // Accessed
#define PTE_D 0x040 // Dirty
#define PTE_PS 0x080 // Page Size (Huge Page)
#define PTE_ADDR(pte) ((addr_t)(pte) & 0x000FFFFFFFFFF000ull)
#define PTE_FLAGS(pte) ((addr_t)(pte) & 0xFFF)

59
include/arch/mp.h Normal file
View file

@ -0,0 +1,59 @@
// See MultiProcessor Specification Version 1.[14]
struct mp { // floating pointer
uchar signature[4]; // "_MP_"
uint32 physaddr; // 32-bit phys addr of MP config table
uchar length; // 1
uchar specrev; // [14]
uchar checksum; // all bytes must add up to 0
uchar type; // MP system config type
uchar imcrp;
uchar reserved[3];
};
struct mpconf { // configuration table header
uchar signature[4]; // "PCMP"
ushort length; // total table length
uchar version; // [14]
uchar checksum; // all bytes must add up to 0
uchar product[20]; // product id
uint32 oemtable_p; // OEM table pointer
ushort oemlength; // OEM table length
ushort entry; // entry count
uint32 lapicaddr_p; // address of local APIC
ushort xlength; // extended table length
uchar xchecksum; // extended table checksum
uchar reserved;
};
struct mpproc { // processor table entry
uchar type; // entry type (0)
uchar apicid; // local APIC id
uchar version; // local APIC verison
uchar flags; // CPU flags
#define MPBOOT 0x02 // This proc is the bootstrap processor.
uchar signature[4]; // CPU signature
uint feature; // feature flags from CPUID instruction
uchar reserved[8];
};
struct mpioapic { // I/O APIC table entry
uchar type; // entry type (2)
uchar apicno; // I/O APIC id
uchar version; // I/O APIC version
uchar flags; // I/O APIC flags
uint32 addr_p; // I/O APIC address
};
// Table entry types
#define MPPROC 0x00 // One per processor
#define MPBUS 0x01 // One per bus
#define MPIOAPIC 0x02 // One per I/O APIC
#define MPIOINTR 0x03 // One per bus interrupt source
#define MPLINTR 0x04 // One per system interrupt source
// The I/O APIC's registers, as the firmware described them. 0xFEC00000 is
// only where they usually are; see kernel/arch/x86_64/mp.c.
#define IOAPIC_DEFAULT_PHYS 0xFEC00000ULL
extern uint64 ioapic_phys;

190
include/arch/schboot.h Normal file
View file

@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifndef SCHBOOT_H
#define SCHBOOT_H
// The loader/kernel hand-off, in one place.
//
// Included by kernel/arch/x86_64/entry.S and by boot/uefi/main.c, so the two
// cannot disagree about the magic or the layout. Defining it twice is exactly
// how a loader ends up scanning for bytes the kernel never wrote — the first
// attempt here searched for "SCH6" while the assembler had stored 0x53434836,
// which lands in the file as "6HCS".
//
// Keep this header assembler-safe: no typedefs, no structs outside the C
// guard, nothing entry.S cannot preprocess.
// Scanned for in the first 16 KB of the kernel image, 8-byte aligned, exactly
// as the BIOS loader scans for the multiboot magic. Followed by a little-endian
// 64-bit physical address: the kernel's 64-bit entry point.
//
// The values spell "SCH6" "4BOO" when stored little-endian, which is what
// makes them findable by eye in a hex dump of the image.
#define SCH64_MAGIC0 0x36484353 /* "SCH6" */
#define SCH64_MAGIC1 0x4f4f4234 /* "4BOO" */
// ---------------------------------------------------------------------------
// The hand-off block.
//
// One page of low memory that a loader fills in and the kernel reads. Every
// field below was previously #defined in whichever file happened to need it --
// five of them, across two loaders and two kernel files -- which is the same
// hazard as defining the magic twice, only quieter: nothing breaks until one
// copy moves.
//
// 0x30000 is clear of stage2 (loaded at 0x7E00 and well under 0xA000), the
// multiboot kernel-search buffer at 0x10000-0x14000, and the kernel's own load
// address at 0x100000.
//
// Both loaders write it, so a field's meaning must not depend on which one did.
// Where they genuinely differ -- the BIOS path knows what medium it booted
// from, UEFI does not -- the field is defined as "0 when unknown" rather than
// left for the reader to guess.
#define SCHBOOT_BASE 0x30000
// A VBE mode info block, 256 bytes. The UEFI loader fills the same four fields
// out of GOP rather than translating a structure it never saw, so the kernel
// cannot tell which firmware booted it.
#define SCHBOOT_VBE_INFO (SCHBOOT_BASE + 0x000)
#define SCHBOOT_VBE_STRIDE (SCHBOOT_VBE_INFO + 16) /* uint16, bytes/line */
#define SCHBOOT_VBE_WIDTH (SCHBOOT_VBE_INFO + 18) /* uint16 */
#define SCHBOOT_VBE_HEIGHT (SCHBOOT_VBE_INFO + 20) /* uint16 */
#define SCHBOOT_VBE_BPP (SCHBOOT_VBE_INFO + 25) /* uint8 */
#define SCHBOOT_VBE_LFB (SCHBOOT_VBE_INFO + 40) /* uint32, physical */
// Non-zero if the loader was asked for a verbose boot.
#define SCHBOOT_VERBOSE_FLAG (SCHBOOT_BASE + 0x100)
// What the system booted from. SCHBOOT_MEDIA_CD matters because the ISO path
// reads through ATAPI rather than ATA; 0 means "not stated", which is what the
// UEFI loader leaves, since it does not read the kernel through either.
#define SCHBOOT_MEDIA_FLAG (SCHBOOT_BASE + 0x101)
// Remote debugging, chosen at the boot menu.
//
// A byte, set by the loader when somebody picks it out of the F8 menu, read by
// the kernel and published as /proc/bootdebug so that schinit can act on it.
//
// **It exists because the machines that need debugging are the ones that
// cannot be debugged.** A laptop has no serial port, so a kernel that stops
// before it can draw has nowhere to say why; the answer is a debugger reachable
// over the network, and the answer to "when should that be listening" cannot be
// "always" -- schdbgd can read the process table, and a listener nobody asked
// for is not a thing to ship. Choosing it at the boot menu is a deliberate act
// by somebody standing at the machine, which is the strongest consent this
// system can ask for and the weakest one that is any use on hardware that will
// not boot.
//
// Zero on every ordinary boot, including every boot of a build made without
// DEBUG=1 -- where the menu entry does not exist and nothing can set it.
#define SCHBOOT_DEBUG_FLAG (SCHBOOT_BASE + 0x102)
#define SCHBOOT_MEDIA_CD 1
// Real-mode spelling of the same byte, for cdboot.S which has no 32-bit
// addressing yet. Segment and offset, not a flat address.
#define SCHBOOT_MEDIA_SEG 0x3000
#define SCHBOOT_MEDIA_OFF 0x101
// The E820 memory map: a uint32 count at BASE+0x200, and the entries, 24 bytes
// each, in a page-and-a-half of their own from BASE+0x1000.
//
// The entries used to sit between the count and the RSDP field, which left
// room for 40. A laptop's UEFI map is 60 to 150 descriptors, and even after
// the loader merges neighbours of the same type the RAM above 4 GB -- which
// the firmware lists last -- was what fell off the end: a 16 GB machine came
// up with 2 or 3 of it. 256 is more than any map seen merges to, and the area
// it takes, 0x31000-0x32800, is clear of everything else at BASE.
#define SCHBOOT_E820_COUNT (SCHBOOT_BASE + 0x200)
#define SCHBOOT_E820_MAP (SCHBOOT_BASE + 0x1000)
#define SCHBOOT_E820_MAX 256
// Where the loader leaves the ACPI RSDP, as a little-endian 64-bit physical
// address. Zero means "not provided, go and look for it yourself".
//
// The kernel's own search scans the EBDA and 0xE0000-0x100000, which is a BIOS
// convention. UEFI publishes the RSDP in its configuration table instead and is
// under no obligation to leave a copy anywhere in particular; OVMF does not.
// Without it the kernel finds no MADT, so no IOAPIC, so no interrupt routing --
// the machine boots to a prompt on polled serial output and then ignores every
// key, which looks like a broken console rather than a missing table.
//
#define SCHBOOT_RSDP_ADDR (SCHBOOT_BASE + 0x600)
// A console typeface, read off the boot volume by the loader.
//
// Zero length means "there was none" and the kernel uses the one linked into
// its own image. That fallback is not a degraded mode: an early panic has to
// be readable, and the only font guaranteed to exist before any filesystem
// does is the one in the image.
//
// **Loaded by the loader rather than by the kernel**, which is the whole
// design decision here. The kernel could read the file itself once the root
// filesystem is mounted -- but the console is drawing before that, so
// switching then means re-measuring the cell and re-laying out every terminal
// mid-boot. That wipes the kernel log off the screen at exactly the moment a
// machine being brought up for the first time needs it. Choosing the font
// before the first line is drawn costs one file read and changes nothing
// afterwards.
//
// The pages are allocated as EfiReservedMemoryType, so the E820 handoff marks
// them reserved and the page allocator never hands them out -- the font is
// used *in place*, and nothing in the kernel has to find room for 100 KB of
// contiguous memory.
#define SCHBOOT_FONT_ADDR (SCHBOOT_BASE + 0x608) /* uint64, physical */
#define SCHBOOT_FONT_LEN (SCHBOOT_BASE + 0x610) /* uint32, bytes */
// The display, described by the loader in its own words rather than in VBE's.
//
// The VBE mode-info block above is a *BIOS* structure, and the UEFI loader has
// been filling one in because the kernel already knew how to read it. Three of
// its fields cannot describe a real machine:
//
// - the framebuffer address is **32 bits**. A laptop with a discrete GPU
// routinely puts its aperture above 4 GB, and the loader was casting
// `(UINT32)gop->Mode->FrameBufferBase` -- so the splash was drawn into a
// truncated address, nothing appeared, and the firmware's own text cursor
// was left blinking on an otherwise black screen. That is the whole of the
// "it boots to a white underscore" report.
// - the stride is 16 bits, which runs out at 16383 pixels of 32-bit colour.
// - there is nowhere to say whether the pixels are RGBX or BGRX, and GOP
// reports both.
//
// So the UEFI path says it properly. The VBE block is still written, for the
// BIOS loader and for any kernel that has not been taught to look here; a
// non-zero FB_BASE means this block is authoritative and the other is a copy.
//
// FB_BASE is *physical*, and deliberately not translated: above 4 GB it is
// outside the kernel's direct map and has to be ioremap()ed, which cannot
// happen until there are page tables to put it in.
#define SCHBOOT_FB_BASE (SCHBOOT_BASE + 0x618) /* uint64, physical */
#define SCHBOOT_FB_SIZE (SCHBOOT_BASE + 0x620) /* uint64, bytes */
#define SCHBOOT_FB_WIDTH (SCHBOOT_BASE + 0x628) /* uint32, pixels */
#define SCHBOOT_FB_HEIGHT (SCHBOOT_BASE + 0x62C) /* uint32, pixels */
#define SCHBOOT_FB_STRIDE (SCHBOOT_BASE + 0x630) /* uint32, bytes */
// 0 = RGBX8888, 1 = BGRX8888 -- the two GOP formats with a linear layout, in
// EFI's own numbering. Everything in the kernel draws 0x00RRGGBB, which is
// BGRX in memory, so format 0 is the one that needs its channels swapped.
#define SCHBOOT_FB_FORMAT (SCHBOOT_BASE + 0x634) /* uint32 */
// The SMBIOS entry point, from the UEFI configuration table: the 64-bit
// "_SM3_" one when the firmware has it, else the 32-bit "_SM_". Zero means
// none was handed over, and the kernel scans 0xF0000-0xFFFFF the BIOS way.
// It is what names the machine -- vendor, model, firmware version -- in
// /sys/class/dmi/id, and on UEFI the scan alone finds nothing on most laptops.
#define SCHBOOT_SMBIOS_ADDR (SCHBOOT_BASE + 0x638) /* uint64, physical */
// SCHBOOT_EFI_MAGIC when the UEFI loader booted this kernel, which is what
// /sys/firmware/efi existing means on Linux.
#define SCHBOOT_EFI_FLAG (SCHBOOT_BASE + 0x640) /* uint32 */
#define SCHBOOT_EFI_MAGIC 0x21494645u /* "EFI!" */
#ifndef __ASSEMBLER__
struct sch64_header {
unsigned int magic0; // SCH64_MAGIC0
unsigned int magic1; // SCH64_MAGIC1
unsigned long long entry; // physical address, entered in long mode
};
#endif /* !__ASSEMBLER__ */
#endif /* SCHBOOT_H */

71
include/arch/traps.h Normal file
View file

@ -0,0 +1,71 @@
#pragma once
// x86 trap and interrupt constants.
// Processor-defined exceptions:
#define T_DIVIDE 0 // divide error
#define T_DEBUG 1 // debug exception
#define T_NMI 2 // non-maskable interrupt
#define T_BRKPT 3 // breakpoint
#define T_OFLOW 4 // overflow
#define T_BOUND 5 // bounds check
#define T_ILLOP 6 // illegal opcode
#define T_DEVICE 7 // device not available
#define T_DBLFLT 8 // double fault
#define T_TSS 10 // invalid task switch segment
#define T_SEGNP 11 // segment not present
#define T_STACK 12 // stack exception
#define T_GPFLT 13 // general protection fault
#define T_PGFLT 14 // page fault
#define T_FPERR 16 // floating point error
#define T_ALIGN 17 // aligment check
#define T_MCHK 18 // machine check
#define T_SIMDERR 19 // SIMD floating point error
// IRQ offsets
#define T_IRQ0 32 // IRQ 0 corresponds to int T_IRQ
#define IRQ_TIMER 0
#define IRQ_KBD 1
#define IRQ_COM1 4
#define IRQ_MOUSE 12
#define IRQ_IDE 14
// 30, not xv6's 19. Vector T_IRQ0+19 is IOAPIC pin 19, which real chipsets
// use for PCI; the LAPIC error interrupt would have shared it.
#define IRQ_ERROR 30
#define IRQ_SPURIOUS 31
// System call interrupt number for legacy int 0x40 or 0x80 if used
// although syscall/sysret is preferred for x64
#define T_SYSCALL 0x40
#ifndef __ASSEMBLER__
struct trapframe {
// Registers saved by trapasm.S
uint64 rax;
uint64 rbx;
uint64 rcx;
uint64 rdx;
uint64 rbp;
uint64 rsi;
uint64 rdi;
uint64 r8;
uint64 r9;
uint64 r10;
uint64 r11;
uint64 r12;
uint64 r13;
uint64 r14;
uint64 r15;
uint64 trapno;
// Error code and info saved by hardware
uint64 err;
uint64 rip;
uint64 cs;
uint64 rflags;
uint64 rsp;
uint64 ss;
};
#endif

21
include/arch/vmap.h Normal file
View file

@ -0,0 +1,21 @@
#pragma once
// Kernel mappings that are neither the direct map nor plain device registers.
// Both take one 1 GB slot of the device window ioremap() hands out, so each is
// limited to a gigabyte and meant to be called a handful of times at boot.
// Program this CPU's PAT so that a mapping can ask for write-combining. Every
// CPU runs it before it touches a write-combined mapping -- the PAT is per
// processor, and one left at its reset value reads the same bits as
// write-back, which on device memory the MTRRs then turn back into uncached.
void pat_init(void);
// Device memory, write-combined: stores are gathered into bursts rather than
// sent one at a time, and reads are as slow as uncached. For a framebuffer,
// never for registers. Returns 0 if there is no window left.
void *ioremap_wc(uint64 pa, uint64 size);
// `size` bytes of ordinary RAM, contiguous in the kernel's address space and
// not physically: kalloc() pages behind a page table of their own. Zeroed.
// For buffers too big for the DMA arena that no device reads. Never freed.
void *vmap_alloc(uint64 size);

255
include/arch/x86.h Normal file
View file

@ -0,0 +1,255 @@
#pragma once
struct dtr {
ushort limit;
uint64 base;
} __attribute__((packed));
static inline uchar
inb(ushort port)
{
uchar data;
asm volatile("in %1,%0" : "=a" (data) : "d" (port));
return data;
}
// Cycle counter. Not a clock -- it does not convert to time without knowing the
// frequency, and it is not synchronised across cores -- but for "which of these
// two paths costs more" it is exactly the right instrument, and the only one
// with fine enough resolution to see inside a single syscall.
static inline uint64
rdtsc(void)
{
uint lo, hi;
asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
return ((uint64)hi << 32) | lo;
}
// The x87/MMX/SSE register file, to and from a 512-byte, 16-byte-aligned area.
static inline void
fxsave(void *area)
{
asm volatile("fxsave64 (%0)" : : "r"(area) : "memory");
}
static inline void
fxrstor(const void *area)
{
asm volatile("fxrstor64 (%0)" : : "r"(area) : "memory");
}
static inline void
insl(int port, void *addr, int cnt)
{
asm volatile("cld; rep insl" :
"=D" (addr), "=c" (cnt) :
"d" (port), "0" (addr), "1" (cnt) :
"memory", "cc");
}
static inline void
outb(ushort port, uchar data)
{
asm volatile("out %0,%1" : : "a" (data), "d" (port));
}
// 16 bits. Added for AC'97, whose mixer is entirely 16-bit registers: reading
// one of them a byte at a time returns the low half twice on some codecs,
// because the read of the low byte is what latches the pair.
static inline ushort
inw(ushort port)
{
ushort data;
asm volatile("inw %1,%0" : "=a" (data) : "d" (port));
return data;
}
static inline uint
inl(ushort port)
{
uint data;
asm volatile("inl %1,%0" : "=a" (data) : "d" (port));
return data;
}
static inline void
outw(ushort port, ushort data)
{
asm volatile("out %0,%1" : : "a" (data), "d" (port));
}
// PCI configuration space is reached through a pair of 32-bit ports, so this
// is not optional decoration -- see kernel/dev/pci.c.
static inline void
outl(ushort port, uint data)
{
asm volatile("outl %0,%1" : : "a" (data), "d" (port));
}
static inline void
outsl(int port, const void *addr, int cnt)
{
asm volatile("cld; rep outsl" :
"=S" (addr), "=c" (cnt) :
"d" (port), "0" (addr), "1" (cnt) :
"cc");
}
static inline void
stosb(void *addr, int data, int cnt)
{
asm volatile("cld; rep stosb" :
"=D" (addr), "=c" (cnt) :
"0" (addr), "1" (cnt), "a" (data) :
"memory", "cc");
}
// 64-bit-only helpers. `rep stosq` and `xchgq` do not exist in 32-bit mode, so
// these must not even be parsed by the -m32 bootloader build -- they compiled
// there before only because uint64 was accidentally 32 bits wide, which is the
// bug include/core/types.h now asserts against.
#ifdef __x86_64__
static inline void
stosq(void *addr, uint64 data, int cnt)
{
asm volatile("cld; rep stosq" :
"=D" (addr), "=c" (cnt) :
"0" (addr), "1" (cnt), "a" (data) :
"memory", "cc");
}
struct segdesc;
static inline void
lgdt(struct segdesc *p, int size)
{
struct dtr d;
d.limit = size - 1;
d.base = (uint64)p;
asm volatile("lgdt (%0)" : : "r" (&d));
}
struct gatedesc;
static inline void
lidt(struct gatedesc *p, int size)
{
struct dtr d;
d.limit = size - 1;
d.base = (uint64)p;
asm volatile("lidt (%0)" : : "r" (&d));
}
static inline void
ltr(ushort sel)
{
asm volatile("ltr %0" : : "r" (sel));
}
static inline uint64
readeflags(void)
{
uint64 flags;
asm volatile("pushfq; pop %0" : "=r" (flags));
return flags;
}
static inline void
cli(void)
{
asm volatile("cli");
}
static inline void
sti(void)
{
asm volatile("sti");
}
static inline void
hlt(void)
{
asm volatile("hlt");
}
static inline uint64
xchg(volatile uint64 *addr, uint64 newval)
{
uint64 result;
asm volatile("lock; xchgq %0, %1" :
"+m" (*addr), "=a" (result) :
"1" (newval) :
"cc");
return result;
}
#endif /* __x86_64__ */
static inline uint64
rcr2(void)
{
uint64 val;
asm volatile("mov %%cr2,%0" : "=r" (val));
return val;
}
static inline void
lcr3(uint64 val)
{
asm volatile("mov %0,%%cr3" : : "r" (val));
}
// Invalidate the TLB entry for one page. Needed any time a PTE is changed
// or cleared for an address space that may still be the active CR3 —
// otherwise the CPU can keep translating through the stale entry until the
// next full CR3 reload (context switch), silently reading/writing the
// wrong physical page.
static inline void
invlpg(void *va)
{
asm volatile("invlpg (%0)" : : "r" (va) : "memory");
}
static inline void
wrmsr(uint32 msr, uint64 val)
{
uint32 low = (uint32)val;
uint32 high = (uint32)(val >> 32);
asm volatile("wrmsr" : : "c" (msr), "a" (low), "d" (high));
}
static inline uint64
rdmsr(uint32 msr)
{
uint32 low, high;
asm volatile("rdmsr" : "=a" (low), "=d" (high) : "c" (msr));
return ((uint64)high << 32) | low;
}
static inline uint64
readcs(void)
{
uint64 val;
asm volatile("mov %%cs, %0" : "=r" (val));
return val;
}
// CPUID. `leaf` in EAX, results in EAX/EBX/ECX/EDX.
//
// %rbx is callee-saved *and* the PIC base register on some builds, so it is
// exchanged out rather than named as an operand -- naming it directly is the
// classic way to get "inline asm clobbers reserved register" or, worse, silent
// corruption in the caller.
static inline void
cpuid_leaf(uint32 leaf, uint32 *a, uint32 *b, uint32 *c, uint32 *d)
{
uint32 ra, rb, rc, rd;
asm volatile("xchgq %%rbx, %q1\n\t"
"cpuid\n\t"
"xchgq %%rbx, %q1"
: "=a"(ra), "=&r"(rb), "=c"(rc), "=d"(rd)
: "0"(leaf), "2"(0));
if (a) *a = ra;
if (b) *b = rb;
if (c) *c = rc;
if (d) *d = rd;
}