All checks were successful
build image / build (push) Successful in 40m1s
The RPMh CXO buffers that clock the transceivers were the best remaining host-side theory — rf_clk1 and rf_clk3 have no consumer in the C630 tree and zero enable votes from Linux, and a transceiver with no reference clock fails exactly the way this one does. Voting all six on changes nothing. Unclaimed regulators are out too, and the DSDT has no WWAN enable line to assert. Also worth recording: aarch64-laptops' published device tree, the basis for believing onboard WiFi once worked here, marks wifi@18800000 disabled and has no modem node at all. Their WiFi instructions are headed UNSTABLE. The regression premise may be wrong, and bracketing cannot test it anyway — neither a hand-built 5.10 nor Fedora's own 6.1.18 reaches userspace on this machine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011XgGF5wfxLDAybVnNz6eNQ
655 lines
30 KiB
Markdown
655 lines
30 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.
|
|
|
|
#### Kernel bracketing has not been possible
|
|
|
|
Testing the regression theory anyway needs an old kernel that boots here, and
|
|
that has not been achieved. 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. Fedora's 6.12.15 boots and crashes identically
|
|
to 7.1.5, leaving the bracket at 6.12 ≤ broken ≤ 7.1 and nothing older testable
|
|
from the internal disk. Booting an old distro from USB is the remaining way in.
|
|
|
|
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.
|