Implements Event Ring TRB parsing and ERDP dequeue-pointer update
(xhci_poll_events(), src/starkernel/usb/xhci.c), called from
sk_repl_idle()'s existing ~1s idle cadence rather than a per-arch
interrupt handler.
A first attempt wired real interrupt delivery (PCI->IOAPIC GSI routing,
a dedicated isr_stub34/vector 0x22, GIC/PLIC routing mirroring
virtio_input.c). Checked live via QMP query-pci before trusting it: the
amd64 PIRQ swizzle formula predicted GSI 16 for the xHCI controller at
PCI slot 4; the real QEMU-assigned IRQ was 10, and embedded ICH9
functions contradicted the same formula too. Reverted all of it back to
the exact committed baseline rather than chasing chipset PIRQ routing
further, and reframed around Section U item 6's own design intent
("interrupt-driven, coarse cadence, cheap early-exit... quick check
blocks... done") via sk_repl_idle() instead -- USB insertion is a
human-timescale event, not a hot path.
Added -device qemu-xhci to all three QEMU launch targets (required for
any of this to be testable). Verified end to end via genuine post-boot
hotplug (QMP device_add/device_del usb-storage): all three architectures
detect a live attach within seconds. A false-alarm heartbeat "freeze"
found mid-verification traced to querying the wrong counter
(vm->heartbeat.tick_count, which only advances during word execution,
not the kernel's real ISR-driven heartbeat_ticks()) -- confirmed via a
temporary diagnostic word, captured and reverted.
Full writeup, including the discarded interrupt-routing attempt and the
false-alarm investigation, in FABRIC-2.md's Milestone 2c/2d entries.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
1070 lines
43 KiB
Makefile
1070 lines
43 KiB
Makefile
# ==============================================================================
|
|
# Makefile.starkernel — StarKernel UEFI Build System
|
|
# Multi-architecture: amd64 (x86_64), aarch64 (ARM64), riscv64 (RISC-V 64)
|
|
#
|
|
# Quick start:
|
|
# make -f Makefile.starkernel — build for host arch
|
|
# make -f Makefile.starkernel ARCH=amd64 qemu — build + boot (x86_64)
|
|
# make -f Makefile.starkernel ARCH=aarch64 qemu — build + boot (aarch64)
|
|
# make -f Makefile.starkernel ARCH=riscv64 qemu — build + boot (riscv64)
|
|
# make -f Makefile.starkernel help — show all targets
|
|
# ==============================================================================
|
|
|
|
# ==============================================================================
|
|
# CONFIGURATION
|
|
# ==============================================================================
|
|
|
|
# Architecture normalization — accept common aliases
|
|
# x86_64 → amd64
|
|
# arm64 → aarch64
|
|
# riscv → riscv64
|
|
canon_arch = $(strip \
|
|
$(if $(filter x86_64,$1),amd64, \
|
|
$(if $(filter amd64,$1),amd64, \
|
|
$(if $(filter arm64,$1),aarch64, \
|
|
$(if $(filter aarch64,$1),aarch64, \
|
|
$(if $(filter riscv riscv64,$1),riscv64,$1))))))
|
|
|
|
UNAME_M := $(shell uname -m)
|
|
ARCH ?= $(call canon_arch,$(UNAME_M))
|
|
override ARCH := $(call canon_arch,$(ARCH))
|
|
|
|
ifeq ($(filter amd64 aarch64 riscv64,$(ARCH)),)
|
|
$(error Unsupported ARCH='$(ARCH)'. Use: amd64, aarch64, riscv64)
|
|
endif
|
|
|
|
# Kconfig bridge (menuconfig/xconfig/config/oldconfig/*_defconfig). See
|
|
# mk/Kconfig.mk for why KCONFIG_ARCH_DIR must be set here, before include.
|
|
# .DEFAULT_GOAL is pinned explicitly because mk/Kconfig.mk's first rule
|
|
# would otherwise silently become Make's default goal, since it's included
|
|
# before this Makefile's own `all:` target is defined below.
|
|
.DEFAULT_GOAL := all
|
|
KCONFIG_ARCH_DIR := $(ARCH)
|
|
include mk/Kconfig.mk
|
|
|
|
# Target type (default: kernel)
|
|
TARGET ?= kernel
|
|
|
|
# Kernel-only flags (Phase 6): repointed through the Kconfig bridge, same
|
|
# drop-in-replacement-for-`?=` behavior as every other family in this
|
|
# migration. Kconfig.kernel's symbols only exist for a kernel-variant
|
|
# .config (`if STARFORTH_VARIANT_KERNEL`), so a hosted-variant .config
|
|
# leaves CONFIG_PARITY_MODE/etc undefined and kconfig_bool falls back to
|
|
# its $2 default exactly as if Kconfig were inactive -- this file is never
|
|
# built from a hosted-variant config in practice, but the fallback is
|
|
# correct either way.
|
|
|
|
# Parity mode (0 = normal, 1 = parity harness)
|
|
$(eval $(call kconfig_bool,PARITY_MODE,0))
|
|
|
|
# StarForth VM integration (default: enabled)
|
|
$(eval $(call kconfig_bool,STARFORTH_ENABLE_VM,1))
|
|
|
|
# Monolithic build — loader + kernel compiled together (default: enabled)
|
|
MONOLITHIC ?= 1
|
|
|
|
# Parallel build — use all available cores when not cleaning
|
|
# (adding -j when 'clean' is also a goal causes a race on include/version.h)
|
|
NPROC := $(shell nproc 2>/dev/null || echo 4)
|
|
ifeq ($(filter clean,$(MAKECMDGOALS)),)
|
|
MAKEFLAGS += -j$(NPROC)
|
|
endif
|
|
|
|
# Version
|
|
# Roadmap:
|
|
# v1.0.x — serial-only production (released)
|
|
# v1.5.x — framebuffer VT100 terminal/console milestone (current)
|
|
# v2.0.0 — StarForth SDK release; v2.x.x development begins from there
|
|
VERSION ?= 3.1.0
|
|
LITHOS_VERSION ?= 1.5.4
|
|
|
|
# ==============================================================================
|
|
# BUILD PATHS
|
|
# ==============================================================================
|
|
|
|
BUILD_DIR := build/$(ARCH)/$(TARGET)
|
|
OBJ_DIR := $(BUILD_DIR)/obj
|
|
LOADER_OBJ_DIR := $(OBJ_DIR)/loader
|
|
KERNEL_OBJ_DIR := $(OBJ_DIR)/kernel
|
|
|
|
LOADER_EFI := $(BUILD_DIR)/starkernel_loader.efi
|
|
KERNEL_ELF := $(BUILD_DIR)/starkernel_kernel.elf
|
|
|
|
KERNEL_SRC := src/starkernel
|
|
KERNEL_INC := include/starkernel
|
|
STARFORTH_CONFIG_HEADER := include/starforth_config.h
|
|
|
|
QEMU_LOG_DIR := logs
|
|
QEMU_ISO := $(BUILD_DIR)/starkernel.iso
|
|
DOE_RUNS_DIR := experiments/bare_metal/runs
|
|
DOE_LATEST_DIR := experiments/bare_metal/latest
|
|
|
|
CAPSULES_DIR ?= capsules
|
|
ARTDISK ?= disk/artemis.img
|
|
MKCAPSULE_SRC = tools/mkcapsule.c
|
|
MKCAPSULE_BIN = $(BUILD_DIR)/tools/mkcapsule
|
|
CAPSULE_GENERATED = $(BUILD_DIR)/capsule_generated.c
|
|
CAPSULE_GENERATED_OBJ = $(BUILD_DIR)/capsule_generated.o
|
|
CAPSULE_GENERATED_KOBJ = $(KERNEL_OBJ_DIR)/capsule_generated.o
|
|
|
|
# ==============================================================================
|
|
# TOOLCHAIN
|
|
# ==============================================================================
|
|
|
|
ifeq ($(ARCH),amd64)
|
|
ifneq ($(shell which x86_64-elf-gcc 2>/dev/null),)
|
|
CC := x86_64-elf-gcc
|
|
LD := x86_64-elf-ld
|
|
OBJCOPY := x86_64-elf-objcopy
|
|
else
|
|
CC := gcc
|
|
LD := ld
|
|
OBJCOPY := objcopy
|
|
endif
|
|
# -fno-pic -fno-pie: this is a fixed-base, statically-linked freestanding
|
|
# image with no dynamic linker to populate a GOT at load time. -fPIC (set
|
|
# in COMMON_CFLAGS, needed by riscv64's -shared link pipeline) makes GCC
|
|
# emit R_X86_64_REX_GOTPCRELX (GOT-indirect) addressing for some cross-TU
|
|
# extern globals depending on per-call-site codegen heuristics; the "GOT
|
|
# slot" is just an unpopulated .bss cell here, so those reads silently
|
|
# return NULL instead of the real address. Root-caused 2026-08-06 via
|
|
# dict_find_word_heat_aware() reading sf_fc_count as NULL while
|
|
# vm_find_word() (same globals, same TU as the definition) read it
|
|
# correctly. These flags come after COMMON_CFLAGS on the command line so
|
|
# they win for amd64 only; riscv64/aarch64 keep -fPIC.
|
|
ARCH_CFLAGS := -m64 -march=x86-64 -mno-red-zone -DARCH_AMD64 -fno-pic -fno-pie
|
|
LOADER_LINKER_SCRIPT := linker/starkernel-loader-amd64.ld
|
|
KERNEL_LINKER_SCRIPT := linker/starkernel-kernel-amd64.ld
|
|
|
|
else ifeq ($(ARCH),aarch64)
|
|
ifneq ($(shell which aarch64-linux-gnu-gcc 2>/dev/null),)
|
|
CC := aarch64-linux-gnu-gcc
|
|
LD := aarch64-linux-gnu-ld
|
|
OBJCOPY := aarch64-linux-gnu-objcopy
|
|
else ifneq ($(shell which aarch64-none-elf-gcc 2>/dev/null),)
|
|
CC := aarch64-none-elf-gcc
|
|
LD := aarch64-none-elf-ld
|
|
OBJCOPY := aarch64-none-elf-objcopy
|
|
else
|
|
$(error No ARM64 toolchain found. Install aarch64-linux-gnu-gcc or aarch64-none-elf-gcc)
|
|
endif
|
|
# aarch64 loader uses clang + lld-link (COFF target — GCC cannot produce aarch64 PE)
|
|
LOADER_CC := clang-18
|
|
# Debian/Ubuntu package only installs the versioned lld-link-18 on PATH by
|
|
# default (unversioned lld-link lives under /usr/lib/llvm-18/bin, which
|
|
# isn't on PATH unless explicitly prepended) — detect whichever resolves.
|
|
LOADER_LD := $(shell which lld-link 2>/dev/null || which lld-link-18 2>/dev/null || echo lld-link)
|
|
ARCH_CFLAGS := -march=armv8-a -mcpu=cortex-a72 -DARCH_AARCH64 -mno-outline-atomics
|
|
LOADER_LINKER_SCRIPT := linker/starkernel-loader-aarch64.ld
|
|
KERNEL_LINKER_SCRIPT := linker/starkernel-kernel-aarch64.ld
|
|
|
|
else ifeq ($(ARCH),riscv64)
|
|
ifneq ($(shell which riscv64-unknown-elf-gcc 2>/dev/null),)
|
|
CC := riscv64-unknown-elf-gcc
|
|
LD := riscv64-unknown-elf-ld
|
|
OBJCOPY := riscv64-unknown-elf-objcopy
|
|
else ifneq ($(shell which riscv64-linux-gnu-gcc 2>/dev/null),)
|
|
CC := riscv64-linux-gnu-gcc
|
|
LD := riscv64-linux-gnu-ld
|
|
OBJCOPY := riscv64-linux-gnu-objcopy
|
|
else
|
|
$(error No RISC-V toolchain found. Install riscv64-unknown-elf-gcc or riscv64-linux-gnu-gcc)
|
|
endif
|
|
ARCH_CFLAGS := -march=rv64gc -mabi=lp64d -DARCH_RISCV64
|
|
LOADER_LINKER_SCRIPT := linker/starkernel-loader-riscv64.ld
|
|
KERNEL_LINKER_SCRIPT := linker/starkernel-kernel-riscv64.ld
|
|
endif
|
|
|
|
# Default loader toolchain = kernel toolchain (overridden for aarch64 above)
|
|
LOADER_CC ?= $(CC)
|
|
LOADER_LD ?= $(LD)
|
|
|
|
# ==============================================================================
|
|
# COMPILER FLAGS
|
|
# ==============================================================================
|
|
|
|
# Common flags for all arches / both loader and kernel.
|
|
# -fPIC + -fvisibility=hidden: needed by riscv64's -shared loader link
|
|
# pipeline (amd64 overrides back to -fno-pic/-fno-pie in its ARCH_CFLAGS
|
|
# above -- see that comment for why: -fPIC made GCC emit GOT-indirect
|
|
# addressing for some cross-TU extern globals, and this freestanding image
|
|
# has no dynamic linker to populate a GOT, so those reads silently returned
|
|
# NULL. PLATFORM_TIME_NO_INLINE (removed 2026-08-06) was a symbol-specific
|
|
# workaround for the same underlying bug, made unnecessary once amd64 got
|
|
# the real fix; see FABRIC.md for the write-up).
|
|
COMMON_CFLAGS := \
|
|
-std=c99 -Wall -Werror -Wextra \
|
|
-O2 -U_FORTIFY_SOURCE \
|
|
-ffreestanding -nostdlib -fno-builtin \
|
|
-fPIC -fvisibility=hidden -fno-stack-protector -fshort-wchar \
|
|
$(ARCH_CFLAGS) \
|
|
-I$(KERNEL_INC) -Iinclude -Isrc \
|
|
-include $(STARFORTH_CONFIG_HEADER) \
|
|
-DPARITY_MODE=$(PARITY_MODE)
|
|
|
|
VMCORE_CFLAGS_COMMON := \
|
|
$(filter-out -I$(KERNEL_INC),$(COMMON_CFLAGS)) \
|
|
-Iinclude -I. -Isrc/word_source -Isrc/test_runner/include \
|
|
-Wno-error=unused-parameter -Wno-error=shift-negative-value \
|
|
-Wno-error=sign-compare -Wno-error=missing-field-initializers
|
|
|
|
# aarch64 loader flags: clang COFF target — drop ELF-only flags (-fPIC,
|
|
# -fvisibility=hidden), add COFF-specific flags instead.
|
|
ifeq ($(ARCH),aarch64)
|
|
LOADER_BASE_CFLAGS := \
|
|
-std=c99 -Wall -Werror -Wextra \
|
|
--target=aarch64-pc-windows-msvc \
|
|
-ffreestanding -fno-builtin \
|
|
-fno-stack-protector -fshort-wchar \
|
|
-mno-stack-arg-probe \
|
|
-march=armv8-a \
|
|
-DARCH_AARCH64 \
|
|
-I$(KERNEL_INC) -Iinclude -Isrc \
|
|
-Iinclude/starkernel/freestanding \
|
|
-include $(STARFORTH_CONFIG_HEADER) \
|
|
-DPARITY_MODE=$(PARITY_MODE) \
|
|
-DPLATFORM_TIME_NO_INLINE
|
|
else
|
|
LOADER_BASE_CFLAGS := $(COMMON_CFLAGS)
|
|
endif
|
|
|
|
DOE_INJECT ?= 0
|
|
DOE_REPS ?= 3
|
|
DOE_SEED ?= 12345
|
|
# EMERGENCY_CONSOLE_ENABLED is blanket-forwarded (unlike the opt-in-only
|
|
# knobs below), so it's repointed through the Kconfig bridge the same way
|
|
# as the hosted Makefile's STRICT_PTR (Phase 3) -- Phase 4 heartbeat family.
|
|
$(eval $(call kconfig_bool,EMERGENCY_CONSOLE_ENABLED,1))
|
|
LOADER_CFLAGS := $(LOADER_BASE_CFLAGS) -I$(KERNEL_SRC)/vm
|
|
KERNEL_CFLAGS := $(COMMON_CFLAGS) -D__STARKERNEL__ -I$(KERNEL_SRC)/vm
|
|
|
|
# Linker flags
|
|
ifeq ($(ARCH),aarch64)
|
|
LOADER_LDFLAGS_PE := # lld-link called directly in link rule
|
|
else ifeq ($(ARCH),riscv64)
|
|
LOADER_LDFLAGS_PE := # ELF→objcopy pipeline in link rule
|
|
else
|
|
LOADER_LINKER_SCRIPT_PE := linker/starkernel-loader-amd64-pe.ld
|
|
LOADER_LDFLAGS_PE := \
|
|
-T $(LOADER_LINKER_SCRIPT_PE) -m i386pep -nostdlib \
|
|
--enable-reloc-section --image-base 0 --subsystem 10 -e efi_main
|
|
endif
|
|
|
|
LOADER_LDFLAGS := \
|
|
-T $(LOADER_LINKER_SCRIPT) -nostdlib --build-id=none \
|
|
--emit-relocs -shared -Bsymbolic -znocombreloc -e efi_main
|
|
KERNEL_LDFLAGS := \
|
|
-T $(KERNEL_LINKER_SCRIPT) -nostdlib --build-id=none \
|
|
--emit-relocs -e kernel_main
|
|
|
|
# ==============================================================================
|
|
# VM INTEGRATION FLAGS
|
|
# ==============================================================================
|
|
|
|
ifeq ($(STARFORTH_ENABLE_VM),1)
|
|
KERNEL_CFLAGS += -DSTARFORTH_ENABLE_VM=1 -DSTARFORTH_MINIMAL=1
|
|
LOADER_CFLAGS += -DSTARFORTH_ENABLE_VM=1 -DSTARFORTH_MINIMAL=1
|
|
|
|
# Physics/SSM knob family: pre-assign from the Kconfig-fed CONFIG_* value
|
|
# when a build/$(ARCH)/.config exists (Kconfig migration Phase 3). This is
|
|
# additive to, not a replacement for, this Makefile's existing opt-in
|
|
# philosophy below: a plain `:=`/derived assignment here makes
|
|
# `origin($1)` "file" (not "undefined"), so `add_vm_flag` picks it up and
|
|
# forwards it -- exactly as if a developer had passed it on the command
|
|
# line. When no .config exists (KCONFIG_ACTIVE unset), none of this fires,
|
|
# and a knob is only forwarded if a developer explicitly sets it, same as
|
|
# before this migration touched the kernel build at all. Command-line
|
|
# values always win regardless, per ordinary Make semantics.
|
|
ifeq ($(KCONFIG_ACTIVE),1)
|
|
$(eval $(call kconfig_bool,STRICT_PTR,1))
|
|
$(eval $(call kconfig_bool,ENABLE_HOTWORDS_CACHE,0))
|
|
$(eval $(call kconfig_bool,ENABLE_PIPELINING,0))
|
|
$(eval $(call kconfig_int,ROLLING_WINDOW_SIZE,4096))
|
|
$(eval $(call kconfig_int,TRANSITION_WINDOW_SIZE,8))
|
|
$(eval $(call kconfig_int,ADAPTIVE_SHRINK_RATE,50))
|
|
$(eval $(call kconfig_int,ADAPTIVE_MIN_WINDOW_SIZE,256))
|
|
$(eval $(call kconfig_int,ADAPTIVE_CHECK_FREQUENCY,512))
|
|
$(eval $(call kconfig_int,ADAPTIVE_GROWTH_THRESHOLD,5))
|
|
$(eval $(call kconfig_int,INITIAL_DECAY_SLOPE_Q48,21845))
|
|
$(eval $(call kconfig_int,DECAY_MIN_INTERVAL,500))
|
|
$(eval $(call kconfig_int,DECAY_RATE_PER_US_Q16,1))
|
|
$(eval $(call kconfig_int,HEARTBEAT_INFERENCE_FREQUENCY,1000))
|
|
$(eval $(call kconfig_int,STADIUM_MAX_VM_COUNT,4))
|
|
$(eval $(call kconfig_int,STADIUM_CONTAINS_DEPTH_MAX,5))
|
|
$(eval $(call kconfig_int,STADIUM_CAPACITY_TICK,1000))
|
|
$(eval $(call kconfig_int,STADIUM_MEMORY_PERCENT,1))
|
|
$(eval $(call kconfig_int,STADIUM_WORD_HEAT_QUANTUM,2048))
|
|
$(eval $(call kconfig_int,STADIUM_WORD_COOL_RATE_Q48,21845))
|
|
$(eval $(call kconfig_int,SSM_ENTROPY_HIGH_THRESHOLD,0.75))
|
|
$(eval $(call kconfig_int,SSM_CV_HIGH_THRESHOLD,0.15))
|
|
$(eval $(call kconfig_int,SSM_TEMPORAL_DECAY_THRESHOLD,0.5))
|
|
$(eval $(call kconfig_int,SSM_TEMPORAL_DECAY_LOW_THRESHOLD,0.3))
|
|
$(eval $(call kconfig_int,SSM_HYSTERESIS_TICKS,5))
|
|
# Heartbeat family (Phase 4). HEARTBEAT_THREAD_ENABLED/HEARTBEAT_TICK_NS
|
|
# `depends on STARFORTH_VARIANT_HOSTED` in Kconfig.heartbeat, so for a
|
|
# kernel-variant .config CONFIG_HEARTBEAT_THREAD_ENABLED is simply absent
|
|
# and kconfig_bool resolves it to 0 -- correctly, since LithosAnanke has no
|
|
# pthreads -- with no special case needed here. The unconditional
|
|
# `VM_FEATURE_OVERRIDES += -DHEARTBEAT_THREAD_ENABLED=0` below is kept as a
|
|
# deliberate second, independent enforcement of the same fact; see
|
|
# Kconfig.heartbeat's help text for why both layers exist.
|
|
$(eval $(call kconfig_bool,HEARTBEAT_THREAD_ENABLED,0))
|
|
$(eval $(call kconfig_int,HEARTBEAT_TICK_NS,10000))
|
|
$(eval $(call kconfig_int,HEARTBEAT_CHECK_FREQUENCY,256))
|
|
$(eval $(call kconfig_int,HEARTBEAT_WINDOW_TUNING_FREQUENCY,1000))
|
|
$(eval $(call kconfig_int,HEARTBEAT_SLOPE_VALIDATION_FREQUENCY,5000))
|
|
# Remaining physics_pipelining_metrics.h constants (Phase 5). Previously
|
|
# hardcoded, non-overridable #defines; see Kconfig.physics for the exact
|
|
# hex/decimal defaults. MINIMUM_PREFETCH_ROI's default was corrected from
|
|
# the long-shipped 0x11999A to 0x1199A (rev y) -- the former was ~17.6 in
|
|
# Q48.16, not the documented 1.10.
|
|
$(eval $(call kconfig_int,SPECULATION_THRESHOLD_Q48,0x8000))
|
|
$(eval $(call kconfig_int,SPECULATION_DEPTH,1))
|
|
$(eval $(call kconfig_int,MIN_SAMPLES_FOR_SPECULATION,10))
|
|
$(eval $(call kconfig_int,MISPREDICTION_COST_Q48,0x190000))
|
|
$(eval $(call kconfig_int,MINIMUM_PREFETCH_ROI,0x1199A))
|
|
# SK_PARITY_DEBUG (Phase 6): previously opt-in-only via VM_FEATURE_FLAG_VARS
|
|
# below with no Kconfig presence at all.
|
|
$(eval $(call kconfig_bool,SK_PARITY_DEBUG,0))
|
|
endif
|
|
|
|
VM_FEATURE_FLAG_VARS := \
|
|
STRICT_PTR ENABLE_HOTWORDS_CACHE ENABLE_PIPELINING \
|
|
ROLLING_WINDOW_SIZE TRANSITION_WINDOW_SIZE \
|
|
ADAPTIVE_SHRINK_RATE ADAPTIVE_MIN_WINDOW_SIZE \
|
|
ADAPTIVE_CHECK_FREQUENCY ADAPTIVE_GROWTH_THRESHOLD \
|
|
INITIAL_DECAY_SLOPE_Q48 DECAY_MIN_INTERVAL \
|
|
DECAY_RATE_PER_US_Q16 HEARTBEAT_THREAD_ENABLED \
|
|
HEARTBEAT_TICK_NS HEARTBEAT_INFERENCE_FREQUENCY \
|
|
HEARTBEAT_CHECK_FREQUENCY HEARTBEAT_WINDOW_TUNING_FREQUENCY \
|
|
HEARTBEAT_SLOPE_VALIDATION_FREQUENCY SK_PARITY_DEBUG \
|
|
EMERGENCY_CONSOLE_ENABLED STADIUM_MAX_VM_COUNT \
|
|
STADIUM_CONTAINS_DEPTH_MAX STADIUM_CAPACITY_TICK \
|
|
STADIUM_MEMORY_PERCENT \
|
|
SSM_ENTROPY_HIGH_THRESHOLD SSM_CV_HIGH_THRESHOLD \
|
|
SSM_TEMPORAL_DECAY_THRESHOLD SSM_TEMPORAL_DECAY_LOW_THRESHOLD \
|
|
SSM_HYSTERESIS_TICKS \
|
|
SPECULATION_THRESHOLD_Q48 SPECULATION_DEPTH \
|
|
MIN_SAMPLES_FOR_SPECULATION MISPREDICTION_COST_Q48 \
|
|
MINIMUM_PREFETCH_ROI
|
|
|
|
VM_FEATURE_OVERRIDES :=
|
|
define add_vm_flag
|
|
ifneq ($$(origin $1),undefined)
|
|
VM_FEATURE_OVERRIDES += -D$1=$$($1)
|
|
endif
|
|
endef
|
|
$(foreach f,$(VM_FEATURE_FLAG_VARS),$(eval $(call add_vm_flag,$f)))
|
|
VM_FEATURE_OVERRIDES += -DHEARTBEAT_THREAD_ENABLED=0
|
|
|
|
KERNEL_CFLAGS += $(VM_FEATURE_OVERRIDES)
|
|
LOADER_CFLAGS += $(VM_FEATURE_OVERRIDES)
|
|
endif
|
|
|
|
# SK_CMD — optional startup FORTH script injected before the interactive REPL.
|
|
# Usage: make -f Makefile.starkernel qemu SK_CMD="TIME-TICKS . BYE"
|
|
ifdef SK_CMD
|
|
KERNEL_CFLAGS += -DSK_STARTUP_FORTH='"$(SK_CMD)"'
|
|
LOADER_CFLAGS += -DSK_STARTUP_FORTH='"$(SK_CMD)"'
|
|
endif
|
|
|
|
# KERNEL_ARGS — runtime boot flags written into starforth.cfg on the ESP image.
|
|
# The loader reads this file from the EFI partition and parses it at boot.
|
|
# Supported flags: --doe --log-level=<debug|info|warn|error>
|
|
# --stack=<N>[MG] --heap=<N>[MG]
|
|
# Usage: make -f Makefile.starkernel qemu KERNEL_ARGS="--doe --log-level=info"
|
|
KERNEL_ARGS ?=
|
|
|
|
# ==============================================================================
|
|
# SOURCE FILES
|
|
# ==============================================================================
|
|
|
|
ifeq ($(MONOLITHIC),1)
|
|
LOADER_CFLAGS += -DMONOLITHIC_BUILD -D__STARKERNEL__
|
|
|
|
LOADER_SRCS_BASE := \
|
|
$(KERNEL_SRC)/boot/uefi_loader.c \
|
|
$(KERNEL_SRC)/boot/elf_loader.c \
|
|
$(KERNEL_SRC)/boot/reloc_stub.c \
|
|
$(KERNEL_SRC)/boot/cmdline.c \
|
|
$(KERNEL_SRC)/kernel_main.c \
|
|
$(wildcard $(KERNEL_SRC)/hal/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/memory/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/math/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/hash/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/capsule/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/pci/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/virtio/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/usb/*.c) \
|
|
$(KERNEL_SRC)/repl.c \
|
|
$(KERNEL_SRC)/doe_log.c \
|
|
$(KERNEL_SRC)/heartbeat.c
|
|
|
|
LOADER_ASM := \
|
|
$(KERNEL_SRC)/arch/$(ARCH)/boot.S \
|
|
$(KERNEL_SRC)/arch/$(ARCH)/isr.S
|
|
|
|
ifeq ($(ARCH),amd64)
|
|
LOADER_ASM += $(KERNEL_SRC)/arch/$(ARCH)/kernel_entry.S
|
|
endif
|
|
ifeq ($(ARCH),riscv64)
|
|
LOADER_ASM += $(KERNEL_SRC)/arch/$(ARCH)/kernel_entry.S
|
|
endif
|
|
|
|
LOADER_ARCH_SRCS := \
|
|
$(KERNEL_SRC)/arch/$(ARCH)/arch.c \
|
|
$(KERNEL_SRC)/arch/$(ARCH)/interrupts.c \
|
|
$(KERNEL_SRC)/arch/$(ARCH)/apic.c \
|
|
$(KERNEL_SRC)/arch/$(ARCH)/timer.c
|
|
|
|
ifeq ($(ARCH),amd64)
|
|
LOADER_ARCH_SRCS += \
|
|
$(KERNEL_SRC)/arch/$(ARCH)/ioapic.c \
|
|
$(KERNEL_SRC)/arch/$(ARCH)/i8042.c
|
|
endif
|
|
ifeq ($(ARCH),riscv64)
|
|
LOADER_ARCH_SRCS += \
|
|
$(KERNEL_SRC)/arch/$(ARCH)/plic.c
|
|
endif
|
|
else
|
|
LOADER_SRCS_BASE := \
|
|
$(KERNEL_SRC)/boot/uefi_loader.c \
|
|
$(KERNEL_SRC)/boot/elf_loader.c \
|
|
$(KERNEL_SRC)/boot/reloc_stub.c
|
|
LOADER_ASM :=
|
|
LOADER_ARCH_SRCS := $(KERNEL_SRC)/arch/$(ARCH)/arch.c
|
|
endif
|
|
|
|
KERNEL_SRCS_BASE := \
|
|
$(KERNEL_SRC)/kernel_main.c \
|
|
$(wildcard $(KERNEL_SRC)/hal/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/memory/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/math/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/hash/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/vm_arbiter/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/capsule/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/pci/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/virtio/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/usb/*.c) \
|
|
$(wildcard $(KERNEL_SRC)/arch/$(ARCH)/*.c) \
|
|
$(KERNEL_SRC)/repl.c \
|
|
$(KERNEL_SRC)/doe_log.c \
|
|
$(KERNEL_SRC)/heartbeat.c
|
|
|
|
KERNEL_ASM := $(wildcard $(KERNEL_SRC)/arch/$(ARCH)/*.S)
|
|
|
|
ifeq ($(STARFORTH_ENABLE_VM),1)
|
|
VM_ALL_SRCS := \
|
|
$(wildcard src/*.c) \
|
|
$(wildcard src/word_source/*.c) \
|
|
$(wildcard src/test_runner/*.c) \
|
|
$(wildcard src/test_runner/modules/*.c)
|
|
|
|
VM_EXCLUDE := \
|
|
src/main.c src/cli.c src/repl.c \
|
|
src/platform/% \
|
|
src/blkio_factory.c src/blkio_file.c src/blkio_ram.c \
|
|
src/log.c src/doe_metrics.c \
|
|
src/vm.c src/vm_core.c src/vm_bootstrap.c src/vm_runtime.c src/vm_time.c \
|
|
src/word_source/q48_16_words.c \
|
|
src/test_runner/modules/break_me_tests.c
|
|
|
|
VM_CORE_SRCS := $(filter-out $(VM_EXCLUDE),$(VM_ALL_SRCS))
|
|
|
|
LOADER_EXTRA_SRCS := \
|
|
$(KERNEL_SRC)/vm/arena.c \
|
|
$(KERNEL_SRC)/vm/parity.c \
|
|
$(KERNEL_SRC)/vm/bootstrap/sk_vm_bootstrap.c \
|
|
$(KERNEL_SRC)/vm/host/shim.c \
|
|
$(KERNEL_SRC)/vm/vm_core.c \
|
|
$(KERNEL_SRC)/vm/vm_bootstrap.c \
|
|
$(KERNEL_SRC)/vm/vm_runtime.c \
|
|
$(KERNEL_SRC)/vm/alloc_kernel.c \
|
|
$(KERNEL_SRC)/vm/q48_stubs.c \
|
|
$(KERNEL_SRC)/vm/stadium.c \
|
|
$(KERNEL_SRC)/vm/stadium_words.c
|
|
|
|
KERNEL_EXTRA_SRCS := $(LOADER_EXTRA_SRCS)
|
|
|
|
LOADER_VM_OBJS := $(patsubst src/%.c,$(LOADER_OBJ_DIR)/vmcore/%.o,$(VM_CORE_SRCS))
|
|
KERNEL_VM_OBJS := $(patsubst src/%.c,$(KERNEL_OBJ_DIR)/vmcore/%.o,$(VM_CORE_SRCS))
|
|
endif
|
|
|
|
LOADER_SRCS := $(LOADER_SRCS_BASE) $(LOADER_EXTRA_SRCS)
|
|
KERNEL_SRCS := $(KERNEL_SRCS_BASE) $(KERNEL_EXTRA_SRCS)
|
|
|
|
LOADER_OBJS := \
|
|
$(patsubst $(KERNEL_SRC)/%.c,$(LOADER_OBJ_DIR)/%.o,$(LOADER_SRCS)) \
|
|
$(patsubst $(KERNEL_SRC)/%.c,$(LOADER_OBJ_DIR)/%.o,$(LOADER_ARCH_SRCS)) \
|
|
$(patsubst $(KERNEL_SRC)/%.S,$(LOADER_OBJ_DIR)/%.o,$(LOADER_ASM)) \
|
|
$(LOADER_VM_OBJS) \
|
|
$(CAPSULE_GENERATED_OBJ)
|
|
|
|
KERNEL_OBJS := \
|
|
$(patsubst $(KERNEL_SRC)/%.c,$(KERNEL_OBJ_DIR)/%.o,$(KERNEL_SRCS)) \
|
|
$(patsubst $(KERNEL_SRC)/%.S,$(KERNEL_OBJ_DIR)/%.o,$(KERNEL_ASM)) \
|
|
$(KERNEL_VM_OBJS) \
|
|
$(CAPSULE_GENERATED_KOBJ)
|
|
|
|
# ==============================================================================
|
|
# PHONY TARGETS
|
|
# ==============================================================================
|
|
|
|
.PHONY: all clean clean-kernel kernel kernel-all
|
|
.PHONY: qemu qemu-esp qemu-gdb
|
|
.PHONY: info help
|
|
|
|
# ==============================================================================
|
|
# MAIN TARGETS
|
|
# ==============================================================================
|
|
|
|
all: include/version.h $(CAPSULE_GENERATED_OBJ) $(CAPSULE_GENERATED_KOBJ) $(LOADER_EFI) $(KERNEL_ELF)
|
|
@echo "StarKernel built successfully for $(ARCH)"
|
|
@echo " Loader: $(LOADER_EFI)"
|
|
@echo " Kernel: $(KERNEL_ELF)"
|
|
|
|
kernel: all
|
|
|
|
kernel-all:
|
|
@$(MAKE) -f Makefile.starkernel ARCH=amd64 TARGET=$(TARGET)
|
|
@$(MAKE) -f Makefile.starkernel ARCH=aarch64 TARGET=$(TARGET)
|
|
@$(MAKE) -f Makefile.starkernel ARCH=riscv64 TARGET=$(TARGET)
|
|
|
|
clean:
|
|
rm -rf build/$(ARCH)/$(TARGET)
|
|
rm -f include/version.h
|
|
|
|
clean-kernel:
|
|
@echo "Cleaning all StarKernel build artifacts..."
|
|
@rm -rf build/*/kernel
|
|
@echo "Done."
|
|
|
|
# ==============================================================================
|
|
# BUILD RULES
|
|
# ==============================================================================
|
|
|
|
include/version.h:
|
|
@mkdir -p include
|
|
@BUILD_TS=$$(date -Iseconds 2>/dev/null || echo "unknown"); \
|
|
printf '#ifndef STARFORTH_VERSION_H\n#define STARFORTH_VERSION_H\n\n' > $@; \
|
|
printf '#define STARFORTH_VERSION "%s"\n' "$(VERSION)" >> $@; \
|
|
printf '#define STARFORTH_ARCH "%s"\n' "$(ARCH)" >> $@; \
|
|
printf '#define STARFORTH_TARGET "%s"\n' "kernel" >> $@; \
|
|
printf '#define STARFORTH_TIMESTAMP "%s"\n' "$$BUILD_TS" >> $@; \
|
|
printf '#define STARFORTH_VERSION_FULL "StarForth v%s %s kernel %s"\n\n' "$(VERSION)" "$(ARCH)" "$$BUILD_TS" >> $@; \
|
|
printf '#define LITHOS_VERSION "%s"\n' "$(LITHOS_VERSION)" >> $@; \
|
|
printf '#define LITHOS_VERSION_STR "LithosAnanke v%s"\n\n' "$(LITHOS_VERSION)" >> $@; \
|
|
printf '#endif /* STARFORTH_VERSION_H */\n' >> $@
|
|
|
|
# Host tool: capsule packer
|
|
$(MKCAPSULE_BIN): $(MKCAPSULE_SRC)
|
|
@mkdir -p $(dir $@)
|
|
@echo "HOSTCC $<"
|
|
@cc -std=c99 -Wall -Wextra -O2 -o $@ $<
|
|
|
|
# Generate capsule_generated.c from capsules/
|
|
CAPSULE_SRCS := $(shell find $(CAPSULES_DIR) -type f ! -name '.*' 2>/dev/null)
|
|
$(CAPSULE_GENERATED): $(MKCAPSULE_BIN) $(CAPSULE_SRCS)
|
|
@mkdir -p $(dir $@)
|
|
@echo " MKCAP $(CAPSULES_DIR) -> $@"
|
|
@$(MKCAPSULE_BIN) $(CAPSULES_DIR) $@
|
|
@echo " MKCAP $(CAPSULES_DIR) -> $(CAPSULES_DIR)/BLOCK_MAP.md"
|
|
@$(MKCAPSULE_BIN) --manifest $(CAPSULES_DIR) $(CAPSULES_DIR)/BLOCK_MAP.md
|
|
|
|
# manifest — regenerate capsules/BLOCK_MAP.md without a full kernel build
|
|
.PHONY: manifest
|
|
manifest: $(MKCAPSULE_BIN)
|
|
@$(MKCAPSULE_BIN) --manifest $(CAPSULES_DIR) $(CAPSULES_DIR)/BLOCK_MAP.md
|
|
@echo " Wrote $(CAPSULES_DIR)/BLOCK_MAP.md"
|
|
|
|
$(CAPSULE_GENERATED_OBJ): $(CAPSULE_GENERATED)
|
|
@echo " CC $<"
|
|
@$(LOADER_CC) $(LOADER_CFLAGS) -c $< -o $@
|
|
|
|
$(CAPSULE_GENERATED_KOBJ): $(CAPSULE_GENERATED) | $(KERNEL_OBJ_DIR)
|
|
@echo " CC (kernel) $<"
|
|
@$(CC) $(KERNEL_CFLAGS) -c $< -o $@
|
|
|
|
# Create build directories
|
|
$(BUILD_DIR) $(LOADER_OBJ_DIR) $(KERNEL_OBJ_DIR):
|
|
@mkdir -p \
|
|
$(LOADER_OBJ_DIR)/boot $(LOADER_OBJ_DIR)/arch/$(ARCH) \
|
|
$(LOADER_OBJ_DIR)/vm $(LOADER_OBJ_DIR)/capsule $(LOADER_OBJ_DIR)/hash
|
|
@mkdir -p \
|
|
$(KERNEL_OBJ_DIR)/boot $(KERNEL_OBJ_DIR)/hal $(KERNEL_OBJ_DIR)/memory \
|
|
$(KERNEL_OBJ_DIR)/vm_arbiter $(KERNEL_OBJ_DIR)/capsule \
|
|
$(KERNEL_OBJ_DIR)/hash $(KERNEL_OBJ_DIR)/arch/$(ARCH) $(KERNEL_OBJ_DIR)/vm
|
|
|
|
# Compile loader C sources
|
|
$(LOADER_OBJ_DIR)/%.o: $(KERNEL_SRC)/%.c | $(LOADER_OBJ_DIR)
|
|
@mkdir -p $(dir $@)
|
|
@echo "CC (loader) $<"
|
|
@$(LOADER_CC) $(LOADER_CFLAGS) -c $< -o $@
|
|
|
|
# Compile loader VM core sources
|
|
$(LOADER_OBJ_DIR)/vmcore/%.o: src/%.c include/version.h | $(LOADER_OBJ_DIR)
|
|
@mkdir -p $(dir $@)
|
|
@echo "CC (loader) $<"
|
|
ifeq ($(ARCH),aarch64)
|
|
@$(LOADER_CC) $(LOADER_CFLAGS) \
|
|
-Iinclude -I. -Isrc/test_runner/include \
|
|
-Wno-error=unused-parameter -Wno-error=shift-negative-value \
|
|
-Wno-error=sign-compare -Wno-error=missing-field-initializers \
|
|
-Wno-unused-function \
|
|
-c $< -o $@
|
|
else
|
|
@$(LOADER_CC) $(VMCORE_CFLAGS_COMMON) $(filter-out -I$(KERNEL_INC),$(LOADER_CFLAGS)) -c $< -o $@
|
|
endif
|
|
|
|
# Compile loader assembly sources
|
|
$(LOADER_OBJ_DIR)/%.o: $(KERNEL_SRC)/%.S | $(LOADER_OBJ_DIR)
|
|
@mkdir -p $(dir $@)
|
|
@echo "AS (loader) $<"
|
|
@$(LOADER_CC) $(LOADER_CFLAGS) -c $< -o $@
|
|
|
|
# Compile kernel C sources
|
|
$(KERNEL_OBJ_DIR)/%.o: $(KERNEL_SRC)/%.c | $(KERNEL_OBJ_DIR)
|
|
@mkdir -p $(dir $@)
|
|
@echo "CC (kernel) $<"
|
|
@$(CC) $(KERNEL_CFLAGS) -c $< -o $@
|
|
|
|
# Compile kernel VM core sources
|
|
$(KERNEL_OBJ_DIR)/vmcore/%.o: src/%.c include/version.h | $(KERNEL_OBJ_DIR)
|
|
@mkdir -p $(dir $@)
|
|
@echo "CC (kernel) $<"
|
|
@$(CC) $(VMCORE_CFLAGS_COMMON) $(filter-out -I$(KERNEL_INC),$(KERNEL_CFLAGS)) -Isrc/starkernel/vm -c $< -o $@
|
|
|
|
# Compile kernel assembly sources
|
|
$(KERNEL_OBJ_DIR)/%.o: $(KERNEL_SRC)/%.S | $(KERNEL_OBJ_DIR)
|
|
@mkdir -p $(dir $@)
|
|
@echo "AS (kernel) $<"
|
|
@$(CC) $(KERNEL_CFLAGS) -c $< -o $@
|
|
|
|
# ==============================================================================
|
|
# LINK RULES
|
|
# ==============================================================================
|
|
|
|
# Link loader to PE32+
|
|
# amd64: ld -m i386pep → PE directly
|
|
# aarch64: lld-link links COFF objects → PE (auto-generates .reloc)
|
|
# riscv64: ELF → gen_pe_reloc.py patches addends → objcopy → pei-riscv64-little
|
|
$(LOADER_EFI): $(LOADER_OBJS) | $(BUILD_DIR)
|
|
ifeq ($(ARCH),aarch64)
|
|
@echo "LLD-LINK (loader PE) $@"
|
|
@$(LOADER_LD) /subsystem:EFI_APPLICATION /entry:efi_main /nodefaultlib \
|
|
/align:4096 /base:0 /out:$@ $(LOADER_OBJS)
|
|
@echo "Loader built: $@"
|
|
else ifeq ($(ARCH),riscv64)
|
|
@echo "LD+OBJCOPY (loader ELF→PE) $@"
|
|
@$(LD) -T $(LOADER_LINKER_SCRIPT) -nostdlib --build-id=none \
|
|
-shared -Bsymbolic -znocombreloc -e efi_main -m elf64lriscv \
|
|
$(LOADER_OBJS) -o $(BUILD_DIR)/starkernel_loader_tmp.elf
|
|
@python3 scripts/gen_pe_reloc.py \
|
|
$(BUILD_DIR)/starkernel_loader_tmp.elf \
|
|
$(BUILD_DIR)/starkernel_loader_patched.elf \
|
|
$(BUILD_DIR)/reloc_rv64.bin
|
|
@$(OBJCOPY) \
|
|
--remove-section=.rela.dyn \
|
|
--remove-section=.dynstr \
|
|
--remove-section=.hash \
|
|
--remove-section=.gnu.hash \
|
|
--remove-section=.dynsym \
|
|
--remove-section=.dynamic \
|
|
--remove-section=.riscv.attributes \
|
|
--update-section=.reloc=$(BUILD_DIR)/reloc_rv64.bin \
|
|
$(BUILD_DIR)/starkernel_loader_patched.elf \
|
|
$(BUILD_DIR)/starkernel_loader_stripped.elf
|
|
@$(OBJCOPY) -O pei-riscv64-little --subsystem 10 \
|
|
$(BUILD_DIR)/starkernel_loader_stripped.elf $@
|
|
@echo "Loader built: $@"
|
|
else
|
|
@echo "LD (loader PE) $@"
|
|
@$(LD) $(LOADER_LDFLAGS_PE) $(LOADER_OBJS) -o $@
|
|
@echo "Loader built: $@"
|
|
endif
|
|
|
|
# Keep ELF for debugging
|
|
$(BUILD_DIR)/starkernel_loader.elf: $(LOADER_OBJS) $(LOADER_LINKER_SCRIPT) | $(BUILD_DIR)
|
|
@echo "LD (loader ELF) $@"
|
|
@$(LD) $(LOADER_LDFLAGS) $(LOADER_OBJS) -o $@
|
|
|
|
# Link kernel ELF
|
|
$(KERNEL_ELF): $(KERNEL_OBJS) $(KERNEL_LINKER_SCRIPT) | $(BUILD_DIR)
|
|
@echo "LD (kernel) $@"
|
|
@$(LD) $(KERNEL_LDFLAGS) $(KERNEL_OBJS) -o $@
|
|
@echo "Kernel built: $@"
|
|
|
|
# ==============================================================================
|
|
# QEMU TARGETS
|
|
# ==============================================================================
|
|
|
|
# qemu — full build, pack boot media, boot under QEMU, serial → timestamped log
|
|
#
|
|
# amd64: El Torito ISO + OVMF pflash
|
|
# aarch64: El Torito ISO + AAVMF -bios
|
|
# riscv64: GPT/FAT32 disk image + EDK2 pflash (cdrom/bios don't work on virt)
|
|
#
|
|
# Requires: qemu-system-* ovmf/qemu-efi-* xorriso mtools gdisk dosfstools
|
|
OVMF_CODE := /usr/share/OVMF/OVMF_CODE_4M.fd
|
|
OVMF_VARS_RO := /usr/share/OVMF/OVMF_VARS_4M.fd
|
|
|
|
# QEMU_DISPLAY — window backend for the framebuffer console (gtk/sdl/none/...).
|
|
# Usage: make -f Makefile.starkernel qemu QEMU_DISPLAY=none # headless/CI
|
|
QEMU_DISPLAY ?= gtk
|
|
|
|
qemu: all
|
|
ifeq ($(ARCH),amd64)
|
|
@echo "=== Building ISO (amd64) ==="
|
|
@mkdir -p $(BUILD_DIR)/iso
|
|
@dd if=/dev/zero of=$(BUILD_DIR)/iso/efi.img bs=512 count=8192 2>/dev/null
|
|
@mformat -i $(BUILD_DIR)/iso/efi.img ::
|
|
@mmd -i $(BUILD_DIR)/iso/efi.img ::/EFI
|
|
@mmd -i $(BUILD_DIR)/iso/efi.img ::/EFI/BOOT
|
|
@mcopy -i $(BUILD_DIR)/iso/efi.img $(LOADER_EFI) ::/EFI/BOOT/BOOTX64.EFI
|
|
$(if $(KERNEL_ARGS),\
|
|
@printf '$(KERNEL_ARGS)\n' > $(BUILD_DIR)/iso/starforth.cfg && \
|
|
mcopy -i $(BUILD_DIR)/iso/efi.img $(BUILD_DIR)/iso/starforth.cfg ::/starforth.cfg,)
|
|
@xorriso -as mkisofs -r -J \
|
|
-e efi.img -no-emul-boot \
|
|
-o $(QEMU_ISO) $(BUILD_DIR)/iso 2>&1
|
|
@if [ ! -f $(OVMF_CODE) ]; then echo "Error: $(OVMF_CODE) not found"; exit 1; fi
|
|
@if [ ! -f $(OVMF_VARS_RO) ]; then echo "Error: $(OVMF_VARS_RO) not found"; exit 1; fi
|
|
@cp $(OVMF_VARS_RO) $(BUILD_DIR)/OVMF_VARS.fd
|
|
@mkdir -p $(QEMU_LOG_DIR) $(DOE_RUNS_DIR) $(DOE_LATEST_DIR)
|
|
@TS=$$(date +%Y%m%d-%H%M%S); \
|
|
SESSION=$(QEMU_LOG_DIR)/$$TS; \
|
|
mkdir -p $$SESSION/amd64; \
|
|
LOG=$$SESSION/amd64/qemu-amd64-$$TS.log; \
|
|
CSV=$(DOE_RUNS_DIR)/doe-amd64-$$TS.csv; \
|
|
SERIAL_SOCK=$(BUILD_DIR)/qemu-serial-amd64-$$TS.sock; \
|
|
QMP_SOCK=$(BUILD_DIR)/qemu-qmp-amd64-$$TS.sock; \
|
|
echo "=== Launching QEMU (amd64) ==="; \
|
|
echo " Serial log: $$LOG"; \
|
|
echo " QMP socket: $$QMP_SOCK"; \
|
|
echo " (interactive — close the window or QEMU monitor 'quit' to exit)"; \
|
|
echo ""; \
|
|
touch $$LOG; \
|
|
tail -n +1 -f $$LOG & TAILPID=$$!; \
|
|
qemu-system-x86_64 \
|
|
-machine q35,accel=tcg \
|
|
-cpu qemu64 \
|
|
-m 1024 \
|
|
-vga std \
|
|
-drive if=pflash,format=raw,readonly=on,file=$(OVMF_CODE) \
|
|
-drive if=pflash,format=raw,file=$(BUILD_DIR)/OVMF_VARS.fd \
|
|
-cdrom $(QEMU_ISO) -boot d \
|
|
-drive id=artdisk,file=$(ARTDISK),format=raw,if=none \
|
|
-device virtio-blk-pci,drive=artdisk \
|
|
-device qemu-xhci,id=xhci0 \
|
|
-chardev socket,id=cserial,path=$$SERIAL_SOCK,server=on,wait=off,logfile=$$LOG \
|
|
-serial chardev:cserial \
|
|
-qmp unix:$$QMP_SOCK,server=on,wait=off \
|
|
-display $(QEMU_DISPLAY) \
|
|
-no-reboot; \
|
|
kill $$TAILPID 2>/dev/null; wait $$TAILPID 2>/dev/null || true; \
|
|
rm -f $$SERIAL_SOCK $$QMP_SOCK; \
|
|
echo ""; \
|
|
echo "=== Serial log: $$LOG ==="; \
|
|
echo "=== Extracting DOE CSV ==="; \
|
|
bash scripts/extract_doe.sh $$LOG $$CSV || true; \
|
|
cp $$CSV $(DOE_LATEST_DIR)/amd64.csv 2>/dev/null || true
|
|
|
|
else ifeq ($(ARCH),aarch64)
|
|
@echo "=== Building ISO (aarch64) ==="
|
|
@mkdir -p $(BUILD_DIR)/iso/EFI/BOOT
|
|
@cp $(LOADER_EFI) $(BUILD_DIR)/iso/EFI/BOOT/BOOTAA64.EFI
|
|
@printf "FS0:\\EFI\\BOOT\\BOOTAA64.EFI\r\n" > $(BUILD_DIR)/iso/startup.nsh
|
|
@dd if=/dev/zero of=$(BUILD_DIR)/iso/efi.img bs=512 count=8192 2>/dev/null
|
|
@mformat -i $(BUILD_DIR)/iso/efi.img ::
|
|
@mmd -i $(BUILD_DIR)/iso/efi.img ::/EFI
|
|
@mmd -i $(BUILD_DIR)/iso/efi.img ::/EFI/BOOT
|
|
@mcopy -i $(BUILD_DIR)/iso/efi.img $(LOADER_EFI) ::/EFI/BOOT/BOOTAA64.EFI
|
|
$(if $(KERNEL_ARGS),\
|
|
@printf '$(KERNEL_ARGS)\n' > $(BUILD_DIR)/iso/starforth.cfg && \
|
|
mcopy -i $(BUILD_DIR)/iso/efi.img $(BUILD_DIR)/iso/starforth.cfg ::/starforth.cfg,)
|
|
@xorriso -as mkisofs -r -J \
|
|
-e efi.img -no-emul-boot \
|
|
-o $(QEMU_ISO) $(BUILD_DIR)/iso 2>&1
|
|
@mkdir -p $(QEMU_LOG_DIR) $(DOE_RUNS_DIR) $(DOE_LATEST_DIR)
|
|
@if [ -f /usr/share/AAVMF/AAVMF_CODE.fd ]; then \
|
|
AAVMF_CODE=/usr/share/AAVMF/AAVMF_CODE.fd; \
|
|
elif [ -f /usr/share/qemu-efi-aarch64/QEMU_EFI.fd ]; then \
|
|
AAVMF_CODE=/usr/share/qemu-efi-aarch64/QEMU_EFI.fd; \
|
|
elif [ -f /usr/share/qemu/edk2-aarch64-code.fd ]; then \
|
|
AAVMF_CODE=/usr/share/qemu/edk2-aarch64-code.fd; \
|
|
else \
|
|
echo "Error: aarch64 UEFI firmware not found. Install qemu-efi-aarch64."; \
|
|
exit 1; \
|
|
fi; \
|
|
TS=$$(date +%Y%m%d-%H%M%S); \
|
|
SESSION=$(QEMU_LOG_DIR)/$$TS; \
|
|
mkdir -p $$SESSION/aarch64; \
|
|
LOG=$$SESSION/aarch64/qemu-aarch64-$$TS.log; \
|
|
CSV=$(DOE_RUNS_DIR)/doe-aarch64-$$TS.csv; \
|
|
SERIAL_SOCK=$(BUILD_DIR)/qemu-serial-aarch64-$$TS.sock; \
|
|
QMP_SOCK=$(BUILD_DIR)/qemu-qmp-aarch64-$$TS.sock; \
|
|
echo "=== Launching QEMU (aarch64) ==="; \
|
|
echo " ISO: $(QEMU_ISO)"; \
|
|
echo " Serial log: $$LOG"; \
|
|
echo " QMP socket: $$QMP_SOCK"; \
|
|
echo " (interactive — close the window or QEMU monitor 'quit' to exit)"; \
|
|
echo ""; \
|
|
touch $$LOG; \
|
|
tail -n +1 -f $$LOG & TAILPID=$$!; \
|
|
qemu-system-aarch64 \
|
|
-machine virt \
|
|
-cpu cortex-a57 \
|
|
-bios $$AAVMF_CODE \
|
|
-drive if=virtio,format=raw,file=$(QEMU_ISO),media=cdrom \
|
|
-drive id=artdisk,file=$(ARTDISK),format=raw,if=none \
|
|
-device virtio-blk-pci,drive=artdisk \
|
|
-device virtio-keyboard-pci,addr=0x3 \
|
|
-device qemu-xhci,id=xhci0,addr=0x4 \
|
|
-device ramfb \
|
|
-chardev socket,id=cserial,path=$$SERIAL_SOCK,server=on,wait=off,logfile=$$LOG \
|
|
-serial chardev:cserial \
|
|
-qmp unix:$$QMP_SOCK,server=on,wait=off \
|
|
-display $(QEMU_DISPLAY) \
|
|
-m 4096 \
|
|
-no-reboot \
|
|
-d guest_errors; \
|
|
kill $$TAILPID 2>/dev/null; wait $$TAILPID 2>/dev/null || true; \
|
|
rm -f $$SERIAL_SOCK $$QMP_SOCK; \
|
|
echo ""; \
|
|
echo "=== Serial log: $$LOG ==="; \
|
|
echo "=== Extracting DOE CSV ==="; \
|
|
bash scripts/extract_doe.sh $$LOG $$CSV || true; \
|
|
cp $$CSV $(DOE_LATEST_DIR)/aarch64.csv 2>/dev/null || true
|
|
|
|
else ifeq ($(ARCH),riscv64)
|
|
@echo "=== Building GPT disk image (riscv64) ==="
|
|
@# riscv64 QEMU virt: pflash firmware + virtio-blk-pci GPT disk.
|
|
@# El Torito ISO and -bios mode do NOT work on riscv64 virt.
|
|
@mkdir -p $(QEMU_LOG_DIR) $(DOE_RUNS_DIR) $(DOE_LATEST_DIR)
|
|
@if ! which sgdisk >/dev/null 2>&1; then echo "Error: sgdisk not found. Install gdisk (apt-get install gdisk)."; exit 1; fi
|
|
@if ! which mkfs.fat >/dev/null 2>&1; then echo "Error: mkfs.fat not found. Install dosfstools (apt-get install dosfstools)."; exit 1; fi
|
|
@if [ -f /usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd ]; then \
|
|
EDK2_RISCV_CODE=/usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd; \
|
|
elif [ -f /usr/share/qemu/edk2-riscv64-code.fd ]; then \
|
|
EDK2_RISCV_CODE=/usr/share/qemu/edk2-riscv64-code.fd; \
|
|
else \
|
|
echo "Error: riscv64 UEFI firmware not found. Install qemu-efi-riscv64."; \
|
|
exit 1; \
|
|
fi; \
|
|
EDK2_RISCV_VARS=""; \
|
|
if [ -f /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd ]; then \
|
|
cp /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd $(BUILD_DIR)/RISCV_VARS.fd; \
|
|
EDK2_RISCV_VARS=$(BUILD_DIR)/RISCV_VARS.fd; \
|
|
fi; \
|
|
DISK=$(BUILD_DIR)/starkernel_rv64.img; \
|
|
dd if=/dev/zero of=$$DISK bs=1M count=64 2>/dev/null; \
|
|
sgdisk -n 1:2048:131038 -t 1:ef00 -c 1:"EFI System" $$DISK >/dev/null 2>&1; \
|
|
dd if=/dev/zero of=$(BUILD_DIR)/esp_rv64.img bs=512 count=128991 2>/dev/null; \
|
|
mkfs.fat -F 32 -n "ESP" $(BUILD_DIR)/esp_rv64.img >/dev/null 2>&1; \
|
|
mmd -i $(BUILD_DIR)/esp_rv64.img ::/EFI; \
|
|
mmd -i $(BUILD_DIR)/esp_rv64.img ::/EFI/BOOT; \
|
|
mcopy -i $(BUILD_DIR)/esp_rv64.img $(LOADER_EFI) ::/EFI/BOOT/BOOTRISCV64.EFI; \
|
|
mcopy -i $(BUILD_DIR)/esp_rv64.img $(KERNEL_ELF) ::/kernel.elf; \
|
|
printf 'FS0:\\EFI\\BOOT\\BOOTRISCV64.EFI\r\n' > $(BUILD_DIR)/startup.nsh; \
|
|
mcopy -i $(BUILD_DIR)/esp_rv64.img $(BUILD_DIR)/startup.nsh ::/startup.nsh; \
|
|
if [ -n "$(KERNEL_ARGS)" ]; then \
|
|
printf '$(KERNEL_ARGS)\n' > $(BUILD_DIR)/starforth.cfg; \
|
|
mcopy -i $(BUILD_DIR)/esp_rv64.img $(BUILD_DIR)/starforth.cfg ::/starforth.cfg; \
|
|
fi; \
|
|
dd if=$(BUILD_DIR)/esp_rv64.img of=$$DISK bs=512 seek=2048 conv=notrunc 2>/dev/null; \
|
|
echo " Disk: $$DISK"; \
|
|
echo ""; \
|
|
PFLASH_VARS_ARG=""; \
|
|
if [ -n "$$EDK2_RISCV_VARS" ]; then \
|
|
PFLASH_VARS_ARG="-drive if=pflash,format=raw,unit=1,file=$$EDK2_RISCV_VARS"; \
|
|
fi; \
|
|
TS=$$(date +%Y%m%d-%H%M%S); \
|
|
SESSION=$(QEMU_LOG_DIR)/$$TS; \
|
|
mkdir -p $$SESSION/riscv64; \
|
|
LOG=$$SESSION/riscv64/qemu-riscv64-$$TS.log; \
|
|
CSV=$(DOE_RUNS_DIR)/doe-riscv64-$$TS.csv; \
|
|
SERIAL_SOCK=$(BUILD_DIR)/qemu-serial-riscv64-$$TS.sock; \
|
|
QMP_SOCK=$(BUILD_DIR)/qemu-qmp-riscv64-$$TS.sock; \
|
|
echo "=== Launching QEMU (riscv64) ==="; \
|
|
echo " Serial log: $$LOG"; \
|
|
echo " QMP socket: $$QMP_SOCK"; \
|
|
echo " (interactive — close the window or QEMU monitor 'quit' to exit)"; \
|
|
echo ""; \
|
|
touch $$LOG; \
|
|
tail -n +1 -f $$LOG & TAILPID=$$!; \
|
|
qemu-system-riscv64 \
|
|
-machine virt \
|
|
-cpu rv64 \
|
|
-m 1024 \
|
|
-drive if=pflash,format=raw,unit=0,file=$$EDK2_RISCV_CODE,readonly=on \
|
|
$$PFLASH_VARS_ARG \
|
|
-drive id=hd0,file=$$DISK,format=raw,if=none \
|
|
-device virtio-blk-pci,drive=hd0,addr=0x2 \
|
|
-drive id=artdisk,file=$(ARTDISK),format=raw,if=none \
|
|
-device virtio-blk-pci,drive=artdisk,addr=0x1 \
|
|
-device virtio-keyboard-pci,addr=0x3 \
|
|
-device qemu-xhci,id=xhci0,addr=0x4 \
|
|
-device ramfb \
|
|
-chardev socket,id=cserial,path=$$SERIAL_SOCK,server=on,wait=off,logfile=$$LOG \
|
|
-serial chardev:cserial \
|
|
-qmp unix:$$QMP_SOCK,server=on,wait=off \
|
|
-display $(QEMU_DISPLAY) \
|
|
-no-reboot \
|
|
-d guest_errors; \
|
|
kill $$TAILPID 2>/dev/null; wait $$TAILPID 2>/dev/null || true; \
|
|
rm -f $$SERIAL_SOCK $$QMP_SOCK; \
|
|
echo ""; \
|
|
echo "=== Serial log: $$LOG ==="; \
|
|
echo "=== Extracting DOE CSV ==="; \
|
|
bash scripts/extract_doe.sh $$LOG $$CSV || true; \
|
|
cp $$CSV $(DOE_LATEST_DIR)/riscv64.csv 2>/dev/null || true
|
|
endif
|
|
|
|
# qemu-esp — interactive dev boot from FAT directory (no disk image rebuild,
|
|
# no auto-kill/timeout/DOE-injection logic — stays up until you quit it
|
|
# yourself with Ctrl-A X or by closing the window).
|
|
# amd64/aarch64: serial → stdio (bidirectional, you can type into the REPL);
|
|
# riscv64: uses proper GPT (esp dir not supported)
|
|
qemu-esp: include/version.h $(LOADER_EFI) $(KERNEL_ELF)
|
|
ifeq ($(ARCH),amd64)
|
|
@rm -rf $(BUILD_DIR)/esp
|
|
@mkdir -p $(BUILD_DIR)/esp/EFI/BOOT
|
|
@cp $(LOADER_EFI) $(BUILD_DIR)/esp/EFI/BOOT/BOOTX64.EFI
|
|
@cp $(KERNEL_ELF) $(BUILD_DIR)/esp/kernel.elf
|
|
@printf "FS0:\\EFI\\BOOT\\BOOTX64.EFI\r\n" > $(BUILD_DIR)/esp/startup.nsh
|
|
$(if $(KERNEL_ARGS),@printf '$(KERNEL_ARGS)\n' > $(BUILD_DIR)/esp/starforth.cfg,)
|
|
@OVMF_CODE=$(OVMF_CODE); \
|
|
if [ ! -f $$OVMF_CODE ]; then echo "Error: $$OVMF_CODE not found"; exit 1; fi; \
|
|
cp $(OVMF_VARS_RO) $(BUILD_DIR)/OVMF_VARS.fd; \
|
|
qemu-system-x86_64 \
|
|
-nodefaults -display $(QEMU_DISPLAY) -vga std \
|
|
-machine q35,accel=tcg \
|
|
-cpu qemu64 \
|
|
-m 1024 \
|
|
-chardev stdio,id=ser0,signal=off \
|
|
-device isa-serial,chardev=ser0,iobase=0x3f8 \
|
|
-drive if=pflash,format=raw,readonly=on,file=$$OVMF_CODE \
|
|
-drive if=pflash,format=raw,file=$(BUILD_DIR)/OVMF_VARS.fd \
|
|
-drive format=raw,file=fat:rw:$(BUILD_DIR)/esp \
|
|
-drive id=artdisk,file=$(ARTDISK),format=raw,if=none \
|
|
-device virtio-blk-pci,drive=artdisk \
|
|
-no-reboot
|
|
|
|
else ifeq ($(ARCH),aarch64)
|
|
@rm -rf $(BUILD_DIR)/esp
|
|
@mkdir -p $(BUILD_DIR)/esp/EFI/BOOT
|
|
@cp $(LOADER_EFI) $(BUILD_DIR)/esp/EFI/BOOT/BOOTAA64.EFI
|
|
@cp $(KERNEL_ELF) $(BUILD_DIR)/esp/kernel.elf
|
|
@printf "FS0:\\EFI\\BOOT\\BOOTAA64.EFI\r\n" > $(BUILD_DIR)/esp/startup.nsh
|
|
@if [ -f /usr/share/AAVMF/AAVMF_CODE.fd ]; then \
|
|
AAVMF_CODE=/usr/share/AAVMF/AAVMF_CODE.fd; \
|
|
elif [ -f /usr/share/qemu-efi-aarch64/QEMU_EFI.fd ]; then \
|
|
AAVMF_CODE=/usr/share/qemu-efi-aarch64/QEMU_EFI.fd; \
|
|
elif [ -f /usr/share/qemu/edk2-aarch64-code.fd ]; then \
|
|
AAVMF_CODE=/usr/share/qemu/edk2-aarch64-code.fd; \
|
|
else \
|
|
echo "Error: aarch64 UEFI firmware not found. Install qemu-efi-aarch64."; exit 1; \
|
|
fi; \
|
|
qemu-system-aarch64 \
|
|
-machine virt \
|
|
-cpu cortex-a57 \
|
|
-bios $$AAVMF_CODE \
|
|
-drive format=raw,file=fat:rw:$(BUILD_DIR)/esp \
|
|
-serial mon:stdio \
|
|
-nographic \
|
|
-m 2048 \
|
|
-d guest_errors
|
|
|
|
else ifeq ($(ARCH),riscv64)
|
|
@echo "riscv64: qemu-esp not supported (FAT dir mode broken on virt); use 'make qemu'"
|
|
@exit 1
|
|
endif
|
|
|
|
# qemu-gdb — launch QEMU with GDB stub on port 1234
|
|
qemu-gdb: include/version.h $(LOADER_EFI) $(KERNEL_ELF)
|
|
ifeq ($(ARCH),amd64)
|
|
@mkdir -p $(BUILD_DIR)/esp/EFI/BOOT
|
|
@cp $(LOADER_EFI) $(BUILD_DIR)/esp/EFI/BOOT/BOOTX64.EFI
|
|
@cp $(KERNEL_ELF) $(BUILD_DIR)/esp/kernel.elf
|
|
@echo "Launching QEMU with GDB server on :1234 (amd64)"
|
|
@echo " gdb $(KERNEL_ELF) -ex 'target remote :1234'"
|
|
@qemu-system-x86_64 \
|
|
-bios $(OVMF_CODE) \
|
|
-drive format=raw,file=fat:rw:$(BUILD_DIR)/esp \
|
|
-serial stdio \
|
|
-m 1024 \
|
|
-s -S
|
|
else ifeq ($(ARCH),aarch64)
|
|
@mkdir -p $(BUILD_DIR)/esp/EFI/BOOT
|
|
@cp $(LOADER_EFI) $(BUILD_DIR)/esp/EFI/BOOT/BOOTAA64.EFI
|
|
@cp $(KERNEL_ELF) $(BUILD_DIR)/esp/kernel.elf
|
|
@echo "Launching QEMU with GDB server on :1234 (aarch64)"
|
|
@echo " gdb $(KERNEL_ELF) -ex 'target remote :1234'"
|
|
@qemu-system-aarch64 \
|
|
-machine virt -cpu cortex-a57 \
|
|
-bios /usr/share/AAVMF/AAVMF_CODE.fd \
|
|
-drive format=raw,file=fat:rw:$(BUILD_DIR)/esp \
|
|
-serial stdio -m 2048 -s -S
|
|
else
|
|
@echo "Error: qemu-gdb not yet implemented for $(ARCH)"; exit 1
|
|
endif
|
|
|
|
# ==============================================================================
|
|
# INFO / HELP
|
|
# ==============================================================================
|
|
|
|
|
|
info:
|
|
@echo "StarKernel Build Configuration"
|
|
@echo "==============================="
|
|
@echo "Architecture: $(ARCH)"
|
|
@echo "Target: $(TARGET)"
|
|
@echo "Compiler: $(CC)"
|
|
@echo "Loader CC: $(LOADER_CC)"
|
|
@echo "Linker: $(LD)"
|
|
@echo "Build dir: $(BUILD_DIR)"
|
|
@echo "Loader script: $(LOADER_LINKER_SCRIPT)"
|
|
@echo "Kernel script: $(KERNEL_LINKER_SCRIPT)"
|
|
@echo "Loader output: $(LOADER_EFI)"
|
|
@echo "Kernel output: $(KERNEL_ELF)"
|
|
@echo "VM integration: $(STARFORTH_ENABLE_VM)"
|
|
@echo "Monolithic: $(MONOLITHIC)"
|
|
|
|
help:
|
|
@echo "StarKernel Build System"
|
|
@echo "======================="
|
|
@echo ""
|
|
@echo "Usage: make -f Makefile.starkernel [ARCH=<arch>] [TARGET=<target>] <goal>"
|
|
@echo ""
|
|
@echo "Architectures (aliases accepted):"
|
|
@echo " ARCH=amd64 / x86_64 — x86-64 (default on x86 host)"
|
|
@echo " ARCH=aarch64 / arm64 — ARM64"
|
|
@echo " ARCH=riscv64 / riscv — RISC-V 64-bit"
|
|
@echo ""
|
|
@echo "Build goals:"
|
|
@echo " all — build loader EFI + kernel ELF (default)"
|
|
@echo " kernel — alias for all"
|
|
@echo " kernel-all — build all three architectures"
|
|
@echo " clean — remove build/\$(ARCH)/\$(TARGET)"
|
|
@echo " clean-kernel — remove build/*/kernel for all arches"
|
|
@echo ""
|
|
@echo "QEMU goals:"
|
|
@echo " qemu — clean boot, serial tee'd live + logs/<session>/<arch>/ + DOE CSV → doe/ (all arches)"
|
|
@echo " qemu-esp — quick boot from FAT directory (amd64, aarch64)"
|
|
@echo " qemu-gdb — boot with GDB stub on :1234"
|
|
@echo ""
|
|
@echo "QEMU display:"
|
|
@echo " QEMU_DISPLAY=gtk — framebuffer window backend for 'qemu' goal (default: gtk)"
|
|
@echo " QEMU_DISPLAY=none — headless (no window), e.g. for CI"
|
|
@echo ""
|
|
@echo "Info goals:"
|
|
@echo " info — show build configuration"
|
|
@echo " help — show this message"
|
|
@echo ""
|
|
@echo "Examples:"
|
|
@echo " make -f Makefile.starkernel ARCH=amd64 qemu"
|
|
@echo " make -f Makefile.starkernel ARCH=aarch64 clean qemu"
|
|
@echo " make -f Makefile.starkernel ARCH=riscv64 clean qemu"
|
|
@echo " make -f Makefile.starkernel ARCH=x86_64 qemu # alias works"
|
|
@echo " make -f Makefile.starkernel kernel-all"
|