剥离armbian的patch并为ASUS Tinkerboard 2构建Archlinux镜像

发布于 | 最后修改于 | 12 分钟
目录

这可能是某种执念。成功了,但最后还是觉得完成后也没什么用。跑点什么好呢?

学习Armbian的设计,获取编译必要信息🔗

一开始没有方向。原以为从启动脚本开始看就好了,但发现Armbian的Bash脚本用了很多高级编程方法,没法轻松追溯各个部分。于是选择从开发板配置开始看。来看看armbian-build/config/boards/tinkerboard-2.csc

# Rockchip RK3399 hexa core 2GB SoC GBe eMMC USB3 WiFi/BT
BOARD_NAME="Tinker Board 2"
BOARDFAMILY="rockchip64"
BOARD_MAINTAINER=""
BOOTCONFIG="tinker-2-rk3399_defconfig"
KERNEL_TARGET="current,edge"
KERNEL_TEST_TARGET="current,edge"
FULL_DESKTOP="yes"
BOOT_LOGO="desktop"
BOOT_FDT_FILE="rockchip/rk3399-tinker-2.dtb"
SERIALCON="ttyS2"
BOOT_SCENARIO="spl-blobs"                   # 'blobless' also works; but some RAM issues found; see rk33/rk3399_ddr_800MHz_v1.27.bin in rockchip64_common.inc
BOARD_FIRMWARE_INSTALL="-full"              # Install full firmware, for rtl8822ce firmware and others
BOOTBRANCH="tag:v2021.07"                   # v2021.07 ...
BOOTPATCHDIR='legacy/u-boot-tinkerboard2'   # ...  with _only_ the patches we need for TB2, not the default rockchip64
DDR_BLOB="rk33/rk3399_ddr_800MHz_v1.27.bin" # Different blob for TB2

全局搜索上面的变量名称,可以发现下面一些文件很有意思

  • armbian-build/config/sources/families/include/rockchip64_common.inc
    • rockchip64平台的配置脚本
  • armbian-build/extensions/rkbin-tools.sh
    • rkbin的编译工具
  • armbian-build/config/sources/common.conf
    • 部分代码定义了内核补丁的默认行为

列一些有用的代码块

CPUMIN=${CPUMIN:="408000"}

...

    rk3399)
		CPUMAX=${CPUMAX:="2016000"}
		BOOT_SCENARIO="${BOOT_SCENARIO:=only-blobs}"
		DDR_BLOB="${DDR_BLOB:-"rk33/rk3399_ddr_933MHz_v1.25.bin"}"
		MINILOADER_BLOB="${MINILOADER_BLOB:-"rk33/rk3399_miniloader_v1.26.bin"}"
		BL31_BLOB="${BL31_BLOB:-"rk33/rk3399_bl31_v1.35.elf"}"
		;;

...

prepare_boot_configuration() {
    ...
        spl-blobs)
			UBOOT_TARGET_MAP="BL31=$RKBIN_DIR/$BL31_BLOB spl/u-boot-spl.bin u-boot.dtb u-boot.itb;;idbloader.img u-boot.itb"
			ATFSOURCE=''
			ATF_COMPILE='no'
			;;
    ...
}

