Files
LithosAnanake/Makefile.starkernel
T
Robert Allan JamesandClaude Sonnet 5 88eb73cfe8 starkernel: item 4.3.5 -- amd64 I/O APIC + i8042 keyboard, interrupt-driven
Punch list §25 item 4.3.5 complete.

New ioapic.c/i8042.c drivers (MADT-derived I/O APIC base, no hardcoded
constants) plus a KBD-SCAN/KBD-DEBUG diagnostic word pair. Three real
bugs found and fixed en route, all blocking this item's own acceptance:
a fatal LAPIC spurious-vector crash (nothing had driven a real external
interrupt through the I/O APIC before), OVMF leaving the keyboard device
itself scanning-disabled (0xF4 fix), and isr.S's stub table only having
individually-numbered stubs through vector 32 -- everything above that,
including our IRQ1 vector 33, silently reported as vector 255 regardless
of which IDT slot actually fired. Verified live via QEMU sendkey against
KBD-SCAN: correct XT Set-1 make/break codes for two different keys.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-08 00:18:48 -04:00

1099 lines
44 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 \
-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
# HEARTBEAT_DOE_LOG is blanket-forwarded (unlike the opt-in-only knobs
# below), so it's repointed through the Kconfig bridge (Phase 6).
$(eval $(call kconfig_bool,HEARTBEAT_DOE_LOG,1))
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 -DHEARTBEAT_DOE_LOG=$(HEARTBEAT_DOE_LOG)
KERNEL_CFLAGS := $(COMMON_CFLAGS) -D__STARKERNEL__ -I$(KERNEL_SRC)/vm -DHEARTBEAT_DOE_LOG=$(HEARTBEAT_DOE_LOG)
# 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) \
$(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
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)/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: 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; \
echo "=== Launching QEMU (amd64) ==="; \
echo " Serial log: $$LOG"; \
echo " (exits when EXEC-DOE completes)"; \
echo ""; \
qemu-system-x86_64 \
-machine q35,accel=tcg \
-cpu qemu64 \
-m 1024 \
-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 \
-chardev socket,id=cserial,path=$$SERIAL_SOCK,server=on,wait=off,logfile=$$LOG \
-serial chardev:cserial \
-display none \
-no-reboot & QPID=$$!; \
DEADLINE=$$(( $$(date +%s) + 43200 )); \
echo " Waiting for ok> / zuse)ok> prompt..."; \
while ! grep -qE '\[Hera\].*(zuse\))?ok>' $$LOG 2>/dev/null; do \
[ $$(date +%s) -ge $$DEADLINE ] && echo " TIMEOUT waiting for ok>" && break; \
sleep 1; \
done; \
if grep -qE '\[Hera\].*(zuse\))?ok>' $$LOG 2>/dev/null; then \
if [ "$(DOE_INJECT)" = "1" ]; then \
echo " ok>/zuse)ok> detected — injecting EXEC-DOE..."; \
sleep 0.5; \
printf '$(DOE_SEED) $(DOE_REPS) EXEC-DOE\n' | socat - UNIX-CONNECT:$$SERIAL_SOCK 2>/dev/null || true; \
echo " Waiting for DoE to complete..."; \
DEADLINE2=$$(( $$(date +%s) + 14400 )); \
while [ "$$(grep -cE '\[Hera\].*(zuse\))?ok>' $$LOG 2>/dev/null)" -lt 3 ]; do \
[ $$(date +%s) -ge $$DEADLINE2 ] && echo " TIMEOUT waiting for DoE" && break; \
sleep 1; \
done; \
else \
echo " ok>/zuse)ok> detected — DOE_INJECT=0, skipping EXEC-DOE injection."; \
fi; \
fi; \
kill $$QPID 2>/dev/null; wait $$QPID 2>/dev/null || true; \
rm -f $$SERIAL_SOCK; \
echo ""; \
echo "=== Serial log: $$LOG ==="; \
cat $$LOG; \
echo ""; \
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; \
echo "=== Launching QEMU (aarch64) ==="; \
echo " ISO: $(QEMU_ISO)"; \
echo " Serial log: $$LOG"; \
echo " (exits when EXEC-DOE completes)"; \
echo ""; \
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 ramfb \
-chardev socket,id=cserial,path=$$SERIAL_SOCK,server=on,wait=off,logfile=$$LOG \
-serial chardev:cserial \
-display none \
-m 2048 \
-no-reboot \
-d guest_errors & QPID=$$!; \
DEADLINE=$$(( $$(date +%s) + 43200 )); \
echo " Waiting for ok> / zuse)ok> prompt..."; \
while ! grep -qE '\[Hera\].*(zuse\))?ok>' $$LOG 2>/dev/null; do \
[ $$(date +%s) -ge $$DEADLINE ] && echo " TIMEOUT waiting for ok>" && break; \
sleep 1; \
done; \
if grep -qE '\[Hera\].*(zuse\))?ok>' $$LOG 2>/dev/null; then \
if [ "$(DOE_INJECT)" = "1" ]; then \
echo " ok>/zuse)ok> detected — injecting EXEC-DOE..."; \
sleep 0.5; \
printf '$(DOE_SEED) $(DOE_REPS) EXEC-DOE\n' | socat - UNIX-CONNECT:$$SERIAL_SOCK 2>/dev/null || true; \
echo " Waiting for DoE to complete..."; \
DEADLINE2=$$(( $$(date +%s) + 14400 )); \
while [ "$$(grep -cE '\[Hera\].*(zuse\))?ok>' $$LOG 2>/dev/null)" -lt 3 ]; do \
[ $$(date +%s) -ge $$DEADLINE2 ] && echo " TIMEOUT waiting for DoE" && break; \
sleep 1; \
done; \
else \
echo " ok>/zuse)ok> detected — DOE_INJECT=0, skipping EXEC-DOE injection."; \
fi; \
fi; \
kill $$QPID 2>/dev/null; wait $$QPID 2>/dev/null || true; \
rm -f $$SERIAL_SOCK; \
echo ""; \
echo "=== Serial log: $$LOG ==="; \
cat $$LOG; \
echo ""; \
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; \
echo "=== Launching QEMU (riscv64) ==="; \
echo " Serial log: $$LOG"; \
echo " (exits when EXEC-DOE completes)"; \
echo ""; \
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 ramfb \
-chardev socket,id=cserial,path=$$SERIAL_SOCK,server=on,wait=off,logfile=$$LOG \
-serial chardev:cserial \
-display none \
-no-reboot \
-d guest_errors & QPID=$$!; \
DEADLINE=$$(( $$(date +%s) + 43200 )); \
echo " Waiting for ok> / zuse)ok> prompt..."; \
while ! grep -qE '\[Hera\].*(zuse\))?ok>' $$LOG 2>/dev/null; do \
[ $$(date +%s) -ge $$DEADLINE ] && echo " TIMEOUT waiting for ok>" && break; \
sleep 1; \
done; \
if grep -qE '\[Hera\].*(zuse\))?ok>' $$LOG 2>/dev/null; then \
if [ "$(DOE_INJECT)" = "1" ]; then \
echo " ok>/zuse)ok> detected — injecting EXEC-DOE..."; \
sleep 0.5; \
printf '$(DOE_SEED) $(DOE_REPS) EXEC-DOE\n' | socat - UNIX-CONNECT:$$SERIAL_SOCK 2>/dev/null || true; \
echo " Waiting for DoE to complete..."; \
DEADLINE2=$$(( $$(date +%s) + 14400 )); \
while [ "$$(grep -cE '\[Hera\].*(zuse\))?ok>' $$LOG 2>/dev/null)" -lt 3 ]; do \
[ $$(date +%s) -ge $$DEADLINE2 ] && echo " TIMEOUT waiting for DoE" && break; \
sleep 1; \
done; \
else \
echo " ok>/zuse)ok> detected — DOE_INJECT=0, skipping EXEC-DOE injection."; \
fi; \
fi; \
kill $$QPID 2>/dev/null; wait $$QPID 2>/dev/null || true; \
rm -f $$SERIAL_SOCK; \
echo ""; \
echo "=== Serial log: $$LOG ==="; \
cat $$LOG; \
echo ""; \
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 — quick iteration: boot from FAT directory (no disk image rebuild)
# amd64/aarch64: serial → stdio; riscv64: uses proper GPT (esp dir not supported)
qemu-esp: $(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
@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 none \
-machine q35,accel=tcg \
-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 \
-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: $(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 → 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 "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"