PROJECT := ariel
TARGET := aarch64-none-elf
BUILD := build
HV_ELF := $(BUILD)/hypervisor.elf
HV_BIN := $(BUILD)/hypervisor.bin
GUEST_ELF := $(BUILD)/guest/diag.elf
GUEST_BIN := $(BUILD)/guest/diag.bin
MAP := $(BUILD)/hypervisor.map
OBJDUMP := $(BUILD)/hypervisor.objdump
READELF := $(BUILD)/hypervisor.readelf
DIST := dist
RELEASE_DIR := $(DIST)/$(PROJECT)-qemu-virt-aarch64
RELEASE_TAR := $(DIST)/$(PROJECT)-qemu-virt-aarch64.tar.gz

CC := clang
LD := ld.lld
OBJCOPY := llvm-objcopy
OBJDUMP_TOOL := llvm-objdump
READELF_TOOL := llvm-readelf
PYTHON ?= python3
QEMU ?= qemu-system-aarch64
QEMU_RAM ?= 1G
comma := ,
LINUX_DIR ?= /tmp/ariel-linux
LINUX_IMAGE_URL ?= http://deb.debian.org/debian/dists/bookworm/main/installer-arm64/current/images/netboot/debian-installer/arm64/linux
LINUX_INITRD_URL ?= http://deb.debian.org/debian/dists/bookworm/main/installer-arm64/current/images/netboot/debian-installer/arm64/initrd.gz
LINUX_INITRD_BYTES := $(if $(LINUX_INITRD),$(shell stat -c %s "$(LINUX_INITRD)" 2>/dev/null),)
LINUX_INITRD_SIZE := $(if $(LINUX_INITRD),$(if $(LINUX_INITRD_BYTES),$(LINUX_INITRD_BYTES),0),0)
LINUX_INITRD_DEVICE := $(if $(LINUX_INITRD),-device loader$(comma)file=$(LINUX_INITRD)$(comma)addr=0x46000000$(comma)force-raw=on,)

COMMON_CFLAGS := --target=$(TARGET) -std=c11 -ffreestanding -fno-builtin \
	-fno-stack-protector -fno-unwind-tables -fno-asynchronous-unwind-tables \
	-fno-exceptions -mgeneral-regs-only -mstrict-align -Wall -Wextra -Werror \
	-Wconversion -Wsign-conversion -Wmissing-prototypes -Wstrict-prototypes \
	-Iinclude -I.
EXTRA_CFLAGS ?=
HV_CFLAGS := $(COMMON_CFLAGS) -O2 -g -D__HYPERVISOR__ $(EXTRA_CFLAGS)
GUEST_CFLAGS := $(COMMON_CFLAGS) -O2 -g -D__GUEST__
ASFLAGS := --target=$(TARGET) -g -Iinclude -I.
LDFLAGS := -nostdlib -static --fatal-warnings

HV_C_SRCS := \
	arch/aarch64/arch.c \
	core/main.c core/el2_mmu.c core/fdt.c core/guest_loader.c core/image.c core/log.c core/mmio_policy.c core/panic.c core/policy.c core/psci.c core/selftest.c \
	core/string.c core/sysreg.c core/trap.c core/vcpu.c core/monitor.c \
	drivers/gicv3.c drivers/pl011.c drivers/timer.c \
	mm/allocator.c mm/stage2.c
HV_S_SRCS := arch/aarch64/entry.S arch/aarch64/vectors.S arch/aarch64/vcpu.S arch/aarch64/guest_blob.S
GUEST_C_SRCS := guest/main.c
GUEST_S_SRCS := guest/start.S

HV_OBJS := $(HV_C_SRCS:%.c=$(BUILD)/%.o) $(HV_S_SRCS:%.S=$(BUILD)/%.o)
GUEST_OBJS := $(GUEST_C_SRCS:%.c=$(BUILD)/%.o) $(GUEST_S_SRCS:%.S=$(BUILD)/%.o)

.PHONY: all clean run run-linux linux-artifacts test-linux-smoke test-diagnostic-smoke test-host test ci release debug objdump readelf
all: $(HV_ELF) $(HV_BIN) $(OBJDUMP) $(READELF)

$(BUILD):
	mkdir -p $(BUILD)

$(BUILD)/guest/%.o: guest/%.c
	mkdir -p $(dir $@)
	$(CC) $(GUEST_CFLAGS) -c $< -o $@

$(BUILD)/%.o: %.c
	mkdir -p $(dir $@)
	$(CC) $(HV_CFLAGS) -c $< -o $@

$(BUILD)/%.o: %.S
	mkdir -p $(dir $@)
	$(CC) $(ASFLAGS) -c $< -o $@

$(BUILD)/arch/aarch64/guest_blob.o: arch/aarch64/guest_blob.S $(GUEST_BIN)
	mkdir -p $(dir $@)
	$(CC) $(ASFLAGS) -c $< -o $@

$(GUEST_ELF): $(GUEST_OBJS) guest/linker.ld
	mkdir -p $(dir $@)
	$(LD) $(LDFLAGS) -T guest/linker.ld $(GUEST_OBJS) -o $@

$(GUEST_BIN): $(GUEST_ELF)
	$(OBJCOPY) -O binary $< $@

$(HV_ELF): $(HV_OBJS) linker.ld
	mkdir -p $(dir $@)
	$(LD) $(LDFLAGS) -T linker.ld -Map=$(MAP) $(HV_OBJS) -o $@

$(HV_BIN): $(HV_ELF)
	$(OBJCOPY) -O binary $< $@

$(OBJDUMP): $(HV_ELF)
	$(OBJDUMP_TOOL) -d -S $< > $@

$(READELF): $(HV_ELF)
	$(READELF_TOOL) -a $< > $@

