Skip to content

Ncm detect retry - #25286

Draft
carlosz1986 wants to merge 4 commits into
openwrt:mainfrom
carlosz1986:ncm-detect-retry
Draft

carlosz1986 wants to merge 4 commits into
openwrt:mainfrom
carlosz1986:ncm-detect-retry

Conversation

@carlosz1986

@carlosz1986 carlosz1986 commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #25285

ncm.sh asks the modem who it is with AT+CGMI and AT+CGMM and takes the first line that is not the echo. A modem that just booted is still printing unsolicited stuff on the same port, so that line is often a URC and not the vendor. Then the interface is marked unsupported, proto_set_available 0 is called, and netifd never tries again until someone runs ifup by hand.

While testing I also noticed ncm.sh dials even when the modem has no network at all, so udhcpc just broadcasts into nothing.

Problem Fix
1 a URC like RDY or a stray OK becomes the vendor or the model, interface ends up dead skip URCs and status lines, match on the echo of the command we sent
2 identification is only tried once unless you set delay, and config_generate never does keep retrying, one shared deadline
3 the model was never retried, so one bad reply silently uses quectel instead of quectel-eg060w retry the model too
4 "Unsupported modem" does not tell you what it read print vendor and model
5 same gcom+awk copy-pasted four times one helper
6 dials with no network, interface reported up but no lease wait for AT+CGATT? first
7 runcommand.gcom only gives an exit code, you cannot check a reply new runquery.gcom

How to reproduce: with wwan up, power cycle the modem. The tty hotplug script calls proto_set_available 1 as soon as the port is back, so setup runs while the modem is still printing. Doing an ifdown first hides it.

Interface 'wwan' is setting up now
wwan (4950): Unsupported modem
wwan (4997): Stopping network wwan
Interface 'wwan' is now down       <- stays down

With the patches the same power cycle just works.

New options:

  • attachwait, seconds to wait for attach before dialling, default 10, 0 is off. On failure it reports NETWORK_REGISTRATION_FAILED and lets netifd retry.
  • delay still works and still overrides the identification deadline, but the default changed: it used to be a single try, now it keeps trying for 20s. I picked 20 because identification took 9s on five boots in a row here.
  • attachatconnect in ncm.json, for profiles that attach with their connect command. samsung dials with AT+CGATT=1, so gating it would never let it connect. Same idea as the existing linkatfinalize.

Costs one extra AT command on a healthy modem, about 1.5s. linksettle is not touched.

Tested on a Cudy LT700 Outdoor v1 with a Quectel EG060W, on a real image with comgt 0.32-r38:

  • the parser against 10 captured replies
  • the power cycle race: dead before, comes up by itself after
  • a modem with no matching profile: waits out the identification deadline and fails after 22s, saying what it read
  • a control port that never answers: same deadline, fails after 21s with GETINFO_FAILED
  • the attach wait with no network: refuses to dial, retries, and came back on its own 32s after I let the network return
  • attachwait=0, and the skip for a modem that does not answer AT+CGATT? (I have no such modem, so I pointed the check at a command this one rejects, which gives the same empty reply)
  • a cold boot with a working network: attach took 3s, lease and traffic fine
  • with and without an APN set

Not tested because I have no hardware: samsung and mikrotik. samsung is the one with attachatconnect so that part is reasoning, not a measurement. On mikrotik attach starts late so 10s might be tight, attachwait can be raised.

Not in here: linksettle is unchanged, I have a follow-up using force_link on the DHCP sub-interfaces that would remove the wait completely but it needs more testing. If the modem loses the network after it came up nothing recovers it, that is not new and not fixed here. waitforconnect in ncm.json has never been read by ncm.sh since 2023, left alone.

(I used some LLM to summarize the PR - validating, review and testing was done by me)

@FredApps this bumps PKG_RELEASE to 38 like your #24974, so whoever is second needs a small rebase, and you are the only one who can try the parser on another vendor. @patrakov this is the check you were ok with. @jonasjelonek you merged
#24689, this is the follow-up. @jlabuz @Leo-PL @blocktrron if you have time.

The same gcom call and awk filter were copy-pasted four times: manufacturer
and model in proto_ncm_setup(), and again in proto_ncm_teardown() for the
path that re-detects when the ubus data is gone. Collapse them into
ncm_query_id(), which takes the gcom script and the AT command tag.

No functional change.

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
The reply to AT+CGMI/AT+CGMM was taken to be the first line that is not the
command echo, whatever that line happened to be. A modem that has just been
powered up is still emitting unsolicited result codes on the same port, so
that line is often one of those.

