Build Fedora aarch64 images for the Lenovo Yoga C630
Assembles a ready-to-write disk image via Gitea Actions. Mainline has carried sdm850-lenovo-yoga-c630.dts since 5.5 and Fedora ships it in kernel-core, so unlike aarch64-laptops/build there is no kernel or GRUB to compile — what is left is producing an image that boots on firmware which hands Linux no device tree. The build runs in an aarch64 container under qemu-user and builds filesystems from directory trees with mke2fs -d and mcopy rather than mounting loop devices, so it works on runners that will not hand out /dev/loop-control. A kernel-install hook writes the devicetree line into each BLS entry; without it the first `dnf update kernel` would produce an unbootable system. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
This commit is contained in:
13
overlay/etc/default/grub.in
Normal file
13
overlay/etc/default/grub.in
Normal file
@@ -0,0 +1,13 @@
|
||||
GRUB_TIMEOUT=5
|
||||
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
|
||||
GRUB_DEFAULT=saved
|
||||
GRUB_DISABLE_SUBMENU=true
|
||||
GRUB_TERMINAL_OUTPUT="console"
|
||||
GRUB_CMDLINE_LINUX="@DEVICE_CMDLINE@"
|
||||
GRUB_DISABLE_RECOVERY=true
|
||||
GRUB_ENABLE_BLSCFG=true
|
||||
|
||||
# Consumed by grub2-mkconfig on aarch64. The BLS entries carry their own
|
||||
# `devicetree` line as well (written by 95-c630-devicetree.install), because
|
||||
# kernel-install does not read this file.
|
||||
GRUB_DEFAULT_DTB=/dtb/@DEVICE_DTB@
|
||||
12
overlay/etc/dracut.conf.d/10-c630.conf
Normal file
12
overlay/etc/dracut.conf.d/10-c630.conf
Normal file
@@ -0,0 +1,12 @@
|
||||
# The image is built on a machine that is nothing like a C630, so the initramfs
|
||||
# must be generic — a host-only initramfs would contain the builder's drivers.
|
||||
hostonly="no"
|
||||
hostonly_cmdline="no"
|
||||
|
||||
# UFS is the C630's only internal storage and the rootfs lives on it, so the
|
||||
# controller and PHY drivers have to be in the initramfs, not modules loaded
|
||||
# later from a filesystem we cannot yet read.
|
||||
add_drivers+=" ufshcd-core ufshcd-pltfrm ufs-qcom phy-qcom-qmp-ufs "
|
||||
|
||||
# Needed before the display comes up, and cheap to include.
|
||||
add_drivers+=" nvmem_qfprom qcom_scm rtc-pm8xxx "
|
||||
47
overlay/etc/kernel/install.d/95-c630-devicetree.install.in
Executable file
47
overlay/etc/kernel/install.d/95-c630-devicetree.install.in
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/bash
|
||||
#
|
||||
# Teach kernel-install about the C630's device tree.
|
||||
#
|
||||
# The C630's UEFI hands Linux no device tree, so GRUB has to load one. Fedora's
|
||||
# BLS snippets have a `devicetree` key for exactly this, but nothing in the
|
||||
# stock toolchain populates it — which means a plain `dnf update kernel` would
|
||||
# otherwise produce an unbootable entry. This hook runs after 20-grub.install
|
||||
# and patches the entry it just wrote.
|
||||
|
||||
set -eu
|
||||
|
||||
COMMAND="${1:?}"
|
||||
KERNEL_VERSION="${2:?}"
|
||||
|
||||
DTB_REL="@DEVICE_DTB@"
|
||||
BOOT_ROOT="${KERNEL_INSTALL_BOOT_ROOT:-/boot}"
|
||||
ENTRIES_DIR="${BOOT_ROOT}/loader/entries"
|
||||
|
||||
case "$COMMAND" in
|
||||
add)
|
||||
dtb_path="/dtb-${KERNEL_VERSION}/${DTB_REL}"
|
||||
if [ ! -e "${BOOT_ROOT}${dtb_path}" ]; then
|
||||
echo "95-c630-devicetree: ${BOOT_ROOT}${dtb_path} is missing;" \
|
||||
"leaving boot entries alone rather than writing a bad one" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
for entry in "${ENTRIES_DIR}"/*-"${KERNEL_VERSION}".conf; do
|
||||
# Drop any stale devicetree line, then append the current one. Doing it
|
||||
# in that order makes the hook idempotent across re-installs.
|
||||
tmp="${entry}.c630.$$"
|
||||
grep -v '^devicetree[[:space:]]' "$entry" > "$tmp"
|
||||
printf 'devicetree %s\n' "$dtb_path" >> "$tmp"
|
||||
mv -f "$tmp" "$entry"
|
||||
echo "95-c630-devicetree: set devicetree ${dtb_path} in ${entry}"
|
||||
done
|
||||
;;
|
||||
remove)
|
||||
# The build writes its own bootstrap entry under a fixed name, which
|
||||
# kernel-install would not otherwise clean up when the kernel goes away.
|
||||
rm -f "${ENTRIES_DIR}/c630-${KERNEL_VERSION}.conf"
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
7
overlay/etc/motd.d/c630.in
Normal file
7
overlay/etc/motd.d/c630.in
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
Fedora @FEDORA_RELEASE@ for the @DEVICE_DESC@ (built @BUILD_DATE@ from @BUILD_REF@)
|
||||
|
||||
Graphics and WiFi work with the firmware Fedora ships. Audio, the sensor
|
||||
hub and hardware video decode need per-model blobs from the Windows
|
||||
partition — run `sudo c630-firmware` once to extract them.
|
||||
|
||||
15
overlay/etc/systemd/system/c630-growfs.service
Normal file
15
overlay/etc/systemd/system/c630-growfs.service
Normal file
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Grow C630 root filesystem to fill the medium
|
||||
DefaultDependencies=no
|
||||
After=systemd-remount-fs.service
|
||||
Before=local-fs.target sysinit.target
|
||||
ConditionPathExists=!/var/lib/c630/growfs-done
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/c630-growfs
|
||||
RemainAfterExit=yes
|
||||
StandardOutput=journal+console
|
||||
|
||||
[Install]
|
||||
WantedBy=sysinit.target
|
||||
49
overlay/usr/local/sbin/c630-firmware
Executable file
49
overlay/usr/local/sbin/c630-firmware
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/bash
|
||||
#
|
||||
# Pull the model-specific Qualcomm blobs off this machine's Windows partition.
|
||||
#
|
||||
# Fedora ships everything for the C630 that Qualcomm allows to be redistributed
|
||||
# — Adreno 630 (graphics) and ath10k WCN3990 (WiFi/Bluetooth) both work out of
|
||||
# the box. What it cannot ship is the per-model signed firmware: the ADSP, the
|
||||
# compute DSP, and the display/UEFI blob. Those are signed against this
|
||||
# machine's own keys and live only in its Windows install.
|
||||
#
|
||||
# Without them you lose audio, the sensor hub (lid switch, accelerometer,
|
||||
# auto-rotate) and hardware video decode. Everything else still works.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "c630-firmware: needs root — try: sudo c630-firmware" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Looking for a Windows installation to extract firmware from..."
|
||||
echo
|
||||
|
||||
if ! command -v qcom-firmware-extract >/dev/null; then
|
||||
echo "qcom-firmware-extract is not installed. Install it with:" >&2
|
||||
echo " sudo dnf install qcom-firmware-extract" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
qcom-firmware-extract "$@"
|
||||
|
||||
echo
|
||||
echo "Regenerating the initramfs so the DSP firmware is available early..."
|
||||
dracut --force --regenerate-all
|
||||
|
||||
cat <<'EOF'
|
||||
|
||||
Done. Reboot to pick up the new firmware.
|
||||
|
||||
If qcom-firmware-extract could not find the Windows partition, mount it by hand
|
||||
and point the tool at it:
|
||||
|
||||
sudo mkdir -p /mnt/windows
|
||||
sudo mount /dev/disk/by-partlabel/Windows /mnt/windows # adjust as needed
|
||||
sudo qcom-firmware-extract --windows-dir /mnt/windows
|
||||
|
||||
See docs/firmware.md in the c630 repo for the full file list and the manual
|
||||
fallback if the tool does not recognise this model.
|
||||
EOF
|
||||
43
overlay/usr/local/sbin/c630-growfs
Executable file
43
overlay/usr/local/sbin/c630-growfs
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/bash
|
||||
#
|
||||
# Grow the root partition and filesystem to fill the medium.
|
||||
#
|
||||
# The image is built at a fixed size so the artifact stays small; whatever it
|
||||
# gets written to is almost always bigger. Runs once, then disables itself.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
STAMP=/var/lib/c630/growfs-done
|
||||
|
||||
root_src="$(findmnt -no SOURCE /)"
|
||||
root_dev="$(basename "$root_src")"
|
||||
|
||||
part_num_file="/sys/class/block/${root_dev}/partition"
|
||||
if [ ! -r "$part_num_file" ]; then
|
||||
echo "c630-growfs: / is on ${root_src}, which is not a partition — nothing to grow"
|
||||
mkdir -p "$(dirname "$STAMP")" && touch "$STAMP"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
part_num="$(cat "$part_num_file")"
|
||||
disk="/dev/$(lsblk -no pkname "$root_src")"
|
||||
|
||||
# The image was written with dd, so the secondary GPT header is still sitting
|
||||
# where the end of the image used to be. growpart refuses to touch a disk in
|
||||
# that state, so move it to the real end of the device first.
|
||||
sgdisk --move-second-header "$disk" || true
|
||||
|
||||
if growpart "$disk" "$part_num"; then
|
||||
echo "c630-growfs: grew ${disk}${part_num}"
|
||||
else
|
||||
# growpart exits non-zero with NOCHANGE when the partition already fills
|
||||
# the disk. That is a success for our purposes.
|
||||
echo "c630-growfs: partition already at full size"
|
||||
fi
|
||||
|
||||
partprobe "$disk" || true
|
||||
resize2fs "$root_src"
|
||||
|
||||
mkdir -p "$(dirname "$STAMP")"
|
||||
touch "$STAMP"
|
||||
echo "c630-growfs: done"
|
||||
Reference in New Issue
Block a user