run: $(HV_ELF)
	$(QEMU) -M virt,virtualization=on,gic-version=3 -cpu cortex-a57 -m $(QEMU_RAM) \
		-nographic -serial mon:stdio -kernel $(HV_ELF)

run-linux:
	@if [ -z "$(LINUX_IMAGE)" ] || [ -z "$(LINUX_DTB)" ]; then \
		echo "usage: make run-linux LINUX_IMAGE=/path/Image LINUX_DTB=/path/qemu-virt.dtb [LINUX_INITRD=/path/initrd]"; \
		echo "builds a Linux-profile hypervisor and loads Image+DTB into guest RAM"; \
	else \
		$(MAKE) -B BUILD=build-linux EXTRA_CFLAGS="-DCONFIG_BOOT_LINUX=1 -DCONFIG_LINUX_INITRD_SIZE=$(LINUX_INITRD_SIZE)ull" all && \
		$(QEMU) -M virt,virtualization=on,gic-version=3 -cpu cortex-a57 -m $(QEMU_RAM) \
			-nographic -serial mon:stdio -kernel build-linux/hypervisor.elf \
			-device loader,file=$(LINUX_IMAGE),addr=0x42000000,force-raw=on \
			-device loader,file=$(LINUX_DTB),addr=0x71e00000,force-raw=on \
			$(LINUX_INITRD_DEVICE); \
	fi

linux-artifacts:
	mkdir -p $(LINUX_DIR)
	$(QEMU) -M virt,virtualization=on,gic-version=3,dumpdtb=$(LINUX_DIR)/virt.dtb -cpu cortex-a57 -m $(QEMU_RAM) -nographic
	curl -L --fail --show-error --output $(LINUX_DIR)/debian-arm64-linux $(LINUX_IMAGE_URL)
	curl -L --fail --show-error --output $(LINUX_DIR)/debian-arm64-initrd.gz $(LINUX_INITRD_URL)

test-linux-smoke: linux-artifacts
	bash -o pipefail -c 'timeout 180s $(MAKE) run-linux LINUX_IMAGE=$(LINUX_DIR)/debian-arm64-linux LINUX_DTB=$(LINUX_DIR)/virt.dtb LINUX_INITRD=$(LINUX_DIR)/debian-arm64-initrd.gz 2>&1 | tee $(LINUX_DIR)/linux-smoke.log; status=$${PIPESTATUS[0]}; test $$status -eq 0 -o $$status -eq 124'
	grep -q 'Booting Linux on physical CPU' $(LINUX_DIR)/linux-smoke.log
	grep -q 'Kernel command line: console=ttyAMA0 earlycon' $(LINUX_DIR)/linux-smoke.log
	grep -q 'CPU: All CPU(s) started at EL1' $(LINUX_DIR)/linux-smoke.log
	grep -q 'Trying to unpack rootfs image as initramfs' $(LINUX_DIR)/linux-smoke.log
	grep -q 'Freeing initrd memory' $(LINUX_DIR)/linux-smoke.log
	grep -q 'Run /init as init process' $(LINUX_DIR)/linux-smoke.log
	! grep -q 'Initramfs unpacking failed' $(LINUX_DIR)/linux-smoke.log
	! grep -q 'Kernel panic' $(LINUX_DIR)/linux-smoke.log

test-diagnostic-smoke: $(HV_ELF)
	bash -o pipefail -c 'printf "status\n" | timeout 14s $(MAKE) run 2>&1 | tee $(BUILD)/diagnostic-smoke.log; status=$${PIPESTATUS[1]}; test $$status -eq 0 -o $$status -eq 124'
	grep -q 'ariel qemu-virt-aarch64' $(BUILD)/diagnostic-smoke.log
	grep -q 'guest: diagnostic guest: requested hv id' $(BUILD)/diagnostic-smoke.log
	grep -q 'monitor entered reason=guest requested pause' $(BUILD)/diagnostic-smoke.log
	! grep -q 'panic:' $(BUILD)/diagnostic-smoke.log

test-host:
	mkdir -p build-host
	$(CC) -std=c11 -Wall -Wextra -Werror -Iinclude tests/host/test_hv_types.c -o build-host/test_hv_types
	build-host/test_hv_types
	$(CC) -std=c11 -Wall -Wextra -Werror -Iinclude tests/host/test_hv_interval.c -o build-host/test_hv_interval
	build-host/test_hv_interval
	$(CC) -std=c11 -Wall -Wextra -Werror -Iinclude tests/host/test_qemu_virt.c -o build-host/test_qemu_virt
	build-host/test_qemu_virt
	$(CC) -std=c11 -Wall -Wextra -Werror -Iinclude tests/host/test_hvc_abi.c -o build-host/test_hvc_abi
	build-host/test_hvc_abi
	$(CC) -std=c11 -Wall -Wextra -Werror -Iinclude tests/host/test_sysreg_key.c core/sysreg.c core/string.c -o build-host/test_sysreg_key
	build-host/test_sysreg_key
	$(PYTHON) tests/test_log_decode.py
	$(PYTHON) scripts/verify_topology.py
	$(PYTHON) scripts/check_release.py

test: all test-host test-diagnostic-smoke

ci: clean release

release: test
	scripts/release.sh

debug: $(HV_ELF)
	@echo "gdb command: gdb-multiarch $(HV_ELF) -ex 'target remote :1234'"
	$(QEMU) -M virt,virtualization=on,gic-version=3 -cpu cortex-a57 -m $(QEMU_RAM) \
		-nographic -serial mon:stdio -S -gdb tcp::1234 -kernel $(HV_ELF)

objdump: $(OBJDUMP)
readelf: $(READELF)

clean:
	rm -rf $(BUILD)
