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
This commit is contained in:
91
docs/firmware.md
Normal file
91
docs/firmware.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Firmware
|
||||
|
||||
The C630's firmware splits into two groups, and the split is about licensing,
|
||||
not difficulty.
|
||||
|
||||
## What ships in the image
|
||||
|
||||
Qualcomm permits redistribution of these, so Fedora packages them and the build
|
||||
installs them:
|
||||
|
||||
| Package | Files | Enables |
|
||||
|---|---|---|
|
||||
| `qcom-firmware` | `qcom/a630_gmu.bin`, `qcom/a630_sqe.fw`, `qcom/sdm845/a630_zap.mbn` | Adreno 630 — accelerated graphics via freedreno |
|
||||
| `atheros-firmware` | `ath10k/WCN3990/hw1.0/{firmware-5.bin,board-2.bin,wlanmdsp.mbn}` | WiFi and Bluetooth |
|
||||
|
||||
So graphics and networking work on a freshly written image with no extra steps.
|
||||
|
||||
## What has to come off the Windows partition
|
||||
|
||||
The SDM850's DSP and display firmware is signed against per-model keys. Nobody
|
||||
can redistribute it, and it exists on your machine only inside its Windows
|
||||
install:
|
||||
|
||||
| File | Enables |
|
||||
|---|---|
|
||||
| `qcadsp850.mbn` | Audio DSP — speakers, microphone |
|
||||
| `qccdsp850.mbn` | Compute DSP — sensor hub, so lid switch, accelerometer, auto-rotate |
|
||||
| `qcdxkmsuc850.mbn` | Display/UEFI secure code |
|
||||
| `qcdsp1v2850.mbn`, `qcdsp2850.mbn` | Modem DSP stages |
|
||||
| `ipa_fws.elf` | IPA — needed by the modem path |
|
||||
|
||||
They belong under `/lib/firmware/updates/qcom/sdm850/LENOVO/81JL/`.
|
||||
|
||||
## Extracting them
|
||||
|
||||
The image ships Fedora's `qcom-firmware-extract`. Run it once:
|
||||
|
||||
```sh
|
||||
sudo c630-firmware
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
That wraps `qcom-firmware-extract` and regenerates the initramfs afterwards,
|
||||
which matters — the ADSP firmware has to be available before the root
|
||||
filesystem is mounted, and `overlay/etc/dracut.conf.d/10-c630.conf` only takes
|
||||
effect on a rebuild.
|
||||
|
||||
If it cannot find Windows, mount it yourself and point the tool at it:
|
||||
|
||||
```sh
|
||||
lsblk -o NAME,SIZE,FSTYPE,LABEL # find the NTFS partition
|
||||
sudo mkdir -p /mnt/windows
|
||||
sudo mount /dev/sda4 /mnt/windows
|
||||
sudo qcom-firmware-extract --windows-dir /mnt/windows
|
||||
```
|
||||
|
||||
`qcom-firmware-extract` was written for the Snapdragon 8cx and X Elite laptops.
|
||||
If it does not recognise the SDM850, copy the files by hand — they are in
|
||||
`Windows/System32/DriverStore/FileRepository/` under a `qcdx*.inf_arm64_*`
|
||||
directory:
|
||||
|
||||
```sh
|
||||
sudo find /mnt/windows/Windows/System32/DriverStore/FileRepository \
|
||||
-iname 'qc*850.mbn' -o -iname 'ipa_fws.elf' -o -iname 'qcdxkmsuc850.mbn'
|
||||
|
||||
sudo mkdir -p /lib/firmware/updates/qcom/sdm850/LENOVO/81JL
|
||||
sudo cp <each file> /lib/firmware/updates/qcom/sdm850/LENOVO/81JL/
|
||||
sudo dracut --force --regenerate-all
|
||||
```
|
||||
|
||||
Check it took:
|
||||
|
||||
```sh
|
||||
dmesg | grep -iE 'remoteproc|adsp|cdsp'
|
||||
```
|
||||
|
||||
## Baking firmware into the image
|
||||
|
||||
If you would rather not repeat the extraction on every reinstall, drop the files
|
||||
into `firmware/local/` in this repo, mirroring the `/usr/lib/firmware/updates/`
|
||||
layout:
|
||||
|
||||
```
|
||||
firmware/local/qcom/sdm850/LENOVO/81JL/qcadsp850.mbn
|
||||
firmware/local/qcom/sdm850/LENOVO/81JL/qccdsp850.mbn
|
||||
...
|
||||
```
|
||||
|
||||
`build/stage2.sh` picks them up automatically. They are gitignored, and should
|
||||
stay that way — this repository is public and the blobs are not yours to
|
||||
publish.
|
||||
97
docs/install.md
Normal file
97
docs/install.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Installing on the Yoga C630
|
||||
|
||||
## Before you start
|
||||
|
||||
Leave Windows in place. Lenovo ships firmware updates only through Windows, and
|
||||
the per-model Qualcomm blobs Linux needs live in that partition — wiping it
|
||||
means losing audio and the sensor hub permanently. Update everything through
|
||||
Lenovo Vantage first.
|
||||
|
||||
Disable Secure Boot. The image is not signed for it:
|
||||
|
||||
- Press **Fn+F2** during startup, go to **Security → Secure Boot → Disabled**, or
|
||||
- **Settings → Update & Security → Recovery → Advanced startup → UEFI Firmware Settings**
|
||||
|
||||
## Write the image
|
||||
|
||||
```sh
|
||||
zstd -d fedora-44-minimal-lenovo-yoga-c630-*.img.zst
|
||||
sudo dd if=fedora-44-minimal-lenovo-yoga-c630-*.img of=/dev/sdX bs=4M status=progress oflag=direct conv=fsync
|
||||
```
|
||||
|
||||
Check `/dev/sdX` twice. An 8 GiB stick is enough for `minimal`, 16 GiB for
|
||||
`workstation`; the root filesystem expands to fill whatever you use.
|
||||
|
||||
## First boot
|
||||
|
||||
Insert the stick and power on holding **Fn+F12** for the boot menu, or enter
|
||||
setup with **Fn+F2** and put USB ahead of the internal drive.
|
||||
|
||||
GRUB should appear with one entry. It has already been told which device tree to
|
||||
load — the C630's UEFI does not supply one, which is the whole reason the
|
||||
`devicetree` line exists in the boot loader entry.
|
||||
|
||||
First boot does two slow things: it relabels the filesystem for SELinux and
|
||||
grows the root partition. Several minutes on a USB stick is normal. Log in as
|
||||
`fedora` / `fedora` and set a new password when prompted.
|
||||
|
||||
## Extract the Qualcomm firmware
|
||||
|
||||
Audio, the sensor hub and video decode do not work until you do this:
|
||||
|
||||
```sh
|
||||
sudo c630-firmware
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
See [firmware.md](firmware.md) if it cannot find the Windows partition.
|
||||
|
||||
## Installing to internal storage
|
||||
|
||||
Once the USB image is behaving, copy it onto the internal UFS.
|
||||
|
||||
Shrink the Windows partition from within Windows (**Disk Management → Shrink
|
||||
Volume**) rather than from Linux — Windows is fussy about its own filesystem
|
||||
being moved underneath it.
|
||||
|
||||
Then, booted from USB, write the image to the free space. The simplest approach
|
||||
that keeps Windows intact is to create the partitions by hand and copy the
|
||||
filesystems across rather than `dd`-ing the whole image, since `dd` would
|
||||
overwrite the existing GPT and the Windows ESP:
|
||||
|
||||
1. `sudo gdisk /dev/sda` — add a `/boot` partition (1 GiB, type 8300) and a root
|
||||
partition (type 8300) in the free space. Keep the existing Windows ESP.
|
||||
2. `mkfs.ext4 -O ^orphan_file,^metadata_csum_seed /dev/sdaN` for both.
|
||||
3. Copy the running system across with `rsync -aHAX --exclude=/dev --exclude=/proc
|
||||
--exclude=/sys --exclude=/run --exclude=/boot`, then `/boot` separately.
|
||||
4. Copy `EFI/fedora` from the USB stick's ESP onto the Windows ESP, and edit
|
||||
`EFI/fedora/grub.cfg` so the `--fs-uuid` matches the new `/boot`.
|
||||
5. Update `/etc/fstab` and the `root=UUID=` in
|
||||
`/boot/loader/entries/c630-*.conf` to the new UUIDs.
|
||||
|
||||
Finally, point the firmware at GRUB from an Administrator command prompt in
|
||||
Windows:
|
||||
|
||||
```
|
||||
bcdedit /set {bootmgr} path \EFI\fedora\grubaa64.efi
|
||||
```
|
||||
|
||||
Windows resets this on some updates. To get back, boot the USB stick and run it
|
||||
again, or use `efibootmgr` from Linux.
|
||||
|
||||
## If it does not boot
|
||||
|
||||
The kernel command line is the first thing to change. It lives in
|
||||
`options` in `/boot/loader/entries/c630-*.conf`, and GRUB lets you edit it for
|
||||
one boot by pressing **e**.
|
||||
|
||||
| Symptom | Try |
|
||||
|---|---|
|
||||
| Hangs immediately after GRUB, no kernel output | Wrong or missing device tree — check the `devicetree` line points at a file that exists in `/boot/dtb-*/qcom/` |
|
||||
| Boots then freezes partway | Drop `efi=noruntime`, or add `pd_ignore_unused` if it is missing |
|
||||
| USB dies during boot | Add `modprobe.blacklist=qcom_q6v5_pas` |
|
||||
| Cannot find root filesystem | The initramfs is missing the UFS drivers — see `overlay/etc/dracut.conf.d/10-c630.conf` |
|
||||
| 90-second stall early in boot | Add `systemd.tpm2_wait=0` |
|
||||
|
||||
Adding `earlycon` and `ignore_loglevel` will tell you a great deal more about
|
||||
which of these it is.
|
||||
61
docs/runner-setup.md
Normal file
61
docs/runner-setup.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Gitea runner setup
|
||||
|
||||
The build runs on runners labelled `metal`, executing directly on the host
|
||||
rather than in a runner-provided container, because it needs to drive podman
|
||||
itself.
|
||||
|
||||
## One-time host preparation
|
||||
|
||||
On each runner host:
|
||||
|
||||
```sh
|
||||
sudo dnf install -y podman qemu-user-static-aarch64
|
||||
sudo systemctl restart systemd-binfmt
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```sh
|
||||
cat /proc/sys/fs/binfmt_misc/qemu-aarch64
|
||||
```
|
||||
|
||||
The `flags:` line must contain `F`. That flag makes the kernel open the
|
||||
interpreter at registration time and keep the reference, so `qemu-aarch64-static`
|
||||
resolves even inside a container that does not have it. Without it, every
|
||||
aarch64 binary inside the build container fails with `exec format error`.
|
||||
`qemu-user-static-aarch64` registers it correctly; plain `qemu-user` does not.
|
||||
|
||||
The workflow attempts this install itself, but that only works if the runner
|
||||
account has passwordless sudo. Run `probe-runner.yaml` to find out.
|
||||
|
||||
## Checking what a runner can do
|
||||
|
||||
The `probe runner` workflow is manual-dispatch only and reports architecture,
|
||||
user, sudo, podman, emulation status and free disk. Run it once per runner
|
||||
before debugging a build failure.
|
||||
|
||||
## Disk space
|
||||
|
||||
The build stages a full root filesystem and three filesystem images alongside
|
||||
the final disk image, under the job workspace. Budget roughly:
|
||||
|
||||
- `minimal` — about 20 GiB
|
||||
- `workstation` — about 45 GiB
|
||||
|
||||
## Runtime
|
||||
|
||||
Every aarch64 binary runs under qemu-user emulation, and rpm scriptlets are the
|
||||
worst case. Expect roughly 45–90 minutes for `minimal` and several hours for
|
||||
`workstation`. The workflow's `timeout-minutes` is set to 600 accordingly.
|
||||
|
||||
If this becomes tiresome, the fix is a native aarch64 runner. Register one with
|
||||
an `aarch64` label and change `runs-on: metal` to `runs-on: aarch64` in
|
||||
`.gitea/workflows/build-image.yaml`; `build/build-image.sh` already skips the
|
||||
emulation check when the host is already the target architecture.
|
||||
|
||||
## Changing which runners are used
|
||||
|
||||
All nine current runners carry `metal` and `podman`. `runs-on: metal` therefore
|
||||
matches any of them. To pin the build to a subset, give those runners a distinct
|
||||
label and use it — Gitea requires a runner to carry every label listed in
|
||||
`runs-on`.
|
||||
Reference in New Issue
Block a user