Splits the ARM64 build into two tracks per docs/arm64-architecture.md: Generic ARM64 (mainline kernel.org, UEFI, virtio, GRUB): - New build/scripts/build-kernel-arm64.sh builds mainline LTS (6.12.x by default) from arm64 defconfig + shared container fragment + arm64-virt enables (VIRTIO_*, EFI_STUB, NVMe). Output: build/cache/kernel-arm64-generic/. - New Makefile targets: kernel-arm64, rootfs-arm64 (now consumes the mainline kernel modules via TARGET_VARIANT=generic). - versions.env: pin MAINLINE_KERNEL_VERSION=6.12.10, declare cdn.kernel.org URL and SHA256 placeholder. Raspberry Pi (raspberrypi/linux fork, custom DTBs, autoboot.txt): - build-kernel-arm64.sh (RPi-flavoured) renamed to build-kernel-rpi.sh; cache dir renamed from custom-kernel-arm64 to custom-kernel-rpi. - New Makefile targets: kernel-rpi, rootfs-arm64-rpi (uses TARGET_VARIANT=rpi). - rpi-image now depends on rootfs-arm64-rpi + kernel-rpi instead of the generic rootfs-arm64. - create-rpi-image.sh + inject-kubesolo.sh updated to reference the new cache path. inject-kubesolo.sh now takes a TARGET_VARIANT env var (rpi|generic) to select which ARM64 kernel modules to consume. Shared substrate: - rpi-kernel-config.fragment renamed to kernel-container.fragment. The contents were never RPi-specific (cgroup, namespaces, AppArmor, netfilter) — just misnamed. Extended with extra subsystem disables (KVM, WLAN, CFG80211, INFINIBAND, PCMCIA, HAMRADIO, ISDN, ATM, INPUT_JOYSTICK, INPUT_TABLET, FPGA) and CONFIG_LSM=lockdown,yama,apparmor. - build-kernel.sh (x86) refactored to apply the shared fragment via a generic apply_fragment function (two-pass for the TC stock config security dance), killing ~50 lines of inline config duplication. Note: rename detection shows build-kernel-arm64.sh as 'modified' because the new file at that path is the mainline build, while the old RPi-flavoured content lives in build-kernel-rpi.sh (which appears as a new file). The git log for build-kernel-rpi.sh is empty; the RPi history is preserved at the original path until this commit. No actual kernel build runs in this commit — that's Phase 3 work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
205 lines
7.4 KiB
Bash
Executable File
205 lines
7.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# build-kernel-arm64.sh — Build generic ARM64 kernel (mainline LTS)
|
|
#
|
|
# Builds a Linux kernel from kernel.org mainline LTS source, suitable for:
|
|
# - qemu-system-aarch64 -machine virt
|
|
# - UEFI ARM64 hosts (Ampere, Graviton, generic ARM64 servers)
|
|
# - Future ARM64 SBCs with UEFI/u-boot generic-distro support
|
|
#
|
|
# This is the GENERIC ARM64 build track. For Raspberry Pi specifically
|
|
# (raspberrypi/linux fork, RPi firmware boot path, custom DTBs), see
|
|
# build/scripts/build-kernel-rpi.sh.
|
|
#
|
|
# Output is cached in $CACHE_DIR/kernel-arm64-generic/ and reused across builds.
|
|
#
|
|
# Requirements:
|
|
# - gcc-aarch64-linux-gnu (cross-compiler)
|
|
# - Standard kernel build deps (bc, bison, flex, libelf-dev, libssl-dev)
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
CACHE_DIR="${CACHE_DIR:-$PROJECT_ROOT/build/cache}"
|
|
|
|
# shellcheck source=../config/versions.env
|
|
. "$SCRIPT_DIR/../config/versions.env"
|
|
|
|
KVER="$MAINLINE_KERNEL_VERSION"
|
|
CUSTOM_KERNEL_DIR="$CACHE_DIR/kernel-arm64-generic"
|
|
CUSTOM_IMAGE="$CUSTOM_KERNEL_DIR/Image"
|
|
CUSTOM_MODULES="$CUSTOM_KERNEL_DIR/modules"
|
|
|
|
mkdir -p "$CACHE_DIR" "$CUSTOM_KERNEL_DIR"
|
|
|
|
# --- Skip if already built ---
|
|
if [ -f "$CUSTOM_IMAGE" ] && [ -d "$CUSTOM_MODULES/lib/modules/$KVER" ]; then
|
|
echo "==> Generic ARM64 kernel already built (cached)"
|
|
echo " Image: $CUSTOM_IMAGE ($(du -h "$CUSTOM_IMAGE" | cut -f1))"
|
|
echo " Kernel: $KVER"
|
|
exit 0
|
|
fi
|
|
|
|
# --- Verify cross-compiler ---
|
|
if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then
|
|
echo "ERROR: aarch64-linux-gnu-gcc not found"
|
|
echo "Install: apt-get install gcc-aarch64-linux-gnu"
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> Building generic ARM64 kernel (mainline $KVER)..."
|
|
echo " Source: $MAINLINE_KERNEL_URL"
|
|
|
|
# --- Download mainline kernel source ---
|
|
KERNEL_SRC_ARCHIVE="$CACHE_DIR/linux-${KVER}.tar.xz"
|
|
if [ ! -f "$KERNEL_SRC_ARCHIVE" ]; then
|
|
echo "==> Downloading mainline kernel source (~140 MB)..."
|
|
wget -q --show-progress -O "$KERNEL_SRC_ARCHIVE" "$MAINLINE_KERNEL_URL" 2>/dev/null || \
|
|
curl -fSL "$MAINLINE_KERNEL_URL" -o "$KERNEL_SRC_ARCHIVE"
|
|
echo " Downloaded: $(du -h "$KERNEL_SRC_ARCHIVE" | cut -f1)"
|
|
else
|
|
echo "==> Kernel source already cached: $(du -h "$KERNEL_SRC_ARCHIVE" | cut -f1)"
|
|
fi
|
|
|
|
# --- Verify checksum if pinned ---
|
|
if [ -n "${MAINLINE_KERNEL_SHA256:-}" ]; then
|
|
actual=$(sha256sum "$KERNEL_SRC_ARCHIVE" | awk '{print $1}')
|
|
if [ "$actual" != "$MAINLINE_KERNEL_SHA256" ]; then
|
|
echo "ERROR: Kernel source checksum mismatch"
|
|
echo " Expected: $MAINLINE_KERNEL_SHA256"
|
|
echo " Got: $actual"
|
|
exit 1
|
|
fi
|
|
echo " Checksum OK"
|
|
fi
|
|
|
|
# --- Extract to case-sensitive fs ---
|
|
# The kernel source has files differing only by case (xt_mark.h vs xt_MARK.h).
|
|
# Build in /tmp (ext4 on Linux runners, case-sensitive).
|
|
KERNEL_BUILD_DIR="/tmp/kernel-build-arm64-generic"
|
|
rm -rf "$KERNEL_BUILD_DIR"
|
|
mkdir -p "$KERNEL_BUILD_DIR"
|
|
|
|
echo "==> Extracting kernel source..."
|
|
tar -xf "$KERNEL_SRC_ARCHIVE" -C "$KERNEL_BUILD_DIR"
|
|
KERNEL_SRC_DIR=$(find "$KERNEL_BUILD_DIR" -maxdepth 1 -type d -name 'linux-*' | head -1)
|
|
if [ -z "$KERNEL_SRC_DIR" ]; then
|
|
echo "ERROR: Could not find extracted source directory"
|
|
ls -la "$KERNEL_BUILD_DIR"/
|
|
exit 1
|
|
fi
|
|
|
|
cd "$KERNEL_SRC_DIR"
|
|
|
|
# --- Base config: arm64 defconfig (generic ARMv8) ---
|
|
echo "==> Applying arm64 defconfig..."
|
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
|
|
|
|
# --- Apply shared container fragment ---
|
|
CONFIG_FRAGMENT="$PROJECT_ROOT/build/config/kernel-container.fragment"
|
|
if [ ! -f "$CONFIG_FRAGMENT" ]; then
|
|
echo "ERROR: Config fragment not found: $CONFIG_FRAGMENT"
|
|
exit 1
|
|
fi
|
|
|
|
apply_fragment() {
|
|
local fragment="$1"
|
|
while IFS= read -r line; do
|
|
case "$line" in
|
|
"# CONFIG_"*" is not set")
|
|
key=$(echo "$line" | sed -n 's/^# \(CONFIG_[A-Z0-9_]*\) is not set$/\1/p')
|
|
[ -n "$key" ] && ./scripts/config --disable "${key#CONFIG_}"
|
|
continue
|
|
;;
|
|
\#*|"") continue ;;
|
|
esac
|
|
key="${line%%=*}"
|
|
value="${line#*=}"
|
|
case "$value" in
|
|
y) ./scripts/config --enable "$key" ;;
|
|
m) ./scripts/config --module "$key" ;;
|
|
n) ./scripts/config --disable "${key#CONFIG_}" ;;
|
|
*) ./scripts/config --set-str "$key" "$value" ;;
|
|
esac
|
|
done < "$fragment"
|
|
}
|
|
|
|
echo "==> Applying kernel-container.fragment (pass 1)..."
|
|
apply_fragment "$CONFIG_FRAGMENT"
|
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig
|
|
|
|
echo "==> Applying kernel-container.fragment (pass 2)..."
|
|
apply_fragment "$CONFIG_FRAGMENT"
|
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig
|
|
|
|
# --- ARM64 virt-host specific enables ---
|
|
# These are needed for the generic UEFI/virtio boot path but are arch-specific
|
|
# so they live in this script rather than the shared fragment.
|
|
echo "==> Enabling ARM64 virt-host configs..."
|
|
./scripts/config --enable CONFIG_EFI
|
|
./scripts/config --enable CONFIG_EFI_STUB
|
|
./scripts/config --enable CONFIG_VIRTIO
|
|
./scripts/config --enable CONFIG_VIRTIO_PCI
|
|
./scripts/config --enable CONFIG_VIRTIO_BLK
|
|
./scripts/config --enable CONFIG_VIRTIO_NET
|
|
./scripts/config --enable CONFIG_VIRTIO_CONSOLE
|
|
./scripts/config --enable CONFIG_VIRTIO_MMIO
|
|
./scripts/config --enable CONFIG_HW_RANDOM_VIRTIO
|
|
# NVMe for cloud / bare-metal ARM64 hosts that don't use virtio
|
|
./scripts/config --enable CONFIG_BLK_DEV_NVME
|
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig
|
|
|
|
# --- Verify critical configs ---
|
|
echo "==> Verifying critical configs..."
|
|
for cfg in CGROUP_BPF SECURITY_APPARMOR AUDIT VIRTIO_BLK EFI_STUB; do
|
|
if ! grep -q "CONFIG_${cfg}=y" .config; then
|
|
echo "ERROR: CONFIG_${cfg} not set after olddefconfig"
|
|
grep "CONFIG_${cfg}" .config || echo " (not found)"
|
|
exit 1
|
|
fi
|
|
echo " CONFIG_${cfg}=y confirmed"
|
|
done
|
|
|
|
# --- Build kernel + modules (no DTBs — UEFI hosts use ACPI/virtio) ---
|
|
NPROC=$(nproc 2>/dev/null || echo 4)
|
|
echo ""
|
|
echo "==> Building ARM64 kernel (${NPROC} parallel jobs)..."
|
|
echo " This may take 20-40 minutes on a 6-core Odroid..."
|
|
|
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j"$NPROC" Image modules 2>&1
|
|
|
|
echo "==> Kernel build complete"
|
|
|
|
# --- Install to staging ---
|
|
echo "==> Installing Image..."
|
|
cp arch/arm64/boot/Image "$CUSTOM_IMAGE"
|
|
|
|
echo "==> Installing modules (stripped)..."
|
|
rm -rf "$CUSTOM_MODULES"
|
|
mkdir -p "$CUSTOM_MODULES"
|
|
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
|
|
INSTALL_MOD_STRIP=1 modules_install INSTALL_MOD_PATH="$CUSTOM_MODULES"
|
|
|
|
# Pick up actual kernel version (e.g. 6.12.10 if KVER differs from package suffix)
|
|
ACTUAL_KVER=$(ls "$CUSTOM_MODULES/lib/modules/" | head -1)
|
|
rm -f "$CUSTOM_MODULES/lib/modules/$ACTUAL_KVER/build"
|
|
rm -f "$CUSTOM_MODULES/lib/modules/$ACTUAL_KVER/source"
|
|
|
|
depmod -a -b "$CUSTOM_MODULES" "$ACTUAL_KVER" 2>/dev/null || true
|
|
|
|
cp .config "$CUSTOM_KERNEL_DIR/.config"
|
|
|
|
# --- Clean up ---
|
|
echo "==> Cleaning kernel build directory..."
|
|
cd /
|
|
rm -rf "$KERNEL_BUILD_DIR"
|
|
|
|
# --- Summary ---
|
|
echo ""
|
|
echo "==> Generic ARM64 kernel build complete:"
|
|
echo " Image: $CUSTOM_IMAGE ($(du -h "$CUSTOM_IMAGE" | cut -f1))"
|
|
echo " Kernel ver: $ACTUAL_KVER"
|
|
MOD_COUNT=$(find "$CUSTOM_MODULES/lib/modules/$ACTUAL_KVER" -name '*.ko*' 2>/dev/null | wc -l)
|
|
echo " Modules: $MOD_COUNT"
|
|
echo " Modules size: $(du -sh "$CUSTOM_MODULES/lib/modules/$ACTUAL_KVER" 2>/dev/null | cut -f1)"
|
|
echo ""
|