rob thijssen fc4d7d7ceb
All checks were successful
build image / build (push) Successful in 39m19s
Stop kernel upgrades writing entries that cannot boot
Simulating an upgrade — running kernel-core's %posttrans command by hand —
turned up two faults that had nothing to do with the full /boot, and would
have broken every future kernel install on their own.

95-c630-devicetree trusted $KERNEL_INSTALL_BOOT_ROOT. `kernel-install inspect`
reports "Layout: other, Boot Root: /boot/efi" here, because the ESP is all it
recognises, while Fedora's 20-grub.install writes BLS entries to
/boot/loader/entries regardless. So the hook looked for the device tree on the
ESP, did not find it, and skipped the patch — leaving an entry with no
devicetree line, which on this machine cannot boot. Paths in a BLS entry are
relative to the filesystem holding the entry, so the only correct boot root is
the one carrying both loader/entries and dtb-<kver>. Search for that instead.

dracut-config-rescue sets dracut_rescue_image=yes, so each install also built
a second ~210 MiB initramfs, which is what refilled /boot straight after it
was cleared. That image cannot boot this machine either: the rescue entry is
<machine-id>-0-rescue.conf, carrying no kernel version, so the devicetree hook
never matches it. Turned off.

Verified by rerunning the simulation: both kernels now have a devicetree line
and a present initrd, no rescue image is generated, snd-soc-wsa881x is rebuilt
automatically, and /boot holds at 72%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XgGF5wfxLDAybVnNz6eNQ
2026-08-14 16:54:41 +03:00
2026-07-28 16:53:05 +03:00

Fedora for the Lenovo Yoga C630 13Q50

Builds a ready-to-write Fedora aarch64 disk image for the Lenovo Yoga C630 (model 81JL, Qualcomm SDM850), using Gitea Actions.

The approach is borrowed from aarch64-laptops/build, but the heavy lifting that project had to do in 2019 is now unnecessary: mainline Linux has carried sdm850-lenovo-yoga-c630.dts since 5.5, and Fedora ships it in kernel-core. There is no kernel to patch and no GRUB to compile. What is left is assembling a disk image that boots on hardware whose firmware hands Linux no device tree.

What you get

output/fedora-44-<variant>-lenovo-yoga-c630-<date>-<ref>.img.zst — a GPT disk image with an ESP, a /boot partition and an ext4 root. Decompress, write it to a USB stick or microSD card, and boot. The root filesystem grows to fill the medium on first boot.

Two variants:

Variant Size Contents
minimal 8 GiB Console, sshd, and enough tools to debug the machine
workstation 16 GiB GNOME desktop

Default login is grenade / grenade, and the password must be changed at first login. Root is locked. Both are set in config/device.env (DEFAULT_USER, DEFAULT_PASSWORD) if you want a different account.

Hardware status

Works Works once you supply firmware Does not work
UFS storage, USB, keyboard, touchpad, touchscreen Adreno 630 initialises — see mesa note below Onboard WiFi — loaded by the modem, which does not stay up
Display (unaccelerated), battery, charging Sensor hub — accelerometer, auto-rotate LTE modem — crashes at RF init; see docs/firmware.md for what has been ruled out
USB WiFi dongles, USB phone tethering Hardware video decode (venus, /dev/video0-3)
Bluetooth — the controller has no burned-in address; c630-bt-addr.service reads the factory one off the DPP partition at boot Audio — needs qcadsp850.mbn plus one kernel module Fedora does not build; see docs/firmware.md

Verified on hardware. docs/firmware.md has the evidence for each, including the things that look like they should help and do not.

Graphics comes with a caveat. With qcdxkmsuc850.mbn in place the GPU genuinely initialises — gpu-initialized: 1, revision 630, executing submissions. But mesa's freedreno EGL path then segfaults in dri2_drm_swap_buffers, taking gnome-shell down in a login loop. Until that is fixed upstream, the desktop needs software rendering:

echo 'MESA_LOADER_DRIVER_OVERRIDE=kms_swrast' | sudo tee /etc/environment

Note LIBGL_ALWAYS_SOFTWARE=1 does not work here — mutter uses EGL, which ignores it.

Getting the firmware: see docs/firmware.md, which compares the two projects that publish it — WOA-Project and aarch64-laptops — and what each one covers. sudo c630-firmware works if the Windows partition survives.

Building

CI does this on every push to main. To run it yourself:

./build/build-image.sh --variant minimal

You need podman and, on an x86_64 host, aarch64 emulation:

sudo dnf install -y qemu-user-static-aarch64
sudo systemctl restart systemd-binfmt

