wwand: add cellular connection manager - #30185
Conversation
BKPepe
left a comment
There was a problem hiding this comment.
Thanks for working on this. I can see the motivation behind having a lightweight, event-driven WWAN manager tightly integrated with netifd, and some of the functionality here is certainly interesting.
However, I don't think this is ready to be merged into the OpenWrt packages feed in its current form.
My main concern is the scope and the amount of functionality being reimplemented. This is not merely another frontend or connection manager using the existing OpenWrt WWAN stack. wwand implements QMI, MBIM and NCM/AT itself and the corresponding packages explicitly conflict with and replace uqmi, umbim and comgt-ncm.
That effectively introduces a second implementation of a significant part of the OpenWrt WWAN stack which we would then need to maintain and review in parallel with the existing implementations.
The scope is also very large for a newly introduced project: QMI, MBIM, NCM/AT, QMAP/multiple PDP contexts, SIM/APDU handling, eSIM, SMS, telemetry, recovery, board profiles, netifd integration and automatic migration of existing configurations are all being introduced at once.
Host-side tests are useful, but they cannot replace real-world modem interoperability testing for protocols such as QMI and MBIM. The PR currently mentions production testing on four devices/modems. Considering the number of modem vendors, firmware versions, protocol quirks and failure/recovery scenarios encountered in the OpenWrt ecosystem, I don't think this provides enough coverage yet to justify replacing the established handlers.
Another concern is project maturity. This is a relatively new implementation of protocol parsers and fairly complex asynchronous state machines, and a substantial part of the implementation was AI-generated. I don't consider the use of AI itself a reason to reject code, but for this kind of low-level protocol and state-machine implementation it makes independent review and real-world validation especially important. At the moment there does not appear to be enough independent review or deployment history to establish that confidence.
There is also a broader architectural question here.
Since this project is specifically designed around OpenWrt (ucode, uloop, ubus, UCI and netifd) and aims to replace several existing OpenWrt WWAN components, I think the architecture should first be discussed with the maintainers of the existing WWAN/netifd components. In particular, it should be considered whether the missing functionality could be implemented by improving the existing stack instead of introducing another complete QMI/MBIM/NCM implementation.
Features such as better recovery, persistent context handling, QMAP support or improved netifd integration may indeed solve real shortcomings. If so, contributing those improvements to the existing components could benefit all existing users without creating two competing OpenWrt-specific WWAN stacks.
I would therefore prefer to see wwand mature as an external feed first, gain broader hardware coverage and users, accumulate real-world bug reports and fixes, and receive independent review of the protocol/state-machine implementation.
Once that has happened, and if there is consensus that maintaining a separate WWAN implementation is preferable to improving the existing stack, inclusion in the official packages feed could be reconsidered.
So for now, I would be against merging this PR. This is not because the project has no value, but because replacing several established OpenWrt WWAN components with a young and very broad implementation creates a substantial long-term maintenance and review burden that I don't think has been justified yet.
|
Thanks for taking a deep look. This code is based on my personal QMI dial-in handler which I developed since 2014. All of my knowledge about quirks with modems is in this project. I can understand you to not support inclusion, but is not the package feed exactly for this? Optional packages, which the user selects if he want's it. The user may use old style stack with all of it's stability problems or may use wwand instead. So this is not about replacing, it is about giving a stable alternative with features which are needed in the field (plmn lock i.e.), that nothing else provides. André |
Shouldn't that only be a showstopper if wwand is set to the default install for wwan interfaces? Since there are already so much packages for manageing WWAN stuff, why is this section important for adding another package? The options you get with wwand are mostly missing on ALL other packages... Cheers, 4920441 |
openwrt-ai
left a comment
There was a problem hiding this comment.
Packaging-level review of net/wwand/Makefile only — the inclusion/scope discussion in the existing review is a maintainer call and I'm not restating it.
Nothing here blocks a merge on its own. The two worth acting on regardless of how the scope question lands are the unresolvable CONFLICTS entry and the glob-then-rm split in Package/wwand/install, which can silently produce two packages owning the same path. The rest are small.
Metadata otherwise checks out: PKG_RELEASE:=1 is correct for a new package, PKG_MAINTAINER is set, the define blocks follow the 2-spaces-for-metadata / tabs-for-recipes convention, all ucode-mod-* and kmod-* dependencies resolve against openwrt main, lpac in this feed is at 2.3.0 so the >= 2.3.0 note holds, and the codeload.github.com/.../refs/tags/ source URL matches what 243 other Makefiles in this feed do. I could not verify PKG_HASH or the v1.0.0 tag — this session's GitHub access is scoped to openwrt/packages.
Generated by Claude Code
|
Thanks for the very thorough and fair review — the scope and maintenance-burden concerns are legitimate, and I've reworked the most load-bearing part in response. The biggest change: wwand no longer replaces the stock WWAN stack. I've removed all
So it's no longer "a second implementation that replaces the existing handlers" — it's additive and opt-in per interface. That directly targets the "two competing OpenWrt-specific WWAN stacks" and "replacing established handlers" concerns: nothing is replaced unless the operator explicitly opts in, and the stock stack keeps owning On maturity and independent review — I fully agree these matter for low-level protocol/state-machine code. It's being maintained and shipped from an external feed (github.com/ddimension/openwrt-repo), runs on production hardware here (QMI/MBIM/NCM across several modems), and I'm actively hardening it. I'd like to keep improving it in the open rather than sit on it. Given the coexistence rework removes the "replaces the existing stack" objection, would you be open to leaving this open for another ~2 weeks while I continue to mature it — broaden hardware coverage, gather field bug reports, and address review feedback? I'll keep the PR updated regularly as that happens. If after that you still feel the scope is too broad for the feed, I completely understand keeping it external for now. |
openwrt-ai
left a comment
There was a problem hiding this comment.
Re-review of the one new commit since my last pass (d802b81, "wwand: 1.1.0 — coexist with the stock WWAN stack"). Packaging-level only; the inclusion/scope discussion is a maintainer call and I'm not restating it.
The three packaging points from the previous round are all addressed: ucode-mod-wwand-io now depends on +ucode, wwand-esim gained SUBMENU:=WWAN, and the install step uses $(CMAKE_BINARY_DIR) — which expands to $(PKG_BUILD_DIR)/build given CMAKE_BINARY_SUBDIR:=build, so the path is unchanged and now tracks L29. The dead qmi-advanced CONFLICTS entry went away with the rest of the CONFLICTS lines. The version bump keeps PKG_RELEASE:=1, which is correct for a PKG_VERSION change. Commit message matches the diff.
Nothing new blocks a merge. The one worth an answer before this lands is device ownership under the new coexistence model: CONFLICTS is what previously guaranteed only one stack could touch a given /dev/cdc-wdmX, and the replacement guarantee is a runtime property of the daemon that the packaging can't show. The takeover / proto qmi alias question is the same theme from the netifd side. The other two are nits.
Two earlier threads are still open and I haven't repeated them: the glob-then-rm split in Package/wwand/install, and the missing test-version.sh for the generic CI version check. Only the FormalityCheck jobs have reported on this head (all green), so the build/test CI hasn't exercised the latter yet.
I still can't verify PKG_HASH or the v1.1.0 tag — this session's GitHub access is scoped to openwrt/packages, so ddimension/wwand is unreachable to both the API and a plain fetch. Treat those as unchecked rather than checked-and-fine.
Generated by Claude Code
Package-G artefacts for the openwrt/packages#30185 + openwrt/luci#8917 push: - upstream/dossier-packages-30185.md — maturity/coexistence dossier to post as a PR comment (ModemManager precedent, independent-verification story, HW/test matrix, audit changelog, device-ownership answer, re-review request). - upstream/rfc-openwrt-devel.md — the architecture RFC (improve-existing-stack vs. a separate opt-in proto) for openwrt-devel. - upstream/checklist.md — the push/tag/PR/feed action sequence. Drafts for author review; sending/posting is manual. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Updated to wwand 1.2.0 (tag Since 1.1.0:
LuCI side is updated in openwrt/luci#8917. |
openwrt-ai
left a comment
There was a problem hiding this comment.
Re-review of the one new commit since my last pass (345c8b1, "wwand: 1.2.0"). Packaging-level only; the inclusion/scope discussion in the maintainer review above is a maintainer call and I'm not restating it.
The bump itself is clean: PKG_RELEASE stays at 1, which is correct for a PKG_VERSION change, and the new /usr/bin/wwandctl install is properly paired with a wwandctl.uc entry in the base package's strip list, so it doesn't also leak into $(UCDIR). The commit message matches the diff. I confirmed the v1.2.0 tag exists in ddimension/wwand (resolves to commit 1d75ae8); PKG_HASH I still can't verify — codeload.github.com is not reachable from this session — so treat that one as unchecked rather than checked-and-fine.
One finding is worth acting on before merge: 1.2.0 also adds files/wwand.hotplug.wwan, a fourth hotplug script that this Makefile never installs, which leaves the "PCIe/MHI (kernel wwan subsystem) discovery" listed in the commit message working only for modems already present when the daemon starts. The other two comments are nits.
Earlier threads are still open and I haven't repeated them: the glob-then-rm split in Package/wwand/install, the missing test-version.sh (one new comment does extend that one, since 1.2.0 adds a second executable that definitely fails the generic check), the proto qmi alias ownership question under takeover, the cdc-wdm device-ownership question, and the PR body still describing the pre-1.1.0 CONFLICTS / auto-migrate model. Only the three FormalityCheck jobs have reported on this head, all green, so the build/test CI still hasn't exercised any of this.
Generated by Claude Code
openwrt-ai
left a comment
There was a problem hiding this comment.
Re-review of the one new commit since my last pass. 345c8b1 was amended into fc0388e (same subject, wwand: 1.2.0); the only delta against what I reviewed last time is the four-line wwan hotplug hunk in Package/wwand/install. Packaging-level only; the inclusion/scope discussion in the maintainer review above is a maintainer call and I'm not restating it.
That hunk resolves the one finding I flagged as worth acting on last round: files/wwand.hotplug.wwan is now installed to /etc/hotplug.d/wwan/20-wwand, and the paired $(INSTALL_DIR) is what arms the dispatch — procd only calls hotplug-call for a subsystem whose directory exists (hotplug.json:83-86). INSTALL_DATA matches the three sibling hotplug installs above it and the in-tree precedent for this same subsystem (modemmanager Makefile:158-160), and the filename doesn't collide with modemmanager's 25-modemmanager-wwan, so both can be installed. Commit message matches the diff, PKG_RELEASE stays at 1, which is correct for the PKG_VERSION change.
Nothing new to flag on this head. Earlier threads are still open and I haven't repeated them: the glob-then-rm split in Package/wwand/install, the missing test-version.sh (three installed executables now), the proto qmi alias ownership question under takeover, the cdc-wdm device-ownership question, the MHI kmod hint in Package/wwand-qmi/description, the bare wwand: 1.2.0 commit subject, and the PR body still describing the pre-1.1.0 CONFLICTS / auto-migrate model.
Two things I could verify last round but cannot on this one: PKG_HASH and the v1.2.0 tag. This session's GitHub access is scoped to openwrt/packages, and codeload.github.com is unreachable, so ddimension/wwand is out of reach to both the API and a plain fetch. PKG_HASH is unchanged by this amend, so nothing new rests on it — but treat it as unchecked here rather than checked-and-fine.
Only the three FormalityCheck jobs have reported on this head, all green, so the build/test CI still hasn't exercised any of this.
Generated by Claude Code
|
Pushed a squashed head (
Answering the two open questions, since they're the ones that can't be read off the packaging:
cdc-wdm device ownership. wwand opens a control device only when a glob-then- |
openwrt-ai
left a comment
There was a problem hiding this comment.
Re-review against the new head. The branch was squashed since my last pass — the four commits I had reviewed (fc0388e and its ancestors) are now the single commit 8ffb9e3, "wwand: add cellular connection manager". Diffing fc0388e..8ffb9e3 on net/wwand/, the only content delta is two things, both closing threads from earlier rounds:
net/wwand/test-version.sh(new) — opts all six binary packages out of the generic version check. It follows the in-tree$PKG_NAMEcase/*)-fallback shape (e.g.libs/expat/test-version.sh), has nogrep -q, and lists every package the Makefile builds, so the*)branch can't fire.Package/wwand-qmi/description(L130-134) — the MHI kmod hint. All three names check out against openwrt main:kmod-mhi-wwan-ctrl,kmod-mhi-net,kmod-mhi-pci-generic.
The squash also resolved the bare-wwand: 1.2.0 subject nit on its own. The single commit's message matches its diff, and PKG_RELEASE:=1 is correct for a new package. Nothing new blocks a merge; the one comment below is a nit.
Worth noting because it changes what is actually verified: the full build CI has now run on this head and is green on all ten architectures, so test-version.sh is confirmed working rather than assumed — the x86_64 runtime job logs wwand-qmi: Version test override script found and [pass] Version check override for each package, then All tests passed. (The * Failed to connect to ubus lines around the wwand post-install/pre-deinstall in that log are just default_postinst's "$i" start on a procd init script in a container with no ubus — not a defect here.) lpac in this feed is still at 2.3.0, so the >= 2.3.0 note in Package/wwand-esim/description continues to hold.
Earlier threads are still open and I'm not repeating them: the glob-then-rm split in Package/wwand/install, the proto qmi alias ownership question under takeover, the cdc-wdm device-ownership question, the qmi-advanced mention that survives in the L121 prose, the ucode-mod-io reuse question, and the PR body still describing the pre-1.1.0 CONFLICTS / auto-migrate model. The scope and inclusion discussion in the maintainer review above is a maintainer call and I'm not restating it.
PKG_HASH and the v1.2.0 tag I still cannot check — this session's GitHub access is scoped to openwrt/packages, so ddimension/wwand is unreachable to both the API and a plain fetch. Neither changed in this delta, but treat them as unchecked rather than checked-and-fine.
Generated by Claude Code
|
Updated to wwand 1.3.0 (tag
New in 1.3.0 as well: the ucode tree ships precompiled to bytecode by default (repo-root CMakeLists, built next to On the broader architecture question: I'd still welcome a discussion with the WWAN/netifd maintainers. wwand is deliberately additive here (opt-in per interface, nothing replaced by default), and I'm happy to feed back any of the recovery / persistent-context / QMAP / netifd-integration work into the existing stack where that's the better home. |
|
I very much welcome and look forward to this component, because the existing proto_qmi, proto_mbim, and proto_ncm only implement basic connection functionality, lacking a GUI interface and advanced features such as band locking, SMS reading, signal strength, etc. This component can provide unified management and reduce the learning cost for users. As for the possibility that the author might stop maintaining it in the future, we can simply remove this package then. |
openwrt-ai
left a comment
There was a problem hiding this comment.
Re-review against the new head. The branch was squashed again — 8ffb9e3 is now the single commit 8e33265 with the same subject, so I diffed 8ffb9e3..8e33265. The delta is the 1.3.0 bump plus four structural changes: the ucode tree is now precompiled to bytecode by a repo-root cmake build (PKG_BUILD_DEPENDS:=ucode/host, UCODE_COMPILER/UCODE_PRECOMPILE, the new CONFIG_WWAND_UCODE_SOURCE opt-out), the base install switched from glob-then-rm to explicit WWAND_BASE_UC/_CODEC/_SCHEMA lists, ucode-mod-wwand-io was folded into the base package (PROVIDES:=ucode-mod-wwand-io) and replaced in the package set by the new wwand-mhi transport bundle, which is also where the wwan hotplug now lives. Packaging-level only; the inclusion/scope discussion in the maintainer review above is a maintainer call and I'm not restating it.
Three earlier threads close with this head, and the replacements check out: the base package no longer installs anything a backend owns; wwand-mhi's DEPENDS names four kmods that all exist in openwrt main, including kmod-mhi-wwan-mbim, which answers the MBIM-over-MHI question I raised last round; and test-version.sh was updated in step with the package set (wwand-mhi in, ucode-mod-wwand-io out), so the *) fallback still cannot fire. The single commit's message matches its diff and PKG_RELEASE:=1 is correct for a new package, so no commit checks.
What is new is that the default build path changed shape. Three of the comments below are questions about that path — bytecode/interpreter skew, whether the SDK's host ucode can compile modules that import ubus/uci/uloop/rtnl, and whether a precompiled main.uc is still directly executable as /usr/sbin/wwand. None is a defect I can demonstrate from the packaging alone, and each has a one-line answer if the upstream build already handles it; I raise them because bytecode is the default while every build and runtime test this PR has accumulated so far exercised the source path, and only the three FormalityCheck jobs have reported on this head. The other two comments are nits.
Earlier threads I'm not repeating: the proto qmi alias ownership under takeover and the cdc-wdm device-ownership question (both answered in the PR conversation but the threads are still open), and the PR body still describing the pre-1.1.0 CONFLICTS / auto-migrate model.
PKG_HASH and the v1.3.0 tag remain unchecked — this session's GitHub access is scoped to openwrt/packages, so ddimension/wwand is unreachable to both the API and a plain fetch. Treat them as unverified rather than verified-fine.
Generated by Claude Code
wwand is an event-driven cellular connection manager written in ucode. It drives netifd over ubus with no_proto_task, so the daemon owns the modem and context lifecycle while netifd keeps ownership of all addressing and routing. Six packages: a backend-neutral base (daemon, netifd shim, codec, the native wwand_io.so) plus wwand-qmi, wwand-mbim, wwand-ncm, wwand-mhi and wwand-esim. A backend is loaded lazily, so a missing backend package surfaces as a status note instead of a failure. It coexists with the stock stack rather than replacing it. There is no CONFLICTS on uqmi/umbim/comgt-ncm, and the shim registers `proto wwand` and nothing else — the `qmi` proto name stays uqmi's, so netifd's handler load order never decides who owns an interface. Handing one over is always an explicit act that rewrites it in place to `proto wwand`: the LuCI modem list, /usr/libexec/wwand/migrate, or the example uci-defaults script shipped inert under /usr/share/wwand/examples/. Nothing is installed under /etc/uci-defaults, so installing or upgrading cannot rewrite an existing configuration. The ucode tree ships as source. Bytecode precompilation is available via CONFIG_WWAND_UCODE_PRECOMPILE but stays opt-in: bytecode carries a format version (UCODE_BYTECODE_VERSION) that an interpreter upgraded past it refuses to load, and that version is independent of libucode's PKG_ABI_VERSION/SONAME, so no package relation expresses the coupling. It is only sound when ucode and wwand are built in the same tree, which a self-built image is and a feed is not. Signed-off-by: André Valentin <avalentin@marcant.net>
Netifd protocol handler for `proto wwand`, the cellular connection manager proposed in openwrt/packages#30185. It renders the connection options the daemon consumes — APN, PDP type, authentication, MUX id, MTU handling — and resolves the modem an interface is bound to. wwand registers `proto wwand` only; the historical `qmi` proto name stays uqmi's, so this handler never competes with the stock one for an interface. Signed-off-by: André Valentin <avalentin@marcant.net>
Status and management UI for the wwand cellular connection manager proposed in openwrt/packages#30185: modem overview, per-modem tools (radio technologies, band selection, network selection and scan, cell lock), SIM and eSIM profile management, SMS, and the per-interface migration entry point that converts a stock cellular interface to `proto wwand` in place. Everything is driven over the daemon's ubus API; the ACL file lists exactly the methods the pages call. Signed-off-by: André Valentin <avalentin@marcant.net>
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed 1 new commit; one question below.
The branch was squashed again — my last pass was 881679d (v1.5.2) and the delta is the single commit 6f5d357, same subject. Diffing 881679d..6f5d357 on net/wwand/, the whole change is the 1.5.2 → 1.6.0 bump (PKG_VERSION, PKG_HASH), the datapath_qmi.uc → modem_datapath_qmi.uc rename in WWAND_BASE_UC, two new wwand-qmi install entries, USB_SUPPORT-conditional driver dependencies on all three backends (+kmod-usb-net-rndis added to wwand-ncm), and a rewritten paragraph in Package/wwand-mhi/description. PKG_RELEASE:=1 is correct for a new package and the commit message matches its diff, so no commit checks. Packaging-level only; the scope and inclusion discussion in the maintainer review above is a maintainer call and I'm not restating it.
Nothing here blocks a merge. The one comment below is a question about whether wwand-ncm belonged in the USB_SUPPORT sweep, since it is the one backend with no non-USB transport.
Verified against the release this pins. github.com/ddimension/wwand is reachable over plain git from this session, so this is a check against the tree rather than an inference. Tag v1.6.0 exists (annotated 11ed391 → commit efbc360), and against it:
- Module ownership is exact. All 84
.ucfiles insrc-ucode/are installed by exactly one package — no orphans, no duplicates, and nothing the Makefile names is missing from the release.v1.5.2..v1.6.0changes exactly three packaged files (datapath_qmi.ucremoved,modem_datapath_qmi.uc+datapath_rmnet_nss.uc+datapath_rmnet_nss_mhi.ucadded) and the install lists moved with all three. That is the failure mode the explicit-per-file lists exist to expose, and this is the second bump in a row where it was caught rather than missed. - The rename is load-bearing, not cosmetic.
daemon.ucglobs$(UCDIR)/datapath_*.ucandrequire()s each match, sodatapath_<name>.ucis the add-on namespace; keeping the olddatapath_qmi.ucin the base list would have registered the QMI bring-up module as a selectable mux plug-in. The base list follows upstream's rename correctly, and the plug-in name is regex-restricted (/^[a-z][a-z0-9_]*$/) before it reachesrequire(). - The two new plug-ins are genuinely inert on a stock kernel, as the install comment claims —
datapath_rmnet_nss.ucprobes onqmap_modesitting directly on the netdev, which only the vendorqmi_wwan_qattribute group (no.name, hence noqmi/subgroup) produces; mainlineqmi_wwanhas no such node. Shipping them inwwand-qmirather than in packages of their own matches how the daemon finds them. - No new dependency is owed. The tree's native imports are still
fs,struct,ubus,uci,uloop,rtnlandwwand_io— all covered by the base package'sDEPENDS. All eight$(PKG_BUILD_DIR)/files/...paths resolve, includingfiles/examples/99-wwand-migrate(#!/bin/sh);files/wwand-proto.shcontains a singleadd_protocol wwandand notakeover, matching the commit message;main.ucandwwandctl.ucstill start with#!/usr/bin/env ucode, so the source-default path installs them directly executable. - The
+SYMBOL:pkgdependency form is right and has in-tree precedent in this feed —bluezuses+USB_SUPPORT:kmod-bluetooth.kmod-usb-net-rndisexists in openwrt main (usb.mk:1541), andkmod-rmnetcarries no dependency of its own, so leaving it unconditional costs a non-USB target nothing. test-version.shis unchanged and still names all six packages, so the*)branch cannot fire;lpacin this feed is still 2.3.0, so the>= 2.3.0note inPackage/wwand-esim/descriptioncontinues to hold; the PR body says "Current release: v1.6.0 — the release this Makefile pins", which matches L27.
What I could not verify. PKG_HASH — codeload.github.com and github.com/.../archive/... both answer this session with an access-denied JSON body rather than a tarball, even though the git endpoint for the same repo is reachable, so the archive bytes are out of reach. Treat it as unverified rather than verified-fine.
Only the three FormalityCheck jobs have reported on this head, all green. The full build/runtime CI has not run since 8ffb9e3 (the 1.3.0 head), so the current default build path — ucode source, precompile off, no host ucode staged — and the current install lists remain unexercised by this repo's CI.
Earlier threads still open and not repeated here: the cdc-wdm device-ownership question, the ucode-mod-io reuse question, the deferred backend/transport split, and — for the opt-in precompile path only — the host-ucode capability and bytecode/interpreter-skew questions.
Generated by Claude Code
| DEPENDS:=+wwand +USB_SUPPORT:kmod-usb-net-cdc-ncm \ | ||
| +USB_SUPPORT:kmod-usb-net-cdc-ether +USB_SUPPORT:kmod-usb-net-rndis |
There was a problem hiding this comment.
Was wwand-ncm meant to be part of the USB_SUPPORT sweep? Unlike wwand-qmi (which keeps +kmod-rmnet unconditional and reaches a modem over MHI) and wwand-mbim (kmod-mhi-wwan-mbim via wwand-mhi), this backend has no non-USB transport at all: context_ncm.uc drives a single cdc_ncm/cdc_ether netdev and the only variant it recognises is rndis_host (self.modem.datapath?.backend != 'rndis_host'), and you noted yourself on the earlier thread that there is no cdc_ncm/cdc_ether/rndis_host netdev on the MHI bus — wwand-mhi's datapath kmods are kmod-mhi-net and kmod-mhi-wwan-mbim.
So the gate that used to come free with the hard +kmod-usb-net-cdc-ncm is gone: on a !USB_SUPPORT target this package was previously unselectable, and is now selectable while pulling no datapath driver whatsoever. Package/wwand-mhi/description L332-335 states the intent as "on a PCIe-only target the backends are selectable and pull no USB stack", which is the desired outcome for QMI/MBIM but means installable-and-inert for NCM.
Keeping the three NCM kmods unconditional restores the gate and changes nothing on a USB target:
| DEPENDS:=+wwand +USB_SUPPORT:kmod-usb-net-cdc-ncm \ | |
| +USB_SUPPORT:kmod-usb-net-cdc-ether +USB_SUPPORT:kmod-usb-net-rndis | |
| DEPENDS:=+wwand +kmod-usb-net-cdc-ncm +kmod-usb-net-cdc-ether \ | |
| +kmod-usb-net-rndis |
(@USB_SUPPORT on the package would express the same thing more explicitly, if you prefer that shape.) Unrelated to the gating question: kmod-usb-net-rndis already pulls kmod-usb-net-cdc-ether itself — usb.mk:1541-1547 — so the middle entry is redundant either way, and listing it explicitly is fine.
Generated by Claude Code
There was a problem hiding this comment.
You are right, and thank you for catching it — wwand-ncm should not have been in that sweep.
The sweep holds for the other two because each has a non-USB transport behind it. wwand-qmi keeps +kmod-rmnet unconditional and reaches a modem over MHI; wwand-mbim gets kmod-mhi-wwan-mbim through wwand-mhi. On a PCIe-only target both stay selectable and functional, which is what Package/wwand-mhi/description promises.
NCM has no such path, exactly as you describe. The backend drives a single cdc_ncm/cdc_ether netdev and the only variant it recognises is rndis_host (context_ncm.uc, modem_ncm.uc); there is no MHI NCM netdev, and wwand-mhi ships kmod-mhi-net and kmod-mhi-wwan-mbim, neither of which NCM can drive. So gating the three kmods on USB_SUPPORT removed the implicit gate the hard dependency used to provide: on a !USB_SUPPORT target the package went from unselectable to selectable-while-pulling-no-datapath-driver — installable and inert, as you put it.
Reverted to unconditional, with a comment at the site recording why NCM is the exception rather than an oversight, so a later sweep does not re-do it. Unselectable on a target that cannot run it is the honest result.
Review point on the USB_SUPPORT sweep, and it is right. wwand-qmi and wwand-mbim can afford a conditional USB kmod because each has a non-USB transport behind it: QMI keeps +kmod-rmnet unconditional and reaches a modem over MHI, MBIM gets kmod-mhi-wwan-mbim through wwand-mhi. So on a PCIe-only target those two are selectable AND functional, which was the point of the sweep. NCM has no such path. The backend drives a single cdc_ncm/cdc_ether netdev and the only variant it recognises is rndis_host (context_ncm.uc) — all USB. Gating its three kmods on USB_SUPPORT therefore removed the implicit gate that the hard dependency used to provide: on a !USB_SUPPORT target the package went from unselectable to selectable-while-pulling-no-datapath-driver, i.e. installable and inert. Reverting these three to unconditional restores that gate. Unselectable on a target that cannot run it is the honest result. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: André Valentin <avalentin@marcant.net>
Correctness work since 1.6.0, most of it found in review: - A destroyed QMI client now refuses further requests and detaches from the hub before running its cancellation callbacks. A callback that reads an error as "carry on" used to issue its next request from inside that loop: the frame went out, a timeout timer was armed, and the pending entry it created was wiped by the very loop that had called it — so the reply could never be dispatched while the timer still charged a protocol timeout to the recovery counter, for a client that no longer existed. - The QMUX decoder is bounded by the frame its header declares rather than by however many bytes the read returned, so a message length claiming more than the frame can no longer take its TLVs from whatever followed it. - SMS: a truncated PDU is refused instead of decoding into a plausible message with a sender built from zero bytes; UCS2 and 8-bit honour the declared user-data length; multipart reassembly keys on sender and store, so two senders sharing a concatenation reference no longer merge into one message. - A modem bound by `option serial` or `option imei` is no longer dropped at parse time, and invalid modems are removed before their interfaces are validated against them. - Datapath setup fails when a requested mux channel was not created, instead of reporting success and leaving the control backend to dial into a netifd device that does not exist. - A GPIO write that fails is reported as a failure, so the recovery ladder falls through to the next rung instead of consuming a hardware rung that touched no hardware. - A hub port and the modem behind it are no longer treated as the same device, which had let a foreign claim on either blocklist the other. New in this release: carrier configuration over QMI PDC, distinct attach-bearer credentials, `option lowpower` with a parked lifecycle the registration supervisor knows about, `option gnss` plus the modem's NMEA port reported over ubus for gpsd to be pointed at, and a startup banner naming the build, the installed backends and the available datapaths. The five modules new since 1.6.0 (carrier_config, version, and the CAT, PDC and TMD schemas) are added to the install lists in the same commit — the file lists and the version have to move together, since none of them exist in the 1.6.0 tarball. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: André Valentin <avalentin@marcant.net>
openwrt-ai
left a comment
There was a problem hiding this comment.
Re-review of the two new commits since my last pass (6f5d357..a81b1d8). The branch was not squashed this time. The whole delta is 20 lines in net/wwand/Makefile: f503296 reverts wwand-ncm's three USB kmods to unconditional, and a81b1d8 bumps 1.6.0 → 1.6.1 and adds the five new modules to the install lists. PKG_RELEASE:=1 is correct for a new package. Both commit messages match their diffs, so no commit checks. Packaging-level only; the scope and inclusion discussion in the maintainer review above is a maintainer call and I'm not restating it.
Nothing here blocks a build. The first comment below is a packaging gap worth closing before merge; the second is a nit that f503296 created.
f503296 is right, and the reasoning checks out against the tree. wwand-ncm really is the one backend with no non-USB transport: neither modem_ncm.uc nor context_ncm.uc has any MHI or /sys/class/wwan path, and the only variants the backend recognises are cdc_ncm / cdc_ether / rndis_host. So gating those three on USB_SUPPORT did turn an unselectable package into a selectable-but-inert one on a !USB_SUPPORT target, which is what the comment says. wwand-qmi (+kmod-rmnet unconditional, MHI transport) and wwand-mbim (kmod-mhi-wwan-mbim via wwand-mhi) keep their conditional form correctly — that asymmetry is now spelled out at L286-296.
Verified against the release this pins. github.com/ddimension/wwand is reachable over plain git from this session. Tag v1.6.1 exists (68a3f84), and against that tree:
- Module ownership is exact. All 89
.ucfiles insrc-ucode/are installed by exactly one package — no orphans, no duplicates, and nothing the Makefile names is missing from the release.v1.6.0..v1.6.1adds exactly five packaged modules (carrier_config.uc,version.uc,codec/schema/{cat,pdc,tmd}.uc) and removes/renames none; the install lists moved with all five, which is what the commit message claims. Running upstream's owntools/check-packaging.py --makefileagainst this head agrees: "owns every .uc exactly once". - No new dependency is owed. The tree's native imports are still
fs,struct,ubus,uci,uloop,rtnl(viarequire()innetlink.uc) andwwand_io— all covered by the base package'sDEPENDS.version.ucreads the apk/opkg package database at runtime and needs no build-time generated constant, so nothing new is owed on the Makefile side either. The onlysystem()calls aredate -u -sand the netlink helper's own argv; no new external binary is spawned, so thegnsswork adds nogpsdrelation. - The AT-over-MBIM lazy load is still guarded, which matters more now that
carrier_config.ucand friends widenedmodem_common.uc:atcmd_mbim.uc/atcmd_mbim_lazy.ucare reached only through thetry/require()inload_at_mbim(), so awwand+wwand-qmi+wwand-mhiinstall with nowwand-mbimreports the capability absent rather than throwing. main.ucandwwandctl.ucstill start with#!/usr/bin/env ucode, so the source-default path installs them directly executable as/usr/sbin/wwandand/usr/bin/wwandctl.test-version.shis untouched by this delta and still names all six packages, so the*)branch cannot fire. The PR body says "Current release: v1.6.0", which now lags L27 by one — the same drift that closed a few rounds back.
What I could not verify. PKG_HASH: codeload.github.com answers the archive URL with an access-denied JSON body rather than a tarball for this session, even though the git endpoint for the same repo is reachable, so the archive bytes stay out of reach. Treat it as unverified rather than verified-fine.
Only the three FormalityCheck jobs have reported on this head, all green. The full build/runtime CI has not run since 8ffb9e3 (the 1.3.0 head), so the current default build path — ucode source, precompile off, no host ucode staged — and the current install lists remain unexercised by this repo's CI, which is why the packaging gap in the first comment is only visible from a read of the release tree.
Earlier threads still open and not repeated here: the cdc-wdm device-ownership question, the ucode-mod-io reuse question, the deferred backend/transport split, and — for the opt-in precompile path only — the host-ucode capability and bytecode/interpreter-skew questions.
Generated by Claude Code
| # tty hotplug: AT ports appearing after the datapath netdev (vendor-serial | ||
| # new_id bind / late kmodloader) re-kick a modem parked in no_at_port backoff | ||
| $(INSTALL_DIR) $(1)/etc/hotplug.d/tty | ||
| $(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.tty $(1)/etc/hotplug.d/tty/20-wwand |
There was a problem hiding this comment.
1.6.1 adds a fifth hotplug script upstream, files/wwand.hotplug.e1820, and no package installs it — it ships in the tarball and lands nowhere. v1.6.0..v1.6.1 adds exactly one new files/ entry, and while the five new .uc modules moved into the install lists in this commit, this one did not.
The script is not cosmetic for the hardware it targets: its own header says it is meant to be /etc/hotplug.d/usb/21-wwand-e1820, and it re-creates a volatile qmi_wwan new_id entry for the Huawei E1820 (12d1:14ac) on every enumeration — without it that modem's control channel never appears at all, because cdc_ether blacklists the device and qmi_wwan's static table entry does not match its class-02 layout.
This is precisely the blind spot in the invariant checks: upstream's own tools/check-packaging.py only asserts that every $(PKG_BUILD_DIR)/files/... path the Makefile names resolves, not that every files/ entry is named by something. Running it against this head reports all 8 files/ paths resolve out of 10 present — the two it cannot see are files/wwand.config (deliberate, documented at L169-171) and this one.
INSTALL_DATA and the paired $(INSTALL_DIR) match the three sibling hotplug installs, and the directory is what arms dispatch — procd only runs hotplug-call for a subsystem whose directory exists (hotplug.json:83-86). Worth knowing before applying: usb is a high-traffic subsystem, so creating /etc/hotplug.d/usb makes procd fork hotplug-call for every USB uevent on the box, not just this modem's.
| $(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.tty $(1)/etc/hotplug.d/tty/20-wwand | |
| $(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.tty $(1)/etc/hotplug.d/tty/20-wwand | |
| # usb hotplug: the Huawei E1820 (12d1:14ac) needs a volatile qmi_wwan new_id | |
| # entry re-created on every enumeration, or its control channel never appears | |
| $(INSTALL_DIR) $(1)/etc/hotplug.d/usb | |
| $(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.e1820 $(1)/etc/hotplug.d/usb/21-wwand-e1820 |
The suggestion puts it in the base package alongside the other three, which is where the sibling hotplugs live — but the script binds qmi_wwan specifically, so wwand-qmi is arguably the better owner by the same reasoning that moved the wwan hotplug into wwand-mhi. Your call; either placement closes the gap. If it is deliberately left out for now, a NOTE: next to the one at L181-184 would say so, the way the wwand hotplug's omission does.
Generated by Claude Code
There was a problem hiding this comment.
This is a temporary not-yet-validated debugging script for my hardware. And when copied to a box containing this modem, it does not actually work after a reboot, for reasons yet unknown.
There was a problem hiding this comment.
Good catch, thank you — it landed nowhere, exactly as you say.
files/wwand.hotplug.e1820 is the whole mechanism for that modem: the kernel defers the E1820 to qmi_wwan (cdc_ether blacklists it, and qmi_wwan's own table entry wants a vendor-specific class this 2009-era stack does not carry), so the control channel only appears once a scoped dynamic new_id binds interface 1 — and that binding is volatile, so it has to be re-created on every replug. Shipping the script without installing it means the modem simply never comes up, with nothing failing to say why.
Installed to /etc/hotplug.d/usb/21-wwand-e1820 in the base package now, matching the path the daemon expects.
The reason it got past me is worth stating, because it is fixable: tools/check-packaging.py in the wwand tree verifies that every path the Makefile NAMES exists in the source, and never asked the other direction — whether every shipped file is named by some package. It printed "ok" while telling me "10 files/ entries" against "8 files/ paths resolve", and I read past the difference. The check now asks both directions and fails by name on an uninstalled file; the two deliberate exceptions (wwand.config, which this Makefile documents as an example that is never installed, and directory entries) are listed with their reason. Removing the install line again makes it fail, which is how I verified it.
There was a problem hiding this comment.
@patrakov Your assessment was right, and the cause turned out to be findable —
so I want to put it back to you, because you own the only E1820 I know of.
The script never bound anything, on any event. Two independent faults, both in
the idempotency guard:
[ -e "/sys/bus/usb/devices/${DEVPATH#/devices/}:1.1/driver" ] && \
return 0 2>/dev/null || exit 0/sys/bus/usb/devicesholds flat kobject names (3-1,3-1:1.1), never
a$DEVPATH-shaped path, so that test could not match anything. Measured on
a MikroTik Chateau 5G against a live modem: the expression resolves to
/sys/bus/usb/devices/platform/soc@0/.../usb3/3-1:1.1/driver, which does not
exist.- Worse, the line does the opposite of what it reads like. When the path is
absent — the case we want to continue past —[ -e ]fails,&&is
skipped, and||firesexit 0. The binder was unreachable in exactly the
situation it exists for.
So new_id was never written from hotplug. Setting it by hand worked, and after
a reboot nothing did — which is precisely what you described, and why the cause
was "yet unknown": the script looked like it ran, and did nothing.
Fixed upstream after 1.6.1 (b7222e6), with the path corrected to the child
form (/sys$DEVPATH/${DEVPATH##*/}:1.1/driver — an interface is a child of its
device, not a sibling; the sibling form that was suggested in review does not
exist either) and a DEVTYPE=usb_device gate, since usb_uevent() emits
PRODUCT for interfaces too. There is a test that runs the real script against
a sysfs miniature and asserts the path shapes separately from the behaviour.
The boot-time modprobe qmi_wwan race was a different fix and it is already
in the 1.6.1 this PR pins — so on your build that half was fine and the guard
was the whole problem.
Would you retest when you have a moment? The corrected script is in the
wwand tree at files/wwand.hotplug.e1820; dropping it in as
/etc/hotplug.d/usb/21-wwand-e1820 and replugging (or rebooting) is the whole
test. If it still does not bind, I would rather hear that than assume the fix
landed — I have never had this hardware and everything above is measured on
other modems plus a sysfs fixture.
Independently of the answer, the script stays out of the base package here. The
reason is no longer your report but the dispatch cost: installing it creates
/etc/hotplug.d/usb, and procd then forks hotplug-call for every USB uevent
on every wwand install. The note in the Makefile now says that instead
(09a56ad).
There was a problem hiding this comment.
Yes, I can retest tomorrow. But I would also like to highlight that wwand is supposed to support PPP-only modems, which my modem without the hotplug script is. And it fails to support my E182e (naming it as it self-identifies via ATI) as a PPP-only stick.
There was a problem hiding this comment.
Nope, this E3372 issue is not a weak-signal issue. The LED is on, but the interface is down:
4: wwand1: <BROADCAST,MULTICAST> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
link/ether 00:1e:10:1f:00:00 brd ff:ff:ff:ff:ff:ff
There was a problem hiding this comment.
After update to latest version the Huawei E182E conns come up fine. Connection over Huawei E3372 also comes up, but sending does not work. I tried several things, but it seems to be "without charge", no volume left. Perhaps you could swap cards between E182E and E3372 to finally verify it.
There was a problem hiding this comment.
I have enough money on the SIM that is in the e3372 and 65 GB remaining out of the package. Unfortunately, swapping the SIM cards between the modems is not possible: the SIM in the E3372 belongs to an operator that does not have 2G/3G coverage at all, while E182E only supports 2G and 3G.
Let me also try fixing the connectivity. Maybe it's just bad signal indeed.
There was a problem hiding this comment.
Well, it came up, but the route is still wrong:
default dev wwand1 proto static scope link metric 90
it needs to be via the gateway, not via the device.
There was a problem hiding this comment.
Fixed, and it is in the release this Makefile pins.
You were right twice over: the device route was wrong on that link, and my first answer to it was wrong too. The shape now depends on whether the link can actually resolve a neighbour, instead of being one rule for every modem:
default via <gw> dev X # ARP/ND link: a nexthop the kernel can resolve
default dev X scope link # IFF_NOARP p2p link: no nexthop to resolvedefault dev X scope link has no nexthop at all, so the kernel ARPs the destination. That only works on a NOARP point-to-point link — which is what the raw-IP modems give you, and why it went unnoticed. Your E3372 hands out an 802.3 link with ARP, and there it is simply broken.
Current state on your box, all three modems:
default dev wwand0 proto static scope link metric 55
default via 100.77.95.133 dev wwand1 proto static metric 90
and the flag the code keys off:
wwand0 flags=0x1083 <- 0x80 = IFF_NOARP -> scope link, no gateway
wwand1 flags=0x1003 <- no 0x80 -> via 100.77.95.133
So wwand1 is exactly the line you quoted, now with the nexthop; wwand0 keeps the device form because on that link it is the correct one. wwand2 (the E182E) reads 0x1003 as well and gets the gateway form when it is up.
Landed in 6d91d2c, contained in v1.6.3 — PKG_VERSION:=1.6.3 in this PR, so nothing further is needed here. The shell guard reads /sys/class/net/$netdev/flags defensively: a missing or non-hex value falls back to the plain default route rather than aborting the handler, since $(( )) on a non-number kills an ash script outright.
| nothing on this path any more: the USB glue of every backend is now a | ||
| USB_SUPPORT-conditional dependency, so on a PCIe-only target the backends are | ||
| selectable and pull no USB stack, while a USB target gets exactly what it did |
There was a problem hiding this comment.
nit: f503296 makes this sentence false as written. "the USB glue of every backend is now a USB_SUPPORT-conditional dependency" was accurate at 6f5d357, but that commit reverted wwand-ncm's three kmods to unconditional (L297-298) on purpose, so "every backend" is now two of the three. The paragraph is only ever talking about the QMI/MBIM pair on an MHI box, where the claim still holds — it is the generalisation that no longer does.
| nothing on this path any more: the USB glue of every backend is now a | |
| USB_SUPPORT-conditional dependency, so on a PCIe-only target the backends are | |
| selectable and pull no USB stack, while a USB target gets exactly what it did | |
| nothing on this path any more: the USB glue of wwand-qmi and wwand-mbim is a | |
| USB_SUPPORT-conditional dependency, so on a PCIe-only target those two are | |
| selectable and pull no USB stack, while a USB target gets exactly what it did |
Generated by Claude Code
There was a problem hiding this comment.
You are right — f503296 made that sentence false, and I did not go back to the prose that described the sweep.
Reworded to say what the paragraph is actually about: the two backends that can serve an MHI modem, wwand-qmi and wwand-mbim, are the ones whose USB kmods are USB_SUPPORT-conditional, so on a PCIe-only target they stay selectable and pull no USB stack. The NCM exception is now stated there rather than left for a reader to discover in the DEPENDS line — it drives a cdc_ncm/cdc_ether or rndis_host netdev, has no non-USB transport, and its hard kmod dependencies are what keep it unselectable where it could not work anyway.
| # dynamic new_id is what makes the control channel appear at all. It is | ||
| # volatile: every replug needs it re-created. | ||
| $(INSTALL_DIR) $(1)/etc/hotplug.d/usb | ||
| $(INSTALL_DATA) $(PKG_BUILD_DIR)/files/wwand.hotplug.e1820 $(1)/etc/hotplug.d/usb/21-wwand-e1820 |
There was a problem hiding this comment.
The idempotency guard in the script this installs can never match: /sys/bus/usb/devices/ holds flat kobject names (1-1, 1-1:1.1), not the pci0000:00/…/usb1/1-1 path that ${DEVPATH#/devices/} yields, so wwand.hotplug.e1820:29 always falls through and new_id is written on every matching add event — each write appends another dynid to qmi_wwan rather than being a no-op. It also runs for the interface uevents, not just the device one, since PRODUCT is exported on those too. Upstream wants /sys$DEVPATH:1.1/driver and a DEVTYPE=usb_device gate.
Generated by Claude Code
There was a problem hiding this comment.
Confirmed, all three parts — and the suggested path does not work either, which
is the useful part of checking it.
The guard never matched. /sys/bus/usb/devices/ holds flat kobject names
(3-1, 3-1:1.1), so ${DEVPATH#/devices/} produced a path that directory
never contains. Evaluated on a MikroTik Chateau 5G (2026-09-05) against a live
modem, that expression resolves to
/sys/bus/usb/devices/platform/soc@0/.../usb3/3-1:1.1/driver and does not
exist.
new_id is not idempotent. usb_store_new_id() kzallocs a usb_dynid and
list_add_tail()s it with no lookup (drivers/usb/core/driver.c, 6.18.41), so
each run really did append another entry.
PRODUCT is on interface events. usb_uevent() in that same file handles
is_usb_interface(dev) explicitly and emits PRODUCT from the parent device's
descriptor. Confirmed on hardware: /sys/bus/usb/devices/3-1:1.1/uevent carries
DEVTYPE=usb_interface alongside PRODUCT=2c7c/122/515.
Where the proposal needs correcting: /sys$DEVPATH:1.1/driver does not exist
either. An interface is not a sibling of its device, it is a child of it —
/sys/bus/usb/devices/3-1:1.1 resolves to .../usb3/3-1/3-1:1.1. On the same
box the sibling form misses and /sys$DEVPATH/${DEVPATH##*/}:1.1/driver hits,
reporting option as the bound driver.
Fixed upstream of this tarball with that path plus the DEVTYPE=usb_device
gate you asked for, and covered by a test that runs the real script against a
sysfs miniature — it asserts the path shapes separately from the behaviour, so
a future kernel layout change reports which property moved rather than only
that binding broke. Reverting either half of the fix turns exactly one check
red.
For this PR the question is moot in the other direction: the script is no
longer installed at all, per the thread above.
| # usb hotplug: bind qmi_wwan to the Huawei E1820's ethernet function (USB | ||
| # 12d1:14ac, interface 1, class 02/06). The kernel defers the device to | ||
| # qmi_wwan — cdc_ether blacklists it, and qmi_wwan's own table entry wants a | ||
| # vendor-specific class this old stack does not carry — so the scoped | ||
| # dynamic new_id is what makes the control channel appear at all. It is | ||
| # volatile: every replug needs it re-created. |
There was a problem hiding this comment.
The owner of the E1820 hardware describes this script as "a temporary not-yet-validated debugging script" that "does not actually work after a reboot, for reasons yet unknown" — which conflicts with the commit message calling it "the whole mechanism" for that modem. Installing it in the base package puts it on every wwand install and arms /etc/hotplug.d/usb dispatch for all USB events; is it ready for that, or should it stay unpackaged until it is confirmed working?
Generated by Claude Code
There was a problem hiding this comment.
You are right, and so is the hardware owner. Removed from the install.
The conflict you spotted is real: the commit message described it as the whole
mechanism for that modem while the person who owns the modem calls it an
unvalidated debugging script that does not survive a reboot. Installing it from
the base package put it on every wwand install and armed /etc/hotplug.d/usb
dispatch for all USB events, which is not a trade worth making on that
evidence.
It stays in the source tree and the maintainer's own feed installs it, so the
people actually testing that modem still get it. The Makefile now records the
omission explicitly rather than leaving it looking like an oversight — the
packaging check this repo's contributor tooling runs would otherwise flag a
shipped-but-uninstalled file, which is exactly how the missing install was found
in the first place.
Separately, the script was broken in two ways; see the thread below for the
evidence and the fix.
| stack, while a USB target gets exactly what it did before. (wwand-ncm is | ||
| deliberately not in that sweep: it drives a cdc_ncm/cdc_ether or rndis_host | ||
| netdev and has no non-USB transport, so its kmods stay hard dependencies and | ||
| keep it unselectable where it could not work anyway.) Without wwand-mbim the capability is simply absent — vendor AT |
There was a problem hiding this comment.
nit: 120 chars, while the rest of this description block wraps at ~80.
| keep it unselectable where it could not work anyway.) Without wwand-mbim the capability is simply absent — vendor AT | |
| keep it unselectable where it could not work anyway.) Without wwand-mbim the | |
| capability is simply absent — vendor AT |
Generated by Claude Code
There was a problem hiding this comment.
Applied, thanks.
For the record, two other lines in this file exceed 100 characters (67 and 146),
but both are make code — a variable assignment and an $(INSTALL_DATA) — where
the prose wrap does not apply and a continuation would cost more readability
than it buys. You flagged the one that was actually prose.
Two independent mistakes in the same three lines, both silent.
The unbound check was built from /sys/bus/usb/devices/${DEVPATH#/devices/} —
but that directory holds only flat kobject names (`3-1`, `3-1:1.1`), never a
$DEVPATH-shaped one, so it matched nothing and the binder ran on every event.
The obvious repair is wrong too: an interface is not a sibling of its device,
so /sys$DEVPATH:1.1 does not exist either. It is a child, hence
/sys$DEVPATH/${DEVPATH##*/}:1.1 (HW-checked on a Chateau 5G, 2026-09-05).
And there was no device/interface gate. usb_uevent() handles is_usb_interface()
and emits PRODUCT from the parent's descriptor (drivers/usb/core/driver.c,
6.18.41), so a plug fired the body once per interface as well as once for the
device. That is not cosmetic: usb_store_new_id() (same file) kzallocs and
list_add_tail()s without a duplicate check, so each run appended another dynid
to qmi_wwan, every plug, forever.
test_hotplug_e1820 runs the real script against a sysfs miniature and asserts
the path shapes separately from the behaviour, so a future kernel layout change
reports which property moved instead of only that binding broke. Reverting
either half of the fix turns exactly one check red.
Reported by openwrt-ai on openwrt/packages#30185; the diagnosis was right and
the suggested path was not.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Netifd protocol handler for `proto wwand`, the cellular connection manager proposed in openwrt/packages#30185. It renders the connection options the daemon consumes — APN, PDP type, authentication, MUX id, MTU handling — and resolves the modem an interface is bound to. wwand registers `proto wwand` only; the historical `qmi` proto name stays uqmi's, so this handler never competes with the stock one for an interface. Signed-off-by: André Valentin <avalentin@marcant.net>
Status and management UI for the wwand cellular connection manager proposed in openwrt/packages#30185: modem overview, per-modem tools (radio technologies, band selection, network selection and scan, cell lock), SIM and eSIM profile management, SMS, and the per-interface migration entry point that converts a stock cellular interface to `proto wwand` in place. Everything is driven over the daemon's ubus API; the ACL file lists exactly the methods the pages call. Signed-off-by: André Valentin <avalentin@marcant.net>
| keep it unselectable where it could not work anyway.) Without wwand-mbim the | ||
| capability is simply absent — vendor AT | ||
| commands, the protocol switch and AT telemetry are unavailable, which is a | ||
| limitation rather than a failure. |
There was a problem hiding this comment.
The rewrap left vendor AT as a 40-char orphan mid-sentence — my earlier suggestion was anchored too narrowly and only covered the first two lines, so the rest of the sentence never got reflowed. Reflowing the whole sentence fixes it:
| keep it unselectable where it could not work anyway.) Without wwand-mbim the | |
| capability is simply absent — vendor AT | |
| commands, the protocol switch and AT telemetry are unavailable, which is a | |
| limitation rather than a failure. | |
| keep it unselectable where it could not work anyway.) Without wwand-mbim the | |
| capability is simply absent — vendor AT commands, the protocol switch and AT | |
| telemetry are unavailable, which is a limitation rather than a failure. |
Generated by Claude Code
There was a problem hiding this comment.
| # The E1820 usb binder (files/wwand.hotplug.e1820) ships in the tarball but is | ||
| # deliberately NOT installed here: the only known E1820 owner reports it as an | ||
| # unvalidated debugging script that does not survive a reboot, so it has no | ||
| # business arming /etc/hotplug.d/usb on every install of the base package. The | ||
| # feed package installs it for the people testing that hardware. | ||
| # check-packaging: not-installed files/wwand.hotplug.e1820 |
There was a problem hiding this comment.
nit: b030d93 adds the E1820 hotplug install and 8ec4574 removes it again, so the pair is net-zero in the series. Dropping the E1820 half of b030d93 (keeping its MHI-note correction, which survives) would leave the history without the round trip.
Generated by Claude Code
There was a problem hiding this comment.
fixed, thanks — solved by dropping both commits in the rewrite, so the round trip is gone from the series.
Generated by Claude Code
|
Hi, I tried to play with RUTC50 and RG520N modem. |
There was a problem hiding this comment.
🟡 Changes recommended
There are consistency/packaging issues to address (PR description vs pinned version mismatch, and a wildcard install that conflicts with the stated explicit per-file ownership approach).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a new OpenWrt package definition for wwand, splitting it into multiple installable binary packages (base + protocol backends + MHI transport + optional eSIM support) and adding a runtime version-test override script for the package set.
Changes:
- Introduces
net/wwand/Makefiledefining the source package and six binary packages (wwand,wwand-qmi,wwand-mbim,wwand-ncm,wwand-mhi,wwand-esim) with explicit install logic. - Adds
net/wwand/test-version.shto opt these packages out of the generic runtime version check since the installed executables do not printPKG_VERSION.
File summaries
| File | Description |
|---|---|
| net/wwand/Makefile | Adds the OpenWrt packaging for wwand, including package splits, dependencies, and install steps. |
| net/wwand/test-version.sh | Adds a package-specific version-test override to bypass the generic runtime version check for wwand packages. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| PKG_NAME:=wwand | ||
| PKG_VERSION:=1.6.1 | ||
| PKG_RELEASE:=1 | ||
|
|
||
| PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz | ||
| PKG_SOURCE_URL:=https://codeload.github.com/ddimension/wwand/tar.gz/refs/tags/v$(PKG_VERSION)? | ||
| PKG_HASH:=c7d85b1d6c7f8b524b7eab3f1381b34961e6e22810852b122dcd00b890289d0c |
There was a problem hiding this comment.
Overtaken by the series since — both halves now read 1.6.3: the body says "Current release: v1.6.3 — the release this Makefile pins" and the Makefile sets PKG_VERSION:=1.6.3, so the description and the pin agree.
| $(INSTALL_DATA) $(WWAND_UCODE)/mbim_lazy.uc $(1)$(UCDIR)/ | ||
| $(INSTALL_DATA) $(WWAND_UCODE)/atcmd_mbim.uc $(1)$(UCDIR)/ | ||
| $(INSTALL_DATA) $(WWAND_UCODE)/atcmd_mbim_lazy.uc $(1)$(UCDIR)/ | ||
| $(INSTALL_DATA) $(WWAND_UCODE)/codec/mbim.uc $(1)$(UCDIR)/codec/ | ||
| $(INSTALL_DATA) $(WWAND_UCODE)/codec/mbim_schema/*.uc $(1)$(UCDIR)/codec/mbim_schema/ |
There was a problem hiding this comment.
Correct on the facts — it is the only wildcard in the file; every other .uc is
installed by name. But I think the trade runs the other way here, so let me put
the reasoning up rather than just change it.
What an explicit list would buy, and what it would cost. The failure mode is
not symmetric:
- With the glob, a new schema file that upstream adds lands in
wwand-mbim
without a Makefile change. The risk is shipping a file nobody re-reviewed
here. - With an explicit list, that same file is silently not installed. It is
loaded by name at runtime (wwand.codec.mbim_schema.<service>), so the result
is an import failure on a live modem — MBIM breaks, and the Makefile that
caused it looks perfectly fine.
I would rather fail towards "a file shipped that nobody asked about" than
towards "a backend broken on hardware", and that is the whole of my preference.
Three things bound the risk the glob carries:
- The directory has exactly one owner.
codec/mbim_schema/is installed by
wwand-mbimand by nothing else, and everything under it is an MBIM service
schema by construction — the only importers aremodem_mbim.uc,
context_mbim.uc,mbim_backend.uc,telemetry_mbim.ucand
qmi_over_mbim.uc. There is no plausible file that could appear there and
belong to a different package. - A new file is not unreviewed upstream. The wwand tree's CMakeLists
carries explicit source lists and fails the configure on drift in either
direction — an unlisted.uc, or a listed one that does not exist. So a
schema file cannot appear without a deliberate change there. - The version bump is the review point either way. Any new file arrives
with a new tarball and a newPKG_HASH; a reviewer who wants to know what
changed reads that diff, not the install list.
The contributor tooling knows about this one exception and is explicit about
it. tools/check-packaging.py asserts every .uc is installed by exactly one
package, and handles the glob by claiming direct children only — it
deliberately does not descend, because claiming every descendant would report a
nested file as installed when the shell glob would silently omit it. That is a
false negative against the one invariant the tool exists for.
So the "explicit per-file list" claim in the description is about package
ownership — which files belong to which of the six packages — and that property
still holds exactly: the glob covers one single-owner directory.
That said, this is your project's convention to set. If you would rather have
the six names spelled out, say so and I will do it — it is a small change, and I
would add a check that fails when the list and the directory disagree, so the
silent-omission failure above cannot happen.
|
@simsasss Thank you — the second one is a regression I caused, and you caught it RUT240 / EC25-E: my bug, fixed
The refusal was not wrong, it was in the wrong layer. Negotiating a format we The one case where that error is correct is kept and now says what it means: a There is a regression test that fails on the first assertion — the modem does The RUTC50 / RG520N: I need one more thing from youThis one I cannot pin from the log, and I do not want to guess: Your What I cannot explain is how it got that way. Either way it is stuck now rather than self-healing, and that part is worth To get going immediately: The segfaultPlease open that as its own issue at ddimension/wwand with the wwand version, Both fixes are in the wwand repo; this PR's pinned version will pick them up at |
|
@simsasss The Your RUT240 / EC25-E should connect again without Note r67 never reached the feed — it was superseded before publishing — so r68 Still open from your reports, and neither is forgotten:
Thank you for testing against the tip; this one was reported and fixed inside a |
|
@simsasss That capture is the whole bug — thank you for reproducing it step by The line that does the damage: Adding a second interface on an existing modem deletes that modem's Cause is an ordering asymmetry between rendering and saving.
The user never sees those values and never touches them. Only Fixed in I verified it by replaying both orders against the real Two notes:
|
"the USB glue of every backend is now a USB_SUPPORT-conditional dependency" was true when written and stopped being true one commit later, when wwand-ncm's kmods went back to unconditional on purpose. The paragraph only ever describes the QMI/MBIM pair on an MHI box, where the claim still holds — it is the generalisation that no longer does. Says two of the three, and states the NCM exception where a reader meets it rather than leaving it to be discovered in a DEPENDS line: that backend drives a cdc_ncm/cdc_ether or rndis_host netdev, has no non-USB transport, and its hard kmod dependencies are what keep it unselectable where it could not work anyway. Signed-off-by: André Valentin <avalentin@marcant.net> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
files/wwand.hotplug.e182e ships in the tarball and no package here installs it.
That is deliberate, and the packaging check this project's contributor tooling
runs flags a shipped-but-uninstalled file, so the reason belongs in the Makefile
rather than in a reviewer's memory.
Installing it from the BASE package would create /etc/hotplug.d/usb on every
wwand install, and procd dispatches hotplug-call for a subsystem whose directory
exists — so every USB uevent on the box would fork a script, for one 2009 Huawei
stick almost nobody has. The maintainer's own feed installs it for the people
testing that hardware.
The note also records what the script is and what was wrong with it, because the
review thread that found this rests on a report ("unvalidated, does not survive
a reboot") whose cause is now known and fixed upstream: the idempotency guard
tested a path /sys/bus/usb/devices never holds, and was written so that it
exited in exactly the case the binder exists for.
Signed-off-by: André Valentin <avalentin@marcant.net>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
openwrt-ai
left a comment
There was a problem hiding this comment.
Commit checks
7551787"wwand: say why the E182E usb binder is not installed" — the body namesfiles/wwand.hotplug.e182e, but the file this release ships (and the path the commit's owncheck-packagingline uses) isfiles/wwand.hotplug.e1820.
Generated by Claude Code
| # here. (The file is `files/wwand.hotplug.e1820` in 1.6.2 — the stick is an | ||
| # E182E and upstream renamed the file after this tag; the path below is the one | ||
| # this release actually contains.) Installing it from the BASE package would |
There was a problem hiding this comment.
nit: 7551787 sits one commit before the 1.6.2 bump, so where this lands the Makefile still pins 1.6.1 and the parenthetical describes a release the tree does not build yet. Ordering it after wwand: update to 1.6.2 makes it true at the commit that introduces it.
Generated by Claude Code
There was a problem hiding this comment.
Gone with the rewrite — that intermediate commit is no longer in the series, and the Makefile now pins 1.6.3 in a single bump rather than passing through 1.6.1/1.6.2 on the way.
| include $(TOPDIR)/rules.mk | ||
|
|
||
| PKG_NAME:=wwand | ||
| PKG_VERSION:=1.6.3 |
There was a problem hiding this comment.
nit: the PR body still reads "Current release: v1.6.2 — the release this Makefile pins". Bring the description to 1.6.3 so it and the pin agree.
Generated by Claude Code
There was a problem hiding this comment.
fixed, thanks — the body reads "Current release: v1.6.3 — the release this Makefile pins", matching PKG_VERSION:=1.6.3.
I also re-checked the check count in that same sentence rather than carrying it forward: a worktree at v1.6.3 runs 52 suites, 3917 checks, 0 failures, so "3917 host-side checks" describes the pinned release accurately.
Routing, an old-stack dial path, and PPP scope — all three measured on hardware. The IPv4 default route carries a nexthop where the link resolves neighbours. A device route has no nexthop, so the kernel resolves the DESTINATION on that link: right on a NOARP point-to-point link, wrong on an ethernet-framed one, where it silently requires the modem to proxy-ARP the whole internet. On a Huawei E3372, alternating only the route shape three times: 100% loss on the device route, 0% via the gateway. NOARP links — every RNDIS and raw-IP deployment — keep exactly the routes they had. Old QMI stacks dial again: no invented profile index is sent to a modem that answered INVALID_PROFILE, and the IP family rides in START_NETWORK where SET_IP_FAMILY is refused. A Huawei E182E answers both with errors and never completed a dial before. PPP-only devices are out of scope by decision, and now say so: a serial-only modem still gets one usbnet mode switch, and every dead end of it names OpenWrt's `proto 3g` rather than "leaving unmanaged". Migration never converted a `proto 3g` interface; there is now a test so a later widening cannot take it silently. `ppp` is gone from the documented values of `option protocol`. 1.6.4 adds a unit conversion the release notes should not have to explain twice: WCDMA Ec/Io arrives over QMI as a raw gint16 in -0.5 dB units but over AT already in dB, so one documented key meant two different things depending on which path answered. It is normalised at every point that stores a raw signal reply — the polled read, the indication and the QMI-over-MBIM passthrough. Also: a firmware protocol switch clears an `option protocol` pin it has just invalidated, and the vanish escalation says why it had nothing to pulse on a multi-modem box. deps.uc and wwandctl_fmt.uc join the installed module list. They were extracted from main.uc and wwandctl in this release and would otherwise ship nowhere, which tools/check-packaging.py in the source tree catches when run against the release tarball. Signed-off-by: André Valentin <avalentin@marcant.net> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>



Adds wwand, an event-driven cellular connection manager written in ucode: native QMI, MBIM and NCM/AT control (no uqmi/qmicli/libqmi/glib), direct netifd integration (the daemon owns the context lifecycle — no per-interface monitor process; transient loss renews in place so IPv6-PD/VRF survive), multi-modem + multi-PDP-context via QMAP multiplexing, SIM/eSIM management (SGP.22 via lpac), SMS (send + receive), telemetry, a recovery ladder, board profiles and zero-config autosetup. A
wwandctlCLI and a LuCI app (openwrt/luci#8917) sit on top.Upstream repo: https://github.com/ddimension/wwand (GPL-2.0-only). Current release: v1.6.4 — the release this Makefile pins (4089 host-side checks run without hardware).
One source package, six binary packages:
wwand— backend-neutral base (daemon, codec, netifd proto shim, SIM/APDU layer,wwandctlCLI). It also ships the small native C transport modulewwand_io.so(message-oriented cdc-wdm/tty I/O + rmnet netlink helper) — wwand-private and always version-locked to the ucode side, so it lives inside the base package (PROVIDES ucode-mod-wwand-iofor older configs).wwand-qmi/wwand-mbim/wwand-ncm— per-protocol control backends; install only what the modems need (wwand-qmipulls inwwand; a typical QMI router needs just that).wwand-mhi— PCIe/MHI transport bundle (MHI bus/PCI/control/data kmods + the kernel-wwan-subsystem hotplug) for modems whose control port appears under/sys/class/wwaninstead ofusbmisc. Backend-neutral: pair withwwand-qmiorwwand-mbim.wwand-esim— optional SGP.22 profile management + SM-DP+ download (depends onlpac>= 2.3.0).Since v1.6.0 the QMI mux datapath is an interface rather than a hard-coded branch:
rmnet,qmimuxand the MBIM session mux are entries in it, andwwand-qmiadditionally carries two plug-ins for the out-of-tree vendor QMAP drivers (qmi_wwan_q,pcie_mhi). Neither driver exists in the kernel tree, so on an OpenWrt kernel both plug-ins are inert — their probe finds none of the children those drivers would have registered and declines, leaving the built-in datapaths in charge. 1.6.0 also fixes the QMAP data-aggregation constant:QMAPV5was 8, which is libqmi's QMAPv4, so modems declined it and every QMI link silently fell back to plain QMAP.Packaging. The ucode tree is flat; each package installs an explicit per-file list — no glob-then-
rm, so every module is owned by exactly one package and a new backend file can never silently ship in the base too. The ucode tree ships as source. Bytecode precompilation is available viaCONFIG_WWAND_UCODE_PRECOMPILEbut stays opt-in: bytecode carries a format version (UCODE_BYTECODE_VERSION) that an interpreter upgraded past it refuses to load, and that version is independent of libucode'sPKG_ABI_VERSION/SONAME, so no package relation expresses the coupling. It is only sound when ucode and wwand are built in the same tree — which a self-built image is and a feed is not.Good-citizen coexistence (no stock stack replaced). The packages do not
CONFLICTSuqmi/umbim/comgt-ncm — they install alongside them. The netifd shim registersproto wwandand nothing else: theqmiproto name stays uqmi's, so netifd's handler load order never decides who owns an interface, and there is no switch that changes this. wwand manages onlyproto wwandinterfaces and never adopts a bareproto qmi/mbim/ncmone, so exactly one dialer owns a given interface and the control device behind it. Handing one over is always user-triggered and rewrites it in place toproto wwand: a "Migratable interfaces" list in the LuCI modem page, themigrateubus method / CLI, or an example uci-defaults script shipped inert under/usr/share/wwand/examples/. Nothing is installed under/etc/uci-defaults, so installing or upgrading wwand cannot rewrite an existing configuration. Inclusion is additive and opt-in per interface, not a second implementation that displaces the existing handlers.Hardware-tested on MikroTik Chateau 5G R17 ax (Quectel RG650E, 5G NSA), Zyxel NR7101 (RG502Q), Zyxel LTE3301-Plus (EG06) and Cudy LT300 v3 (MeiG SLM770A-R), plus a GL.iNet GL-X3000 (RM520N-GL); the PCIe/MHI path (
wwand-mhi) is under active validation with community testers on Foxconn T99W175 hardware. Maintained and shipped from an external feed (github.com/ddimension/openwrt-repo) while it broadens coverage and gathers field reports.🤖 Generated with Claude Code