CROSS := i686-elf-
CC := $(CROSS)gcc
LD := $(CROSS)ld
QEMU := qemu-system-i386
MACHINE := pc-i440fx-7.2
BIOS := /usr/share/seabios/bios-256k.bin
include layout.mk
QEMU_COMMON := -machine $(MACHINE) -accel tcg -cpu qemu32 -smp 1 -m 64M -bios $(BIOS) -boot order=c,strict=on -nic none -no-reboot
QEMU_FLAGS := $(QEMU_COMMON) -drive file=build/mbr.img,format=raw,if=ide
QEMU_BLANK_FLAGS := $(QEMU_COMMON) -drive file=build/blank.img,format=raw,if=ide
QEMU_BADSIG_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-badsig.img,format=raw,if=ide
QEMU_TRUNCATED_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-truncated.img,format=raw,if=ide
QEMU_BADMAGIC_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-badmagic.img,format=raw,if=ide
QEMU_CORRUPT_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-corrupt.img,format=raw,if=ide
QEMU_OVERSIZED_HEADER_FLAGS := $(QEMU_COMMON) -drive file=build/mbr-oversized-header.img,format=raw,if=ide
CFLAGS := -std=c11 -ffreestanding -O0 -g -Wall -Wextra -Werror -fno-pie -fno-stack-protector -fno-asynchronous-unwind-tables -mno-sse -mno-sse2 -mno-mmx -msoft-float -fno-builtin

.PHONY: build run debug screenshot check check-layout help
build: build/sample.elf build/blank.img build/day02-mbr.img build/mbr.img build/mbr-badsig.img build/mbr-truncated.img build/mbr-badmagic.img build/mbr-oversized-header.img build/mbr-corrupt.img build/payload-layout.txt

build/.dir:
	mkdir -p build
	touch $@

build/layout.inc: layout.mk | build/.dir
	{ printf '%%define STAGE2_LBA %s\n' '$(STAGE2_LBA)'; printf '%%define STAGE2_SECTORS %s\n' '$(STAGE2_SECTORS)'; printf '%%define HEADER_LBA %s\n' '$(HEADER_LBA)'; printf '%%define PAYLOAD_LBA %s\n' '$(PAYLOAD_LBA)'; printf '%%define PAYLOAD_MAX_BYTES %s\n' '$(PAYLOAD_MAX_BYTES)'; printf '%%define PAYLOAD_SEGMENT %s\n' '$(PAYLOAD_SEGMENT)'; printf '%%define READ_CHUNK_SECTORS %s\n' '$(READ_CHUNK_SECTORS)'; printf '%%define HEADER_MAGIC %s\n' '$(HEADER_MAGIC)'; printf '%%define HEADER_VERSION %s\n' '$(HEADER_VERSION)'; } > $@

build/demo.o: sample/demo.c | build/.dir
	$(CC) $(CFLAGS) -c $< -o $@

build/entry.o: sample/entry.asm | build/.dir
	nasm -f elf32 -g -F dwarf $< -o $@

build/sample.elf: build/entry.o build/demo.o sample/linker.ld
	$(LD) -z noexecstack -T sample/linker.ld build/entry.o build/demo.o -o $@

build/mbr.bin: boot/mbr.asm build/layout.inc | build/.dir
	nasm -f bin $< -o $@
	test "$$(wc -c < $@)" -eq 512

build/day02-mbr.bin: boot/mbr.asm build/layout.inc | build/.dir
	nasm -D DAY02_ONLY -f bin $< -o $@
	test "$$(wc -c < $@)" -eq 512

build/stage2.bin: boot/stage2.asm boot/bootinfo.inc build/layout.inc | build/.dir
	nasm -f bin $< -o $@
	test "$$(wc -c < $@)" -eq 4096

