Files

437 lines
15 KiB
Makefile

ifneq (,$(wildcard ./.env))
include .env
endif
ROOT_DIR := $(CURDIR)
OUT_DIR := $(ROOT_DIR)/out
SCRIPT_DIR := $(ROOT_DIR)/scripts
CONFIG_DIR := $(ROOT_DIR)/config
CLANG_DIR ?= $(ROOT_DIR)/toolchain/clang
CLANG_URL := https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.6/clang+llvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04.tar.xz
SHARED_DIR := $(ROOT_DIR)/shared
VERBOSE ?= 0
ARCH ?= x86_64
GREEN := $(shell tput setaf 2)
YELLOW := $(shell tput setaf 3)
NC := $(shell tput sgr0)
ifeq ($(ARCH),aarch64)
$(warning $(YELLOW)Incorrect architecture 'aarch64', using 'arm64' instead $(NC))
ARCH := arm64
else ifeq ($(ARCH),x86)
$(warning $(YELLOW)Incorrect architecture 'x86', using 'x86_64' instead $(NC))
ARCH := x86_64
else ifeq ($(filter x86_64 arm64 i386,$(ARCH)),)
$(error Invalid architecture $(ARCH))
endif
.PHONY: default
default: linux tools-vm
.PHONY: clean
clean: linux_clean tools-vm_clean
.PHONY: help
help:
@echo '$(GREEN)General Environment Variables:$(NC)'
@echo ' ARCH - Specify one of the supported architectures: x86_64, i386, arm64 (default: x86_64)'
@echo ' ACK - Set to 1 to build ACK instead of the Linux kernel. Does not need to be set for `ack` and `run-ack` targets (default: 0)'
@echo ' VERBOSE - Set to 1 to enable verbose output (default: 0)'
@echo ''
@echo '$(GREEN)Build/Config:$(NC)'
@echo ' Targets:'
@echo ' linux (default) - Build the Linux kernel'
@echo ' linux_defconfig - Run `make defconfig`'
@echo ' linux_menuconfig - Run `make menuconfig`'
@echo ' linux_modules - Build the Linux kernel modules'
@echo ' linux_debpkg - Creates a Debian package for the kernel'
@echo ' ack - Build the Android Common Kernel'
@echo ' tools-vm - Build linux/tools/vm'
@echo ''
@echo ' Environment Variables:'
@echo ' LINUX_DEFCONFIG - The defconfig to use when building the kernel (default: defconfig, ACK default: gki_defconfig)'
@echo ' LINUX_SRC - The path to the kernel source directory (default: linux, ACK default: ack/common)'
@echo ' LINUX_OUT - The path where the kernel build output should be stored (default: out/linux/$$ARCH, ACK default: out/ack/common/$$ARCH)'
@echo ' LINUX_CONFIG_FRAGMENT - A kernel config fragment to merge with the defconfig (default: config/config.fragment)'
@echo ''
@echo '$(GREEN)Clean:$(NC)'
@echo ' Targets:'
@echo ' clean - Clean output from default build targets'
@echo ' <target>_clean - Clean output for <target>, where <target> is one of: ack, linux, tools-vm, rootfs'
@echo ''
@echo '$(GREEN)Run/Debug:$(NC)'
@echo ' Targets:'
@echo ' run - Run QEMU with the built kernel and rootfs image'
@echo ' run-ack - Same as `run` but runs ACK instead'
@echo ''
@echo ' Environment Variables:'
@echo ' GDB - Set to 1 to start a gdbserver and wait for GDB when running QEMU (default: 0)'
@echo ' CPU - The number of CPUs to use when running QEMU (default: 4)'
@echo ' MEM - The memory size in MB to use when running QEMU (default: 1024)'
@echo ' QEMU_EXTRA_ARGS - Additional arguments to pass to QEMU (default: "")'
@echo ' QEMU_EXTRA_KERNEL_CMDLINE - Additional arguments to pass to the kernel (default: "")'
@echo ' QEMU_KERNEL_IMAGE - The path to the kernel image to run (x86_64/i386 default: $$LINUX_OUT/arch/$$ARCH/boot/bzImage, arm64 default: $$LINUX_OUT/arch/$$ARCH/boot/Image)'
@echo ' ROOTFS - The path to the rootfs image file (default: rootfs/alpine-$$ARCH.img)'
@echo ' ROOTFS_FORMAT - The format of the rootfs image file: raw, qcow2 (default: qcow2)'
@echo ' INITRD - Set to 1 to use the $$CPIO_FILE initramfs instead of the $$ROOTFS image as the rootfs, or specify the path to an alternative CPIO file (default: "")'
@echo ' RDINIT - The value of the `rdinit` kernel command line parameter (default: "", default if INITRD is set: /sbin/init)'
@echo ' ECHR - The value of the QEMU `-echr` flag (default: 1)'
@echo ' ROOT - The value of the `root` kernel command line parameter (default: /dev/vda)'
@echo ' RW - Whether to mount the rootfs as read-write or read-only: ro, rw (default: rw)'
@echo ' KASLR - Set to 1 to enable KASLR (default: 0)'
@echo ''
@echo '$(GREEN)rootfs:$(NC)'
@echo ' Targets:'
@echo ' rootfs-init - Extract the Alpine Linux rootfs to $$ROOTFS_DIR and then run the `rootfs-overlay` target'
@echo ' rootfs-overlay - Apply arch-specific changes to $$ROOTFS_DIR and run the `rootfs` target'
@echo ' rootfs - Run the `ext4` and `cpio` targets'
@echo ' ext4 - Build a $$ROOTFS $$ROOTFS_FORMAT (default qcow2) image with an ext4 filesystem from $$ROOTFS_DIR'
@echo ' cpio - Build a $$CPIO_FILE gzipped initramfs CPIO file from $$ROOTFS_DIR'
@echo ' initramfs - An alias for the `cpio` target'
@echo ' uncpio - Extract $$CPIO_FILE to $$ROOTFS_DIR'
@echo ' rootfs-mount - Mount $$ROOTFS image at /tmp/rootfs'
@echo ' rootfs-unmount - Unmount rootfs image from /tmp/rootfs'
@echo ' chroot - chroot into $$ROOTFS_DIR'
@echo ''
@echo ' Environment Variables:'
@echo ' EXT4_SIZE - The disk size of the rootfs image to build'
@echo ' ROOTFS_DIR - The directory to create the ext4 rootfs image and initramfs CPIO from (default: rootfs/alpine-$$ARCH)'
@echo ' ROOTFS - The path to the rootfs image file (default: rootfs/alpine-$$ARCH.img)'
@echo ' ROOTFS_FORMAT - The format of the rootfs image file: raw, qcow2 (default: qcow2)'
@echo ' CPIO_FILE - The path to the CPIO file to create (default: rootfs/alpine-$$ARCH.cpio.gz)'
@echo ''
@echo '$(GREEN)Miscellaneous:$(NC)'
@echo ' Targets:'
@echo ' linux_download - Downloads an archive of the Linux kernel source for the version specified in $$VERSION'
@echo ' linux_checkout - Checks out the version specified by $$VERSION of the linux kernel in $$LINUX_SRC'
@echo ''
@echo ' Environment Variables:'
@echo " VERSION - The version to download or checkout. For checkout only, if the third number in the version string is a 'y', the latest version of the kernel with that major and minor version is used. Examples: 5.10, 5.10.107, v5.10, 5.10.y, linux-5.10.y"
$(SHARED_DIR):
mkdir -p $(SHARED_DIR)
##
## Linux Kernel and Android Common Kernel
##
ACK_TARGETS := ack ack_clean run-ack
ACK ?= 0
ACK_SRC := $(ROOT_DIR)/ack/common
ACK_OUT := $(OUT_DIR)/ack/common/$(ARCH)
ifneq ($(filter $(ACK_TARGETS),$(MAKECMDGOALS)),)
ACK := 1
endif
# If we're building ACK instead of Linux, set the appropriate variables
ifeq ($(ACK),1)
LINUX_SRC ?= $(ACK_SRC)
LINUX_OUT ?= $(ACK_OUT)
LINUX_DEFCONFIG ?= gki_defconfig
else
LINUX_SRC ?= $(ROOT_DIR)/linux
LINUX_OUT ?= $(OUT_DIR)/linux/$(ARCH)
LINUX_DEFCONFIG ?= defconfig
endif
LINUX_CONFIG_FRAGMENT ?= $(CONFIG_DIR)/config.fragment
LINUX_OUT_MODULES_DEP := $(LINUX_OUT)/modules_install.stamp
LINUX_MODULES_INSTALL_PATH := $(LINUX_OUT)/modules_install
LINUX_CONFIG := $(LINUX_OUT)/.config
ifeq ($(ARCH),x86_64)
TARGET := x86_64-pc-linux-gnu
KERNEL_IMAGE := $(LINUX_OUT)/arch/$(ARCH)/boot/bzImage
else ifeq ($(ARCH),i386)
TARGET := i386-pc-linux-gnu
KERNEL_IMAGE := $(LINUX_OUT)/arch/$(ARCH)/boot/bzImage
else ifeq ($(ARCH),arm64)
TARGET := aarch64-linux-gnu
KERNEL_IMAGE := $(LINUX_OUT)/arch/$(ARCH)/boot/Image
endif
LINUX_MAKE := \
PATH=$(CLANG_DIR)/bin:$(PATH) \
$(MAKE) \
-C $(LINUX_SRC) \
ARCH=$(ARCH) \
CROSS_COMPILE=$(TARGET)- \
LLVM=1 LLVM_IAS=1 \
V=$(VERBOSE) \
O=$(LINUX_OUT) \
-j `nproc`
$(CLANG_DIR):
$(warning $(YELLOW)Clang directory $(CLANG_DIR) does not exist, downloading prebuilt binaries $(NC))
wget --no-verbose --show-progress $(CLANG_URL) -O clang.tar.xz
mkdir -p $(CLANG_DIR)
tar -xf clang.tar.xz -C $(CLANG_DIR) --strip-components=1
.PHONY: linux_defconfig
linux_defconfig $(LINUX_CONFIG): $(LINUX_CONFIG_FRAGMENT) | $(CLANG_DIR)
+ $(LINUX_MAKE) $(LINUX_DEFCONFIG)
KCONFIG_CONFIG=$(LINUX_CONFIG) \
$(LINUX_SRC)/scripts/kconfig/merge_config.sh \
-m \
$(LINUX_CONFIG) \
$(LINUX_CONFIG_FRAGMENT)
+ $(LINUX_MAKE) olddefconfig
$(SCRIPT_DIR)/check_merged_config.sh $(LINUX_CONFIG) $(LINUX_CONFIG_FRAGMENT)
.PHONY: linux_menuconfig
linux_menuconfig:
+ $(LINUX_MAKE) menuconfig
$(SCRIPT_DIR)/check_merged_config.sh $(LINUX_CONFIG) $(LINUX_CONFIG_FRAGMENT)
.PHONY: linux
linux $(KERNEL_IMAGE): $(LINUX_CONFIG) | $(CLANG_DIR)
+ $(LINUX_MAKE)
# Older versions of Linux don't have this script
ifneq (,$(wildcard ./.env))
$(LINUX_SRC)/scripts/clang-tools/gen_compile_commands.py -d $(LINUX_OUT)
endif
.PHONY: linux_modules
linux_modules $(LINUX_OUT_MODULES_DEP): $(KERNEL_IMAGE)
+ $(LINUX_MAKE) modules
+ $(LINUX_MAKE) INSTALL_MOD_PATH=$(LINUX_MODULES_INSTALL_PATH) modules_install
@find $(LINUX_MODULES_INSTALL_PATH) -type f | sort | \
xargs sha1sum > $(LINUX_OUT_MODULES_DEP).tmp
@cmp $(LINUX_OUT_MODULES_DEP).tmp $(LINUX_OUT_MODULES_DEP) || \
mv $(LINUX_OUT_MODULES_DEP).tmp $(LINUX_OUT_MODULES_DEP)
.PHONY: linux_clean
linux_clean:
+ $(LINUX_MAKE) mrproper
.PHONY: linux_download
linux_download:
ifndef VERSION
$(error VERSION environment variable is not defined)
endif
$(SCRIPT_DIR)/download_linux.sh $(VERSION) $(ACK)
.PHONY: linux_checkout
linux_checkout:
ifndef VERSION
$(error VERSION environment variable is not defined)
endif
$(SCRIPT_DIR)/checkout_linux.sh $(VERSION) $(LINUX_SRC)
# Use xz compression instead of zstd, as the latter is not supported by some
# versions of dpkg.
.PHONY: linux_debpkg
linux_debpkg: $(KERNEL_IMAGE)
+ $(LINUX_MAKE) KDEB_COMPRESS=xz bindeb-pkg
# These targets do the same thing as the `linux` targets, but because they're in
# `ACK_TARGETS`, the `ACK` variable is set to 1 and the ACK source and output
# directories are used instead of the Linux directories. The same thing can be
# achieved by setting the `ACK=1` environment variable, so these are just
# provided for convenience.
.PHONY: ack
ack: linux
.PHONY: ack_clean
ack_clean: linux_clean
# Because of how `tools/vm/Makefile` is designed, we can't use `$(LINUX_MAKE)`
# here, as specifying the `O` variable causes compilation errors, and `clang`
# won't properly cross-compile the binary so we need to use GCC
TOOLS_MAKE := $(MAKE) \
-C $(LINUX_SRC)/tools \
DESTDIR=$(SHARED_DIR) \
sbindir=/tools/$(ARCH)
# Assuming we're building on an x86 system, we should only set `CROSS_COMPILE`
# if we're building arm64.
ifeq ($(ARCH),arm64)
TOOLS_MAKE += CROSS_COMPILE=$(TARGET)-
endif
.PHONY: tools-vm
tools-vm: | $(SHARED_DIR)
+ $(TOOLS_MAKE) vm_install
.PHONY: tools-vm_clean
tools-vm_clean:
+ $(TOOLS_MAKE) vm_clean
rm -f $(SHARED_DIR)/tools/$(ARCH)/{page_owner_sort,page-types,slabinfo}
##
## Generate rootfs images
##
ROOTFS_PARENT_DIR := $(ROOT_DIR)/rootfs
ROOTFS ?= $(ROOTFS_PARENT_DIR)/$$(basename $(ROOTFS_DIR)).img
ROOTFS_FORMAT ?= qcow2
ROOTFS_DIR ?= $(ROOTFS_PARENT_DIR)/alpine-$(ARCH)
CPIO_FILE ?= $(ROOTFS_PARENT_DIR)/$$(basename $(ROOTFS_DIR)).cpio.gz
EXT4_SIZE ?= 1G
ifeq ($(SUDO),1)
SUDO := sudo
endif
# The user might set a relative path for `CPIO_FILE`, so we need to get the
# absolute path, as we need to reference it after we've changed directories
CPIO_FILE := $(shell realpath $(CPIO_FILE))
$(ROOTFS_DIR):
mkdir -p $(ROOTFS_DIR)
.PHONY: rootfs-init
rootfs-init: | $(ROOTFS_DIR)
tar -xf $(ROOTFS_PARENT_DIR)/alpine-minirootfs-*-$(ARCH).tar.gz -C $(ROOTFS_DIR)
$(MAKE) rootfs-overlay
.PHONY: rootfs-overlay
rootfs-overlay: | $(ROOTFS_DIR)
$(SUDO) cp -f -r $(CONFIG_DIR)/rootfs-overlay/. $(ROOTFS_DIR)
$(SUDO) $(SCRIPT_DIR)/arch_specific.sh $(ARCH) $(ROOTFS_DIR)
$(MAKE) rootfs
.PHONY: ext4
ext4: | $(ROOTFS_DIR)
dd if=/dev/zero of=$(ROOTFS) bs=1 count=0 seek=$(EXT4_SIZE)
$(SUDO) mkfs.ext4 -b 4096 -d $(ROOTFS_DIR) -F $(ROOTFS)
qemu-img convert -O $(ROOTFS_FORMAT) $(ROOTFS) $(ROOTFS).tmp
mv $(ROOTFS).tmp $(ROOTFS)
.PHONY: cpio initramfs
initramfs cpio: | $(ROOTFS_DIR)
(cd $(ROOTFS_DIR) && $(SUDO) find . -print0 \
| $(SUDO) cpio --null --create --verbose --format=newc) \
| gzip --best > $(CPIO_FILE)
.PHONY: uncpio
uncpio: | $(ROOTFS_DIR)
cd $(ROOTFS_DIR) && zcat $(CPIO_FILE) | $(SUDO) cpio --extract --make-directories --format=newc --no-absolute-filenames
.PHONY: rootfs
rootfs: ext4 cpio
.PHONY: rootfs-mount
rootfs-mount:
sudo modprobe nbd
sudo qemu-nbd -c /dev/nbd0 -f $(ROOTFS_FORMAT) $(ROOTFS)
mkdir -p /tmp/rootfs
sudo mount /dev/nbd0 /tmp/rootfs
.PHONY: rootfs-unmount
rootfs-unmount:
sudo umount /dev/nbd0
sudo qemu-nbd -d /dev/nbd0
.PHONY: chroot
chroot:
sudo chroot $(ROOTFS_DIR) /bin/sh
.PHONY: rootfs_clean
rootfs_clean:
$(SUDO) rm -rf $(ROOTFS_DIR)
$(SUDO) rm -f $(CPIO_FILE)
$(SUDO) rm -f $(ROOTFS)
##
## Run QEMU
##
CPU ?= 4
MEM ?= 1024
QEMU_EXTRA_ARGS ?=
QEMU_EXTRA_KERNEL_CMDLINE ?=
GDB ?= 0
INITRD ?=
RDINIT ?= /sbin/init
QEMU_KERNEL_IMAGE ?= $(KERNEL_IMAGE)
ECHR ?= 1
ROOT ?= /dev/vda
RW ?= rw
KASLR ?= 0
QEMU_KERNEL_CMDLINE := selinux=0
QEMU_ARGS := \
-m $(MEM) \
-smp $(CPU) \
-nographic \
-no-reboot \
-kernel $(QEMU_KERNEL_IMAGE) \
-netdev user,id=eth0,hostfwd=tcp::7777-:7777,hostfwd=tcp::2222-:22,hostfwd=tcp::2223-:23 -device virtio-net-pci,netdev=eth0 \
-virtfs local,security_model=mapped-xattr,path=$(SHARED_DIR),mount_tag=shared \
-echr $(ECHR) \
$(QEMU_EXTRA_ARGS)
ifneq ($(INITRD),)
ifeq ($(INITRD),1)
INITRD := $(CPIO_FILE)
endif
QEMU_KERNEL_CMDLINE += rdinit=$(RDINIT)
QEMU_ARGS += -initrd $(INITRD)
else
QEMU_ARGS += -drive file=$(ROOTFS),if=virtio,format=$(ROOTFS_FORMAT)
QEMU_KERNEL_CMDLINE += root=$(ROOT) $(RW)
endif
ifeq ($(GDB),1)
QEMU_ARGS += -s -S
endif
ifeq ($(ARCH),x86_64)
QEMU_BIN := qemu-system-x86_64
QEMU_KERNEL_CMDLINE += console=ttyS0 kpti no5lvl
QEMU_ARGS += -cpu kvm64,+smep,+smap
# We can't use KVM with GitHub Actions CI
ifneq ($(CI),true)
QEMU_ARGS += -accel kvm
endif
else ifeq ($(ARCH),i386)
QEMU_BIN := qemu-system-i386
QEMU_KERNEL_CMDLINE += console=ttyS0
else
QEMU_BIN := qemu-system-aarch64
QEMU_KERNEL_CMDLINE += console=ttyAMA0
QEMU_ARGS += \
-M virt \
-cpu cortex-a53 \
-semihosting-config enable=on,target=native
endif
ifeq ($(KASLR),0)
QEMU_KERNEL_CMDLINE += nokaslr
endif
QEMU_ARGS += -append "$(QEMU_KERNEL_CMDLINE) $(QEMU_EXTRA_KERNEL_CMDLINE)"
RUN_DEPS := $(QEMU_KERNEL_IMAGE)
.PHONY: run
run: $(RUN_DEPS) | $(SHARED_DIR)
@echo "$(GREEN)Running QEMU, press 'ctrl-a x' to quit $(NC)"
ifeq ($(GDB),1)
@echo "$(ARCH) $(ACK)" > $(OUT_DIR)/.gdb
@echo "$(GREEN)Waiting for GDB, attach with \`scripts/gdb.sh\` $(NC)"
ifdef TERMINAL_CMD
$(TERMINAL_CMD) $(SCRIPT_DIR)/gdb.sh
endif
endif
@echo ''
$(QEMU_BIN) $(QEMU_ARGS)
.PHONY: run-ack
run-ack: run