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
The WCN3990 has no burned-in Bluetooth address, so the kernel registers
the controller unconfigured and bluetoothd never sees it. The factory
address is on the machine all along: QCOM/BT.PROVISION on the DPP
partition, a three-byte header followed by the six-byte address.
c630-bt-addr reads it and hands it to btmgmt before bluetooth.service
starts. Verified on hardware: controller configures, powers, scans, and
streams A2DP.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XgGF5wfxLDAybVnNz6eNQ
aarch64-laptops' support table ticks WiFi for this machine, so the path exists.
Following their WiFi README got considerably further and then stopped somewhere
useful to have documented.
Two of the four services they list are in the kernel now (pd-mapper, qrtr-ns).
The other two, rmtfs and tqftpserv, are packaged in Fedora and shipped
disabled — tqftpserv was not even installed. Both are now in base.pkgs and
enabled by stage2. Enabling them took qrtr-lookup from 19 registered services
to 25; a working setup is said to show around 40.
The step worth having written down: the modem does not read wlanmdsp.mbn from
/lib/firmware. It asks tqftpserv for it over TFTP, from
/lib/firmware/readonly/firmware/image/. Nothing reports this as an error —
ath10k_snoc binds, registers a QMI client, and waits forever for a service that
never registers. c630-firmware now places the file there, and creates the
writable area the modem asks tqftpserv for.
Where it stops: the modem boots and dies at "RF stuck in QLINK start state",
about every 42 seconds, never reaching the point of requesting wlanmdsp. Three
firmware pairings give three distinct failures, recorded in the doc — the _nm
"no modem" variants, which are the obvious idea and would skip cellular RF
entirely, turn out not to be signed for this device.
The result that narrows it: aarch64-laptops' own wifi directory carries the
qcdsp1v2850.mbn, qcdsp2850.mbn and wlanmdsp.mbn from the setup whose table ticks
WiFi. All three differ from the WOA-Project copies. Installed here with the
services running and wlanmdsp in the TFTP path, they produce the identical QLINK
failure — so it is neither the firmware nor the userspace. What is left is the
kernel: 5.x from 2019 there against 7.1.5 here, with both of their ath10k
patches long since upstream. That points at a regression, and confirming it
means a bisect rather than another file.
Also record what is untried: the other eight driver versions (this machine's
UEFI is from 2019 and the newest package may be the wrong vintage),
mcfg_subsys_ext850.cab, and building board-2.bin from the C630's own bdwlan.*
files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
The image booted. Kernel came up, the device tree loaded — UFS, display,
WiFi and IPA all probed as platform devices — framebuffer console came up, root
mounted off USB and systemd started. Then:
systemd[1]: Unable to fix SELinux security context of /dev/tty..: Permission denied
(x hundreds)
systemd[1]: Too many messages being logged to kmsg, ignoring
[!!!!!!] Failed to allocate manager object.
The filesystem had no SELinux labels. mke2fs -d carries security.* xattrs
across faithfully, but nothing had ever set them: the tree came from dnf, not
from a running SELinux system. I had relied on /.autorelabel, which cannot
work here — PID 1 dies long before anything acts on the flag.
Label the tree with setfiles instead, after the bind mounts are torn down (or
it would walk the builder's /proc) and before /boot is split out, so /boot's
files are labelled along with everything else. Verified in a privileged
container beforehand that security.selinux xattrs can actually be written
through a bind mount, rather than assuming it.
I had listed policycoreutils in the gongfoo build base for exactly this and
then never called setfiles. It is now also in stage2's fallback toolchain, so
the stock-Fedora path works too.
Ship permissive regardless. The labels make enforcing viable, but the failure
mode is unusually punishing — no login prompt, no shell, nothing to repair from
— and on a machine this awkward to reach that is not a default worth choosing.
SELINUX_MODE in config/device.env flips it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
First boot on the hardware failed, returning to the GRUB menu with:
can't find command 'load_video'
file '/boot/vmlinuz-7.1.5-200.fc44.aarch64' not found
you need to load the kernel first
The title in that message was the tell: "Fedora Linux (7.1.5-...) 44 (Forty
Four)" is Fedora's stock format, not ours. GRUB was booting an entry we did not
write. Confirmed by unpacking the published image and reading the boot
filesystem — /loader/entries held two files, and the one named after the
machine-id sorts before c630-*, so it won.
kernel-install wrote it during the dnf transaction, from inside the install
root where /boot is an ordinary directory rather than a partition. It was wrong
three ways: paths of /boot/vmlinuz-… that resolve nowhere once /boot is its own
filesystem; an empty options line, so no root= and no command line at all; and
no devicetree, so even had it loaded, the kernel would not have known what
machine it was on. Ours, sitting right beside it, was correct throughout.
So: delete any entry we did not write, and define load_video in grub.cfg —
blscfg emits menuentries that call it, and Fedora's generated config defines it
where a hand-written one must too.
Also write /etc/kernel/cmdline. That empty options line was not a build-time
quirk; it is what kernel-install produces when nothing tells it the command
line. The same gap would have bitten on the device at the first
`dnf update kernel`, writing an entry with no root= and no C630 quirks, and the
laptop would not have come back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
Build 18744 died at "Building filesystems" with:
mkfs.vfat: file /work/esp.img already exists
esp.img was left there by build 18700, which was cancelled during compression
and so never reached its end-of-build cleanup. Making $WORK persist so the
staged base could be reused made everything else in it persist as well, and
that turns out not to be inert: mkfs.vfat -C refuses to overwrite, and
`mv $ROOTFS/boot $WORK/boot` would have nested inside a surviving directory
rather than replacing it — a subtler failure that would have produced a /boot
filesystem containing a stray boot/ subdirectory.
Clear the work directory at the start of each run, keeping only the staged base
and its stamp, so the invariant is stated positively rather than depending on
the previous run having exited cleanly. Cleanup that only runs on success is
not cleanup.
Also record why 95-set-boot-entry.install exits 1 during the transaction, so
the next person to read a build log does not go hunting. It is a consequence of
suppressing initramfs generation, is confined to the build, and cannot occur on
the device.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
The first real run took 100 minutes. Roughly 60 of those were spent doing
things that either did not need doing or did not need doing under emulation.
dracut ran three times. The kernel's %posttrans runs kernel-install, which
builds an initramfs, and dracut-config-rescue makes it build a second, rescue
one — both before this build has written /etc/dracut.conf.d/10-c630.conf and
before /proc is bind-mounted, so both are wrong as well as expensive. stage2
then builds the real one. Setting initrd_generator=none in the install root for
the duration of the transaction suppresses both: 50-dracut.install and
51-dracut-rescue.install each bail when KERNEL_INSTALL_INITRD_GENERATOR is not
"dracut". Excluding dracut-config-rescue also spares the laptop a rescue
initramfs on every future kernel update, which on this hardware is not cheap.
That file must not ship. With it in place the machine would boot fine and then
fail to come back after its next kernel update — a bug that surfaces weeks
later looking nothing like an image problem. It is removed from the working
copy, and asserted absent again immediately before the root filesystem is
built.
Compression was running as an aarch64 binary under qemu-user for no reason;
zstd does not care what architecture it runs on. It now runs on the host when
the host has zstd, falling back to in-container otherwise so the build never
depends on it. Measured ~20 minutes against ~2.
Add BASE_RECIPE to the staged-base stamp. Excluding a weak dependency changes
what the base contains without changing the package list it hashes, so without
this the next build would happily reuse a stale base.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
The dnf transaction is essentially the whole cost of a build — emulated rpm
scriptlets for 502 packages on minimal, 1929 on workstation. Everything after
it is minutes. Getting this laptop to boot will take several attempts at the
kernel command line and the dracut driver list, and paying for a reinstall each
time is not tenable.
Stage the post-dnf tree under <work>/base, keyed on a hash of the package
lists, release and variant, and copy it per build with --reflink=auto (a CoW
clone on btrfs). Config and overlay edits now reuse it; package list edits
invalidate it on their own, so --fresh is only needed to force the issue.
Keep downloaded rpms in a cachedir outside the install root, so even --fresh
re-runs the scriptlets without re-downloading. keepcache=0 was exactly the
wrong setting for a build meant to be run repeatedly.
Add --work so CI can put both outside the job workspace, which is wiped between
runs, and default the build container to the gongfoo aarch64 build base so the
assembly tooling is not installed under emulation every time. That image is a
speedup, not a dependency: fall back to stock Fedora when it is unreachable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
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