check-layout:
	test $(STAGE2_LBA) -eq 1
	test $(STAGE2_SECTORS) -eq 8
	test $(HEADER_VERSION) -eq 1
	test $(PAYLOAD_MAX_BYTES) -eq 65536
	test $(PAYLOAD_RESERVED_SECTORS) -eq 128
	test $$(( $(PAYLOAD_SEGMENT) * 16 )) -eq $$(( $(PAYLOAD_LOAD) ))
	test $$(( $(PAYLOAD_LOAD) )) -eq 65536
	test $(FAT16_START_LBA) -lt $(IMAGE_SECTORS)
	test $(READ_CHUNK_SECTORS) -gt 0
	test $$(( 128 % $(READ_CHUNK_SECTORS) )) -eq 0
	test "$(HEADER_LBA)" -eq "$$(( $(STAGE2_LBA) + $(STAGE2_SECTORS) ))"
	test "$(PAYLOAD_LBA)" -eq "$$(( $(HEADER_LBA) + 1 ))"
	test "$$(( $(PAYLOAD_LBA) + $(PAYLOAD_RESERVED_SECTORS) ))" -le "$(FAT16_START_LBA)"
	test "$(READ_CHUNK_SECTORS)" -le 127
	test "$$(( $(READ_CHUNK_SECTORS) * 512 ))" -le 65536

build/mkpayload: tools/mkpayload.c | build/.dir
	cc -std=c11 -O2 -Wall -Wextra -Werror $< -o $@

build/kernel-%.o: kernel/%.c kernel/runtime.h kernel/console.h | build/.dir
	$(CC) $(CFLAGS) -c $< -o $@

build/kernel-entry.o: kernel/entry.asm | build/.dir
	nasm -f elf32 -g -F dwarf $< -o $@

build/kernel-entry-badbss.o: kernel/entry.asm | build/.dir
	nasm -D SKIP_BSS_CLEAR -f elf32 -g -F dwarf $< -o $@

build/kernel.elf: build/kernel-entry.o build/kernel-main.o build/kernel-runtime.o build/kernel-console.o build/kernel-exceptions.o build/kernel-isr.o build/kernel-timer.o kernel/linker.ld
	$(LD) -z noexecstack -T kernel/linker.ld $(filter %.o,$^) -o $@

build/kernel-badbss.elf: build/kernel-entry-badbss.o build/kernel-main.o build/kernel-runtime.o build/kernel-console.o build/kernel-exceptions.o build/kernel-isr.o build/kernel-timer.o kernel/linker.ld
	$(LD) -z noexecstack -T kernel/linker.ld $(filter %.o,$^) -o $@

build/kernel.bin: build/kernel.elf
	$(CROSS)objcopy -O binary $< $@

build/header.bin build/header-badmagic.bin build/header-oversized.bin &: build/kernel.bin build/kernel.elf build/mkpayload tools/kernel-header.sh layout.mk
	sh tools/kernel-header.sh build/kernel $(PAYLOAD_MAX_BYTES) $(HEADER_MAGIC) $(HEADER_VERSION)

build/kernel-badbss.bin: build/kernel-badbss.elf
	$(CROSS)objcopy -O binary $< $@

build/kernel-badbss-header.bin: build/kernel-badbss.bin build/mkpayload tools/kernel-header.sh layout.mk
	sh tools/kernel-header.sh build/kernel-badbss $(PAYLOAD_MAX_BYTES) $(HEADER_MAGIC) $(HEADER_VERSION)

build/mbr-badbss.img: build/mbr.img build/kernel-badbss-header.bin
	cp $< $@
	dd if=build/kernel-badbss-header.bin of=$@ bs=512 seek=$(HEADER_LBA) conv=notrunc status=none
	dd if=build/kernel-badbss.bin of=$@ bs=512 seek=$(PAYLOAD_LBA) conv=notrunc status=none


build/kernel.pad: build/kernel.bin build/header.bin
	dd if=/dev/zero of=$@ bs=512 count=$(PAYLOAD_RESERVED_SECTORS) status=none
	dd if=$< of=$@ bs=512 conv=notrunc status=none
	test "$$(wc -c < $@)" -eq $$(( $(PAYLOAD_RESERVED_SECTORS) * 512 ))

