aarch64-laptops' support table ticks WiFi for this machine, so the path exists. Following their WiFi README got considerably further and then stopped somewhere useful to have documented. Two of the four services they list are in the kernel now (pd-mapper, qrtr-ns). The other two, rmtfs and tqftpserv, are packaged in Fedora and shipped disabled — tqftpserv was not even installed. Both are now in base.pkgs and enabled by stage2. Enabling them took qrtr-lookup from 19 registered services to 25; a working setup is said to show around 40. The step worth having written down: the modem does not read wlanmdsp.mbn from /lib/firmware. It asks tqftpserv for it over TFTP, from /lib/firmware/readonly/firmware/image/. Nothing reports this as an error — ath10k_snoc binds, registers a QMI client, and waits forever for a service that never registers. c630-firmware now places the file there, and creates the writable area the modem asks tqftpserv for. Where it stops: the modem boots and dies at "RF stuck in QLINK start state", about every 42 seconds, never reaching the point of requesting wlanmdsp. Three firmware pairings give three distinct failures, recorded in the doc — the _nm "no modem" variants, which are the obvious idea and would skip cellular RF entirely, turn out not to be signed for this device. The result that narrows it: aarch64-laptops' own wifi directory carries the qcdsp1v2850.mbn, qcdsp2850.mbn and wlanmdsp.mbn from the setup whose table ticks WiFi. All three differ from the WOA-Project copies. Installed here with the services running and wlanmdsp in the TFTP path, they produce the identical QLINK failure — so it is neither the firmware nor the userspace. What is left is the kernel: 5.x from 2019 there against 7.1.5 here, with both of their ath10k patches long since upstream. That points at a regression, and confirming it means a bisect rather than another file. Also record what is untried: the other eight driver versions (this machine's UEFI is from 2019 and the newest package may be the wrong vintage), mcfg_subsys_ext850.cab, and building board-2.bin from the C630's own bdwlan.* files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
16 KiB
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.
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:
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:
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 — 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. Three routes, easiest first:
WOA-Project. https://github.com/WOA-Project/Qualcomm-Reference-Drivers collects the Windows-on-ARM driver packages by SoC. The C630's are three cabinet files, and between them they hold every file this machine asks for:
| 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, and WLANMDSP.MBN |
That last one is a bonus — the WiFi firmware, which is not in the device tree's
firmware-name list but is what the onboard ath10k needs.
cabextract or 7z x will open them:
for f in qcdx850.cab qcipa850.cab qcsubsys850.cab; do 7z x -o./extracted "$f"; done
find ./extracted -iname 'qc*850.mbn' -o -iname 'ipa_fws.elf' -o -iname 'WLANMDSP.MBN'
Ignore the _nm and _CLS variants; the device tree asks for the plain names.
Verify what you get with file — they should report as
ELF 32-bit LSB executable, QUALCOMM DSP6, not as text or HTML.
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 all eight files. Slower than the cabinets, but it is unambiguously
your own machine's firmware.
Another C630 owner. The aarch64-laptops project is where these have circulated before, and 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.
What the firmware does not fix
Installing all of it still leaves two things broken, both for reasons that are not about firmware. Recorded here so nobody repeats the search.
Onboard WiFi: everything is in place except a working modem
The name gives it away: wlanmdsp 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_snocbinds successfully and is not in deferred probe- the device tree carries
qcom,calibration-variant = "Lenovo_C630" - Fedora's
board-2.bincontains a matchingvariant=Lenovo_C630entry modemst1andmodemst2hold ~2 MB of real NV data — not wiped
The userspace services are required, and mostly missing by default
aarch64-laptops' WiFi README 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 |
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.
The modem fetches wlanmdsp over TFTP
This is the step that is easy to miss, and the reason tqftpserv exists.
tqftpserv serves from /lib/firmware/, and the modem asks it for the WLAN
firmware by path:
sudo mkdir -p /lib/firmware/readonly/firmware/image
sudo cp wlanmdsp.mbn /lib/firmware/readonly/firmware/image/
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
sudo mkdir -p /lib/firmware/readwrite/ota_firewall
sudo touch /lib/firmware/readwrite/ota_firewall/ruleset
sudo chmod -R 0777 /lib/firmware/readwrite
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 are not signed for this device and cannot be
made to load.
Firmware has been eliminated as the variable
The decisive test: aarch64-laptops' wifi directory contains the
qcdsp1v2850.mbn, qcdsp2850.mbn and wlanmdsp.mbn from the setup whose
support table ticks WiFi. All three differ from the WOA-Project copies — the
modem pair matches on size but not checksum. Installed on this machine, with
rmtfs and tqftpserv running and wlanmdsp in the TFTP path, they produce
the identical QLINK failure.
So the difference is not the firmware, and not the userspace services. What
remains is the kernel: that setup ran aarch64-laptops/linux branch gpu,
circa 2019 on 5.x, against 7.1.5 here. Both ath10k patches it applied are long
upstream — the device tree carries qcom,snoc-host-cap-8bit-quirk from the
first of them. Six years of qcom-q6v5-mss churn sits between the two.
That points at a kernel regression rather than anything missing on disk. Confirming it means bisecting or booting an older kernel, which nothing here does yet.
Things that sound like they should help and do not:
- Unplugging the USB WiFi dongle. Different bus, different driver.
ath10k_snocis waiting on the modem, not competing for a radio. rmtfsalone. Necessary, not sufficient. Its "failed to update start state" warnings are its optional remoteproc-control helper and are harmless.- The
_nmvariants. See above.
Untried, in rough order of promise: the other eight driver versions under
Lenovo/YogaC630/ (this machine's UEFI is 9UCN33WW(V2.06) from 2019, and the
newest package may not be the right vintage); mcfg_subsys_ext850.cab, which
holds modem configuration data; and building board-2.bin from the C630's own
bdwlan.* files rather than relying on Fedora's.
Audio is a kernel driver problem
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. What fails is one sub-device of the codec:
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 codec's SoundWire block declares two data-in ports where the controller expects six, so it never probes, so the sound card has no codec DAI and never appears. That is a mismatch inside the kernel, and needs a kernel or device-tree change rather than any blob.
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 — but it
does not get past the SoundWire mismatch.
Extracting them
The image ships Fedora's qcom-firmware-extract. Run it once:
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:
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:
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:
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.