uboot_custom_postprocess() {
	[[ -z ${BOOT_SOC} ]] && exit_with_error "BOOT_SOC not defined for scenario '${BOOT_SCENARIO}' for BOARD'=${BOARD}' and BOOTCONFIG='${BOOTCONFIG}'"

	if [[ $BOOT_SCENARIO == "blobless" || $BOOT_SCENARIO == "tpl-spl-blob" ]]; then
		:
	elif [[ $BOOT_SCENARIO == "spl-blobs" || $BOOT_SCENARIO == "tpl-blob-atf-mainline" ]]; then
		# Bomb if DDR_BLOB not defined or does not exist
		declare SPL_BIN_PATH="${RKBIN_DIR}/${DDR_BLOB}"
		[[ -z ${SPL_BIN_PATH} ]] && exit_with_error "DDR_BLOB not defined for scenario ${BOOT_SCENARIO}"
		[[ ! -f "${SPL_BIN_PATH}" ]] && exit_with_error "DDR_BLOB ${SPL_BIN_PATH} does not exist for scenario ${BOOT_SCENARIO}"

		display_alert "mkimage for '${BOOT_SOC}' for scenario ${BOOT_SCENARIO}" "SPL_BIN_PATH: ${SPL_BIN_PATH}" "debug"
		run_host_command_logged tools/mkimage -n "${BOOT_SOC_MKIMAGE}" -T rksd -d "${SPL_BIN_PATH}:spl/u-boot-spl.bin" idbloader.img

...
# If KERNELPATCHDIR is unset, set it to "archive/${KERNELPATCHDIR}-${KERNEL_MAJOR_MINOR}" # @TODO: no, use LINUXFAMILY directly
	if [[ -z ${KERNELPATCHDIR} ]]; then
		display_alert "KERNELPATCHDIR is unset; using 'archive/${KERNEL_PATCH_ARCHIVE_BASE}-${KERNEL_MAJOR_MINOR}'" "common_defaults_for_mainline" "info"
		KERNELPATCHDIR="archive/${KERNEL_PATCH_ARCHIVE_BASE}-${KERNEL_MAJOR_MINOR}" # previously was KERNELPATCHDIR="$LINUXFAMILY-$BRANCH"
	fi

看了下Linux的编译操作分散在 kernel*.sh 中。Armbian的默认做法是给主线内核打补丁,使其兼容目标平台。用户也可以使用变量配置实际内核的来源和分支。

编译u-boot🔗

看tinkerboard2相关commit说,因为一些内存初始化问题,用RK官方提供的blob会更好,这里就不用编译ATF。复用了一些Armbian的变量、抠了一些代码出来用,参考流程如下:

git clone -b master https://github.com/armbian/rkbin rkbin-tools
git clone https://github.com/u-boot/u-boot u-boot
RKBIN_DIR=$(readlink -f rkbin-tools)
DDR_BLOB=rk33/rk3399_ddr_800MHz_v1.27.bin
BL31_BLOB=rk33/rk3399_bl31_v1.35.elf
BOOT_SOC_MKIMAGE=rk3399

# configure u-boot
pushd u-boot
git checkout v2021.07
git checkout -b tinkerboard-2-build
# apply patches in armbian-build/patch/u-boot/legacy/u-boot-tinkerboard2
git apply ../u-boot-tinkerboard2-patches/*.patch
# use custom version control version, here's empty, so no -dirty will be appended
touch .scmversion

make CROSS_COMPILE=aarch64-linux-gnu- tinker-2-rk3399_defconfig
# emm?
echo -e "CONFIG_ZERO_BOOTDELAY_CHECK=y" >> .config
popd
# u-boot done configuring

# build u-boot
pushd u-boot
UBOOT_CFLAGS_ARRAY=(
    "-fdiagnostics-color=always" # color messages
    "-Wno-error=maybe-uninitialized"
    "-Wno-error=misleading-indentation"   # patches have mismatching indentation
    "-Wno-error=attributes"               # for very old-uboots
    "-Wno-error=address-of-packed-member" # for very old-uboots
    "-Wno-error=array-parameter" # very old uboots, apply when gcc version >= 11
)
make -j$(nproc) CROSS_COMPILE=aarch64-linux-gnu- olddefconfig
UBOOT_TARGET_MAP="BL31=$RKBIN_DIR/$BL31_BLOB spl/u-boot-spl.bin u-boot.dtb u-boot.itb;;idbloader.img u-boot.itb"
target_make=$(cut -d';' -f1 <<< "${UBOOT_TARGET_MAP}")
# target_patchdir=$(cut -d';' -f2 <<< "${UBOOT_TARGET_MAP}")
target_files=$(cut -d';' -f3 <<< "${UBOOT_TARGET_MAP}")
make -j$(nproc) CFLAGS="${UBOOT_CFLAGS_ARRAY[*]}" CROSS_COMPILE=aarch64-linux-gnu- ${target_make}

# post process
SPL_BIN_PATH="${RKBIN_DIR}/${DDR_BLOB}"
tools/mkimage -n "${BOOT_SOC_MKIMAGE}" -T rksd -d "${SPL_BIN_PATH}:spl/u-boot-spl.bin" idbloader.img

mkdir ../u-boot-binaries
cp $target_files ../u-boot-binaries
popd

# Installation? TBW

编译linux🔗

大差不大。有一点注意,Armbian按natural sort排序依序应用patch(这个和python的sortedls -v一致)。看源码说,部分情况下还需要拆分mailbox形式的patch,不过我没遇到过。

参考准备流程如下:

git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux-stable.git linux-stable

pushd linux-stable
git checkout v6.6.30 # latest 6.6, referenced as "current"
git checkout -b "tinkerboard-2-current"

# apply patches in armbian-build/patch/kernel/archive/rockchip64-6.6
# order matters, use natural sort(-v)
for i in $(ls -1Av ../linux-rockchip64-patchset-6.6/*.patch); do
    patch -Np1 < $i
done
# overwrite devicetrees
cp ../linux-rockchip64-patchset-6.6/dt/* arch/arm64/boot/dts/rockchip
[ -d arch/arm64/boot/dts/rockchip/overlay ] || mkdir -p arch/arm64/boot/dts/rockchip/overlay
cp ../linux-rockchip64-patchset-6.6/overlay/* arch/arm64/boot/dts/rockchip/overlay
# auto patch Makefile. some kernel version may need extra '-y'
for dts in $(ls -1Av ../linux-rockchip64-patchset-6.6/dt/); do
    if ! grep "dtb-${CONFIG_ARCH_ROCKCHIP} += $dts" arch/arm64/boot/dts/rockchip/Makefile > /dev/null; then
        echo "dtb-${CONFIG_ARCH_ROCKCHIP} += $dts" >> arch/arm64/boot/dts/rockchip/Makefile
    fi
done

# no "-dirty", hahaha
touch .scmversion

popd
# patch done

配置然后编译内核。在第一遍还没有config用的时候就这样吧。

# here we need linux-rockchip64-current.config from
# armbian-build/config/kernel/linux-rockchip64-current.config
# at the time of writing, this is the one for kernel 6.6.* used by armbian
cp ../linux-rockchip64-current.config .config
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

然后按正常流程来编译内核,不再详叙。打包就参考linux-aarch64,略。不过Armbian默认配置带的驱动还真多啊。

准备rootfs🔗

跳过!这段不写!研究下pacstrap吧~

存储器分区🔗

使用单分区配置(只有rootfs一个分区,不含EFI之类)。根据瑞芯微wiki,boot分区应从32768扇区开始,不过实际可以单单把rootfs放在这里。大概是u-boot实现了从ext4加载的功能,暂不深究。

parted -s -a optimal -- "${IMAGE_FILE}" mklabel msdos
parted -s -a optimal -- "${IMAGE_FILE}" mkpart primary ext4 32768s 100%

之后把rootfs释放进去,把u-boot要用的脚本、kernel image、initrd之类的丢进/boot就好。

安装bootloader🔗

根据瑞芯微的wiki,把idbloader.imgu-boot.itb写到对应位置。BTW扇区大小512字节。

dd if=u-boot-binaries/idbloader.img of=${_DEVICE} seek=64
dd if=u-boot-binaries/u-boot.itb of=${_DEVICE} seek=16384

按习惯我会直接用u-boot加载initramfs、kernel、config。但是实际测试竟然不能启动? 接串口看了看日志,说是initrd格式错误。 看起来所用u-boot不支持直接加载initramfs(类似rootfs只是文件打包),需要改成initrd(一个虚拟block设备,需要挂载)。可以使用下面的指令进行转换:

mkimage -A arm64 -O linux -T ramdisk -d initramfs-linux.img uInitrd

说起来是不是应该有种方法让kernel自己加载initramfs?不太了解。

下面的脚本是mkinitcpio的post hook,这样能每次自动生成initrd:

#!/usr/bin/env bash

kernel=$1
initramfs=$2
unified_image=$3

initrd_name="uInitrd"

if [[ $initramfs = *fallback* ]]; then
    initrd_name="$initrd_name-fallback"
fi

initramfs_path=$(dirname "$(realpath $initramfs)")

echo "- Creating initrd image $initrd_name and saving to folder $initramfs_path"
mkimage -A arm64 -O linux -T ramdisk -d "$initramfs" "$initramfs_path/$initrd_name"

启动脚本的部分我把dietpi用的搬了过来,稍微改了改。毕竟u-boot用的都是armbian版。

boot.cmd

# DO NOT EDIT THIS FILE
#
# Please edit /boot/bootEnv.txt to set supported parameters
# This file is adapted from dietpi's work.
#
# If you must, edit /boot/boot.cmd and recompile /boot/boot.scr with:
# mkimage -C none -A arm64 -T script -d /boot/boot.cmd /boot/boot.scr

# Default environment
setenv rootdev "/dev/mmcblk0p1"
setenv rootfstype "ext4"
setenv consoleargs "console=tty1"
setenv docker_optimizations "off"
setenv overlay_path "amlogic"
setenv overlay_prefix "meson"

# Load addresses
setenv scriptaddr "0x9000000"
setenv overlay_error "false"

# Load environment file
if load "${devtype}" "${devnum}" "${scriptaddr}" "${prefix}bootEnv.txt"; then
	env import -t "${scriptaddr}" "${filesize}"
fi

# Define kernel command-line arguments
setenv bootargs "root=${rootdev} rootfstype=${rootfstype} rootwait ${consoleargs} consoleblank=0 coherent_pool=2M usb-storage.quirks=${usbstoragequirks} ${extraargs}"

# Add bootargs for Docker
if test "${docker_optimizations}" = "on"; then setenv bootargs "${bootargs} cgroup_enable=memory swapaccount=1"; fi

# Load kernel, initramfs and device tree
load "${devtype}" "${devnum}" "${kernel_addr_r}" "${prefix}Image"
load "${devtype}" "${devnum}" "${ramdisk_addr_r}" "${prefix}uInitrd"
load "${devtype}" "${devnum}" "${fdt_addr_r}" "${prefix}dtbs/${fdtfile}"
fdt addr "${fdt_addr_r}"

# Apply DT overlays
if test -n "${overlays}${user_overlays}"; then
	fdt resize 65536
	for overlay in ${overlays}; do
		if load "${devtype}" "${devnum}" "${scriptaddr}" "${prefix}dtbs/${overlay_path}/overlay/${overlay_prefix}-${overlay}.dtbo"; then
			echo "Applying kernel provided DT overlay ${overlay_prefix}-${overlay}.dtbo"
			fdt apply "${scriptaddr}" || setenv overlay_error "true"
		fi
	done

	for overlay in ${user_overlays}; do
		if load "${devtype}" "${devnum}" "${scriptaddr}" "${prefix}overlay-user/${overlay}.dtbo"; then
			echo "Applying user provided DT overlay ${overlay}.dtbo"
			fdt apply "${scriptaddr}" || setenv overlay_error "true"
		fi
	done

	if test "${overlay_error}" = "true"; then
		echo "Error applying DT overlays, restoring original DT"
		load "${devtype}" "${devnum}" "${fdt_addr_r}" "${prefix}dtbs/${fdtfile}"
	else
		if load "${devtype}" "${devnum}" "${scriptaddr}" "${prefix}dtbs/${overlay_path}/overlay/${overlay_prefix}-fixup.scr"; then
			echo "Applying kernel provided DT fixup script ${overlay_prefix}-fixup.scr"
			source "${scriptaddr}"
		fi
		if test -e "${devtype}" "${devnum}" "${prefix}fixup.scr"; then
			if load "${devtype}" "${devnum}" "${scriptaddr}" "${prefix}fixup.scr"; then
				echo "Applying user provided DT fixup script fixup.scr"
				source "${scriptaddr}"
			fi
		fi
	fi
fi

# Boot
booti "${kernel_addr_r}" "${ramdisk_addr_r}" "${fdt_addr_r}"

bootEnv.txt

rootdev=UUID=CHANGE_ME
rootfstype=ext4
# The init system logs to the console defined last.
consoleargs=console=ttyS2,1500000 console=tty1
usbstoragequirks=
extraargs=net.ifnames=0
docker_optimizations=off
overlay_path=rockchip
overlay_prefix=rockchip
overlays=
user_overlays=

一些问题🔗

不知道为啥,用 armbian 的内核 HDMI 就会抖,例如字体纵向边缘有波纹抖动,但是在dietpi上没这个问题。已测试不是设备树的问题。在dietpi的相关资料里也没翻到他们怎么编译内核的。

Update:意外发现,好像是我显示器HDMI接口不太灵……

所以armbian/build是什么?🔗

看起来这是一套代码复用率很高的打包框架。不过有些地方的设计还挺令我头疼的。简单说,我第一次用时对整个流程都不是很了解,不透明,而且那时候不懂使用地区镜像,网速慢死,劝退劝退。

它通过平台/开发板配置文件指定各个流程用到的关键配置,包括源码来源、内核版本、u-boot配置、各种乱七八糟的东西。编译不同版本的u-boot和linux的流程大差不大,armbian就把它们整合起来了。中间有一些编译期间的quirks,这个没办法单独列出来。整体上就是下载源码、打补丁、交互模式时的手动配置、编译、转移/打包编译成果。

好像在这篇文里提Arch有点多余?🔗

看起来是这样。毕竟打包和安装rootfs的部分都省略了。