755031ac1651dfec8bdb35ea71c8a459eff104bd
16
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
755031ac16 |
Remove the custom motd, and stop asserting a guess as the cause
build image / build (push) Has been cancelled
Two problems, one of them mine twice over. The motd is gone entirely. It had been wrong in a way that mattered — telling anyone who logged in that onboard WiFi worked when it does not — and a login banner is a bad place to keep claims that need revising every time we learn something. The worse issue is what it and the docs said about why the modem fails. I inferred from the WOA-Project repository being named "Qualcomm-Reference- Drivers" that its modem image was a generic reference build unsuited to this machine's radio, and wrote that up as the explanation. That was a guess resting on a repository name, and the naming is about redistribution rather than the contents. Removed from both README and docs/firmware.md. What is actually established stays: the modem boots, loads mpss, and dies at "RF stuck in QLINK start state", roughly every 42 seconds. The cause is not known. The machine has a SIM slot and its modem worked under Windows, so the radio hardware is present — and the same cabinets supply the GPU, DSP and venus firmware, all of which work, so whatever is wrong is specific to the modem. The docs now say that and stop there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS |
||
|
|
8fa461a41b |
docs: record what firmware fixed, and what it turns out not to fix
build image / build (push) Has been cancelled
The motd was the worst of it — it told anyone logging in that "WiFi and Bluetooth work with the firmware Fedora ships", which is precisely backwards for the onboard adapter, and pointed at a Windows partition this machine no longer has. What the blobs did fix, verified: the GPU initialises, ADSP/CDSP/SLPI all run, sensors appear as IIO devices, venus registers /dev/video0-3. What they did not, and why, so nobody repeats the search: Onboard WiFi is loaded by the modem — wlan*mdsp* is the WLAN Modem DSP — so ath10k_snoc binds and then waits silently on a QMI service that never arrives. The modem boots and dies at "RF stuck in QLINK start state", the RF front-end, which is board-specific wiring in a way the SoC-level blobs are not. These are Qualcomm *reference* drivers; good enough for the GPU and DSPs, evidently not for this machine's radio. Three plausible fixes that do not work are recorded too: rmtfs (correct to enable, does not help), the _nm firmware variants (rejected at PBL), and unplugging the USB dongle (different bus entirely — it was never competing). Audio gets further than expected. The codec answers over SLIMbus with its chip id, so hardware and ADSP are fine; what fails is the codec's SoundWire block declaring two data-in ports where the controller expects six. That is a kernel mismatch and needs a kernel change. Also record the mesa caveat properly: the GPU works, but freedreno's EGL path segfaults gnome-shell into a login loop, and LIBGL_ALWAYS_SOFTWARE does nothing because mutter uses EGL. MESA_LOADER_DRIVER_OVERRIDE=kms_swrast is what works. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS |
||
|
|
18252b37f5 |
docs: the firmware is downloadable — WOA-Project reference drivers
build image / build (push) Successful in 24m42s
The blobs turned out to be a public download rather than something to recover from a lost backup. WOA-Project/Qualcomm-Reference-Drivers collects the Windows-on-ARM driver cabinets by SoC, and three of them cover this machine completely: qcdx850.cab has the zap shader and venus, qcipa850.cab has ipa_fws.elf, and qcsubsys850.cab has the four DSP images plus WLANMDSP.MBN — the WiFi firmware, which is not in the device tree's firmware-name list at all and so was never on our list of things to look for. Correct three claims elsewhere in the file that this disproves. `file` reports these as ELF QUALCOMM DSP6 images, not as `data` — worth stating precisely, since it is the quickest way to tell a real blob from a download that returned an HTML error page. The sizes span 14KB to 60MB rather than "a few hundred KB to a few MB". And the heading claiming they must come off the Windows partition is no longer true of the easiest route. Also narrow what c630-firmware puts in the initramfs. It globbed every .mbn in the firmware directory, which would have pulled in the 60MB qcdsp2850.mbn and inflated the initramfs roughly twentyfold. Only the GPU needs to be there — msm_dpu probes at six seconds, while the initramfs is still root; the ADSP, CDSP, SLPI, venus and modem all probe around forty seconds in and load from the real root perfectly happily. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS |
||
|
|
1423366f30 |
docs: the missing firmware is sourceable, not lost
build image / build (push) Has been cancelled
The Windows backups never turned up, which looked terminal. It is not, because these blobs are signed per model rather than per machine — a copy from any other Yoga C630 works. The evidence is in linux-firmware itself, which already ships the exact analogous set for the ThinkPad X13s under qcom/sc8280xp/LENOVO/21BX: the zap shader, ADSP, CDSP, SLPI and venus firmware, one copy serving every unit of that model. So redistribution is a licensing question rather than a technical one, and qcom/sdm850/LENOVO/81JL/ has simply never been contributed. It will not arrive in a linux-firmware update. Record two routes: Lenovo's Digital Download Recovery Service, which builds a Windows recovery image against a machine's serial for its owner and carries the whole DriverStore; and other C630 owners, where these have circulated before. Reconcile two earlier claims that this contradicts — that nobody can redistribute them, and that a backup or surviving Windows partition were the only routes. Both were wrong in the same way: they described this machine's situation as though it were a property of the files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS |
||
|
|
2457df7a37 |
docs: record what the GPU firmware trace actually proves
build image / build (push) Successful in 53m10s
Tested on the machine. With the generic Adreno firmware reachable from the
initramfs and the model-signed blob absent, the GPU gets two files in and stops:
loaded qcom/a630_sqe.fw from new location
loaded qcom/a630_gmu.bin from new location
*ERROR* Unable to load qcom/sdm850/LENOVO/81JL/qcdxkmsuc850.mbn
*ERROR* gpu hw init failed: -2
That settles a question the docs had been hedging: the zap shader is a hard
requirement, not an optimisation, and the generic pieces are necessary but not
sufficient. Getting past sqe and gmu only to die at zap is about as clean a
demonstration as the hardware will give.
Two things fall out of the same trace and are worth writing down, because
nothing upstream documents them for this machine. The driver never falls back
to the installed sdm845/a630_zap.mbn — the device tree pins the model-specific
path — and a fallback would not help anyway, since the shader is signed against
this device's own secure-boot chain. And the display survives the failure
entirely: msm_dpu still binds the panel, so a dead GPU costs acceleration
rather than a picture, and a desktop runs on llvmpipe.
Also record why these files have to travel in the initramfs at all: msm_dpu
probes around six seconds in, while the initramfs is still root, and never
retries.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
|
||
|
|
3a2fc2a329 |
docs: record the clock trap, and confirm booting from internal storage
build image / build (push) Has been cancelled
It boots from the internal drive with the stick removed — 18s to multi-user,
no failed units.
The first thing tried on it was `dnf install net-tools`, which failed with:
Transaction failed: Rpm transaction failed.
- systemd is needed by net-tools-2.0-0.77.20160912git.fc44.aarch64
systemd is obviously installed; the machine had just booted. The cause is the
clock. This machine's RTC cannot be set at all — ioctl(RTC_SET_TIME) returns
"No such device" — and reads 1970 every boot, so systemd advances it to its own
build date. That is close enough to look plausible and early enough that rpm
treats package signatures created after it as not yet valid, skips those
headers outright, and reports the packages as absent:
Signature created at Sat Jul 25 18:33:30 2026 invalid: signature is not alive
error: rpmdbNextIterator: skipping h# 282
package systemd is not installed
chrony was already added to base.pkgs for the drift; it turns out to be load
bearing rather than a nicety, and it syncs within about five seconds of the
network appearing. Documented, because the symptom points nowhere near the
cause.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
|
||
|
|
f0b2d7846a |
Add install-to-disk.sh, and get the GPU firmware into the initramfs
build image / build (push) Has been cancelled
Validated by running it on the machine, twice, and inspecting the result. Two properties of this laptop rule out the obvious approach, and the script exists mainly to encode them. Its internal UFS reports 4096-byte logical sectors, so the image — built with 512-byte geometry — cannot be dd'd onto it; the GPT header and every partition offset would land in the wrong place. And there are no EFI runtime variables, so efibootmgr cannot register a boot entry and GRUB has to sit at the removable-media path where the firmware looks unprompted. Three things the validation runs caught that review would not have: rsync is not in the image. I had put it in the build container and never in the package list, so the first run died at the copy. It now falls back to tar (--xattrs-include='*', or SELinux labels are silently dropped and the result does not boot), and rsync is in base.pkgs for the progress output. Copying a live root makes tar exit non-zero — files change underneath it, and this machine's clock is wrong besides, so every mtime looks like it is in the future. With pipefail that aborted the install after the root filesystem and before /boot, leaving a half-installed disk that looked plausible. Warning-level exits are now tolerated and only a fatal exit 2 stops the run. systemd-machine-id-setup keeps an existing valid id, and one had just been copied off the stick, so the installed system was a clone. The file is removed first now. Also: msm_dpu probes ~6s in, while the initramfs is still root, and asks for qcom/a630_sqe.fw before the real filesystem carrying it is reachable. It never retries. Adding the Adreno firmware to the initramfs is a few tens of kilobytes. c630-firmware does the same for the DSP blobs once they exist, since dracut rejects install_items globs that match nothing. chrony, because the RTC reads 1970 and nothing was correcting it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS |
||
|
|
482c5d9c9a |
Label the filesystem for SELinux at build time
build image / build (push) Successful in 25m5s
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
|
||
|
|
2243166642 |
Boot fixes: drop the stock BLS entry, define load_video, set kernel cmdline
build image / build (push) Successful in 23m57s
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
|
||
|
|
a5f492d115 |
ci: upload artifacts with v3 — v4 cannot talk to Gitea
build image / build (push) Successful in 24m3s
Build 18745 produced a working image (8.00 GiB compressed to 972 MiB in 44
seconds, checksum recorded) and then failed on the very last step:
GHESNotSupportedError: @actions/artifact v2.0.0+, upload-artifact@v4+ and
download-artifact@v4+ are not currently supported on GHES.
Gitea's artifact backend identifies as GHES, and the v2 artifact client behind
upload-artifact@v4 refuses GHES outright rather than falling back. I chose v4
on the assumption Gitea 1.25 supported it; it does not. v3 works.
Dropping compression-level with it — v3 has no such input, and the image is
already zstd so it would have been a no-op anyway.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
|
||
|
|
5ec2443261 |
Clear stale intermediates from the persistent work dir
build image / build (push) Failing after 25m28s
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
|
||
|
|
a9f18c9951 |
Cut ~60 minutes of emulated work out of the build
build image / build (push) Failing after 44m21s
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 |
||
|
|
3762f13d8b |
docs: correct the firmware split — graphics needs a Windows blob too
I had this wrong. The claim that accelerated graphics works on the firmware Fedora ships does not survive contact with the device tree. Fedora does package an sdm845/a630_zap.mbn, which is what led me astray, but that is the generic Snapdragon 845 zap shader. The C630's node in sdm850-lenovo-yoga-c630.dts names qcom/sdm850/LENOVO/81JL/qcdxkmsuc850.mbn specifically — model-signed, present only in the machine's Windows partition. Until it is supplied the display is unaccelerated. Also add qcslpi850.mbn and qcvss850.mbn, which were missing entirely, and attribute the sensor hub to slpi_pas rather than cdsp_pas. The list is now every firmware-name property in the mainline device tree rather than a recollection of forum posts, so it should be complete: eight files, not five. WiFi and Bluetooth are unaffected — ath10k WCN3990 including wlanmdsp.mbn is genuinely redistributable and genuinely shipped. Add a section on locating the blobs on an old backup, for the case where Windows is long gone from the machine. Only comments changed in config/packages/base.pkgs, so the package set hashes identically and the staged base stays valid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS |
||
|
|
d168df0977 |
ci: build variants in a shell loop, not a dynamic matrix
build image / build (push) Has been cancelled
The first run produced one job leg with an empty variant, and the build rejected it. The prepare job was fine — it set variants=["minimal"] and the output evaluated correctly — but Gitea's runner resolves strategy.matrix while planning the workflow, before `needs` has produced anything, so fromJSON(needs.prepare.outputs.variants) saw an empty string. Dynamic matrices from needs outputs are not supported here. Drop the prepare job and loop in shell instead: one job, no expression-engine dependency, and a non-zero exit only after every requested variant has been attempted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS |
||
|
|
4d1fd98683 |
Cache the expensive half of the build so iteration is cheap
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 |
||
|
|
280874f564 |
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 |