Probing a Quectel EG060W once a second with nothing else on the port, from
the moment its /dev/ttyUSB2 appears:

  [+15s] CGMI[]  CGMM[ OK AT*APPOWERIND=0;*POWERIND=0 +CESQ: 99,99,255,255,
         255,255 ^MODE:0,0 +CIREPI: 0 *BANDIND: OK AT+CGMI Quectel OK
         AT+CGMM EG060W-EAAA OK ]
  [+18s] CGMI[AT+CGMI Quectel OK  RDY ]   CGMM[AT+CGMM EG060W-EAAA OK ]
  [+20s] CGMI[AT+CGMI Quectel OK ]        CGMM[AT+CGMM EG060W-EAAA OK ]

Two ways that goes wrong. A URC can become the manufacturer - had the read
window at +18s opened a moment later, RDY would have been the first line -
and then no profile matches, "Unsupported modem" is reported and
proto_set_available 0 makes it permanent. Or a stray OK becomes the model,
as in the +15s capture, and the model specific profile is silently missed.

Skip command echoes, URCs and bare status lines, and anchor the answer on
the echo of the command that was sent so that a backlog holding both
exchanges is still parsed correctly. Modems with echo off have no such
anchor, so keep the first plausible line as a fallback.

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
@openwrt openwrt Bot added the core packages pull request/issue for core (in-tree) packages label Sep 20, 2026
Identification was attempted once unless the delay option was set, and
config_generate never sets it for an ncm interface. The tty hotplug handler
calls proto_set_available 1 as soon as the port is recreated, so setup runs
while the modem is still coming up. On a Quectel EG060W the port appears 15s
after power-on and AT+CGMI answers nothing at all for the first few seconds:

  Interface 'wwan' is setting up now
  wwan (4950): Unsupported modem
  wwan (4997): Stopping network wwan
  Interface 'wwan' is now down

proto_set_available 0 is called on that path, so netifd does not try again
and the interface stays down until someone runs ifup by hand.

Keep asking until the modem identifies itself, bounded by one deadline
shared by both queries so an absent modem costs one wait and not two.

The default of 20s is measured rather than guessed. On this board the port
appears 21s into the boot and the modem needs roughly five more seconds
before it answers cleanly, which took three attempts at about 2.6s each:
identification completed 9s after setup started, on two consecutive boots.
A deadline only bounds how long we keep trying, since success leaves the
loop immediately, so the headroom is free. delay still overrides it.

The model is now retried as well. It never was, which left it dependent on a
single reply landing cleanly, and a modem that reports no model at all still
falls back to its manufacturer entry once the deadline passes.

Report what was actually read when giving up, because "Unsupported modem" on
its own says nothing about why.

A modem that reports no model at all would otherwise wait out the whole
deadline on every bring-up, so only insist on one while ncm.json actually has
a "<manufacturer>-<model>" entry that could match it.

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
ncm.sh dials whatever the modem says and hands the interface to netifd
without ever asking whether there is a network. A modem in limited service
answers the dial command happily, cdc_ncm raises carrier as soon as the
netdev is enabled, and udhcpc is then left broadcasting into nothing:

  +CGATT: 0
  +CGPADDR: 1,""
  +QENG: "servingcell","LIMSRV",...
  wwan_4: udhcpc: broadcasting discover   (repeating, no lease, interface "up")

Ask AT+CGATT? until the modem reports it is attached, and fail the setup if
it never does, so netifd tears down, sends the disconnect and tries again
instead of reporting an interface that cannot pass traffic. This is the same
shape qmi.sh uses for registration, including its
NETWORK_REGISTRATION_FAILED error and leaving proto_block_restart alone so
that retries continue.

AT+CGATT? is 3GPP 27.007, but a modem that does not answer it is not held
up: an empty reply skips the check. attachwait sets the budget in seconds
and defaults to 10; 0 turns the check off. The name avoids qmi.sh's timeout,
where 0 means wait forever rather than do not wait.

Two alternatives did not work out. AT+CGPADDR cannot be used because the LTE
default bearer already carries an address at attach, on Quectel and Fibocom
alike, so the check would pass immediately. getcarrier.gcom already polls
AT+CGATT?, and treats a silent modem the same way this does, but its timeout is
a fixed ~20s that cannot be passed in, and it is shared with directip.sh and
3g.sh, so giving it a deadline would change behaviour for two other protocols.

runquery.gcom is added because runcommand.gcom reports only an exit status,
so no caller could look at what the modem actually replied.