build/build-image.sh runs on the host and only sets up the container. build/stage2.sh runs inside an aarch64 Fedora container and does everything else: dnf --installroot, the overlay, dracut, and the disk assembly. It builds filesystems from directory trees with mke2fs -d and mcopy rather than mounting loop devices, so it does not need /dev/loop-control — which CI runners generally will not hand out.

Iterating

Every aarch64 binary runs under emulation, and the dnf transaction is essentially all of the cost — 500-odd packages' worth of rpm scriptlets for minimal, four times that for workstation. Everything after it takes minutes. Since getting this machine to boot will take a few attempts, the build is arranged so you only pay that once:

  • The post-dnf root filesystem is staged under <work>/base, keyed on a hash of the package lists, release and variant. Editing config/device.env, overlay/, or the bootloader config reuses it. Editing config/packages/ invalidates it automatically.
  • The working copy is made with cp --reflink=auto, so on btrfs or xfs it is a copy-on-write clone rather than a real copy.
  • Downloaded rpms live in .cache/dnf, outside the staged tree, so even --fresh re-runs the scriptlets without re-downloading.

In practice a kernel-command-line change rebuilds in a few minutes.

./build/build-image.sh --variant minimal            # reuses the staged base
./build/build-image.sh --variant minimal --fresh    # forces a reinstall
./build/build-image.sh --variant minimal --keep-rootfs   # keep the tree to poke at

CI points --work and --cache at /var/tmp/c630-build so both survive between jobs. That is per-runner, so the first build on a given runner is cold.

The build container defaults to git.lair.cafe/gongfoo/build-fedora-44-aarch64, which ships the assembly tooling so it does not have to be installed under emulation on every run. If that image is not reachable the build falls back to stock Fedora and installs the tooling itself — slower, but it works.

Repository layout

config/device.env            C630 parameters: DTB path, kernel command line, geometry
config/packages/*.pkgs       Package lists — base plus one file per variant
overlay/                     Files copied into the root filesystem (*.in are templated)
build/build-image.sh         Host driver: emulation checks, podman invocation
build/stage2.sh              The actual build, inside an aarch64 container
firmware/local/              Optional drop-in for firmware you extracted yourself (gitignored)
.gitea/workflows/            CI
docs/                        Installation, firmware, runner setup

Documentation

Boot status

Confirmed on hardware, from a USB stick:

  • GRUB loads the kernel and the device tree
  • The kernel comes up and probes the SDM850 — UFS controller, display subsystem, WiFi and IPA all appear as platform devices, so DEVICE_CMDLINE and the DTB are right
  • The framebuffer console works (simple-framebuffer, 240x67)
  • The root filesystem mounts, systemd starts, and it reaches a login prompt
  • Networking works over a USB WiFi dongle, and sshd is reachable
  • build/install-to-disk.sh copies it onto the internal UFS, and it boots from there with the stick removed — 18s to multi-user, no failed units

The RTC cannot be set on this machine and reads 1970 at every boot, so chrony is installed and corrects the clock once the network is up. Until it does, rpm rejects package signatures dated after the (wrong) current time and reports installed packages as missing — see docs/install.md.

With the per-model firmware supplied, the GPU initialises, the ADSP, CDSP and SLPI all run, sensors appear as IIO devices and venus registers /dev/video0-3. Audio additionally needs one kernel module Fedora does not build (CONFIG_SND_SOC_WSA881X); with it compiled out of tree, the sound card registers and the speakers play. Onboard WiFi remains broken: the modem that hosts the WLAN firmware dies at RF init, and firmware versions, userspace services and kernel 6.12-vs-7.1 have all been eliminated as the cause — see docs/firmware.md, which records the evidence and the many plausible fixes that turn out not to work.

The internal drive needs build/install-to-disk.sh rather than dd: its UFS uses 4096-byte logical sectors, which the 512-byte image geometry cannot be written onto directly. See docs/install.md.

SELinux is shipped permissive — see below.

SELinux

The build labels the filesystem offline with setfiles, so enforcing should work. It ships permissive anyway, because the failure mode is unusually punishing: an unlabelled root takes PID 1 down with Failed to allocate manager object before anything can be logged into and repaired, and /.autorelabel cannot save it because nothing survives long enough to act on the flag.

Once the machine is up and you are happy with it:

sudo setenforce 1                 # try it for this boot
sudo sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config

Or set SELINUX_MODE=enforcing in config/device.env and rebuild.

Caveats

The kernel command line and firmware layout are taken from Fedora's Snapdragon WoA documentation and the aarch64-laptops project; the parts specific to the C630's older SDM850 are reasoned from those rather than derived from documentation for this machine. Findings belong in this README.

Description
No description provided
Readme 399 KiB
Languages
Shell 100%