build/payload-layout.txt: build/kernel.bin
	{ printf 'mbr_lba=0\n'; printf 'stage2_lba=$(STAGE2_LBA)\n'; printf 'stage2_sectors=$(STAGE2_SECTORS)\n'; printf 'header_lba=$(HEADER_LBA)\n'; printf 'payload_lba=$(PAYLOAD_LBA)\n'; printf 'payload_bytes=%s\n' "$$(wc -c < build/kernel.bin)"; printf 'payload_sectors=%s\n' "$$(( ($$(wc -c < build/kernel.bin) + 511) / 512 ))"; printf 'payload_reserved_sectors=$(PAYLOAD_RESERVED_SECTORS)\n'; printf 'payload_load=$(PAYLOAD_LOAD)\n'; printf 'fat16_start_lba=$(FAT16_START_LBA)\n'; printf 'read_chunk_sectors=$(READ_CHUNK_SECTORS)\n'; } > $@

build/day02-mbr.img: build/day02-mbr.bin
	cp $< $@
	truncate -s $$(( $(IMAGE_SECTORS) * 512 )) $@

build/mbr.img: build/mbr.bin build/stage2.bin build/header.bin build/kernel.pad
	cp $< $@
	truncate -s $$(( $(IMAGE_SECTORS) * 512 )) $@
	dd if=build/stage2.bin of=$@ bs=512 seek=$(STAGE2_LBA) conv=notrunc status=none
	dd if=build/header.bin of=$@ bs=512 seek=$(HEADER_LBA) conv=notrunc status=none
	dd if=build/kernel.pad of=$@ bs=512 seek=$(PAYLOAD_LBA) conv=notrunc status=none

build/mbr-badsig.img: build/mbr.img
	cp $< $@
	printf '\0\0' | dd of=$@ bs=1 seek=510 conv=notrunc status=none

build/mbr-truncated.img: build/mbr.img
	cp $< $@
	truncate -s $$(( ($(PAYLOAD_LBA) + ($$(wc -c < build/kernel.bin) + 511) / 512 - 1) * 512 )) $@

build/mbr-badmagic.img: build/mbr.img build/header-badmagic.bin
	cp build/mbr.img $@
	dd if=build/header-badmagic.bin of=$@ bs=512 seek=$(HEADER_LBA) conv=notrunc status=none

build/mbr-oversized-header.img: build/mbr.img build/header-oversized.bin
	cp build/mbr.img $@
	dd if=build/header-oversized.bin of=$@ bs=512 seek=$(HEADER_LBA) conv=notrunc status=none

build/mbr-corrupt.img: build/mbr.img
	cp $< $@
	printf '\0' | dd of=$@ bs=1 seek=$$(( $(PAYLOAD_LBA) * 512 )) conv=notrunc status=none

build/blank.img: | build/.dir
	dd if=/dev/zero of=$@ bs=512 count=2048 status=none

run: build
	LC_ALL=C.UTF-8 $(QEMU) $(QEMU_FLAGS) -display curses -serial none

debug: build
	$(QEMU) $(QEMU_FLAGS) -display none -serial none -monitor none -S -gdb tcp:127.0.0.1:1234

screenshot: build
	QEMU='$(QEMU)' QEMU_COMMON='$(QEMU_COMMON)' sh tools/check-timer.sh screenshot

check: build
	QEMU='$(QEMU)' QEMU_FLAGS='$(QEMU_FLAGS)' QEMU_BLANK_FLAGS='$(QEMU_BLANK_FLAGS)' QEMU_BADSIG_FLAGS='$(QEMU_BADSIG_FLAGS)' QEMU_TRUNCATED_FLAGS='$(QEMU_TRUNCATED_FLAGS)' QEMU_BADMAGIC_FLAGS='$(QEMU_BADMAGIC_FLAGS)' QEMU_CORRUPT_FLAGS='$(QEMU_CORRUPT_FLAGS)' QEMU_OVERSIZED_HEADER_FLAGS='$(QEMU_OVERSIZED_HEADER_FLAGS)' PAYLOAD_MAX_BYTES='$(PAYLOAD_MAX_BYTES)' sh tools/check.sh

