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>
250 lines
8.1 KiB
C
250 lines
8.1 KiB
C
/*
|
||
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 */
|
||
}
|