Survive kernel updates on a /boot this small
Some checks failed
build image / build (push) Has been cancelled

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-<kver> 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XgGF5wfxLDAybVnNz6eNQ
This commit is contained in:
2026-08-14 16:17:52 +03:00
parent b5c5218b81
commit 477d43aacd
7 changed files with 231 additions and 1 deletions

View File

@@ -365,6 +365,23 @@ if [ -s \$prefix/grubenv ]; then
load_env load_env
fi 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-<kver>
#
# 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 blscfg
EOF EOF
: > "$ROOTFS/boot/grub2/grubenv" : > "$ROOTFS/boot/grub2/grubenv"

View File

@@ -34,7 +34,13 @@
# --- image geometry (MiB) ----------------------------------------------- # --- image geometry (MiB) -----------------------------------------------
: "${ESP_SIZE_MIB:=512}" : "${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 # Total image size. The root partition takes whatever is left, and grows to
# fill the target medium on first boot. # fill the target medium on first boot.

View File

@@ -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 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 by `depmod -a`, it autoloads on the SoundWire modalias at boot and the
`Lenovo-YOGA-C630-13Q50` card registers, headphone-jack detection included. `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 The UCM profile has been in alsa-ucm-conf since 2020; `alsaucm` itself is in
`alsa-ucm-utils`, so on the minimal image: `alsa-ucm-utils`, so on the minimal image:

View File

@@ -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 Any roughly-correct time will do; it only has to be later than the packages
were signed. 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-<kver>`
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-<old-version>
sudo dracut --force --kver <kver> /boot/initramfs-<kver>.img
sudo /etc/kernel/install.d/95-c630-devicetree.install add <kver>
```
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-<kver>
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 ## If it does not boot
The kernel command line is the first thing to change. It lives in The kernel command line is the first thing to change. It lives in

View File

@@ -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-<kver> ~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

View File

@@ -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/<kver>/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

View File

@@ -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}"