# Nuke built-in rules and variables. MAKEFLAGS += -rR .SUFFIXES: # Target architecture to build for. Default to x86_64. ARCH := x86_64 # Default user QEMU flags. These are appended to the QEMU command calls. QEMUFLAGS := -smp 4 -m 2G -d int -D qemu.log -no-reboot override IMAGE_NAME := montauk-$(ARCH) # KERNEL_ONLY=1 builds a ramdisk holding just the boot files (no # userspace), for bringing up a kernel on a new architecture. KERNEL_ONLY ?= 0 # programs/ builds x86_64 into bin/, other architectures into bin-/. ifeq ($(ARCH),x86_64) PROGRAMS_BIN := programs/bin else PROGRAMS_BIN := programs/bin-$(ARCH) endif override EFI_LOADER_x86_64 := BOOTX64.EFI override EFI_LOADER_aarch64 := BOOTAA64.EFI override EFI_LOADER_riscv64 := BOOTRISCV64.EFI override EFI_LOADER_loongarch64 := BOOTLOONGARCH64.EFI # The host strip handles x86_64 images; other targets use their toolchain's. ifeq ($(ARCH),x86_64) STRIP := strip else STRIP := toolchain/local/bin/$(ARCH)-montauk-strip endif # AArch64 UEFI firmware for QEMU (Debian/Ubuntu: qemu-efi-aarch64). AARCH64_EFI_FIRMWARE ?= /usr/share/qemu-efi-aarch64/QEMU_EFI.fd # Toolchain for building the 'limine' executable for the host. HOST_CC := cc HOST_CFLAGS := -g -O2 -pipe HOST_CPPFLAGS := HOST_LDFLAGS := HOST_LIBS := .PHONY: all all: $(IMAGE_NAME).iso .PHONY: all-hdd all-hdd: $(IMAGE_NAME).hdd .PHONY: run run: run-$(ARCH) .PHONY: run-hdd run-hdd: run-hdd-$(ARCH) .PHONY: run-x86_64 run-x86_64: $(IMAGE_NAME).iso qemu-system-$(ARCH) \ -enable-kvm \ -M q35 \ -bios /usr/share/ovmf/OVMF.fd \ -cdrom $(IMAGE_NAME).iso \ -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ -netdev user,id=net0 \ $(QEMUFLAGS) .PHONY: run-hdd-x86_64 run-hdd-x86_64: $(IMAGE_NAME).hdd qemu-system-$(ARCH) \ -M q35 \ -bios /usr/share/ovmf/OVMF.fd \ -hda $(IMAGE_NAME).hdd \ -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ -netdev user,id=net0 \ $(QEMUFLAGS) .PHONY: run-aarch64 # The machine mirrors the Raspberry Pi 4/400: Cortex-A72 cores, a GICv2, and # keyboard and mouse behind a hub (every USB 2 device on the Pi is: the # VL805's one USB 2 port carries an on-board hub). QEMU's hub is full-speed, # so the transaction-translator path is only exercised on the real board. # The kernel console is mirrored to the PL011 (-serial stdio). run-aarch64: $(IMAGE_NAME).iso qemu-system-$(ARCH) \ -M virt,gic-version=2 \ -cpu cortex-a72 \ -device ramfb \ -device qemu-xhci,id=xhci \ -device usb-hub,bus=xhci.0,port=1 \ -device usb-kbd,bus=xhci.0,port=1.1 \ -device usb-mouse,bus=xhci.0,port=1.2 \ -bios $(AARCH64_EFI_FIRMWARE) \ -cdrom $(IMAGE_NAME).iso \ -serial stdio \ $(QEMUFLAGS) .PHONY: run-hdd-aarch64 run-hdd-aarch64: $(IMAGE_NAME).hdd qemu-system-$(ARCH) \ -M virt,gic-version=2 \ -cpu cortex-a72 \ -device ramfb \ -device qemu-xhci,id=xhci \ -device usb-hub,bus=xhci.0,port=1 \ -device usb-kbd,bus=xhci.0,port=1.1 \ -device usb-mouse,bus=xhci.0,port=1.2 \ -bios $(AARCH64_EFI_FIRMWARE) \ -drive if=none,id=hd0,format=raw,file=$(IMAGE_NAME).hdd \ -device virtio-blk-pci,drive=hd0 \ -serial stdio \ $(QEMUFLAGS) # The SD card image on QEMU, with the root read through the SDHCI driver: # the card goes on sdhci-pci as a padded copy (QEMU wants a power-of-two card; # RPI4_QEMU_CARD_MIB, default 4 GiB, leaves the first-boot grow room to work # with; every run starts from a fresh copy, so every run is a first boot), # and since QEMU's UEFI has no SD driver, the card's ESP is also # attached alone (the card's first partition, as its own disk) over virtio # for the firmware to boot Limine from. RPI4_QEMU_CARD_MIB ?= 4096 .PHONY: run-rpi4-sd run-rpi4-sd: montauk-rpi4.img cp --sparse=always montauk-rpi4.img rpi4-sd-qemu.img size=$$(stat -c %s rpi4-sd-qemu.img); p=$$(($(RPI4_QEMU_CARD_MIB) * 1048576)); \ while [ $$p -lt $$size ]; do p=$$((p * 2)); done; truncate -s $$p rpi4-sd-qemu.img end=$$(PATH=$$PATH:/usr/sbin:/sbin sfdisk -d montauk-rpi4.img | awk -F'[=,]' '/montauk-rpi4.img1/ {print $$2 + $$4}'); \ dd if=montauk-rpi4.img of=rpi4-esp-qemu.img bs=512 count=$$end status=none PATH=$$PATH:/usr/sbin:/sbin sfdisk --quiet --delete rpi4-esp-qemu.img 2 2>/dev/null || true qemu-system-aarch64 \ -M virt,gic-version=2 \ -cpu cortex-a72 \ -device ramfb \ -device qemu-xhci,id=xhci \ -device usb-hub,bus=xhci.0,port=1 \ -device usb-kbd,bus=xhci.0,port=1.1 \ -device usb-mouse,bus=xhci.0,port=1.2 \ -bios $(AARCH64_EFI_FIRMWARE) \ -drive if=none,id=esp,format=raw,file=rpi4-esp-qemu.img \ -device virtio-blk-pci,drive=esp \ -device sdhci-pci \ -drive if=none,id=sd,format=raw,file=rpi4-sd-qemu.img \ -device sd-card,drive=sd \ -serial stdio \ $(QEMUFLAGS) .PHONY: run-riscv64 run-riscv64: $(IMAGE_NAME).iso qemu-system-$(ARCH) \ -M virt \ -cpu rv64 \ -device ramfb \ -device qemu-xhci,id=xhci \ -device usb-kbd,bus=xhci.0 \ -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -cdrom $(IMAGE_NAME).iso \ $(QEMUFLAGS) .PHONY: run-hdd-riscv64 run-hdd-riscv64: $(IMAGE_NAME).hdd qemu-system-$(ARCH) \ -M virt \ -cpu rv64 \ -device ramfb \ -device qemu-xhci,id=xhci \ -device usb-kbd,bus=xhci.0 \ -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -hda $(IMAGE_NAME).hdd \ $(QEMUFLAGS) .PHONY: run-loongarch64 run-loongarch64: $(IMAGE_NAME).iso qemu-system-$(ARCH) \ -M virt \ -cpu la464 \ -device ramfb \ -device qemu-xhci,id=xhci \ -device usb-kbd,bus=xhci.0 \ -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -cdrom $(IMAGE_NAME).iso \ $(QEMUFLAGS) .PHONY: run-hdd-loongarch64 run-hdd-loongarch64: $(IMAGE_NAME).hdd qemu-system-$(ARCH) \ -M virt \ -cpu la464 \ -device ramfb \ -device qemu-xhci,id=xhci \ -device usb-kbd,bus=xhci.0 \ -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -hda $(IMAGE_NAME).hdd \ $(QEMUFLAGS) .PHONY: run-bios run-bios: $(IMAGE_NAME).iso qemu-system-$(ARCH) \ -M q35 \ -cdrom $(IMAGE_NAME).iso \ -boot d \ -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ -netdev user,id=net0 \ $(QEMUFLAGS) .PHONY: run-hdd-bios run-hdd-bios: $(IMAGE_NAME).hdd qemu-system-$(ARCH) \ -M q35 \ -hda $(IMAGE_NAME).hdd \ -device e1000,netdev=net0,mac=52:54:00:68:00:99 \ -netdev user,id=net0 \ $(QEMUFLAGS) .PHONY: toolchain toolchain: ./toolchain/build-toolchain.sh # Build the Montauk SDK: native binutils + GCC that run on MontaukOS, # shipped into the image at 0:/sdk by the programs/ devkit target. # One-time (long) build; idempotent afterwards. .PHONY: sdk sdk: ./toolchain/build-native-sdk.sh limine/limine: rm -rf limine git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1 $(MAKE) -C limine \ CC="$(HOST_CC)" \ CFLAGS="$(HOST_CFLAGS)" \ CPPFLAGS="$(HOST_CPPFLAGS)" \ LDFLAGS="$(HOST_LDFLAGS)" \ LIBS="$(HOST_LIBS)" kernel-deps: ./kernel/get-deps touch kernel-deps .PHONY: kernel kernel: kernel-deps $(MAKE) -C kernel .PHONY: programs programs: $(MAKE) -C programs # Build out-of-tree ports (~/dev/mtkports) and stage their app bundles into # programs/bin/apps/. Opt-in: the base ISO ships only what lives in # programs/src/, so this is never a dependency of the default build. Run it # before 'make ramdisk' to include ports in the image. MTKPORTS ?= $(HOME)/dev/mtkports .PHONY: ports ports: programs @if [ -d "$(MTKPORTS)" ]; then \ $(MAKE) -C "$(MTKPORTS)" install MONTAUKOS="$(CURDIR)"; \ else \ echo "ports: $(MTKPORTS) not found - skipping (set MTKPORTS=/path/to/mtkports)"; \ fi .PHONY: ramdisk ifeq ($(KERNEL_ONLY),1) ramdisk: limine/limine kernel rm -rf .kernel-only-root mkdir -p .kernel-only-root/boot/limine cp -v limine/$(EFI_LOADER_$(ARCH)) limine.conf .kernel-only-root/boot/limine/ cp -v kernel/bin-$(ARCH)/kernel .kernel-only-root/boot/ ./scripts/mkramdisk.sh .kernel-only-root ramdisk.tar rm -rf .kernel-only-root else ramdisk: limine/limine kernel programs mkdir -p $(PROGRAMS_BIN)/boot/limine cp -v limine/$(EFI_LOADER_$(ARCH)) $(PROGRAMS_BIN)/boot/limine/ cp -v limine.conf $(PROGRAMS_BIN)/boot/limine/ cp -v kernel/bin-$(ARCH)/kernel $(PROGRAMS_BIN)/boot/ rm -f $(PROGRAMS_BIN)/boot/ramdisk.tar ./scripts/mkramdisk.sh $(PROGRAMS_BIN) ramdisk.tar endif $(IMAGE_NAME).iso: limine/limine kernel ramdisk rm -rf iso_root mkdir -p iso_root/boot cp -v kernel/bin-$(ARCH)/kernel iso_root/boot/ cp -v ramdisk.tar iso_root/boot/ mkdir -p iso_root/boot/limine cp -v limine.conf iso_root/boot/limine/ mkdir -p iso_root/EFI/BOOT ifeq ($(ARCH),x86_64) cp -v limine/limine-bios.sys limine/limine-bios-cd.bin limine/limine-uefi-cd.bin iso_root/boot/limine/ cp -v limine/BOOTX64.EFI iso_root/EFI/BOOT/ cp -v limine/BOOTIA32.EFI iso_root/EFI/BOOT/ xorriso -as mkisofs -R -r -J -b boot/limine/limine-bios-cd.bin \ -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus \ -apm-block-size 2048 --efi-boot boot/limine/limine-uefi-cd.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ iso_root -o $(IMAGE_NAME).iso ./limine/limine bios-install $(IMAGE_NAME).iso endif ifeq ($(ARCH),aarch64) cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ cp -v limine/BOOTAA64.EFI iso_root/EFI/BOOT/ xorriso -as mkisofs -R -r -J \ -hfsplus -apm-block-size 2048 \ --efi-boot boot/limine/limine-uefi-cd.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ iso_root -o $(IMAGE_NAME).iso endif ifeq ($(ARCH),riscv64) cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ cp -v limine/BOOTRISCV64.EFI iso_root/EFI/BOOT/ xorriso -as mkisofs -R -r -J \ -hfsplus -apm-block-size 2048 \ --efi-boot boot/limine/limine-uefi-cd.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ iso_root -o $(IMAGE_NAME).iso endif ifeq ($(ARCH),loongarch64) cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ cp -v limine/BOOTLOONGARCH64.EFI iso_root/EFI/BOOT/ xorriso -as mkisofs -R -r -J \ -hfsplus -apm-block-size 2048 \ --efi-boot boot/limine/limine-uefi-cd.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ iso_root -o $(IMAGE_NAME).iso endif rm -rf iso_root # Example and regression-test programs that are built into 0:/os for # development but have no place on a release image. RELEASE_EXCLUDE_PROGRAMS := rusthello.elf futextest.elf memtest.elf .PHONY: release release: limine/limine kernel ramdisk rm -rf iso_root .release-tmp mkdir -p iso_root/boot cp kernel/bin-$(ARCH)/kernel iso_root/boot/ $(STRIP) iso_root/boot/kernel cp -r $(PROGRAMS_BIN) .release-tmp # A release ISO ships only in-tree apps, so drop any out-of-tree port bundles # that happen to be staged in programs/bin/apps (each carries a .port marker). # Pass RELEASE_WITH_PORTS=1 to deliberately cut a ports-included image. ifneq ($(RELEASE_WITH_PORTS),1) @for m in $$(find .release-tmp/apps -name .port 2>/dev/null); do \ d=$$(dirname $$m); \ echo "release: excluding port bundle $$(basename $$d)"; \ rm -rf "$$d"; \ done else @echo "release: RELEASE_WITH_PORTS=1 - including out-of-tree port bundles" endif # The Montauk SDK (native GCC + binutils and their target sysroot under # 0:/sdk) dominates the image size. Pass RELEASE_WITHOUT_SDK=1 to cut a slim # ISO without it. Only the devkit-owned subtrees go: sdk/tcc and sdk/lua are # installed by their own targets and stay, as does 0:/tmp, which the libc # uses for tmpnam() and not just for compiler intermediates. ifeq ($(RELEASE_WITHOUT_SDK),1) @echo "release: RELEASE_WITHOUT_SDK=1 - excluding the Montauk SDK (0:/sdk dev tools)" rm -rf .release-tmp/sdk/bin .release-tmp/sdk/lib .release-tmp/sdk/include \ .release-tmp/sdk/libexec .release-tmp/sdk/x86_64-montauk endif rm -f $(addprefix .release-tmp/os/,$(RELEASE_EXCLUDE_PROGRAMS)) find .release-tmp -name '*.elf' -exec $(STRIP) {} + # The ramdisk carries its own copy of the kernel at 0:/boot/kernel -- that is # the image the Installer lays down on the target disk -- and it is not named # *.elf, so the find above misses it. Unstripped it is ~6 MB against ~0.7 MB. $(STRIP) .release-tmp/boot/kernel ./scripts/mkramdisk.sh .release-tmp iso_root/boot/ramdisk.tar rm -rf .release-tmp mkdir -p iso_root/boot/limine cp limine.conf iso_root/boot/limine/ mkdir -p iso_root/EFI/BOOT ifeq ($(ARCH),x86_64) cp limine/limine-bios.sys limine/limine-bios-cd.bin limine/limine-uefi-cd.bin iso_root/boot/limine/ cp limine/BOOTX64.EFI iso_root/EFI/BOOT/ cp limine/BOOTIA32.EFI iso_root/EFI/BOOT/ xorriso -as mkisofs -R -r -J -b boot/limine/limine-bios-cd.bin \ -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus \ -apm-block-size 2048 --efi-boot boot/limine/limine-uefi-cd.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ iso_root -o $(IMAGE_NAME).iso ./limine/limine bios-install $(IMAGE_NAME).iso endif ifeq ($(ARCH),aarch64) cp limine/limine-uefi-cd.bin iso_root/boot/limine/ cp limine/BOOTAA64.EFI iso_root/EFI/BOOT/ xorriso -as mkisofs -R -r -J \ -hfsplus -apm-block-size 2048 \ --efi-boot boot/limine/limine-uefi-cd.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ iso_root -o $(IMAGE_NAME).iso endif ifeq ($(ARCH),riscv64) cp limine/limine-uefi-cd.bin iso_root/boot/limine/ cp limine/BOOTRISCV64.EFI iso_root/EFI/BOOT/ xorriso -as mkisofs -R -r -J \ -hfsplus -apm-block-size 2048 \ --efi-boot boot/limine/limine-uefi-cd.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ iso_root -o $(IMAGE_NAME).iso endif ifeq ($(ARCH),loongarch64) cp limine/limine-uefi-cd.bin iso_root/boot/limine/ cp limine/BOOTLOONGARCH64.EFI iso_root/EFI/BOOT/ xorriso -as mkisofs -R -r -J \ -hfsplus -apm-block-size 2048 \ --efi-boot boot/limine/limine-uefi-cd.bin \ -efi-boot-part --efi-boot-image --protective-msdos-label \ iso_root -o $(IMAGE_NAME).iso endif rm -rf iso_root # Raspberry Pi 4/400 SD card image (UEFI firmware + Limine + MontaukOS). # ROOTFS=ext2 (default) puts the system on an ext2 partition read through the # kernel's SD driver; ROOTFS=ramdisk has Limine load ramdisk.tar instead. # Phony so switching ROOTFS always rebuilds the image. ROOTFS ?= ext2 .PHONY: rpi4-sd montauk-rpi4.img rpi4-sd: montauk-rpi4.img montauk-rpi4.img: limine/limine kernel ramdisk ifneq ($(ARCH),aarch64) $(error rpi4-sd needs ARCH=aarch64) endif ./scripts/mk-rpi4-sd.sh kernel/bin-$(ARCH)/kernel ramdisk.tar $@ $(ROOTFS) $(PROGRAMS_BIN)-rpi # Graphical imager: write montauk-rpi4.img to an SD card, or update the # installation on one in place (scripts/montauk-imager.py, docs/raspberry-pi.md). .PHONY: rpi4-imager rpi4-imager: ./scripts/montauk-imager.py $(IMAGE_NAME).hdd: limine/limine kernel ramdisk rm -f $(IMAGE_NAME).hdd dd if=/dev/zero bs=1M count=0 seek=64 of=$(IMAGE_NAME).hdd PATH=$$PATH:/usr/sbin:/sbin sgdisk $(IMAGE_NAME).hdd -n 1:2048 -t 1:ef00 ifeq ($(ARCH),x86_64) ./limine/limine bios-install $(IMAGE_NAME).hdd endif mformat -i $(IMAGE_NAME).hdd@@1M mmd -i $(IMAGE_NAME).hdd@@1M ::/EFI ::/EFI/BOOT ::/boot ::/boot/limine mcopy -i $(IMAGE_NAME).hdd@@1M kernel/bin-$(ARCH)/kernel ::/boot mcopy -i $(IMAGE_NAME).hdd@@1M ramdisk.tar ::/boot mcopy -i $(IMAGE_NAME).hdd@@1M limine.conf ::/boot/limine ifeq ($(ARCH),x86_64) mcopy -i $(IMAGE_NAME).hdd@@1M limine/limine-bios.sys ::/boot/limine mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTX64.EFI ::/EFI/BOOT mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTIA32.EFI ::/EFI/BOOT endif ifeq ($(ARCH),aarch64) mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTAA64.EFI ::/EFI/BOOT endif ifeq ($(ARCH),riscv64) mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTRISCV64.EFI ::/EFI/BOOT endif ifeq ($(ARCH),loongarch64) mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTLOONGARCH64.EFI ::/EFI/BOOT endif .PHONY: clean clean: $(MAKE) -C kernel clean # $(MAKE) -C programs clean rm -rf iso_root .release-tmp $(IMAGE_NAME).iso $(IMAGE_NAME).hdd montauk-rpi4.img rpi4-sd-qemu.img rpi4-esp-qemu.img ramdisk.tar .PHONY: distclean distclean: $(MAKE) -C kernel distclean rm -rf iso_root *.iso *.hdd kernel-deps limine ovmf