Boot minos on the real Zynq-7020 PS (Cortex-A9) without Vivado, an FSBL,
a bitstream, or DDR init — loaded straight into on-chip RAM over JTAG.
- link-zynq-ocm.ld: link the ARM build into OCM (0x0, 192 KiB), which is
alive from reset, so no FSBL/ps7_init and no DDR are needed.
- zynq/openocd-zynq.cfg: OpenOCD config for the on-board FT2232H. Its
EEPROM reports a generic 0403:6010 ("Adapt Device"), which Xilinx
hw_server refuses to auto-detect but OpenOCD drives directly; it sees
the full chain (PL boundary-scan 0x23727093 + ARM DAP 0x4ba00477).
- Makefile: 'zynq' builds the OCM ELF; 'run-zynq' halts A9 core0, loads
the image, and resumes.
The one non-obvious step: the on-board boot firmware leaves the core with
MMU + D-cache + I-cache enabled, which shadow the freshly loaded OCM and
make a plain resume run nothing. run-zynq clears SCTLR to 0x08C54C78 and
forces SVC mode before resuming. Halting (not resetting) keeps the
firmware's PS clock setup so the UART stays live.
Verified on hardware: banner once, then live keystroke echo over the
UART console (ttyUSB1 @115200). Same shared C core now boots on real
RISC-V (D1) and real ARM (Zynq) silicon, plus QEMU x86/riscv/arm.
156 lines
5.7 KiB
Makefile
156 lines
5.7 KiB
Makefile
# minos — minimal multi-arch kernel (M1)
|
|
# One clang, three ISAs. Build all in parallel: make -j
|
|
#
|
|
# make build all three (x86, riscv, arm)
|
|
# make x86 build one arch
|
|
# make run-riscv build + boot that arch under QEMU (-nographic; Ctrl-A X to quit)
|
|
# make sizes compare code size across arches
|
|
# make clean
|
|
|
|
CC := clang
|
|
LD := ld.lld
|
|
OBJCOPY := llvm-objcopy
|
|
|
|
ARCHES := x86 riscv arm
|
|
|
|
# Shared flags. Per-arch target/flags below.
|
|
CFLAGS := -ffreestanding -nostdlib -fno-pic -O2 -Wall -Wextra -Iinclude -g -MMD -MP
|
|
COMMON := common/main.c common/console.c
|
|
|
|
# --- per-arch knobs -------------------------------------------------------
|
|
x86_TARGET := --target=i386-unknown-none -m32
|
|
x86_LDEMU := elf_i386
|
|
x86_SRC := arch/x86/boot.S arch/x86/arch.c $(COMMON)
|
|
x86_QEMU := qemu-system-i386 -kernel build/x86/minos.elf -nographic
|
|
|
|
riscv_TARGET := --target=riscv64-unknown-none-elf -march=rv64imac -mabi=lp64 -mcmodel=medany
|
|
riscv_LDEMU := elf64lriscv
|
|
riscv_SRC := arch/riscv/boot.S arch/riscv/trap.S arch/riscv/arch.c $(COMMON)
|
|
riscv_QEMU := qemu-system-riscv64 -machine virt -bios none -nographic -kernel build/riscv/minos.elf
|
|
|
|
arm_TARGET := --target=arm-none-eabi -mcpu=cortex-a9 -marm
|
|
arm_LDEMU := armelf
|
|
arm_SRC := arch/arm/boot.S arch/arm/vectors.S arch/arm/arch.c $(COMMON)
|
|
arm_QEMU := qemu-system-arm -machine xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel build/arm/minos.elf
|
|
|
|
.PHONY: all clean sizes $(ARCHES) $(addprefix run-,$(ARCHES))
|
|
|
|
all: $(ARCHES)
|
|
|
|
# Generate per-arch build rules.
|
|
define ARCH_rules
|
|
$(1)_OBJS := $$(patsubst %,build/$(1)/%.o,$$(basename $$(notdir $$($(1)_SRC))))
|
|
|
|
build/$(1)/%.o: arch/$(1)/%.S | build/$(1)
|
|
$$(CC) $$($(1)_TARGET) $$(CFLAGS) -c $$< -o $$@
|
|
build/$(1)/%.o: arch/$(1)/%.c | build/$(1)
|
|
$$(CC) $$($(1)_TARGET) $$(CFLAGS) -c $$< -o $$@
|
|
build/$(1)/%.o: common/%.c | build/$(1)
|
|
$$(CC) $$($(1)_TARGET) $$(CFLAGS) -c $$< -o $$@
|
|
|
|
build/$(1):
|
|
mkdir -p $$@
|
|
|
|
build/$(1)/minos.elf: $$($(1)_OBJS) arch/$(1)/link.ld
|
|
$$(LD) -m $$($(1)_LDEMU) -T arch/$(1)/link.ld -nostdlib $$($(1)_OBJS) -o $$@
|
|
@echo " [$(1)] linked -> $$@"
|
|
|
|
$(1): build/$(1)/minos.elf
|
|
run-$(1): build/$(1)/minos.elf
|
|
@echo "== booting $(1) (Ctrl-A X to quit) =="
|
|
$$($(1)_QEMU)
|
|
|
|
-include $$($(1)_OBJS:.o=.d)
|
|
endef
|
|
|
|
$(foreach a,$(ARCHES),$(eval $(call ARCH_rules,$(a))))
|
|
|
|
sizes: all
|
|
@echo "arch text+data+bss (bytes)"; \
|
|
for a in $(ARCHES); do \
|
|
printf '%-7s ' $$a; \
|
|
$(OBJCOPY) -O binary build/$$a/minos.elf /tmp/_sz.bin 2>/dev/null; \
|
|
stat -c '%s' /tmp/_sz.bin; \
|
|
done; rm -f /tmp/_sz.bin
|
|
|
|
# --- real hardware: Allwinner D1 / Lichee RV via xfel ------------------------
|
|
# Same RISC-V sources, rebuilt with -DBOARD_D1 (UART 0x02500000) and linked
|
|
# for DRAM 0x40000000, emitted as a flat binary xfel can load.
|
|
XFEL ?= xfel
|
|
D1_TARGET := $(riscv_TARGET) -DBOARD_D1
|
|
D1_OBJS := $(patsubst %,build/lichee/%.o,$(basename $(notdir $(riscv_SRC))))
|
|
|
|
build/lichee/%.o: arch/riscv/%.S | build/lichee
|
|
$(CC) $(D1_TARGET) $(CFLAGS) -c $< -o $@
|
|
build/lichee/%.o: arch/riscv/%.c | build/lichee
|
|
$(CC) $(D1_TARGET) $(CFLAGS) -c $< -o $@
|
|
build/lichee/%.o: common/%.c | build/lichee
|
|
$(CC) $(D1_TARGET) $(CFLAGS) -c $< -o $@
|
|
build/lichee:
|
|
mkdir -p $@
|
|
|
|
build/lichee/minos.elf: $(D1_OBJS) arch/riscv/link-d1.ld
|
|
$(LD) -m $(riscv_LDEMU) -T arch/riscv/link-d1.ld -nostdlib $(D1_OBJS) -o $@
|
|
build/lichee/minos.bin: build/lichee/minos.elf
|
|
$(OBJCOPY) -O binary $< $@
|
|
@echo " [lichee] flat binary -> $@ ($$(stat -c%s $@) bytes)"
|
|
|
|
.PHONY: lichee run-lichee
|
|
lichee: build/lichee/minos.bin ## build the D1 flat binary
|
|
|
|
# Load & run on the board (must be in FEL mode; needs sudo for USB).
|
|
run-lichee: build/lichee/minos.bin
|
|
@echo "== loading minos onto D1 via xfel =="
|
|
$(XFEL) ddr d1
|
|
$(XFEL) write 0x40000000 build/lichee/minos.bin
|
|
$(XFEL) exec 0x40000000
|
|
@echo "== running. Watch /dev/ttyUSB0 @115200 for output. =="
|
|
|
|
# --- real hardware: Zynq-7000 PS (Cortex-A9) via JTAG/OpenOCD ----------------
|
|
# Same ARM sources, re-linked into on-chip RAM (OCM at 0x0) so no FSBL/DDR
|
|
# init is needed. Loaded over JTAG with OpenOCD (the on-board FT2232H is a
|
|
# generic 0403:6010 that Xilinx hw_server won't auto-detect, but OpenOCD does).
|
|
OPENOCD ?= openocd
|
|
OOCD_CFG := zynq/openocd-zynq.cfg
|
|
ZYNQ_OBJS := $(patsubst %,build/zynq/%.o,$(basename $(notdir $(arm_SRC))))
|
|
|
|
build/zynq/%.o: arch/arm/%.S | build/zynq
|
|
$(CC) $(arm_TARGET) $(CFLAGS) -c $< -o $@
|
|
build/zynq/%.o: arch/arm/%.c | build/zynq
|
|
$(CC) $(arm_TARGET) $(CFLAGS) -c $< -o $@
|
|
build/zynq/%.o: common/%.c | build/zynq
|
|
$(CC) $(arm_TARGET) $(CFLAGS) -c $< -o $@
|
|
build/zynq:
|
|
mkdir -p $@
|
|
|
|
build/zynq/minos.elf: $(ZYNQ_OBJS) arch/arm/link-zynq-ocm.ld
|
|
$(LD) -m $(arm_LDEMU) -T arch/arm/link-zynq-ocm.ld -nostdlib $(ZYNQ_OBJS) -o $@
|
|
@echo " [zynq] OCM ELF -> $@"
|
|
|
|
.PHONY: zynq run-zynq
|
|
zynq: build/zynq/minos.elf ## build the Zynq OCM ELF
|
|
|
|
# Halt A9 core0, load the ELF into OCM, set PC to _start, resume.
|
|
# Watch the UART console (ttyUSB1 @115200) for output.
|
|
#
|
|
# Critical: the on-board boot firmware leaves the core with MMU + D-cache +
|
|
# I-cache enabled (SCTLR bits 0/2/12). If we resume like that, the stale
|
|
# TLB/cache shadow our freshly loaded OCM and nothing runs. So we clear those
|
|
# bits (SCTLR -> 0x08C54C78) and force SVC mode before resuming. We keep the
|
|
# firmware's PS clock setup (incl. UART) by halting rather than resetting.
|
|
run-zynq: build/zynq/minos.elf
|
|
@echo "== loading minos into Zynq OCM via OpenOCD =="
|
|
$(OPENOCD) -f $(OOCD_CFG) \
|
|
-c "init" \
|
|
-c "targets zynq.cpu0" \
|
|
-c "halt" \
|
|
-c "load_image build/zynq/minos.elf" \
|
|
-c "arm mcr 15 0 1 0 0 0x08C54C78" \
|
|
-c "reg pc 0x00000000" \
|
|
-c "reg cpsr 0x000001d3" \
|
|
-c "resume" \
|
|
-c "shutdown"
|
|
@echo "== running. Watch /dev/ttyUSB1 @115200 for output. =="
|
|
|
|
clean:
|
|
rm -rf build
|