help:
	@printf '%s\n' 'make build: ELF32 sample, blank disk, day-02 MBR variant and day-08 boot disk' 'make run: boot day-08 image in curses display' 'make debug: CPU paused, GDB on container loopback port 1234' 'make screenshot: save build/day08-screen.png with QEMU monitor' 'make check: ELF, missing-symbol, boot-sector, EDD header/payload read, checksum and failure-path checks'

# Fault injection never modifies the normal image.
build/stage2-badgdt.bin: boot/stage2.asm boot/bootinfo.inc build/layout.inc
	nasm -D BAD_GDT -f bin $< -o $@
build/stage2-a20fail.bin: boot/stage2.asm boot/bootinfo.inc build/layout.inc
	nasm -D FORCE_A20_FAIL -f bin $< -o $@
build/mbr-badgdt.img: build/mbr.img build/stage2-badgdt.bin
	cp $< $@
	dd if=build/stage2-badgdt.bin of=$@ bs=512 seek=$(STAGE2_LBA) conv=notrunc status=none
build/mbr-a20fail.img: build/mbr.img build/stage2-a20fail.bin
	cp $< $@
	dd if=build/stage2-a20fail.bin of=$@ bs=512 seek=$(STAGE2_LBA) conv=notrunc status=none
build: build/mbr-badgdt.img build/mbr-a20fail.img
check: check-pm
.PHONY: check-pm
check-pm: build
	QEMU='$(QEMU)' QEMU_COMMON='$(QEMU_COMMON)' sh tools/check-pm.sh

build/stage2-a20off.bin: boot/stage2.asm boot/bootinfo.inc build/layout.inc
	nasm -D FORCE_A20_OFF -f bin $< -o $@
build/stage2-e820full.bin: boot/stage2.asm boot/bootinfo.inc build/layout.inc
	nasm -D E820_CAPACITY=1 -f bin $< -o $@
build/mbr-a20off.img: build/mbr.img build/stage2-a20off.bin
	cp $< $@
	dd if=build/stage2-a20off.bin of=$@ bs=512 seek=$(STAGE2_LBA) conv=notrunc status=none
build/mbr-e820full.img: build/mbr.img build/stage2-e820full.bin
	cp $< $@
	dd if=build/stage2-e820full.bin of=$@ bs=512 seek=$(STAGE2_LBA) conv=notrunc status=none
build: build/mbr-a20off.img build/mbr-e820full.img

build: build/mbr-badbss.img
.PHONY: check-kernel
check-kernel: build
	QEMU='$(QEMU)' QEMU_COMMON='$(QEMU_COMMON)' sh tools/check-kernel.sh
check: check-kernel

build/stage2-chunk1.bin: boot/stage2.asm boot/bootinfo.inc build/layout.inc
	nasm -D ONE_SECTOR_READS -f bin $< -o $@
build/mbr-chunk1.img: build/mbr.img build/stage2-chunk1.bin
	cp $< $@
	dd if=build/stage2-chunk1.bin of=$@ bs=512 seek=$(STAGE2_LBA) conv=notrunc status=none
build: build/mbr-chunk1.img
# All checks share one GDB port; serialize even when invoked with make -j.
.NOTPARALLEL: check check-pm check-kernel

# Independent assertion image shares the normal loader and runtime.
build/kernel-main-panic.o: kernel/main.c kernel/runtime.h kernel/console.h kernel/exceptions.h kernel/timer.h
	$(CC) $(CFLAGS) -D TEST_PANIC -c $< -o $@
build/kernel-panic.elf: build/kernel-entry.o build/kernel-main-panic.o build/kernel-runtime.o build/kernel-console.o build/kernel-exceptions.o build/kernel-isr.o build/kernel-timer.o kernel/linker.ld
	$(LD) -z noexecstack -T kernel/linker.ld $(filter %.o,$^) -o $@
build/kernel-panic.bin: build/kernel-panic.elf
	$(CROSS)objcopy -O binary $< $@
