All checks were successful
build image / build (push) Successful in 25m48s
postmarketOS ships a kernel for this laptop, which makes testing an old kernel cheap: their apk carries vmlinuz, the C630 dtb and modules, no image writing needed. 6.9.0-sdm845 crashes exactly like 6.12 and 7.1 — 23 QLINK crashes in 70 seconds, wlfw never appears. Three kernels across two years, one of them from a distro that lists this machine as supported, all identical. That, plus aarch64-laptops publishing a device tree with wifi disabled and no modem node at all, leaves the regression premise with nothing supporting it. Record the method, the modem-test harness that captures verdicts without networking, and the /lib symlink trap that makes installing foreign modules dangerous. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XgGF5wfxLDAybVnNz6eNQ
678 lines
31 KiB
Markdown
678 lines
31 KiB
Markdown
# 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 |
|
|
|---|---|---|
|
|
| `atheros-firmware` | `ath10k/WCN3990/hw1.0/{firmware-5.bin,board-2.bin,wlanmdsp.mbn}` | WiFi and Bluetooth |
|
|
| `qcom-firmware` | `qcom/a630_gmu.bin`, `qcom/a630_sqe.fw` | The generic half of the Adreno 630 — necessary but not sufficient |
|
|
|
|
Networking therefore works on a freshly written image. Graphics does not: see
|
|
below.
|
|
|
|
## What you have to supply
|
|
|
|
These are signed against per-model keys and Fedora does not ship them, so a
|
|
stock install has none of them. Nothing about them is unredistributable in
|
|
principle — the equivalent files for the
|
|
ThinkPad X13s *are* in linux-firmware — it is that nobody has obtained
|
|
permission for this model. See [If you have no
|
|
backup](#if-you-have-no-backup).
|
|
|
|
The list is not a guess: it is every `firmware-name` property in the mainline
|
|
device tree, `arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts`:
|
|
|
|
| File | Device tree node | Lost without it |
|
|
|---|---|---|
|
|
| `qcdxkmsuc850.mbn` | `gpu_zap_shader` | **Accelerated graphics** |
|
|
| `qcadsp850.mbn` | `adsp_pas` | Audio — speakers, microphone |
|
|
| `qcslpi850.mbn` | `slpi_pas` | Sensor hub — lid switch, accelerometer, auto-rotate |
|
|
| `qccdsp850.mbn` | `cdsp_pas` | Compute DSP offload |
|
|
| `qcvss850.mbn` | `venus` | Hardware video decode |
|
|
| `qcdsp1v2850.mbn` | `mss_pil` | Modem, stage 1 |
|
|
| `qcdsp2850.mbn` | `mss_pil` | Modem, stage 2 |
|
|
| `ipa_fws.elf` | `ipa` | IPA datapath, needed by the modem |
|
|
|
|
They belong under `/lib/firmware/updates/qcom/sdm850/LENOVO/81JL/`.
|
|
|
|
The GPU one is worth calling out, because it is easy to assume otherwise:
|
|
Fedora does ship an `sdm845/a630_zap.mbn`, but that is the generic
|
|
Snapdragon 845 zap shader and **not** what this device asks for. The C630's
|
|
device tree names `qcom/sdm850/LENOVO/81JL/qcdxkmsuc850.mbn` specifically, so
|
|
until you supply it you get an unaccelerated display.
|
|
|
|
Each file is independent, so a partial recovery is still worth having —
|
|
`qcdxkmsuc850.mbn` on its own buys you working graphics.
|
|
|
|
### Confirmed on the hardware
|
|
|
|
This is not inferred from the device tree. On a machine with the generic
|
|
Adreno firmware present and the model-signed blob absent, the GPU gets two
|
|
files in and then stops:
|
|
|
|
```
|
|
[drm:adreno_request_fw] loaded qcom/a630_sqe.fw from new location
|
|
[drm:adreno_request_fw] loaded qcom/a630_gmu.bin from new location
|
|
[drm:zap_shader_load_mdt] *ERROR* Unable to load qcom/sdm850/LENOVO/81JL/qcdxkmsuc850.mbn
|
|
[drm:adreno_load_gpu] *ERROR* gpu hw init failed: -2
|
|
```
|
|
|
|
So the generic pieces are necessary and not sufficient, and the zap shader is a
|
|
hard requirement rather than an optimisation. Two details follow from that
|
|
trace:
|
|
|
|
The driver never falls back to `sdm845/a630_zap.mbn`, even though the file is
|
|
installed — the device tree's `firmware-name` pins the model-specific path. Nor
|
|
would falling back help: the zap shader is signed against the device's own
|
|
secure-boot chain, and the Snapdragon 845 development board's copy will not
|
|
validate here.
|
|
|
|
The display keeps working throughout. `msm_dpu` binds the panel and the console
|
|
lands on `msmdrmfb` regardless, so a failed GPU costs you acceleration, not a
|
|
picture. A desktop still runs on llvmpipe — though `/dev/dri/renderD128` is
|
|
present even when `hw init` has failed, so mesa may try freedreno, fail, and
|
|
take the display manager down with it. `LIBGL_ALWAYS_SOFTWARE=1` in
|
|
`/etc/environment` avoids that.
|
|
|
|
### These have to be in the initramfs
|
|
|
|
`msm_dpu` probes about six seconds in, while the initramfs is still the root
|
|
filesystem, and does not retry. Firmware that only exists on the real root is
|
|
therefore invisible to it:
|
|
|
|
```
|
|
msm_dpu ae01000.display-controller: Direct firmware load for qcom/a630_sqe.fw failed with error -2
|
|
```
|
|
|
|
`overlay/etc/dracut.conf.d/10-c630.conf` puts the Adreno files in the initramfs
|
|
for this reason, and `c630-firmware` adds `qcdxkmsuc850.mbn` once it exists.
|
|
|
|
Only the GPU needs this. The ADSP, CDSP, SLPI, venus and modem all probe around
|
|
forty seconds in, well after the real root is mounted, and load from it
|
|
perfectly happily. That distinction is worth keeping: `qcdsp2850.mbn` alone is
|
|
60 MB, so sweeping the whole firmware directory into the initramfs would inflate
|
|
it roughly twentyfold for no benefit.
|
|
|
|
## Finding them on a backup
|
|
|
|
If Windows is long gone from the machine but you kept a copy of the files, one
|
|
search catches all eight, since seven share a suffix:
|
|
|
|
```sh
|
|
find /mnt/backup -type f \
|
|
\( -iname 'qc*850.mbn' -o -iname 'ipa_fws.elf' -o -iname 'bdwlan*.bin' \) \
|
|
-printf '%10s %p\n'
|
|
```
|
|
|
|
They originally lived in `C:\Windows\System32\DriverStore\FileRepository\`, in
|
|
directories named `qcdx850.inf_arm64_*` and `qcadsp*.inf_arm64_*`. If the loose
|
|
files turn up nothing, look for those directories wholesale:
|
|
|
|
```sh
|
|
find /mnt/backup -type d -iname 'qc*.inf_arm64_*'
|
|
```
|
|
|
|
Sizes range from 14 KB (`qcdxkmsuc850.mbn`) to 60 MB (`qcdsp2850.mbn`).
|
|
Despite the `.mbn` extension they are ELF images, and `file` should say so:
|
|
|
|
```
|
|
qcdxkmsuc850.mbn: ELF 32-bit LSB executable, QUALCOMM DSP6, version 1 (SYSV)
|
|
```
|
|
|
|
Anything reporting as text or HTML is a download that went wrong.
|
|
|
|
Lenovo does not appear to publish a driver package for the 81JL containing
|
|
these. If the backups turn up nothing, see [If you have no
|
|
backup](#if-you-have-no-backup) — the files are not tied to your individual
|
|
machine, so yours is not the only possible source.
|
|
|
|
## If you have no backup
|
|
|
|
Losing the Windows partition is not the end of it, because **these blobs are
|
|
signed per model, not per machine**. A copy from any other Yoga C630 works on
|
|
yours.
|
|
|
|
The evidence is in linux-firmware itself, which already ships the exact
|
|
analogous set for the ThinkPad X13s — one copy serving every unit of that
|
|
model:
|
|
|
|
```
|
|
qcom/sc8280xp/LENOVO/21BX/qcdxkmsuc8280.mbn zap shader
|
|
qcom/sc8280xp/LENOVO/21BX/qcadsp8280.mbn audio
|
|
qcom/sc8280xp/LENOVO/21BX/qccdsp8280.mbn compute DSP
|
|
qcom/sc8280xp/LENOVO/21BX/qcslpi8280.mbn sensors
|
|
qcom/sc8280xp/LENOVO/21BX/qcvss8280.mbn video
|
|
```
|
|
|
|
A one-to-one match with the C630's list, differing only in the model. So these
|
|
files are redistributable once Lenovo and Qualcomm permit it — it is a
|
|
licensing question, not a technical one.
|
|
|
|
`qcom/sdm850/LENOVO/81JL/` has simply never been contributed. Fedora ships no
|
|
sdm850 model firmware at all, so it will not turn up in a `linux-firmware`
|
|
update. Four sources are known:
|
|
|
|
Two projects publish C630 firmware. They overlap but neither is a superset of
|
|
the other, and both are worth knowing about.
|
|
|
|
**WOA-Project** — <https://github.com/WOA-Project/Qualcomm-Reference-Drivers>
|
|
|
|
Windows-on-ARM driver packages by vendor and model, under `Lenovo/YogaC630/`.
|
|
Nine versions are published, `200.0.1.0` through `200.0.19.0`; the cabinets
|
|
differ between them. This is the only source here for the GPU, DSP, sensor and
|
|
venus blobs:
|
|
|
|
| Cabinet | Contains |
|
|
|---|---|
|
|
| `qcdx850.cab` | `qcdxkmsuc850.mbn` (zap shader), `qcvss850.mbn` (venus) |
|
|
| `qcipa850.cab` | `ipa_fws.elf` |
|
|
| `qcsubsys850.cab` | `qcadsp850.mbn`, `qccdsp850.mbn`, `qcslpi850.mbn`, `qcdsp1v2850.mbn`, `qcdsp2850.mbn`, `WLANMDSP.MBN` |
|
|
| `qcwlan850.cab` | `bdwlan.bin`, `bdwlan.b3*` — ath10k board data |
|
|
| `mcfg_subsys_ext850.cab` | `MCFG/*` modem configuration |
|
|
| `qcsubsys_extension850.cab` | ADSP/CDSP runtime modules, `fastrpc_shell_*` |
|
|
|
|
`cabextract` or `7z x` opens them. Ignore the `_nm` and `_CLS` variants; the
|
|
device tree asks for the plain names. Fetch via the GitHub API's
|
|
`download_url` — the `raw.githubusercontent.com` paths return 404.
|
|
|
|
**aarch64-laptops** — <https://github.com/aarch64-laptops/build>
|
|
|
|
Files checked directly into `misc/lenovo-yoga-c630/`, no extraction needed.
|
|
A narrower set, but with things WOA-Project does not carry:
|
|
|
|
| Path | Contains |
|
|
|---|---|
|
|
| `wifi/` | `qcdsp1v2850.mbn`, `qcdsp2850.mbn`, `wlanmdsp.mbn`, `bdwlan.*` |
|
|
| `wifi/create-board-2.bin/` | a prebuilt `board-2.bin`, and the script that makes it |
|
|
| `gpu/firmware/` | `a630_gmu.bin` |
|
|
| `acpi/`, `laptop-lenovo-yoga-c630.dts` | the machine's ACPI tables and a device tree |
|
|
|
|
It has none of `qcdxkmsuc850.mbn`, `qcadsp850.mbn`, `qccdsp850.mbn`,
|
|
`qcslpi850.mbn`, `qcvss850.mbn` or `ipa_fws.elf`. Note `wifi/firmware-5.bin` is
|
|
an HTML error page in the checkout, not firmware — Fedora's `atheros-firmware`
|
|
ships a real one.
|
|
|
|
**Which to use.** For the GPU, DSPs, sensors and venus, WOA-Project is the only
|
|
option of the two. For the modem and WLAN trio both publish files, and they are
|
|
different builds — same sizes for the modem pair, different checksums. On this
|
|
machine both produce identical results, so nothing here distinguishes them; see
|
|
[What the firmware does not fix](#what-the-firmware-does-not-fix).
|
|
|
|
**Lenovo's Digital Download Recovery Service.** Lenovo builds a Windows
|
|
recovery image against a machine's serial number and lets the owner download
|
|
it. That image contains `Windows/System32/DriverStore/FileRepository`, and
|
|
therefore the whole set as shipped for your specific machine. Slower than
|
|
either repository, and untested here.
|
|
|
|
**Another C630 owner.** Any 81JL's copy is as good as your own.
|
|
|
|
Whichever route, drop the result into `firmware/local/` and rebuild — see
|
|
[Baking firmware into the image](#baking-firmware-into-the-image).
|
|
|
|
## What the firmware does not fix
|
|
|
|
Installing all of it still leaves two things not working out of the box, for
|
|
reasons that are not about firmware. Audio turns out to be a missing Fedora
|
|
kernel module and is fixable today; onboard WiFi remains genuinely broken.
|
|
Recorded here so nobody repeats the search.
|
|
|
|
### Onboard WiFi: everything is in place except a working modem
|
|
|
|
The name gives it away: **wlan*mdsp*** is the WLAN *Modem* DSP. On SDM845 the
|
|
WiFi firmware runs on the modem, so `ath10k_snoc` binds to `18800000.wifi` and
|
|
then sits silent. It logs nothing and fails nothing — it has registered a QMI
|
|
client and is waiting for service 69, `wlfw`, which only appears once the modem
|
|
is up. `qrtr-lookup` shows what is and is not there.
|
|
|
|
Everything on the WiFi side checks out:
|
|
|
|
- `ath10k_snoc` binds successfully and is not in deferred probe
|
|
- the device tree carries `qcom,calibration-variant = "Lenovo_C630"`
|
|
- Fedora's `board-2.bin` contains a matching `variant=Lenovo_C630` entry
|
|
- `modemst1` and `modemst2` hold ~2 MB of real NV data — not wiped
|
|
|
|
#### The userspace services are required, and mostly missing by default
|
|
|
|
[aarch64-laptops' WiFi README][aal-wifi] lists four services. On a current
|
|
Fedora two of them are now in the kernel, and the other two are packaged but
|
|
inactive:
|
|
|
|
| Service | Status here |
|
|
|---|---|
|
|
| `pd-mapper` | in-kernel as `qcom_pd_mapper` — nothing to install |
|
|
| `qrtr-ns` | in-kernel — the `qrtr` package supplies only `qrtr-lookup`/`qrtr-cfg` |
|
|
| `rmtfs` | packaged, **disabled by default** — serves the modem's NV partitions |
|
|
| `tqftpserv` | packaged, **not installed** — serves files to the modem over TFTP |
|
|
|
|
```sh
|
|
sudo dnf install rmtfs tqftpserv qrtr
|
|
sudo systemctl enable --now rmtfs tqftpserv
|
|
```
|
|
|
|
Enabling these took `qrtr-lookup` from 19 services to 25. A working setup is
|
|
said to show around 40.
|
|
|
|
[aal-wifi]: https://github.com/aarch64-laptops/build/tree/master/misc/lenovo-yoga-c630/wifi
|
|
|
|
#### The modem fetches wlanmdsp over TFTP
|
|
|
|
This is the step that is easy to miss, and the reason `tqftpserv` exists.
|
|
The modem asks it for the WLAN firmware by path — but where tqftpserv serves
|
|
from depends on the version, and the layout below (from aarch64-laptops) does
|
|
**not** work with Fedora's current build; see [The tqftpserv paths above are
|
|
version-dependent](#the-tqftpserv-paths-above-are-version-dependent). On
|
|
current Fedora:
|
|
|
|
```sh
|
|
sudo ln -sf updates/qcom/sdm850/LENOVO/81JL/wlanmdsp.mbn /lib/firmware/wlanmdsp.mbn
|
|
```
|
|
|
|
The modem also wants a writable area, and says so in the `tqftpserv` journal:
|
|
|
|
```
|
|
[TQFTP] RRQ: /readwrite/ota_firewall/ruleset → unable to open (2), reject
|
|
[TQFTP] WRQ: /readwrite/server_check.txt
|
|
```
|
|
|
|
On current Fedora that lives in `/tmp/tqftpserv` (recreate after each boot, or
|
|
add a tmpfiles.d entry):
|
|
|
|
```sh
|
|
sudo mkdir -p /tmp/tqftpserv/ota_firewall
|
|
sudo touch /tmp/tqftpserv/ota_firewall/ruleset
|
|
sudo chmod -R 0777 /tmp/tqftpserv
|
|
```
|
|
|
|
Watching `journalctl -u tqftpserv` while starting the modem is the best
|
|
available feedback channel — it shows exactly which paths the modem wants.
|
|
|
|
#### Where it actually stops
|
|
|
|
The modem boots and then dies at RF front-end initialisation, roughly every 42
|
|
seconds until stopped:
|
|
|
|
```
|
|
qcom-q6v5-mss 4080000.remoteproc: MBA booted without debug policy, loading mpss
|
|
qcom-q6v5-mss 4080000.remoteproc: fatal error received:
|
|
RFLM@rflm_qlnk.cpp:1087 [3,3] RF stuck in QLINK start state: 0x0 after maximum
|
|
remoteproc remoteproc0: crash detected ... recovering
|
|
```
|
|
|
|
It never gets far enough to request `wlanmdsp`, so WLAN never starts.
|
|
|
|
Three firmware pairings, three distinct failures — worth recording, because the
|
|
differences are informative:
|
|
|
|
| MBA (stage 1) | mpss (stage 2) | Result |
|
|
|---|---|---|
|
|
| full | full | boots, dies at `RF stuck in QLINK start state` |
|
|
| `_nm` | `_nm` | `PBL returned unexpected status` — rejected by the boot ROM |
|
|
| full | `_nm` | `MPSS header authentication failed: -3` |
|
|
|
|
The `_nm` ("no modem") variants are the obvious idea — Fedora ships the
|
|
equivalent `modem_nm.mbn` for generic sdm845, and skipping cellular RF is
|
|
exactly what is wanted here. They cannot be made to load. WOA-Project's nine
|
|
packages contain **two** distinct `_nm` builds (an older one in `200.0.1.0`
|
|
through `200.0.10.0`, a newer one from `200.0.11.0` on), and both are rejected
|
|
by the boot ROM in the same way:
|
|
|
|
```
|
|
qcom-q6v5-mss 4080000.remoteproc: PBL returned unexpected status -284557301
|
|
remoteproc remoteproc3: can't start rproc 4080000.remoteproc: -22
|
|
```
|
|
|
|
So this is not a matter of finding the right vintage: the PBL will not accept
|
|
either `_nm` MBA on this hardware, and without the `_nm` MBA the `_nm` mpss
|
|
fails authentication. The avenue is closed.
|
|
|
|
#### Firmware has been eliminated as the variable
|
|
|
|
The nine driver versions under `Lenovo/YogaC630/` contain five distinct
|
|
modem-firmware builds — `200.0.1.0` through `200.0.4.0` share one 2018-vintage
|
|
pair (a different mpss size from all later builds, and the closest match to
|
|
this machine's 2018-10 manufacture date), `200.0.10.0`, `200.0.11.0`,
|
|
`200.0.14.0`/`200.0.16.0`, and `200.0.19.0` are the others, and
|
|
aarch64-laptops' copies differ again by checksum. Every one of them was booted
|
|
on this machine, with `rmtfs` and `tqftpserv` running. Every one produces the
|
|
**identical** QLINK failure, crashing about 0.4 s after mpss comes up.
|
|
|
|
So neither the firmware nor its vintage is the variable. Nor is the kernel
|
|
version, at least not recently: Fedora's 6.12.15 (`kernel-core` from koji,
|
|
booted with the same device tree) produces the identical crash. The
|
|
aarch64-laptops setup that ticked WiFi ran a 2019 5.x branch, so a very old
|
|
regression is not excluded — but their issue #51 records modem crash/recovery
|
|
lockups on that setup too, so "WiFi worked" and "the modem crashed" may well
|
|
have coexisted, with `wlanmdsp` loading in the window before the first crash.
|
|
An earlier note in this project's history has the crash loop at roughly 42 s;
|
|
today it is ~3.4 s. If those cadences are both real, what changed — and
|
|
whether a long first window is enough for `wlfw` to appear — is an open
|
|
question.
|
|
|
|
#### What QLINK is, and what the modem reads before dying
|
|
|
|
QLINK is the SerDes link between the SoC and the SDR845 RF transceiver chip;
|
|
`rflm_qlnk.cpp` is the modem firmware's manager for it. "Stuck in start state
|
|
0x0" means the transceiver never answered — the signature of a chip that is
|
|
absent, unpowered, or misconfigured. This unit is an LTE SKU (SIM slot,
|
|
factory-label IMEI, modem worked under Windows), so the chip is present and
|
|
functional; under Windows, ACPI and the Qualcomm PEP handle subsystem power,
|
|
and under device-tree boot whatever they did for the RF front end has no
|
|
equivalent. That is a hypothesis, not a finding — the DSDT keeps its power
|
|
management in opaque PEP tables, so it could not be confirmed from the tables
|
|
alone.
|
|
|
|
`rmtfs -v` shows the modem's storage dialogue in the half-second before the
|
|
crash: it opens `modem_fs1`, `modem_fs2`, `modem_fsg` and `modem_fsc`
|
|
successfully, requests two partitions Linux's rmtfs has never heard of —
|
|
`modem_fsg_oem_1` and `modem_fsg_oem_2`, names that appear inside the mpss
|
|
image itself, with no provider found in any WOA-Project cabinet — allocates
|
|
its 2 MB buffer, reads a superblock's worth, and dies. On this machine
|
|
`modemst1`/`modemst2` carry valid `IMGEFS1`/`IMGEFS2` filesystems, while
|
|
**`fsg` and `fsc` are entirely blank**. Byte copies of all four live in
|
|
`firmware/local/nv-backup/` (gitignored), taken before any experimentation.
|
|
|
|
#### The tqftpserv paths above are version-dependent
|
|
|
|
The `/lib/firmware/readonly/firmware/image/` and `/lib/firmware/readwrite/`
|
|
layout described earlier matches the old aarch64-laptops tqftpserv. Fedora's
|
|
current build is different: `/readwrite/*` is served from `/tmp/tqftpserv`
|
|
(tmpfs — anything created there is gone on reboot), and
|
|
`/readonly/firmware/image/<file>` resolves through the normal firmware search
|
|
path, i.e. `/lib/firmware/<file>`. So `wlanmdsp.mbn` needs to be reachable as
|
|
`/lib/firmware/wlanmdsp.mbn` (a symlink to the copy under `updates/` works),
|
|
and the writable tree belongs under `/tmp/tqftpserv/`.
|
|
|
|
The full Windows-side TFTP tree can be reconstructed, and was: the driver
|
|
INFs are TFTP-mapping manifests, and `mcfg_subsys_ext850.inf` maps 159 modem
|
|
configuration files — carrier configs and, notably, `mcfg_hw` hardware RF
|
|
configs for the SDX20 — into `readonly/firmware/image/modem_pr/mcfg/...`.
|
|
Serving all of it changes nothing: the modem crashes before requesting any of
|
|
it.
|
|
|
|
#### The host provides no RF power, on either operating system
|
|
|
|
The obvious remaining theory was that Windows powers the SDR845 through some
|
|
rail the device tree does not describe. It does not. The C630's ACPI tables
|
|
carry Qualcomm's PEP power-management data, and decoding every PEP entry for
|
|
`\_SB.AMSS` gives the complete set of host resources Windows gives the modem:
|
|
|
|
- seven clocks — `gcc_boot_rom_ahb_clk`, `gcc_mss_cfg_ahb_clk`,
|
|
`gcc_mss_q6_memnoc_axi_clk`, `gcc_mss_snoc_axi_clk`, `gcc_mss_mfab_axis_clk`,
|
|
`gcc_prng_ahb_clk` and `gcc_mss_axis2_clk`
|
|
- one regulator vote, `SMPS2_C` at 752 mV — the MSS core rail
|
|
- ARC/NPA votes on `rail_cx` and `rail_mx` at level 384
|
|
- IPA bus bandwidth
|
|
|
|
No GPIOs, no footswitches, and — decisively — **no votes on the CXO RF clock
|
|
buffers** (`RFCLK1/2/3`), even though the PMIC registry defines them and the
|
|
audio tables vote on their own buffers. RF front-end power is managed by the
|
|
modem firmware itself over RPMh/SPMI on both operating systems, so there is no
|
|
missing host-side rail to add to the device tree.
|
|
|
|
Mainline's `mss_pil` node already carries six of the seven clocks. The seventh,
|
|
`gcc_mss_axis2_clk`, is the only host-side difference, and it is not the cause:
|
|
holding it enabled with an out-of-tree module before starting the modem
|
|
produces the identical QLINK crash.
|
|
|
|
The RF reference clocks are the other obvious suspect and also innocent.
|
|
`rf_clk1`, `rf_clk2` and `rf_clk3` are the PMIC CXO buffers that clock the
|
|
transceivers — `clk_summary` shows `rf_clk2` claimed by `18800000.wifi` as
|
|
`cxo_ref_clk_pin`, and `rf_clk1`/`rf_clk3` claimed by nobody, with zero enable
|
|
votes from Linux. A transceiver with no reference clock would fail exactly like
|
|
this. Voting all six on (including the `_ao` variants) from a module, verified
|
|
at 38.4 MHz in `clk_summary`, changes nothing.
|
|
|
|
Unclaimed regulators are innocent too, though the test is weaker: booting with
|
|
`regulator_ignore_unused` leaves `smps2`, `smps7` and a dozen LDOs still off,
|
|
because nothing ever turned them on — the parameter only prevents Linux from
|
|
disabling rails, it cannot enable what UEFI left off. The ACPI offers no
|
|
candidate either: the DSDT defines exactly one `PowerResource` on this machine,
|
|
`WRST`, and it resets WLAN. There is no WWAN enable or disable line to assert.
|
|
|
|
The modem's own coredump would settle it, but `/sys/class/remoteproc/*/coredump`
|
|
yields a 124 MB image with none of the firmware's strings in it — production
|
|
fuses, encrypted dump. The strings are plainly visible in `qcdsp2850.mbn`
|
|
itself, so the dump is not simply empty.
|
|
|
|
#### The premise itself is shaky
|
|
|
|
The reason to suspect a regression is aarch64-laptops' support table, which
|
|
ticks WiFi for this machine. Their published device tree does not support that
|
|
claim. `misc/lenovo-yoga-c630/laptop-lenovo-yoga-c630.dts` marks
|
|
`wifi@18800000` as `status = "disabled"` and contains **no modem node at all** —
|
|
no `4080000.remoteproc`, no MSS remoteproc of any compatible. Their WiFi
|
|
instructions are headed "UNSTABLE", and their issue #51 records WCN3990
|
|
crash-and-lockup behaviour. So "WiFi worked in 2019" may never have been true
|
|
in the form assumed here, and the modem may simply have never got past RF init
|
|
under Linux on this machine.
|
|
|
|
#### It is not a recent regression: 6.9 fails identically
|
|
|
|
postmarketOS supports this machine and builds a kernel for it, which makes
|
|
their package the easiest old kernel to test — it contains `vmlinuz`, a C630
|
|
device tree and its modules, and needs no image writing:
|
|
|
|
```sh
|
|
curl -O http://mirror.postmarketos.org/postmarketos/v24.06/aarch64/linux-postmarketos-qcom-sdm845-6.9.0-r2.apk
|
|
tar xzf linux-postmarketos-qcom-sdm845-6.9.0-r2.apk # an apk is a tarball
|
|
```
|
|
|
|
Install the modules under `/usr/lib/modules/6.9.0-sdm845` — **not** by
|
|
extracting a `lib/`-rooted archive into `/`, which replaces Fedora's
|
|
`/lib -> usr/lib` symlink and takes the machine down — then `depmod`, build an
|
|
initramfs with `dracut --kver`, and add a BLS entry.
|
|
|
|
**Kernel 6.9.0-sdm845 produces the identical failure**: 23 QLINK crashes in
|
|
70 seconds, same trace, `wlfw` never appears. So the modem fails the same way
|
|
on 6.9, 6.12 and 7.1 — three kernels across two years, one of them built and
|
|
shipped by a distribution that lists this laptop as supported. Whatever is
|
|
wrong is not a recent kernel regression.
|
|
|
|
Older still is untestable from the internal disk: neither a hand-built 5.10.261
|
|
(defconfig plus the SDM845 platform, UFS, PHY and ext4, all verified in
|
|
`.config`) nor Fedora's own 6.1.18 reaches userspace. Both die after the EFI
|
|
stub with nothing in the journal, with `earlycon=efifb`, `keep_bootcon` and
|
|
`arm-smmu.disable_bypass=0` tried, and with the initramfs rebuilt without this
|
|
repo's dracut config in case its forced `phy-qcom-qmp-ufs` was the problem. UFS
|
|
is present in the 6.1 device tree, so that is not the cause.
|
|
|
|
A useful harness for this: `c630-modem-test`, a oneshot unit gated on
|
|
`/var/lib/c630-modem-test-armed` that starts the modem, waits, and appends a
|
|
verdict to `/var/log/c630-modem-test.txt`. Results survive a kernel that cannot
|
|
bring up networking — which the pmOS kernel could not, having no driver for the
|
|
USB dongle in use.
|
|
|
|
Booting test kernels here is otherwise cheap and safe: add a BLS entry, set
|
|
`next_entry` in the GRUB environment for a one-shot boot, and a power-cycle
|
|
returns to the pinned default. Pin the default by entry id rather than
|
|
`set default=0` — adding an entry can otherwise silently make the *test* kernel
|
|
the default.
|
|
|
|
#### The modem starts itself, and crashes forever
|
|
|
|
Fedora runs `rmtfs -r`, and `-r` makes rmtfs start the modem remoteproc at
|
|
boot. Since the modem cannot get past RF init, the machine sits in a fatal
|
|
crash and recovery cycle every ~3.4 seconds from boot until shutdown. It is
|
|
easy to miss — the modem is already at crash #15 or so by the time anyone logs
|
|
in, which also makes manual experiments confusing, and rapid start/stop cycling
|
|
on top of it will wedge the remoteproc until a reboot.
|
|
`overlay/etc/systemd/system/rmtfs.service.d/50-no-remoteproc.conf` drops the
|
|
`-r`, so the modem stays offline until started deliberately.
|
|
|
|
Things that sound like they should help and do not:
|
|
|
|
- **Unplugging the USB WiFi dongle.** Different bus, different driver.
|
|
`ath10k_snoc` is waiting on the modem, not competing for a radio.
|
|
- **`rmtfs` alone.** Necessary, not sufficient. Its "failed to update start
|
|
state" warnings are its optional remoteproc-control helper and are harmless.
|
|
- **The `_nm` variants.** See above.
|
|
- **Any published firmware version.** All five distinct builds fail
|
|
identically; see above.
|
|
- **The MCFG configuration tree.** Served in full over TFTP; the modem never
|
|
gets far enough to ask for it.
|
|
- **`clk_ignore_unused pd_ignore_unused`.** Already on the command line
|
|
(postmarketOS ships the same for this machine).
|
|
- **`gcc_mss_axis2_clk`.** The one clock Windows enables that mainline does
|
|
not; holding it on changes nothing. See above.
|
|
- **Both `_nm` firmware builds.** Rejected by the boot ROM. See above.
|
|
- **The RPMh RF reference clocks.** `rf_clk1/2/3` voted on explicitly; no
|
|
change. See above.
|
|
- **`regulator_ignore_unused`.** The unclaimed rails were never on to begin
|
|
with. See above.
|
|
|
|
Untried, in rough order of promise: reporting upstream — the error string
|
|
appears nowhere public, and linux-arm-msm (Bjorn Andersson, Dmitry Baryshkov,
|
|
Steev Klimaszewski all have C630 history) is now clearly the best next step,
|
|
since every locally testable hypothesis above has been eliminated; Lenovo's
|
|
Digital Download Recovery Service image
|
|
for this serial, which would show the exact original driver set and whatever
|
|
provides `modem_fsg_oem_*` under Windows; building `board-2.bin` from the
|
|
C630's own `bdwlan.*` files (only relevant once the modem stays up); and the
|
|
5.x aarch64-laptops kernel itself, to test whether its crash cadence leaves a
|
|
usable window.
|
|
|
|
### Audio: Fedora does not build the speaker-amp driver
|
|
|
|
The ADSP runs, the audio services register, and the codec answers:
|
|
|
|
```
|
|
wcd934x-slim 217:250:1:0: WCD934x chip id major 0x108, minor 0x1
|
|
```
|
|
|
|
So SLIMbus, the ADSP and the hardware are all fine. The trail of suspicion
|
|
used to end at this SoundWire message:
|
|
|
|
```
|
|
qcom-soundwire wcd934x-soundwire.12.auto: din-ports (2) mismatch with controller (6)
|
|
platform sound: deferred probe pending: msm-snd-sdm845: SLIM Playback: codec dai not found
|
|
```
|
|
|
|
The first line is a red herring. Since kernel 6.18 (commit `9e53a66a2f2f`,
|
|
"soundwire: qcom: deprecate qcom,din/out-ports") it is a warning: the
|
|
device-tree value overrides the register value afterwards and the probe
|
|
continues.
|
|
|
|
The real blocker is the second line. The device tree's `SLIM Playback` link
|
|
names four codec DAIs: the WCD9340, the SoundWire master inside it, and the
|
|
two WSA881x speaker amplifiers hanging off that master. Fedora's aarch64
|
|
kernel has never set `CONFIG_SND_SOC_WSA881X` — while the newer siblings
|
|
WSA883X and WSA884X, which the ThinkPad X13s needs, are both `=m`. With no
|
|
amplifier driver the two SoundWire slaves never bind, the machine driver
|
|
cannot find its DAIs, and the card sits in deferred probe forever.
|
|
|
|
Building the one missing module proves it. `sound/soc/codecs/wsa881x.c` is a
|
|
single self-contained file; compiled against the running kernel
|
|
(`kernel-devel`, `gcc`, `make`, then `make -C /lib/modules/$(uname -r)/build
|
|
M=$PWD modules` with `obj-m += snd-soc-wsa881x.o` and `snd-soc-wsa881x-objs :=
|
|
wsa881x.o`), installed under `/lib/modules/$(uname -r)/extra/` and picked up
|
|
by `depmod -a`, it autoloads on the SoundWire modalias at boot and the
|
|
`Lenovo-YOGA-C630-13Q50` card registers, headphone-jack detection included.
|
|
The UCM profile has been in alsa-ucm-conf since 2020; `alsaucm` itself is in
|
|
`alsa-ucm-utils`, so on the minimal image:
|
|
|
|
```sh
|
|
alsaucm -c Lenovo-YOGA-C630-13Q50 set _verb HiFi set _enadev Speaker
|
|
speaker-test -D plughw:0,0 -c2 -t sine -f 440 -l 1
|
|
```
|
|
|
|
The `overflow error on RX port` and register-read `-16` messages logged when a
|
|
stream stops are noise, seen on working sdm845 devices too.
|
|
|
|
**PipeWire needs one more thing.** The ADSP front-end advertises S24_LE
|
|
(24-bit samples in 32-bit containers) and then garbles it: playback runs
|
|
without any error, consumes data at the right rate, and powers every DAPM
|
|
widget — a byte-for-byte identical `/sys/kernel/debug/asoc` widget dump
|
|
against a working stream — while producing ~-48 dB of near-silence,
|
|
consistent with the samples being bit-shifted inside the container. PipeWire
|
|
prefers the widest format a device offers, so every PipeWire client (mpv,
|
|
GNOME, `pw-play`) is inaudible while `aplay` and `speaker-test`, which
|
|
default to S16_LE, work perfectly. This cost most of a day: the failure is
|
|
per-format, invisible in every log, and easy to misattribute to routing,
|
|
suspend/resume or per-stream volumes, all of which look guilty and are not.
|
|
`overlay/etc/wireplumber/wireplumber.conf.d/51-c630-alsa.conf` pins the
|
|
built-in card to S16_LE, which fixes every client. The underlying q6asm
|
|
format bug is worth a report to alsa-devel.
|
|
|
|
Unrelated to the hardware, two pieces of routing politics are worth knowing
|
|
on a machine like this: PipeWire's RAOP discovery will happily make a
|
|
neighbour's AirPlay speaker the default sink, and a newly connected
|
|
Bluetooth device also takes over the default. If sound "does not work",
|
|
check `wpctl status` for where the default actually points before blaming
|
|
the drivers.
|
|
|
|
The durable fix is one line of Fedora kernel config — flipping
|
|
`CONFIG_SND_SOC_WSA881X` to `=m` for aarch64, an easy case given the in-tree
|
|
device tree requires it and both newer WSA drivers are already enabled. Until
|
|
that lands, the module has to be built out of tree per kernel.
|
|
|
|
One thing worth knowing if you go looking: `slim_qcom_ngd_ctrl` can probe before
|
|
the ADSP is ready and log `QMI wait timeout`. Reloading the module afterwards
|
|
gets the codec detected, which is how the chip ID above was obtained. A series
|
|
of NGD probe-ordering fixes (Bjorn Andersson, mid-2026, Cc: stable) targets
|
|
exactly this class of race and should reach Fedora with the 7.2 rebase.
|
|
|
|
## 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.
|