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.
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.
| 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.
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.
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:
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.cp --reflink=auto, so on btrfs or xfs it is
a copy-on-write clone rather than a real copy..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.
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
Confirmed on hardware, from a USB stick:
DEVICE_CMDLINE and the DTB are rightsimple-framebuffer, 240x67)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 unitsThe 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.
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.
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.
26 activities
fc4d7d7 Stop kernel upgrades writing entries that cannot boot36dc54e Keep the DSPs out of the initramfs, and let the headphone port be chosen477d43a Survive kernel updates on a /boot this smallb5c5218 Default the image account to grenade61b2f59 docs: 6.9 fails identically — not a recent regression9503577 docs: rule out the RF clocks and unclaimed rails, and question the premise05e54b0 Stop the modem crash loop, and record what it is not0ad478e docs: draft the upstream escalations7585c5f docs: audio root causes found; WiFi narrowed to the modem's own RF path9b28150 Pin the built-in card to S16_LE for PipeWire0a82d2c Give Bluetooth its factory address from the DPP partition8041c8b docs: document both firmware sources on equal termsb05c982 Write up the onboard WiFi investigation, and ship the services it needs6ef4e25 docs: name WOA-Project rather than describing its contents755031a Remove the custom motd, and stop asserting a guess as the cause8fa461a docs: record what firmware fixed, and what it turns out not to fix18252b3 docs: the firmware is downloadable — WOA-Project reference drivers1423366 docs: the missing firmware is sourceable, not lost2457df7 docs: record what the GPU firmware trace actually proves3a2fc2a docs: record the clock trap, and confirm booting from internal storagef0b2d78 Add install-to-disk.sh, and get the GPU firmware into the initramfs482c5d9 Label the filesystem for SELinux at build time2243166 Boot fixes: drop the stock BLS entry, define load_video, set kernel cmdlinea5f492d ci: upload artifacts with v3 — v4 cannot talk to Gitea5ec2443 Clear stale intermediates from the persistent work dira9f18c9 Cut ~60 minutes of emulated work out of the build3762f13 docs: correct the firmware split — graphics needs a Windows blob tood168df0 ci: build variants in a shell loop, not a dynamic matrix