From 477d43aacdc3d74dac34035f27dbf4bf5415e831 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Fri, 14 Aug 2026 16:17:52 +0300 Subject: [PATCH] Survive kernel updates on a /boot this small MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A dnf upgrade to 7.1.8 installed the kernel rpm cleanly, then ran /boot out of space. dracut wrote no initramfs, and because kernel-install stops at the first failing plugin, 95-c630-devicetree never ran either — leaving a boot entry with neither an initrd nor a devicetree line, which on this machine can never boot. dnf reported success and nothing retried. A kernel costs ~336 MiB here: a 210 MiB hostonly=no initramfs, a 98 MiB dtb- directory carrying every board's device tree, plus vmlinuz and System.map. Three of those cannot fit 1 GiB, so /boot goes to 2 GiB and installonly_limit drops to 2. Also fixes the quieter half of the same trap. snd-soc-wsa881x lives outside the kernel package, so the speakers go silent after any kernel update with nothing in the logs to explain it. c630-wsa881x rebuilds it and 96-c630-wsa881x.install calls it on each kernel-install add — always exiting 0, since a plugin failure is precisely what caused the damage above. grub.cfg gains the next_entry one-shot block that has been carried by hand all along, so testing a kernel costs a power cycle rather than a rescue. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011XgGF5wfxLDAybVnNz6eNQ --- build/stage2.sh | 17 ++++ config/device.env | 8 +- docs/firmware.md | 21 +++++ docs/install.md | 48 ++++++++++++ .../libdnf5.conf.d/50-c630-installonly.conf | 23 ++++++ .../kernel/install.d/96-c630-wsa881x.install | 37 +++++++++ overlay/usr/local/sbin/c630-wsa881x | 78 +++++++++++++++++++ 7 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 overlay/etc/dnf/libdnf5.conf.d/50-c630-installonly.conf create mode 100755 overlay/etc/kernel/install.d/96-c630-wsa881x.install create mode 100755 overlay/usr/local/sbin/c630-wsa881x diff --git a/build/stage2.sh b/build/stage2.sh index d4b2b63..cb869f2 100755 --- a/build/stage2.sh +++ b/build/stage2.sh @@ -365,6 +365,23 @@ if [ -s \$prefix/grubenv ]; then load_env fi +# One-shot boot selection. Booting a test kernel on this machine means either +# catching a 5-second menu on a laptop that may not have its display up yet, or +# making the test kernel the permanent default and hoping it boots — which has +# already cost one recovery via rd.break. Instead: +# +# sudo grub2-editenv /boot/grub2/grubenv set next_entry=c630- +# +# takes effect for exactly one boot and then reverts, so a kernel that hangs +# costs a power cycle rather than a rescue. Entry ids are the BLS file names in +# /boot/loader/entries without the .conf. +if [ "\$next_entry" ] ; then + set default="\$next_entry" + set next_entry= + save_env next_entry + set boot_once=true +fi + blscfg EOF : > "$ROOTFS/boot/grub2/grubenv" diff --git a/config/device.env b/config/device.env index bba3278..122d015 100644 --- a/config/device.env +++ b/config/device.env @@ -34,7 +34,13 @@ # --- image geometry (MiB) ----------------------------------------------- : "${ESP_SIZE_MIB:=512}" -: "${BOOT_SIZE_MIB:=1024}" + +# /boot has to hold whole kernels, and this machine's are fat: the initramfs is +# built hostonly=no (~210 MiB) and kernel-core's dtb directory carries every +# board's device tree (~98 MiB), so an installed kernel costs ~336 MiB. 1024 +# fits only two with nothing to spare, and running out mid-update leaves an +# unbootable entry — see overlay/etc/dnf/libdnf5.conf.d/50-c630-installonly.conf. +: "${BOOT_SIZE_MIB:=2048}" # Total image size. The root partition takes whatever is left, and grows to # fill the target medium on first boot. diff --git a/docs/firmware.md b/docs/firmware.md index 494364c..92dcc8f 100644 --- a/docs/firmware.md +++ b/docs/firmware.md @@ -573,6 +573,27 @@ M=$PWD modules` with `obj-m += snd-soc-wsa881x.o` and `snd-soc-wsa881x-objs := wsa881x.o`), installed under `/lib/modules/$(uname -r)/extra/` and picked up by `depmod -a`, it autoloads on the SoundWire modalias at boot and the `Lenovo-YOGA-C630-13Q50` card registers, headphone-jack detection included. + +Because the module lives outside the kernel package, it has to be rebuilt for +every kernel, and the symptom of forgetting is unhelpful: the speakers simply +go silent after a `dnf upgrade`, with nothing in the logs tying the two +together. `overlay/usr/local/sbin/c630-wsa881x` does the rebuild — it fetches +`wsa881x.c` at the matching upstream tag, builds it against `kernel-devel` and +installs it — and `overlay/etc/kernel/install.d/96-c630-wsa881x.install` calls +it on every `kernel-install add`. That hook always exits 0: `kernel-install` +stops at the first plugin that fails, and a missing compiler costing you the +speakers is a far better outcome than it costing you the `devicetree` line and +therefore the machine. It needs `gcc`, `make`, `kernel-devel` and a network, +none of which the minimal image installs, so on a fresh image run it by hand +once: + +```sh +sudo dnf install -y gcc make kernel-devel +sudo c630-wsa881x +``` + +None of this is necessary once Fedora sets the config; see +[drafts/fedora-wsa881x-request.md](drafts/fedora-wsa881x-request.md). The UCM profile has been in alsa-ucm-conf since 2020; `alsaucm` itself is in `alsa-ucm-utils`, so on the minimal image: diff --git a/docs/install.md b/docs/install.md index 175a3e3..f9dc9b1 100644 --- a/docs/install.md +++ b/docs/install.md @@ -125,6 +125,54 @@ sudo date -u -s "2026-07-27 16:33:00" Any roughly-correct time will do; it only has to be later than the packages were signed. +## Kernel updates and a full /boot + +A kernel costs about 336 MiB here — a ~210 MiB initramfs (built `hostonly=no`, +so it carries the whole driver and firmware set), a ~98 MiB `dtb-` +directory because `kernel-core` ships every board's device tree, plus vmlinuz +and System.map. Three of those do not fit a 1 GiB `/boot`, which is why images +now use 2 GiB and `installonly_limit=2`. + +If `/boot` does fill up, the failure is quiet and genuinely dangerous: + +1. The kernel rpm installs fine — `rpm -V` is clean and the module tree is + complete. +2. `50-dracut.install` then dies on ENOSPC, so **no initramfs is written**. +3. `kernel-install` stops at the first failing plugin, so + `95-c630-devicetree.install` never runs and the boot entry gets **no + `devicetree` line** — which on this machine means it can never boot, + because the UEFI supplies no device tree. +4. `dnf` reports success. Nothing retries, and later `dnf upgrade` runs look + clean because they no longer touch the kernel. + +So an update can leave a kernel that is installed, listed in the boot menu, and +unbootable. To check whether it actually completed: + +```sh +rpm -q kernel-core # what rpm thinks is installed +ls /boot/initramfs-* # one per kernel, or the update failed +sudo grep -L devicetree /boot/loader/entries/*.conf # any output is unbootable +``` + +To repair it, free space first, then redo the two steps that were skipped: + +```sh +sudo dnf remove kernel-core- +sudo dracut --force --kver /boot/initramfs-.img +sudo /etc/kernel/install.d/95-c630-devicetree.install add +``` + +Both are idempotent. Test the result with a one-shot boot rather than by +changing the default, so a kernel that hangs costs only a power cycle: + +```sh +sudo grub2-editenv /boot/grub2/grubenv set next_entry=c630- +sudo reboot +``` + +Note that `speakers go silent after a kernel update` is the same event wearing a +different hat — see `c630-wsa881x` in [firmware.md](firmware.md). + ## If it does not boot The kernel command line is the first thing to change. It lives in diff --git a/overlay/etc/dnf/libdnf5.conf.d/50-c630-installonly.conf b/overlay/etc/dnf/libdnf5.conf.d/50-c630-installonly.conf new file mode 100644 index 0000000..8c0b70b --- /dev/null +++ b/overlay/etc/dnf/libdnf5.conf.d/50-c630-installonly.conf @@ -0,0 +1,23 @@ +# Keep two kernels, not the stock three. +# +# This machine's initramfs is unusually large — hostonly is off (see +# /etc/dracut.conf.d/10-c630.conf) so every image carries the full driver and +# firmware set, and each installed kernel costs roughly: +# +# initramfs ~210 MiB +# dtb- ~98 MiB (kernel-core ships every board's device tree) +# vmlinuz ~17 MiB +# System.map ~11 MiB +# --------------------- +# total ~336 MiB +# +# Three of those is ~1008 MiB, which does not fit a 1 GiB /boot. The failure is +# nastier than a full disk: dnf installs the kernel rpm successfully, then +# dracut dies on ENOSPC, and because kernel-install stops at the first failing +# plugin, 95-c630-devicetree never runs either. The result is a boot entry with +# no initrd and no devicetree — unbootable — while dnf reports success and +# never retries. +# +# Two kernels is ~672 MiB, which leaves comfortable headroom. +[main] +installonly_limit=2 diff --git a/overlay/etc/kernel/install.d/96-c630-wsa881x.install b/overlay/etc/kernel/install.d/96-c630-wsa881x.install new file mode 100755 index 0000000..a9cf8a3 --- /dev/null +++ b/overlay/etc/kernel/install.d/96-c630-wsa881x.install @@ -0,0 +1,37 @@ +#!/usr/bin/bash +# +# Rebuild the speaker-amplifier module for each new kernel. +# +# snd-soc-wsa881x is not in any Fedora package (see c630-wsa881x), so it lives +# in /lib/modules//extra and has to be rebuilt whenever a kernel is +# installed. Without this the speakers go silent the first time you run +# `dnf upgrade`, and nothing in the logs connects the two events. +# +# This runs at 96, after 95-c630-devicetree, because it must not come between +# 90-loaderentry.install — which rewrites the BLS entry — and the hook that +# puts the devicetree line back. +# +# It always exits 0. kernel-install stops at the first plugin that fails, and +# this machine has already been bitten once by that: a full /boot made +# 50-dracut.install fail, which meant 95-c630-devicetree never ran, which left +# a boot entry with neither an initrd nor a devicetree. Silent speakers are +# worth a warning; they are not worth an unbootable machine. + +set -u + +COMMAND="${1:?}" +KERNEL_VERSION="${2:?}" + +[ "$COMMAND" = "add" ] || exit 0 + +if ! command -v c630-wsa881x >/dev/null; then + exit 0 +fi + +if ! c630-wsa881x "$KERNEL_VERSION"; then + echo "96-c630-wsa881x: could not build snd-soc-wsa881x for ${KERNEL_VERSION};" \ + "the built-in speakers will be silent on that kernel until you run" \ + "'sudo c630-wsa881x ${KERNEL_VERSION}'" >&2 +fi + +exit 0 diff --git a/overlay/usr/local/sbin/c630-wsa881x b/overlay/usr/local/sbin/c630-wsa881x new file mode 100755 index 0000000..001f7c5 --- /dev/null +++ b/overlay/usr/local/sbin/c630-wsa881x @@ -0,0 +1,78 @@ +#!/usr/bin/bash +# +# Build the speaker-amplifier driver Fedora does not ship. +# +# The C630's two WSA881x amplifiers sit on the WCD9340's SoundWire bus, and +# Fedora's aarch64 kernel has never set CONFIG_SND_SOC_WSA881X — the newer +# WSA883X and WSA884X the ThinkPad X13s needs are both =m, but this one was +# missed. Without it the SoundWire slaves never bind, the machine driver cannot +# find its DAIs, and the sound card sits in deferred probe forever: no card, no +# speakers, no error that points anywhere useful. +# +# sound/soc/codecs/wsa881x.c is one self-contained file with no out-of-tree +# patches, so building it against kernel-devel and dropping it in extra/ is +# enough. Because it lives outside the kernel package, it has to be rebuilt for +# every kernel — hence 96-c630-wsa881x.install, which calls this on each +# kernel-install add. Audio otherwise goes silent the first time you take a +# kernel update, with nothing in the logs to connect the two. +# +# The real fix is Fedora flipping the config; see docs/drafts/. +# +# Usage: c630-wsa881x [kernel-version] (defaults to the running kernel) + +set -euo pipefail + +KVER="${1:-$(uname -r)}" +BUILD="/lib/modules/${KVER}/build" +DEST="/lib/modules/${KVER}/extra/snd-soc-wsa881x.ko" + +if [ -f "$DEST" ]; then + echo "c630-wsa881x: ${DEST} already built" + exit 0 +fi + +# Refuse rather than half-do it: a partial build leaves no module and no clue. +missing=() +[ -d "$BUILD" ] || missing+=("kernel-devel-${KVER}") +command -v gcc >/dev/null || missing+=(gcc) +command -v make >/dev/null || missing+=(make) +if [ "${#missing[@]}" -gt 0 ]; then + echo "c630-wsa881x: cannot build for ${KVER}, missing: ${missing[*]}" >&2 + echo "c630-wsa881x: install them and re-run 'c630-wsa881x ${KVER}'," \ + "or the built-in speakers will be silent on that kernel" >&2 + exit 1 +fi + +# Fedora's 7.1.8-200.fc44.aarch64 is upstream 7.1.8. A .0 upstream release is +# tagged v7.1, not v7.1.0, so try both. +base="${KVER%%-*}" +tags=("v${base}") +[ "${base}" != "${base%.0}" ] && tags+=("v${base%.0}") + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT + +src="" +for tag in "${tags[@]}"; do + url="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/sound/soc/codecs/wsa881x.c?h=${tag}" + if curl -fsSL --retry 3 -o "$work/wsa881x.c" "$url"; then + echo "c630-wsa881x: fetched wsa881x.c at ${tag}" + src="$tag" + break + fi +done +if [ -z "$src" ]; then + echo "c630-wsa881x: could not fetch wsa881x.c for ${KVER} (tried: ${tags[*]})" >&2 + echo "c630-wsa881x: needs network; re-run 'c630-wsa881x ${KVER}' once online" >&2 + exit 1 +fi + +cat > "$work/Makefile" <<'EOF' +obj-m += snd-soc-wsa881x.o +snd-soc-wsa881x-objs := wsa881x.o +EOF + +make -C "$BUILD" M="$work" modules +install -D -m 0644 "$work/snd-soc-wsa881x.ko" "$DEST" +depmod -a "$KVER" +echo "c630-wsa881x: installed ${DEST}"