build/kernel-panic-header.bin: build/kernel-panic.bin build/mkpayload tools/kernel-header.sh layout.mk
	sh tools/kernel-header.sh build/kernel-panic $(PAYLOAD_MAX_BYTES) $(HEADER_MAGIC) $(HEADER_VERSION)
build/mbr-panic.img: build/mbr.img build/kernel-panic-header.bin
	cp $< $@
	dd if=build/kernel-panic-header.bin of=$@ bs=512 seek=$(HEADER_LBA) conv=notrunc status=none
	dd if=build/kernel-panic.bin of=$@ bs=512 seek=$(PAYLOAD_LBA) conv=notrunc status=none
build: build/mbr-panic.img
.PHONY: check-console
check-console: build
	QEMU='$(QEMU)' QEMU_COMMON='$(QEMU_COMMON)' sh tools/check-console.sh
check: check-console
.NOTPARALLEL: check-console

# Classical qemu32 CPU exceptions plus IRQ0 on vector 32.
build/kernel-isr.o: kernel/exceptions.asm | build/.dir
	nasm -f elf32 -g -F dwarf $< -o $@
build/kernel-exceptions.o: kernel/exceptions.h
build/kernel-main.o: kernel/exceptions.h

define EXCEPTION_IMAGE
build/kernel-main-$(1).o: kernel/main.c kernel/runtime.h kernel/console.h kernel/exceptions.h kernel/timer.h
	$$(CC) $$(CFLAGS) -D $(2) -c $$< -o $$@
build/kernel-$(1).elf: build/kernel-entry.o build/kernel-main-$(1).o build/kernel-runtime.o build/kernel-console.o build/kernel-exceptions.o build/kernel-isr.o build/kernel-timer.o kernel/linker.ld
	$$(LD) -z noexecstack -T kernel/linker.ld $$(filter %.o,$$^) -o $$@
build/kernel-$(1).bin: build/kernel-$(1).elf
	$$(CROSS)objcopy -O binary $$< $$@
build/kernel-$(1)-header.bin: build/kernel-$(1).bin build/mkpayload tools/kernel-header.sh layout.mk
	sh tools/kernel-header.sh build/kernel-$(1) $$(PAYLOAD_MAX_BYTES) $$(HEADER_MAGIC) $$(HEADER_VERSION)
build/mbr-$(1).img: build/mbr.img build/kernel-$(1)-header.bin
	cp $$< $$@
	dd if=build/kernel-$(1)-header.bin of=$$@ bs=512 seek=$$(HEADER_LBA) conv=notrunc status=none
	dd if=build/kernel-$(1).bin of=$$@ bs=512 seek=$$(PAYLOAD_LBA) conv=notrunc status=none
endef
$(eval $(call EXCEPTION_IMAGE,divide,TEST_DIVIDE))
$(eval $(call EXCEPTION_IMAGE,ud2,TEST_UD2))
$(eval $(call EXCEPTION_IMAGE,gp,TEST_GP))
.PHONY: check-exceptions
check-exceptions: build build/mbr-divide.img build/mbr-ud2.img build/mbr-gp.img
	QEMU='$(QEMU)' QEMU_COMMON='$(QEMU_COMMON)' sh tools/check-exceptions.sh
check: check-exceptions
.NOTPARALLEL: check-exceptions

# Day 08 diagnostics are independent images; normal boot still reaches _start.halt.
$(eval $(call EXCEPTION_IMAGE,timer-monitor,TEST_TIMER_MONITOR))
$(eval $(call EXCEPTION_IMAGE,timer-noeoi,TEST_TIMER_NOEOI))
build/kernel-timer.o build/kernel-main.o build/kernel-exceptions.o: kernel/timer.h
.PHONY: check-timer
check-timer: build build/mbr-timer-monitor.img build/mbr-timer-noeoi.img
	QEMU='$(QEMU)' QEMU_COMMON='$(QEMU_COMMON)' sh tools/check-timer.sh
check: check-timer
.NOTPARALLEL: check-timer