Not every profile can be gated this way: samsung dials with AT+CGATT=1, so
waiting for AT+CGATT? to report 1 beforehand would never let it connect at
all. Mark such profiles attachatconnect, in the same spirit as
linkatfinalize, and skip the wait for them.

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit checks

  • c4a2a56 "comgt: ncm: factor modem identification into a helper" — "No functional change" is not accurate: the teardown manufacturer probe had no [ "$manufacturer" = "error" ] normalisation before, so a modem answering ERROR there now returns GETINFO_FAILED instead of falling through to the profile lookup. Either note the behaviour change or keep that path byte-identical.

Generated by Claude Code

state=$(COMMAND="AT+CGATT?" gcom -d "$device" -s /etc/gcom/runquery.gcom | \
awk -v RS='\r?\n' '/\+CGATT: [0-9]/ { sub(/.*\+CGATT: /, ""); print $1 + 0; exit }')

[ -z "$state" ] && return 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An empty reply cannot be distinguished from a slow one here: runquery.gcom uses get 1 "" $s, so any answer that does not land inside that 1 s window yields an empty $state and silently disables the attach gate for the whole setup — on the first probe, in exactly the just-powered-up race the rest of the series is about. Consider only bailing out fail-open after the modem has been seen to answer something (or after N empty probes), rather than on the first one.


Generated by Claude Code

Comment on lines +276 to +282
[ -z "$attachatconnect" ] && [ "${attachwait:-10}" -gt 0 ] && {
ncm_wait_attach "$device" $(($(date +%s) + ${attachwait:-10})) || {
echo "Modem did not attach to the network"
proto_notify_error "$interface" NETWORK_REGISTRATION_FAILED
return 1
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failing setup here makes netifd re-run initialize/configure on every retry, and for the mikrotik profile configure is AT+CFUN=4AT+CFUN=1 — a radio restart. With the 10 s default that profile can be pushed into a loop where the radio is cycled before it ever gets the chance to attach, on a board that previously came up (slowly). Either give such profiles a larger default, or attachatconnect-style opt-out, or move the wait ahead of configure?


Generated by Claude Code

@FredApps

Copy link
Copy Markdown

Tried the parser on the other vendor, as asked.

Setup: built this branch's comgt against the matching ramips/mt7621 25.12-SNAPSHOT SDK, with the plain fibocom key from #24974 spliced into ncm.json (the modem is otherwise unsupported). Installed r38 on an ASUS 4G-AX56, Fibocom FG621-EA. Reproduced your power-cycle case with AT+CFUN=15, which re-enumerates USB the same way.

Identification works, and it takes a path yours probably does not. This modem answers without a prefix and does echo the command:

AT+CGMM
FG621-EA
OK

so nothing matches sub(("\+" tag ": "), ""), and it is the seen branch that produces the answer — not the +CGMM: path the EG060W uses, and not the ATE0 fallback. Result: fibocom / fg621-ea, and fg621 after the suffix strip. The URCs this modem emits on that port (+SPNWNAME:, +GTRNDIS:) are skipped correctly by the ^[+*^] rule.

runquery.gcom works here too, and AT+CGATT? is answered normally, so nothing in the new path trips on this vendor.

The retry does what it says. After the reset, hotplug starts setup while the modem is still booting, and the log shows exactly the sequence you describe, then recovery:

comgt -> Error @118, line 9, Could not write to COM device. (1)
  ... 7s later ...
sending -> AT+CFUN=1

Recovery after AT+CFUN=15: 4 of 5 came back unattended. Three consecutive clean runs (no AT traffic of mine competing) recovered at t+51s, t+51s and t+54s.

The first one did not: interface up, no lease, and nothing retried. The modem was fully registered at that point — +CGATT: 1, +CREG: 0,1, +CGPADDR: 1,"95.197.10.255" — but AT+GTRNDIS? returned an empty reply, so the connect had not taken. I could not reproduce it in four further attempts, and I would not read much into it: the fibocom profile is mine from #24974, so that connect command is my code rather than yours. Flagging it only so it is on the record if someone else sees the same shape.

One structural note, not an objection. attachwait is effectively a no-op on this modem: it reads +CGATT: 1 almost immediately, because the default bearer activates at attach here — the same behaviour you measured on the EG060W and I confirmed on #24689. It costs nothing, but it will not gate modems of this class, so if the intent is "do not dial before the network is usable", CGATT alone will not carry it on Fibocom.

Also confirming the collision you flagged: this and #24974 both take PKG_RELEASE 38, so whichever is second needs the bump. Mine is the one that should move — yours is further along, and #24974 has had no reviewer.

Tested-by: Fredrik Hallin fredappsdev@outlook.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core packages pull request/issue for core (in-tree) packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

comgt-ncm: modem detection is confused by boot-time URCs, leaving the interface permanently down

3 participants