All checks were successful
build image / build (push) Successful in 39m19s
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
215 lines
9.0 KiB
Markdown
215 lines
9.0 KiB
Markdown
# 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 accelerated graphics, 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
|
|
`grenade` / `grenade` — or whatever `DEFAULT_USER` and `DEFAULT_PASSWORD` in
|
|
`config/device.env` were set to — and set a new password when prompted.
|
|
|
|
## Extract the Qualcomm firmware
|
|
|
|
Accelerated graphics, 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. Boot from the
|
|
USB stick and run, on the laptop:
|
|
|
|
```sh
|
|
sudo ./build/install-to-disk.sh --dry-run # show the plan, touch nothing
|
|
sudo ./build/install-to-disk.sh # do it
|
|
```
|
|
|
|
It partitions the disk, copies the running system across, and rewrites every
|
|
UUID that has to change. Reboot afterwards and pick the internal drive from the
|
|
boot menu (**Fn+F12**), or just pull the stick out.
|
|
|
|
**This wipes the target disk.** If you still have Windows and want to keep it,
|
|
do not run this — shrink the Windows partition from within Windows first and
|
|
adapt the steps by hand.
|
|
|
|
Two things about this machine make the obvious approaches fail, both of which
|
|
the script handles:
|
|
|
|
**You cannot `dd` the image onto the internal drive.** The UFS reports
|
|
**4096-byte logical sectors**; the disk image is built with 512-byte sector
|
|
geometry. Its GPT header and every partition offset would land in the wrong
|
|
place. The disk has to be partitioned on the machine, at its own sector size.
|
|
|
|
**`efibootmgr` does not work here.** There are no EFI runtime variables —
|
|
`efibootmgr` reports *"EFI variables are not supported on this system"* — so
|
|
nothing can register a boot entry. GRUB therefore goes at the removable-media
|
|
path, `EFI/BOOT/BOOTAA64.EFI`, where the firmware finds it unprompted.
|
|
|
|
If you would rather do it by hand, the sequence is: `sgdisk` an ESP, a `/boot`
|
|
and a root partition; `mkfs.vfat` and `mkfs.ext4 -O
|
|
^orphan_file,^metadata_csum_seed`; copy `/`, `/boot` and the ESP separately
|
|
(preserving xattrs, or SELinux labels are lost); then update `/etc/fstab`,
|
|
`/etc/kernel/cmdline`, `root=UUID=` in `/boot/loader/entries/*.conf`, and the
|
|
`--fs-uuid` in both `/boot/grub2/grub.cfg` and `EFI/fedora/grub.cfg`.
|
|
|
|
## The clock, and why dnf lies about missing packages
|
|
|
|
This machine's RTC cannot be set — `hwclock --systohc` fails with
|
|
`ioctl(RTC_SET_TIME) to /dev/rtc0 to set the time failed: No such device` — and
|
|
it reads 1970 at every boot. systemd compensates by advancing the clock to its
|
|
own build date, which is close enough to look right and wrong enough to break
|
|
rpm.
|
|
|
|
The symptom is baffling if you have not seen it before:
|
|
|
|
```
|
|
$ sudo dnf install net-tools
|
|
Transaction failed: Rpm transaction failed.
|
|
- systemd is needed by net-tools-2.0-0.77.20160912git.fc44.aarch64
|
|
```
|
|
|
|
systemd is plainly installed — the machine booted. But with the clock behind
|
|
the package's signing date, rpm rejects the signature as *not yet valid*, skips
|
|
that header entirely, and concludes the package is absent:
|
|
|
|
```
|
|
Signature 85b4 created at Sat Jul 25 18:33:30 2026 invalid: signature is not alive
|
|
because: Not live until 2026-07-25T18:28:30Z
|
|
error: rpmdbNextIterator: skipping h# 282
|
|
package systemd is not installed
|
|
```
|
|
|
|
`chrony` is installed and enabled, and fixes this within a few seconds of the
|
|
network coming up — check with `timedatectl`. The gap only matters if you run
|
|
`dnf` before that, or on a machine with no network yet. Set it by hand in that
|
|
case:
|
|
|
|
```sh
|
|
sudo date -u -s "2026-07-27 16:33:00"
|
|
```
|
|
|
|
Any roughly-correct time will do; it only has to be later than the packages
|
|
were signed.
|
|
|
|
## Kernel updates and a full /boot
|
|
|
|
A kernel costs about 336 MiB here — a ~210 MiB initramfs (built `hostonly=no`,
|
|
so it carries the whole driver and firmware set), a ~98 MiB `dtb-<kver>`
|
|
directory because `kernel-core` ships every board's device tree, plus vmlinuz
|
|
and System.map. Three of those do not fit a 1 GiB `/boot`, which is why images
|
|
now use 2 GiB and `installonly_limit=2`.
|
|
|
|
If `/boot` does fill up, the failure is quiet and genuinely dangerous:
|
|
|
|
1. The kernel rpm installs fine — `rpm -V` is clean and the module tree is
|
|
complete.
|
|
2. `50-dracut.install` then dies on ENOSPC, so **no initramfs is written**.
|
|
3. `kernel-install` stops at the first failing plugin, so
|
|
`95-c630-devicetree.install` never runs and the boot entry gets **no
|
|
`devicetree` line** — which on this machine means it can never boot,
|
|
because the UEFI supplies no device tree.
|
|
4. `dnf` reports success — `kernel-install` is invoked from kernel-core's
|
|
`%posttrans` as `... || exit 0`, which swallows the failure. Nothing
|
|
retries, and later `dnf upgrade` runs look clean because they no longer
|
|
touch the kernel.
|
|
|
|
Two things made that worse, both fixed, both worth knowing if you are reading
|
|
an older image:
|
|
|
|
**The devicetree hook used to skip silently even with space to spare.** It
|
|
trusted `$KERNEL_INSTALL_BOOT_ROOT`, and on this machine `kernel-install
|
|
inspect` reports `Layout: other, Boot Root: /boot/efi` — it recognises only
|
|
the ESP. Fedora's `20-grub.install` ignores that and writes entries to
|
|
`/boot/loader/entries` anyway, so the hook went looking for the device tree on
|
|
the ESP, did not find it, and left the entry alone. The paths inside a BLS
|
|
entry are relative to the filesystem holding the entry, so the hook now
|
|
searches for the root that has both `loader/entries` and `dtb-<kver>` instead
|
|
of being told.
|
|
|
|
**The rescue image refilled `/boot` after every cleanup.** `dracut-config-rescue`
|
|
sets `dracut_rescue_image=yes`, so each kernel install built a *second*
|
|
~210 MiB initramfs. It could never boot this machine either: the rescue entry
|
|
is named `<machine-id>-0-rescue.conf` and carries no kernel version, so the
|
|
devicetree hook does not match it. `dracut_rescue_image="no"` in
|
|
`/etc/dracut.conf.d/10-c630.conf` turns it off.
|
|
|
|
So an update can leave a kernel that is installed, listed in the boot menu, and
|
|
unbootable. To check whether it actually completed:
|
|
|
|
```sh
|
|
rpm -q kernel-core # what rpm thinks is installed
|
|
ls /boot/initramfs-* # one per kernel, or the update failed
|
|
sudo grep -L devicetree /boot/loader/entries/*.conf # any output is unbootable
|
|
```
|
|
|
|
To repair it, free space first, then redo the two steps that were skipped:
|
|
|
|
```sh
|
|
sudo dnf remove kernel-core-<old-version>
|
|
sudo dracut --force --kver <kver> /boot/initramfs-<kver>.img
|
|
sudo /etc/kernel/install.d/95-c630-devicetree.install add <kver>
|
|
```
|
|
|
|
Both are idempotent. Test the result with a one-shot boot rather than by
|
|
changing the default, so a kernel that hangs costs only a power cycle:
|
|
|
|
```sh
|
|
sudo grub2-editenv /boot/grub2/grubenv set next_entry=c630-<kver>
|
|
sudo reboot
|
|
```
|
|
|
|
Note that `speakers go silent after a kernel update` is the same event wearing a
|
|
different hat — see `c630-wsa881x` in [firmware.md](firmware.md).
|
|
|
|
## 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` |
|
|
| `dnf` says a package that is obviously installed is missing | The clock is behind — see above |
|
|
|
|
Adding `earlycon` and `ignore_loglevel` will tell you a great deal more about
|
|
which of these it is.
|