Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure

Source tree reorganization:
- Move StarForth v3 engine to v3/ (src/, include/, Makefile)
- Move kernel to kernel/ (src/, include/, linker/, Makefile)
- Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md)
- Move FABRIC-0..4.md to docs/fabric/
- Move ONTOLOGY.md and ROADMAP.md to docs/

Board infrastructure:
- Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/
- Each board has board.mk (ISA, CPU flags, boot recipe) and README.md
- Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET
- make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board
- ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet)
- scripts/mkdiskimage.sh builds disk images for all boards

Docs pipeline:
- docs/book/ with LaTeX master (main.tex) and Makefile
- pandoc converts Markdown to LaTeX at build time
- Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks)
- make docs builds single PDF (754 pages, 0 missing characters)
- make docs TARGET=<board> adds board appendix
- build/docs/<book|board>/meta.tex stamps git commit into PDF

Bug fixes:
- 42 include paths that only worked by accident now use correct relative paths
- clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build)
- Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved
- Doxyfile, .clang-tidy, README.md, Kconfig paths updated

Verified:
- Hosted v3 build passes 1012 tests, 0 failures
- SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE)
- Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes
- make clean TARGET=<board> removes only that board and its ISA objects
- make all builds all boards, hosted v3, and docs in one run

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
rajames
2026-10-01 15:40:09 -04:00
co-authored by Junie
parent 3c709c115b
commit a8b70e88d3
630 changed files with 2646 additions and 2036 deletions
+11
View File
@@ -0,0 +1,11 @@
# src/starkernel/arch/amd64/
AMD64 architecture support for LithosAnanke.
- `arch.c` — architecture init entry point.
- `apic.c` — Local APIC interrupt controller driver.
- `timer.c` — TSC/HPET/APIC timer setup (100 Hz heartbeat tick).
- `interrupts.c` — IDT setup and interrupt dispatch.
- `boot.S` — early boot assembly.
- `kernel_entry.S` — kernel entry trampoline invoked from the loader.
- `isr.S` — interrupt service routine stubs.
File diff suppressed because it is too large Load Diff
+249
View File
@@ -0,0 +1,249 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* arch.c (amd64) - Architecture abstractions for StarKernel on x86_64
*/
#include "arch.h"
#include <stdint.h>
/*
* GDT entries for 64-bit mode.
* In long mode, the base and limit fields of code/data segments are ignored,
* but the access and flags bytes must be correct.
*/
struct gdt_entry {
uint16_t limit_low;
uint16_t base_low;
uint8_t base_mid;
uint8_t access;
uint8_t granularity; /* includes high nibble of limit */
uint8_t base_high;
} __attribute__((packed));
struct gdtr {
uint16_t limit;
uint64_t base;
} __attribute__((packed));
/*
* GDT layout:
* 0x00: Null descriptor
* 0x08: 64-bit kernel code segment (selector used in IDT)
* 0x10: Kernel data segment
*/
static struct gdt_entry gdt[3] __attribute__((aligned(16)));
/**
* @brief Populate a GDT entry with limit 0xFFFF and a zero base.
*
* In 64-bit long mode the base and limit fields of code/data segment
* descriptors are ignored by the processor; only the @p access and
* @p granularity flag bytes are architecturally significant. This helper
* writes the canonical long-mode descriptor layout: limit = 0xFFFF, all base
* bytes = 0, then stores the provided access and granularity values.
*
* @param index Index into the @c gdt[] array (0 = null, 1 = code, 2 = data).
* @param access Access byte (e.g., 0x9A for kernel code, 0x92 for data).
* @param granularity Granularity/flags byte (e.g., 0x20 sets the L-bit for 64-bit code).
*/
static void gdt_set_entry(int index, uint8_t access, uint8_t granularity)
{
gdt[index].limit_low = 0xFFFF;
gdt[index].base_low = 0;
gdt[index].base_mid = 0;
gdt[index].access = access;
gdt[index].granularity = granularity;
gdt[index].base_high = 0;
}
/**
* @brief Initialise and load the minimal 3-entry GDT required for long mode.
*
* Builds three GDT entries in the @c gdt[] array and loads the descriptor via
* @c LGDT followed by a far-return sequence to reload @c CS to selector 0x08
* and all other segment registers to 0x10:
*
* - Slot 0 (0x00): Null descriptor — all fields zero.
* - Slot 1 (0x08): 64-bit kernel code (access=0x9A, granularity=0x20).
* - Slot 2 (0x10): Kernel data (access=0x92, granularity=0x00).
*
* Must be called before @c arch_interrupts_init() because the IDT uses
* selector 0x08 for all gate descriptors.
*/
static void gdt_init(void)
{
/* Null descriptor */
gdt[0].limit_low = 0;
gdt[0].base_low = 0;
gdt[0].base_mid = 0;
gdt[0].access = 0;
gdt[0].granularity = 0;
gdt[0].base_high = 0;
/*
* 64-bit kernel code segment (selector 0x08)
* Access: 0x9A = Present (1), DPL 0 (00), S=1 (code/data), Exec (1), Conforming (0), Readable (1), Accessed (0)
* Granularity: 0x20 = Long mode (L=1), Default operand size (D=0, must be 0 for long mode)
*/
gdt_set_entry(1, 0x9A, 0x20);
/*
* Kernel data segment (selector 0x10)
* Access: 0x92 = Present (1), DPL 0 (00), S=1 (code/data), Exec (0), Direction (0), Writable (1), Accessed (0)
* Granularity: 0x00 (no special flags for data segment)
*/
gdt_set_entry(2, 0x92, 0x00);
struct gdtr gdtr_val;
gdtr_val.limit = sizeof(gdt) - 1;
gdtr_val.base = (uint64_t)&gdt[0];
__asm__ volatile (
"lgdt %0\n\t"
"pushq $0x08\n\t" /* Push new CS */
"leaq 1f(%%rip), %%rax\n\t" /* Load address of label 1 */
"pushq %%rax\n\t" /* Push return address */
"lretq\n\t" /* Far return to reload CS */
"1:\n\t"
"mov $0x10, %%ax\n\t" /* Load new DS/ES/SS/FS/GS */
"mov %%ax, %%ds\n\t"
"mov %%ax, %%es\n\t"
"mov %%ax, %%ss\n\t"
"mov %%ax, %%fs\n\t"
"mov %%ax, %%gs\n\t"
:
: "m"(gdtr_val)
: "rax", "memory"
);
}
/**
* @brief Perform the earliest x86-64 architecture initialisation.
*
* Called immediately after the UEFI boot stub transfers control to the
* kernel, before the console or interrupt controller are available.
* Loads the minimal three-entry GDT via @c gdt_init() so that subsequent
* code has valid segment selectors. IDT setup is deferred until after the
* console is live (called separately via @c arch_interrupts_init()).
*/
void arch_early_init(void)
{
gdt_init(); /* Must be done before IDT, since IDT uses selector 0x08 */
/* IDT setup is done later via arch_interrupts_init() after console is live */
}
/**
* @brief Enable hardware interrupts on the current CPU core (x86-64 STI).
*
* Issues the @c STI instruction to set the Interrupt Flag. Must be called
* after the IDT is loaded and the APIC is configured, otherwise the first
* interrupt will triple-fault.
*/
void arch_enable_interrupts(void)
{
__asm__ volatile ("sti");
}
/**
* @brief Disable hardware interrupts on the current CPU core (x86-64 CLI).
*
* Issues the @c CLI instruction to clear the Interrupt Flag. Used around
* critical sections in the single-threaded kernel where interrupt delivery
* would corrupt shared state.
*/
void arch_disable_interrupts(void)
{
__asm__ volatile ("cli");
}
/**
* @brief Halt the CPU until the next interrupt (x86-64 HLT).
*
* Issues the @c HLT instruction. In the kernel panic path this is called
* in an infinite loop (interrupts disabled) to stop the processor permanently.
* In normal use it is called once per idle iteration to reduce power consumption.
*/
void arch_halt(void)
{
__asm__ volatile ("hlt");
}
void arch_cold_reset(void)
{
arch_disable_interrupts();
/* Pulse keyboard controller reset line — standard bare-metal amd64 reset */
__asm__ volatile ("outb %0, %1" : : "a"((uint8_t)0xFE), "Nd"((uint16_t)0x64));
/* Should not reach here; spin as fallback */
for (;;) __asm__ volatile ("cli; hlt");
}
/**
* @brief Read the x86-64 Time Stamp Counter (RDTSC).
*
* Executes @c RDTSC and combines @c EDX:EAX into a 64-bit value. Does not
* serialise instruction execution; use @c RDTSCP or a surrounding @c CPUID
* where ordering guarantees are required.
*
* @return Current TSC value in CPU cycles.
*/
uint64_t arch_read_timestamp(void)
{
uint32_t lo, hi;
__asm__ volatile ("rdtsc" : "=a"(lo), "=d"(hi));
return ((uint64_t)hi << 32) | lo;
}
/**
* @brief Initialise the x86-64 MMU (stub — deferred to a later milestone).
*
* Full four-level page table setup is deferred; the VMM subsystem
* (@c vmm.c) handles page table management independently. This function
* exists as an architectural hook for future per-CPU MMU configuration
* that must run after GDT/IDT setup but before the first memory access
* through a non-identity-mapped virtual address.
*/
void arch_mmu_init(void)
{
/* Stub: paging will be wired up in later milestones */
}
+10
View File
@@ -0,0 +1,10 @@
/*
* boot.S (amd64) - Placeholder entry for future low-level bring-up.
* The UEFI loader currently calls efi_main() directly; this stub exists
* to reserve the symbol for later early-boot work.
*/
.section .text.entry
.code64
.global arch_bootstrap
arch_bootstrap:
ret
+150
View File
@@ -0,0 +1,150 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* i8042.c - PS/2 keyboard controller driver (amd64)
*
* Item 4.3.5 (FABRIC-0.md §27.5).
*/
#ifndef __STARKERNEL__
#error "i8042.c is kernel-only"
#endif
#include <stdint.h>
#include "starkernel/i8042.h"
#include "console.h"
#define I8042_DATA_PORT 0x60
#define I8042_STATUS_PORT 0x64
#define I8042_CMD_PORT 0x64
#define I8042_STATUS_OUTPUT_FULL (1u << 0)
#define I8042_STATUS_INPUT_FULL (1u << 1)
#define I8042_CMD_READ_CONFIG 0x20
#define I8042_CMD_WRITE_CONFIG 0x60
#define I8042_CONFIG_IRQ1_ENABLE (1u << 0)
#define I8042_DEV_CMD_ENABLE_SCANNING 0xF4
#define I8042_DEV_ACK 0xFA
/* Bounded wait -- command handshakes during one-time init only, never in
* the interrupt path or a per-keystroke loop. */
#define I8042_INIT_WAIT_ITERS 100000
static inline void outb(uint16_t port, uint8_t val) {
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
}
static inline uint8_t inb(uint16_t port) {
uint8_t val;
__asm__ volatile ("inb %1, %0" : "=a"(val) : "Nd"(port));
return val;
}
void i8042_init(void) {
/* Drain any stale output byte left by firmware before enabling IRQ1 —
* otherwise the first "interrupt" would really be this leftover byte
* read via polling, not a real IRQ1 delivery. */
while (inb(I8042_STATUS_PORT) & I8042_STATUS_OUTPUT_FULL) {
(void)inb(I8042_DATA_PORT);
}
outb(I8042_CMD_PORT, I8042_CMD_READ_CONFIG);
uint8_t config = inb(I8042_DATA_PORT);
config |= I8042_CONFIG_IRQ1_ENABLE;
outb(I8042_CMD_PORT, I8042_CMD_WRITE_CONFIG);
outb(I8042_DATA_PORT, config);
/* Tell the keyboard device itself to (re)enable scanning. OVMF's own
* PS/2 driver commonly sends 0xF5 (disable scanning) to the device as
* part of its own teardown before handoff, the same way it stops
* driving the PIT/PIC -- the controller's IRQ1-enable bit above only
* governs the controller, not whether the device generates scancodes
* at all. Bounded waits: one-time init handshake, not steady-state
* polling. */
{
int i;
for (i = 0; i < I8042_INIT_WAIT_ITERS; i++) {
if (!(inb(I8042_STATUS_PORT) & I8042_STATUS_INPUT_FULL)) break;
}
outb(I8042_DATA_PORT, I8042_DEV_CMD_ENABLE_SCANNING);
for (i = 0; i < I8042_INIT_WAIT_ITERS; i++) {
if (inb(I8042_STATUS_PORT) & I8042_STATUS_OUTPUT_FULL) {
uint8_t resp = inb(I8042_DATA_PORT);
if (resp == I8042_DEV_ACK) {
console_println("i8042: keyboard ACKed enable-scanning");
} else {
console_println("i8042: keyboard responded (not ACK) to enable-scanning");
}
break;
}
}
}
console_println("i8042: IRQ1 enabled in controller config byte");
}
void i8042_drain_stale(void) {
/* Edge-triggered IRQ1 + OBF already set at unmask time == no rising
* edge ever again (the textbook "fires once, then never" pattern).
* i8042_init()'s drain happens at M4; VM bootstrap runs for real wall
* time between then and unmask, so re-drain immediately before. */
while (inb(I8042_STATUS_PORT) & I8042_STATUS_OUTPUT_FULL) {
(void)inb(I8042_DATA_PORT);
}
}
/* Ring buffer: single producer (the ISR), single reader (i8042_pop_scancode(),
* called from ordinary non-interrupt context, e.g. a diagnostic FORTH word).
* 32 entries is far more than one QEMU test keypress burst can fill. */
#define RING_SIZE 32
static volatile uint8_t ring[RING_SIZE];
static volatile uint8_t ring_head = 0; /* next slot the ISR writes */
static volatile uint8_t ring_tail = 0; /* next slot the reader takes */
/* Count of real keyboard IRQs serviced since boot -- a plain counter, no
* I/O, is cheap enough to keep in the ISR itself and doubles as the
* quickest way to confirm the interrupt path is alive without touching
* the ring buffer at all. */
volatile uint32_t g_i8042_isr_count = 0;
void i8042_handle_irq(void) {
/* Must always read 0x60, even to discard — otherwise OBF never clears
* and the controller stops delivering further interrupts. */
uint8_t scancode = inb(I8042_DATA_PORT);
g_i8042_isr_count++;
uint8_t next_head = (uint8_t)((ring_head + 1) % RING_SIZE);
if (next_head != ring_tail) { /* drop the byte if the ring is full */
ring[ring_head] = scancode;
ring_head = next_head;
}
}
int i8042_pop_scancode(uint8_t *out) {
if (ring_tail == ring_head) return 0; /* empty */
*out = ring[ring_tail];
ring_tail = (uint8_t)((ring_tail + 1) % RING_SIZE);
return 1;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+227
View File
@@ -0,0 +1,227 @@
/*
* isr.S - Interrupt/exception stubs for amd64
*
* Stack on entry to ISR stub (after CPU pushes):
* [rsp+0] = error code (if any, or pushed 0)
* [rsp+8] = vector (pushed by stub)
* [rsp+16] = RIP
* [rsp+24] = CS
* [rsp+32] = RFLAGS
* [rsp+40] = RSP (if privilege change)
* [rsp+48] = SS (if privilege change)
*
* For IRQs (vector >= 32), we return via IRETQ.
* For exceptions (vector < 32), we halt.
*/
.intel_syntax noprefix
.section .text
/* Mark symbols referenced from C as hidden so the C compiler uses direct
* RIP-relative addressing (LEA) rather than GOT indirection. Without this,
* -fPIC code collapses the GOT reference to a direct MOV that reads the
* symbol's *bytes* instead of its *address*, corrupting the IDT setup. */
.hidden isr_stub_table
.hidden isr_stub0
.hidden isr_stub32
.hidden isr_stub33
.hidden isr_stub_default
.hidden isr_common_entry
.macro ISR_NOERR num
.global isr_stub\num
isr_stub\num:
push 0 /* fake error code */
push \num /* vector number */
jmp isr_common_entry
.endm
.macro ISR_ERR num
.global isr_stub\num
isr_stub\num:
/* error code already on stack from CPU */
push \num /* vector number */
jmp isr_common_entry
.endm
.global isr_common_entry
isr_common_entry:
/* Save all general-purpose registers */
push rax
push rbx
push rcx
push rdx
push rsi
push rdi
push rbp
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
/* Stack layout now:
* [rsp+0] = r15
* [rsp+8] = r14
* ...
* [rsp+112] = rax
* [rsp+120] = vector
* [rsp+128] = error
* [rsp+136] = rip
* [rsp+144] = cs
* [rsp+152] = rflags
*/
/* FABRIC-3.md SXXVIII (Stage 0, preemptive-context-switch trap-frame
* parity, 2026-09-13): FXSAVE/FXRSTOR added here to close a real,
* pre-existing gap, not just a future-preemption nicety -- confirmed
* live double-precision FP code reachable from ordinary interpreter
* dispatch (vm_runtime.c's Loop #5/#6 physics inference, called from
* vm_tick() during word execution, not just at boot). The GPR pushes
* above only cover integer state; on x86-64 SysV all XMM registers are
* caller-saved, meaning the compiler is free to leave a live value in
* one between any two instructions of straight-line code -- an
* asynchronous interrupt is not a call site the interrupted code's own
* instructions know about, so nothing here previously guaranteed FP
* state actually survived a timer tick landing mid-computation.
*
* rbp is repurposed below as a fixed anchor pointing at "rsp right after
* the 15 GPR pushes" (rbp's own true pushed value already lives safely
* on the stack above and is restored by the ordinary `pop rbp` later) --
* FXSAVE requires a 16-byte-aligned address and rsp's alignment here is
* unpredictable (the interrupt could have landed anywhere), so the
* FXSAVE area is carved out and aligned separately, with rbp used to
* keep addressing everything else at its original, fixed offsets
* regardless of that adjustment. */
mov rbp, rsp
sub rsp, 528 /* 512-byte FXSAVE area + 16 slack for alignment */
and rsp, -16
fxsave [rsp]
/* Set up arguments for isr_common_handler(vector, error, rip, cs, rflags, cr2) */
mov rdi, [rbp + 120] /* vector */
mov rsi, [rbp + 128] /* error */
mov rdx, [rbp + 136] /* rip */
mov rcx, [rbp + 144] /* cs */
mov r8, [rbp + 152] /* rflags */
mov r9, cr2 /* cr2 */
/* Pass stack frame pointer (after pushes) as 7th argument */
push rbp
call isr_common_handler
add rsp, 8
fxrstor [rsp]
mov rsp, rbp /* collapse back to right after the GPR pushes */
/* Restore general-purpose registers */
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
/* Remove vector and error code from stack */
add rsp, 16
/* Return from interrupt */
iretq
/* Exception/interrupt stubs (0-31 explicit) */
ISR_NOERR 0
ISR_NOERR 1
ISR_NOERR 2
ISR_NOERR 3
ISR_NOERR 4
ISR_NOERR 5
ISR_NOERR 6
ISR_NOERR 7
ISR_ERR 8
ISR_NOERR 9
ISR_ERR 10
ISR_ERR 11
ISR_ERR 12
ISR_ERR 13
ISR_ERR 14
ISR_NOERR 15
ISR_NOERR 16
ISR_ERR 17
ISR_NOERR 18
ISR_NOERR 19
ISR_NOERR 20
ISR_ERR 21
ISR_NOERR 22
ISR_NOERR 23
ISR_NOERR 24
ISR_NOERR 25
ISR_NOERR 26
ISR_NOERR 27
ISR_NOERR 28
ISR_NOERR 29
ISR_ERR 30
ISR_NOERR 31
/* Stub for APIC Timer (vector 0x20 = 32) */
/* Diagnostic: write raw 'I' to COM1 (0x3F8) when stub is entered.
* If 'I' never appears in the serial log, the stub is never reached
* (bad IDT entry or fault during CPU interrupt delivery). */
.global isr_stub32
isr_stub32:
push 0
push 0x20
jmp isr_common_entry
/* Stub for i8042 keyboard IRQ1, routed via I/O APIC to vector 0x21 = 33
* (item 4.3.5, FABRIC-0.md §27.5). Before this stub existed, vector 33 fell
* through to isr_stub_default below, which unconditionally reports "255"
* regardless of which IDT slot actually fired -- the CPU legitimately took
* IDT[33] (confirmed via the LAPIC's own ISR register), but isr_common_handler
* only ever saw vector 255 and treated a real keyboard IRQ as spurious,
* silently skipping EOI and leaving vector 33 permanently stuck in-service. */
.global isr_stub33
isr_stub33:
push 0
push 33
jmp isr_common_entry
/* Default stub for other vectors (34-255) */
.global isr_stub_default
isr_stub_default:
push 0 /* fake error */
push 255 /* unknown vector, will be logged */
jmp isr_common_entry
/* Use .data instead of .rodata so UEFI applies relocations */
.section .data
.align 8
.global isr_stub_table
isr_stub_table:
.quad isr_stub0, isr_stub1, isr_stub2, isr_stub3
.quad isr_stub4, isr_stub5, isr_stub6, isr_stub7
.quad isr_stub8, isr_stub9, isr_stub10, isr_stub11
.quad isr_stub12, isr_stub13, isr_stub14, isr_stub15
.quad isr_stub16, isr_stub17, isr_stub18, isr_stub19
.quad isr_stub20, isr_stub21, isr_stub22, isr_stub23
.quad isr_stub24, isr_stub25, isr_stub26, isr_stub27
.quad isr_stub28, isr_stub29, isr_stub30, isr_stub31
/* Vector 32 = APIC Timer */
.quad isr_stub32
/* Vector 33 = i8042 keyboard IRQ1 (item 4.3.5) */
.quad isr_stub33
/* Vectors 34-255 default to unknown stub */
.rept 222
.quad isr_stub_default
.endr
+63
View File
@@ -0,0 +1,63 @@
/*
* kernel_entry.S — kernel stack setup and entry trampoline (amd64)
*
* UEFI hands control to kernel_main() running on a small UEFI-provided stack
* (typically 128 KB–256 KB). The FORTH interpreter + DOE experiment loop can
* easily exceed that depth. This trampoline allocates a 2 MiB BSS stack and
* switches to it before calling the real implementation (kernel_main_impl).
*
* kernel_main is the symbol the UEFI loader calls (extern in uefi_loader.c).
* kernel_main_impl is defined in kernel_main.c and contains all the C code.
*
* Calling convention: SysV AMD64 — boot_info pointer arrives in %rdi and is
* passed unchanged to kernel_main_impl via the tail-call.
*/
.intel_syntax noprefix
#include "starkernel/boot_info_offsets.h"
/* 2 MiB kernel stack in BSS — fallback when no dynamic stack requested */
.section .bss
.align 16
.global g_kernel_stack
g_kernel_stack:
.space 0x200000
g_kernel_stack_top:
.global g_kernel_stack_top
.section .text
.extern kernel_main_impl
/*
* kernel_main — stack-switch trampoline, then tail-call kernel_main_impl.
*
* rdi = BootInfo* (SysV AMD64 ABI — first argument register)
*
* Stack selection (checked in order):
* 1. boot_info->kernel_stack_base != 0 → use loader-allocated stack
* (top = base + size, aligned down to 16, minus 8 for ABI)
* 2. Otherwise → fall back to g_kernel_stack (2 MiB BSS)
*
* Never returns; kernel_main_impl runs the REPL forever or panics.
*/
.global kernel_main
kernel_main:
mov rax, [rdi + BOOT_INFO_KERNEL_STACK_BASE_OFFSET]
test rax, rax
jnz .Ldynamic_stack
/* BSS fallback */
lea rax, [rip + g_kernel_stack_top]
jmp .Lstack_ready
.Ldynamic_stack:
mov rcx, [rdi + BOOT_INFO_KERNEL_STACK_SIZE_OFFSET]
add rax, rcx /* top = base + size (stack grows down) */
.Lstack_ready:
and rax, -16 /* 16-byte alignment */
sub rax, 8 /* SysV ABI: RSP ≡ 8 mod 16 at C call boundary */
mov rsp, rax
xor rbp, rbp /* terminate frame chain */
jmp kernel_main_impl /* rdi (boot_info) already in place */
+79
View File
@@ -0,0 +1,79 @@
/*
* switch.S (amd64) - Cooperative VM context switch primitive.
*
* FABRIC-3.md §XXVIII (Stage 2, preemptive-context-switch save/restore
* primitive, 2026-09-13). This is deliberately NOT the same mechanism as
* isr.S's interrupt trap frame (Stage 0) -- that saves every register
* because an interrupt is not a call site the interrupted code knows
* about. sk_vm_switch_to() IS an ordinary function call (SWITCH-TO's C
* handler calls it directly), so the standard System V AMD64 ABI already
* guarantees every caller-saved register (rax, rcx, rdx, rsi, rdi, r8-r11,
* all XMM) is the caller's own problem to preserve across the call, exactly
* like any other call. Only the callee-saved set -- rbx, rbp, r12-r15 --
* needs explicit handling here, because this function does not really
* "return" in the normal sense: it swaps to a completely different stack
* and (from the newly-resumed context's point of view) appears to return
* from whichever earlier call to this same function originally parked it.
* SysV has no callee-saved XMM registers at all, so there is no FP state
* to carry here.
*
* void sk_vm_switch_to(uint64_t *save_sp_out, uint64_t new_sp);
* rdi = save_sp_out, rsi = new_sp
*
* For a VM that has never run before, new_sp must point at a synthetic
* frame built to this exact layout by the C glue (switch.c): six zeroed
* quadwords (r15, r14, r13, r12, rbx, rbp -- values irrelevant, never a
* real prior context) followed by the trampoline entry address at
* new_sp+48, so the final `ret` below jumps there instead of into some
* prior caller.
*/
.intel_syntax noprefix
.section .text
.hidden sk_vm_switch_to
.global sk_vm_switch_to
sk_vm_switch_to:
push rbp
push rbx
push r12
push r13
push r14
push r15
mov [rdi], rsp /* *save_sp_out = rsp (this context's parked SP) */
mov rsp, rsi /* rsp = new_sp (switch stacks) */
pop r15
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
/*
* uint64_t sk_vm_switch_prime(uint64_t stack_top, void *entry);
*
* Builds the synthetic frame described above at the top of a fresh native
* stack, so the first ever sk_vm_switch_to() into it lands at `entry`
* instead of some real prior caller. Returns the resulting sp value to
* pass as sk_vm_switch_to()'s new_sp. Kept in this file (not C) so the
* layout can never drift from what sk_vm_switch_to() above actually
* expects -- one set of offsets, not two copies to keep in sync.
*/
.hidden sk_vm_switch_prime
.global sk_vm_switch_prime
sk_vm_switch_prime:
mov rax, rdi
sub rax, 56
mov qword ptr [rax+0], 0 /* r15 */
mov qword ptr [rax+8], 0 /* r14 */
mov qword ptr [rax+16], 0 /* r13 */
mov qword ptr [rax+24], 0 /* r12 */
mov qword ptr [rax+32], 0 /* rbx */
mov qword ptr [rax+40], 0 /* rbp */
mov [rax+48], rsi /* return address = entry */
ret
File diff suppressed because it is too large Load Diff