# ============================================================================== # 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 (per docs/lithosananke/ROADMAP.md "Release Versioning Policy" and # FABRIC-3.md §G — X.0.0 = QEMU release, X.5.0 = hardware bare-metal release): # v1.0.x — serial-only production (released) # v1.5.x — framebuffer VT100 terminal/console milestone (released) # v2.0.0 — QEMU release (even major = LTS): three-arch QEMU story complete # v2.5.0 — hardware bare-metal release: real per-arch RNG + real-board boot VERSION ?= 3.1.0 LITHOS_VERSION ?= 2.0.0 # ============================================================================== # 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 # ZUSEDISK -- Zuse's own minted USB thumbdrive, attached by default so a # plain `make qemu` lands already authenticated into the Zuse identity # (Captain Bob, 2026-08-28). Attached at QEMU launch time via -drive/ # -device on the qemu-xhci controller each arch's qemu target already # creates; xhci_bringup()'s initial port scan (src/starkernel/usb/xhci.c) # is what makes an already-connected-at-launch device visible at all -- # xhci_poll_events() alone is purely hotplug-event-driven and would never # see a device present before controller reset. Override to "" (empty) to # boot without Zuse attached, e.g. `make -f Makefile.starkernel qemu ZUSEDISK=`. ZUSEDISK ?= disk/zuse.img # Precomputed (not inlined as $(if ...,...)) because the drive/device specs # below are comma-heavy and GNU make's $(if) function splits its own # arguments on every top-level comma -- inlining it silently truncated the # flags to just "-drive if=none" with everything past the next comma # dropped, confirmed live via `ps aux` showing the truncated qemu command # line (2026-08-28). ifneq ($(strip $(ZUSEDISK)),) ZUSEDISK_QEMU_ARGS := -drive if=none,id=zusedrv,file=$(ZUSEDISK),format=raw -device usb-storage,bus=xhci0.0,drive=zusedrv else ZUSEDISK_QEMU_ARGS := endif MKCAPSULE_SRC = tools/mkcapsule.c tools/pkcs8_ed25519.c \ src/starkernel/crypto/ed25519.c \ src/starkernel/crypto/fe25519.c \ src/starkernel/crypto/scalar25519.c \ src/starkernel/crypto/sha512.c MKCAPSULE_BIN = $(BUILD_DIR)/tools/mkcapsule # Milestone 6 (Phase 8): the snakeoil intermediate's private key, generated # offline outside this repo entirely (see FABRIC-3.md's Phase 8 §Milestone 6 # writeup) -- not present in CI or a fresh checkout, so signing is skipped # gracefully ($(wildcard ...) below) rather than failing the build. Override # with `make SIGN_KEY=/path/to/key.pem ...` on a machine that holds it. SIGN_KEY ?= /home/rajames/CLionProjects/lithosananke-ca/intermediate/snakeoil-intermediate.key SIGN_KEY_ARGS = $(if $(wildcard $(SIGN_KEY)),--sign-key $(SIGN_KEY),) 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,STADIUM_BLOCK_HEAT_QUANTUM,2048)) $(eval $(call kconfig_int,STADIUM_BLOCK_COOL_RATE_Q48,21845)) $(eval $(call kconfig_int,STADIUM_BLOCK_TRACK_CAP_MULT,2)) $(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= # --stack=[MG] --heap=[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)/crypto/*.c) \ $(wildcard $(KERNEL_SRC)/capsule/*.c) \ $(wildcard $(KERNEL_SRC)/pci/*.c) \ $(wildcard $(KERNEL_SRC)/virtio/*.c) \ $(wildcard $(KERNEL_SRC)/rng/*.c) \ $(wildcard $(KERNEL_SRC)/usb/*.c) \ $(KERNEL_SRC)/repl.c \ $(KERNEL_SRC)/doe_log.c \ $(KERNEL_SRC)/heartbeat.c \ $(KERNEL_SRC)/homeblocks_sig.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)/crypto/*.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)/rng/*.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_SRC)/homeblocks_sig.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_SRC)/vm/stadium_blocks.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 $(MKCAPSULE_SRC)" @cc -std=c99 -Wall -Wextra -O2 -Iinclude -Itools -o $@ $(MKCAPSULE_SRC) # 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) $(SIGN_KEY_ARGS) $(CAPSULES_DIR) $@ @echo " MKCAP $(CAPSULES_DIR) -> $(CAPSULES_DIR)/BLOCK_MAP.md" @$(MKCAPSULE_BIN) --manifest $(SIGN_KEY_ARGS) $(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 $(SIGN_KEY_ARGS) $(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_EXTRA — extra raw qemu-system-* args appended verbatim to every arch's # qemu invocation below. Empty by default (no behavior change for a normal # boot). Existing use: attaching a disposable USB test image for WRITE(10) # validation, e.g. # QEMU_EXTRA="-drive if=none,id=usbtest,file=disk/usbwrite-test.img,format=raw \ # -device usb-storage,bus=xhci0.0,drive=usbtest" QEMU_EXTRA ?= 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 @if [ ! -f $(BUILD_DIR)/OVMF_VARS.fd ]; then \ cp $(OVMF_VARS_RO) $(BUILD_DIR)/OVMF_VARS.fd; \ fi @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 \ $(ZUSEDISK_QEMU_ARGS) \ -object rng-random,id=rng0,filename=/dev/urandom \ -device virtio-rng-pci,rng=rng0 \ -chardev socket,id=cserial,path=$$SERIAL_SOCK,server=on,wait=off,logfile=$$LOG \ -serial chardev:cserial \ -qmp unix:$$QMP_SOCK,server=on,wait=off \ $(QEMU_EXTRA) \ -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; \ AAVMF_VARS_RO=/usr/share/AAVMF/AAVMF_VARS.fd; \ elif [ -f /usr/share/qemu-efi-aarch64/QEMU_EFI.fd ]; then \ AAVMF_CODE=/usr/share/qemu-efi-aarch64/QEMU_EFI.fd; \ AAVMF_VARS_RO=/usr/share/qemu-efi-aarch64/vars-template-pflash.raw; \ elif [ -f /usr/share/qemu/edk2-aarch64-code.fd ]; then \ AAVMF_CODE=/usr/share/qemu/edk2-aarch64-code.fd; \ AAVMF_VARS_RO=/usr/share/qemu/edk2-aarch64-vars.fd; \ else \ echo "Error: aarch64 UEFI firmware not found. Install qemu-efi-aarch64."; \ exit 1; \ fi; \ if [ -f "$$AAVMF_VARS_RO" ]; then \ if [ ! -f $(BUILD_DIR)/AAVMF_VARS.fd ]; then \ cp $$AAVMF_VARS_RO $(BUILD_DIR)/AAVMF_VARS.fd; \ fi; \ AAVMF_VARS=$(BUILD_DIR)/AAVMF_VARS.fd; \ else \ echo " (no split VARS template found for this firmware -- NVRAM will not persist across runs)"; \ AAVMF_VARS=""; \ 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 \ $$(if [ -n "$$AAVMF_VARS" ]; then \ echo "-drive if=pflash,format=raw,readonly=on,file=$$AAVMF_CODE -drive if=pflash,format=raw,file=$$AAVMF_VARS"; \ else \ echo "-bios $$AAVMF_CODE"; \ fi) \ -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 \ $(ZUSEDISK_QEMU_ARGS) \ -object rng-random,id=rng0,filename=/dev/urandom \ -device virtio-rng-pci,rng=rng0,addr=0x5 \ -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 \ $(QEMU_EXTRA) \ -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 \ if [ ! -f $(BUILD_DIR)/RISCV_VARS.fd ]; then \ cp /usr/share/qemu-efi-riscv64/RISCV_VIRT_VARS.fd $(BUILD_DIR)/RISCV_VARS.fd; \ fi; \ 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 \ $(ZUSEDISK_QEMU_ARGS) \ -object rng-random,id=rng0,filename=/dev/urandom \ -device virtio-rng-pci,rng=rng0,addr=0x5 \ -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 \ $(QEMU_EXTRA) \ -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=] [TARGET=] " @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/// + 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"