This page explains the commercial user equipment (UE) used in the COSMOS over-the-air and O-RAN 7.2
tutorials: a Quectel RM520N-GL 5G modem on a COSMOS node. It covers what the modem is and why the
tutorials use one, the SIM it carries, how to put it into 5G Standalone-only mode, how to check that
it has registered, how to bring up a data session without cutting off the node's management access,
and how to recover it when it misbehaves. Every tutorial that attaches the modem links here for the
general procedure and keeps only its own values (band, APN) inline.
A commercial off-the-shelf (COTS) UE is a device built for real operator networks. Unlike a software
UE (srsRAN srsue, OAI nr-uesoftmodem, Amarisoft lteue), it has a real RF front end, a
certified baseband and its own opinions about which networks it is allowed to use. Attaching one
proves that a cell is standards-compliant enough for a phone, and it measures throughput through a
real radio chain. The price is that the modem's behaviour is partly hidden: it stores state across
attaches, it applies operator profiles, and some of its failures look exactly like a RAN or core
fault.
Most "the UE will not attach" and "registered but no data" reports on COSMOS turn out to be modem
state, not the gNB or the core. Knowing the modem's interfaces, the order its settings must be
applied in, and the handful of stuck states it can enter saves hours of debugging in the wrong
place.
The Quectel RM520N-GL is an M.2 5G module supporting 5G NR Standalone (SA) and Non-Standalone (NSA)
as well as LTE. On COSMOS it sits on USB 3 in a node and appears to Linux as several interfaces at
once:
| Interface | Linux device | Used for |
|---|---|---|
| AT command port | /dev/ttyUSB2 (sometimes /dev/ttyUSB3); /dev/quectel-at where the image provides the symlink |
configuration and status: RAT, bands, APN, PLMN lock, registration, serving cell |
| QMI control | /dev/cdc-wdmN: cdc-wdm0 on a node with one modem, but not always (see below) |
starting and stopping the data session (qmicli) |
| Data network interface | wwanN (driver qmi_wwan, raw-IP mode): wwan0 on a node with one modem |
the UE's IP traffic |
Note: A UE node can carry two QMI modems.
node1.rrailhas a SIMCom (cdc-wdm0,wwan0) and the Quectel (cdc-wdm1,wwan1), socdc-wdm0would start the data call on the wrong modem. The command blocks below therefore look up the Quectel's own devices from the USB device behind/dev/quectel-atand use$QMIand$WWAN; run those two lines in the same shell first.
AT commands are text commands (AT+...) sent over a serial port; Quectel adds vendor commands
such as AT+QNWPREFCFG and AT+QENG. The Qualcomm MSM Interface (QMI) is a binary control
protocol; qmicli (from libqmi) uses it to ask the modem to establish a PDU session and report the
IP address the network assigned. MBIM is an alternative control protocol that COSMOS tutorials do
not use.
ModemManager is the Linux service that normally owns modems and drives them through QMI/MBIM
and AT. The tutorials drive the modem directly instead, so they stop ModemManager first; while it
runs it holds the AT and QMI ports, and qmicli fails with
CID allocation failed in the CTL client: Transaction timed out. Only one process may use an AT
port at a time: an AT query sent while another script holds the port returns nothing.
The AT port number can move when the modem re-enumerates on USB (after AT+CFUN=1,1 or an NV
reset). On lte5gue images the udev symlink /dev/quectel-at follows the right interface; the
COSMOS modem helpers use it automatically when it exists. Without it, find the port by sending AT
to each /dev/ttyUSB* and taking the one that answers OK.
| UE host | Modem | SIM IMSI | Used by |
|---|---|---|---|
sdr1-in3.sb1 |
RM520N-GL, firmware RM520NGLAAR01A08M4G |
001010000000032 |
sb1 over-the-air and 7.2 tutorials, the Benetel O-RU tutorials |
node1.osc |
RM520N-GL | 001010000000033 |
osc 7.2 tutorials (LiteOn, soft O-RU) |
The UE hosts run the lte5gue.ndz image, which provides qmicli, iperf3 and the modem helper
scripts. Do not power-cycle a modem host with omf tell unless the tutorial says so: these nodes
have their own power policy, and the RF space is shared with other users.
Each modem carries a programmed test SIM on the COSMOS standard Public Land Mobile Network (PLMN)
00101 (Mobile Country Code 001, Mobile Network Code 01, the ITU test network). The IMSI (the
subscriber identity, which begins with the PLMN) is inventory and may appear in pages and results.
The SIM's authentication secrets — the subscriber key Ki and the operator key OPc — may not. They
live only on the core servers, in the image's SIM database (cosmos-sim-db), and each core
provisions itself from it (cosmos-sim-gen <core>, or cosmos-sims-check.py <core> --provision).
A tutorial bundle may carry an IMSI; it never carries K or OP/OPc. The
5G SA fundamentals page explains
IMSI, SUPI, Ki, OPc and the 5G-AKA authentication they feed.
Check the SIM from the AT port:
AT+CIMI # IMSI (SIM present/ready)
AT+CPIN? # SIM ready
If the core rejects the UE with Authentication failure (MAC failure) on a fresh registration, the
key provisioned in the core does not match the SIM: re-provision the core from cosmos-sim-db.
A new or reset modem prefers whatever network it finds first, which in the COSMOS RF space includes
a commercial 001/01 LTE cell at EARFCN 3350. To make it attach to your 5G SA cell and nothing else,
five settings must be right:
| Setting | AT command | Why |
|---|---|---|
| Radio access technology | AT+QNWPREFCFG="mode_pref",NR5G |
NR only, no LTE fallback |
| SA enabled | AT+QNWPREFCFG="nr5g_disable_mode",0 |
on this firmware 1 disables SA (not NSA): the modem then sees the cell but can never camp on it |
| Band mask | AT+QNWPREFCFG="nr5g_band",<bands> |
must include the cell's band. n78 at 3.75 GHz enumerates as band 77, so 7.2 cells need 77:78; the sb1 over-the-air cell is n41 |
| APN / DNN | AT+CGDCONT=1,"IP","<apn>" |
the data network name the core serves, IPv4. internet for Open5GS, COSMOS-O for the OCUDU 7.2 bundle, oai for OAI CN5G |
| PLMN lock | AT+COPS=1,2,"00101",12 |
manual selection of 00101 on NR (access technology 12) |
Two modem behaviours decide the order:
AT+COPS=0 (automatic selection) and QMI data calls silently revert mode_pref to AUTO, aftermode_pref=NR5G after any COPS=0 andROW_Commercial, whichAT+QMBNCFG="AutoSel",0 then AT+QMBNCFG="Deactivate", but even aAT+CFUN=0/AT+CFUN=1 re-activates it on this firmware. Deactivate it after the last+CPIN: READY, then lock the PLMN (a lock sent earlier fails with+CME ERROR: 13). The keyword is case-sensitive: lowercase "autosel" answers OK and doesAfter repeated attach attempts the modem may also write the test PLMN into the SIM's forbidden-PLMN
list (EF_FPLMN) and then camp on a healthy cell without ever registering. Clearing it is part of
every configuration:
AT+CRSM=214,28539,0,0,12,"FFFFFFFFFFFFFFFFFFFFFFFF" # clear forbidden-PLMN list
The tutorial bundles ship a helper that applies all of the above in the right order, clears
EF_FPLMN, switches the carrier MBN off after the last radio cycle, and first heals the USB data path
if needed (see Recovery). From the OCUDU 7.2 bundle, on the UE host:
ssh root@sdr1-in3
systemctl stop ModemManager
python3 ocudu-72_modem.py configure /dev/quectel-at 00101 41 COSMOS-O
python3 ocudu-72_modem.py wait-reg /dev/quectel-at 240 00101
+QENG: "servingcell","NOCONN","NR5G-SA","TDD",001,01,00066C000,3,7,516030,41,1,-97,-10,18,1,42
REGISTERED
The arguments are the AT port, the PLMN, the band mask and the APN. Each bundle has its own copy
with the same interface (ocudu-ota_modem.py, duranta-ota_modem.py, ocudu-72_modem.py); the
helper also offers status <at_port> and usbheal [wwan]. The AT commands it sends:
AT+QMBNCFG="AutoSel",0 # and AT+QMBNCFG="Deactivate" if a carrier MBN is active
AT+QNWPREFCFG="mode_pref",NR5G
AT+QNWPREFCFG="nr5g_disable_mode",0
AT+QNWPREFCFG="nr5g_band",41
AT+CGDCONT=1,"IP","COSMOS-O"
AT+COPS=2 ; AT+CFUN=0 ; AT+CFUN=1
AT+COPS=1,2,"00101",12
AT+C5GREG? # wait for +C5GREG: 0,1
The over-the-air tutorials give the same configuration as a short Python block that talks to the AT
port with pyserial. This is the verified OCUDU over-the-air version (band mask covering n41, n77
and n78; APN internet for Open5GS):
ssh root@<ue-node>
systemctl stop ModemManager
python3 - <<'PY'
import serial, time
s = serial.Serial("/dev/ttyUSB2", 115200, timeout=3)
at = lambda c, w=1.0: (s.write((c+"\r\n").encode()), time.sleep(w), s.read_all().decode())[2]
at('AT+QNWPREFCFG="mode_pref",NR5G')
at('AT+QNWPREFCFG="nr5g_disable_mode",0') # 0 = SA enabled (1 DISABLES SA on this modem!)
at('AT+QNWPREFCFG="nr5g_band",41:77:78') # this cell is n41; a mask left at 77:78 by a 7.2 (n78) run never sees it
at('AT+CGDCONT=1,"IP","internet"') # IPv4 APN
at('AT+COPS=2',2); at('AT+CFUN=0',3); at('AT+CFUN=1',5); at('AT+COPS=1,2,"00101",12',6) # lock PLMN
time.sleep(8)
print("C5GREG:", at('AT+C5GREG?')); print("QENG:", at('AT+QENG="servingcell"'))
s.close()
PY
For a Duranta cell with OAI CN5G, the APN is oai (AT+CGDCONT=1,"IP","oai"). When the modem has
been used on a network that set automatic selection, the Duranta commercial O-RU tutorial uses this
sequence on the AT port, where the order matters:
AT+COPS=2 # deregister
AT+CFUN=0
AT+CRSM=214,28539,0,0,12,"FFFFFFFFFFFFFFFFFFFFFFFF" # clear forbidden-PLMN list
AT+CGDCONT=1,"IP","oai"
AT+CFUN=1
AT+COPS=0 # automatic — FIRST
AT+QNWPREFCFG="mode_pref",NR5G # re-assert AFTER COPS=0, which reverts it
AT+COPS=1,2,"00101",12
AT+QENG="servingcell"
For an n78 cell at 3.75 GHz (the commercial O-RUs), the band lock is 77:78, not 78:
AT+QNWPREFCFG="nr5g_band",77:78
Registration is complete when the modem reports +C5GREG: 0,1: the second field 1 means
registered on the home network. 0,2 means it is still trying and 0,0 that it is idle or
searching. AT+QENG="servingcell" shows the cell it camps on:
+QENG: "servingcell","NOCONN","NR5G-SA","TDD",001,01,00066C000,1,7,647328,78,3,-68,-10,30,1,71
Read it as: state NOCONN (camped, RRC idle), access technology NR5G-SA, duplex TDD, PLMN
001/01, cell identity 00066C000, physical cell id 1, tracking area code 7, SSB ARFCN
647328, band 78, then RSRP -68 dBm, RSRQ -10, SINR 30 dB. SEARCH means no suitable cell;
LIMSRV means limited service (camped, not registered); LIMSRV,LTE means it drifted to commercial
LTE.
Two scans help separate "no cell" from "cell but not allowed":
AT+QSCAN=3,1 # RAW detection scan — lists ALL decodable cells: RAT,MCC,MNC,ARFCN,PCI,RSRP,RSRQ,SINR,SCS,...,TAC. Sees a cell even when it WON'T camp on it
AT+COPS=? # network scan — lists only SELECTABLE PLMNs (excludes barred/forbidden/SCS-mismatch/SA-disabled)
A cell that appears in AT+QSCAN but not in AT+COPS=? is on air and reaching the modem; the
block is a selection rule on the UE side (SA disabled, forbidden PLMN, active carrier MBN, TAC or
PLMN mismatch), not RF. On COSMOS the osc, rrail and sb1 radios share one RF space, so "the modem
sees nothing" is never an antenna or distance problem: either the cell is not really radiating or
the modem is refusing it.
On the core side, registration appears in the AMF log, for example with Open5GS:
ssh root@<du-node> "grep -a 'Registration complete' /tmp/open5gs/amf.log | tail -1"
[gmm] INFO: [imsi-001010000000032] Registration complete
Registration gives the UE a signalling connection; user data needs a PDU session, which the modem
requests when told to start a data call. The host then configures wwan0 with the address the
network assigned. wwan0 must be in raw-IP mode (IP packets without an Ethernet header), which can
only be changed while the interface is down:
# the Quectel's own QMI device + netdev; never assume cdc-wdm0/wwan0: a UE node can carry two modems
D=$(readlink -f /sys/class/tty/$(basename $(readlink -f /dev/quectel-at))/device/../..)
QMI=/dev/$(ls $D/*/usbmisc); WWAN=$(ls $D/*/net); echo "Quectel: $QMI $WWAN"
ip link set $WWAN down; echo Y > /sys/class/net/$WWAN/qmi/raw_ip; ip link set $WWAN up # raw_ip only changes while DOWN
qmicli -d $QMI --wds-start-network="apn=internet,ip-type=4" --client-no-release-cid
IP=$(qmicli -d $QMI --wds-get-current-settings | awk '/IPv4 address/{print $NF}')
ip addr add $IP/22 dev $WWAN; ip link set $WWAN mtu 1400 up
ip route replace 192.168.100.0/22 dev $WWAN
ping -I $WWAN -c 5 192.168.100.1 # core gateway
ping -I $WWAN -c 5 8.8.8.8 # the internet
+C5GREG: 0,1 # registered, home, on NR5G-SA 001/01
IPv4 address: 192.168.100.2
5 packets transmitted, 5 received, 0% packet loss (8.8.8.8)
Use the APN of your core in --wds-start-network (internet, COSMOS-O or oai). The -p flag
(qmicli -p -d $QMI ...), used in some tutorials, routes the request through qmi-proxy
so several clients can share the device. The UE pool on every COSMOS core is 192.168.100.0/22,
gateway 192.168.100.1. The MTU of 1400 leaves room for the GTP-U encapsulation on the core's user
plane.
What to look for: an IPv4 address from 192.168.100.0/22, then 0 % loss to the core gateway
and to 8.8.8.8. If the first ping batch shows 100 % loss, the modem is leaving RRC idle; the next
batch succeeds. ping: Network is unreachable means wwan0 has no address yet, which is different
from packet loss: wait for --wds-get-current-settings to return an address. The first
--wds-start-network after a gNB restart can return CallFailed; see Recovery.
Throughput is measured with iperf3 bound to the UE address, against a server on the core side,
for example the UPF gateway on an Open5GS host:
ssh root@<du-node> 'iperf3 -s -B 192.168.100.1 &'
ssh root@<ue-node> 'iperf3 -c 192.168.100.1 -B 192.168.100.2 -t 8 -R; \
iperf3 -c 192.168.100.1 -B 192.168.100.2 -t 8'
-R reverses the direction (server to UE, i.e. downlink). Read the iperf3 receiver line — it
is the measurement.
The UE host is reached over its management interface, through the default route. If the data
session installs default via <wwan gateway> dev wwan0, every reply to your SSH session leaves
through the 5G link and the node disappears from the network. Add only scoped routes through
wwan0:
D=$(readlink -f /sys/class/tty/$(basename $(readlink -f /dev/quectel-at))/device/../..)
QMI=/dev/$(ls $D/*/usbmisc); WWAN=$(ls $D/*/net)
ip route replace 192.168.100.0/22 dev $WWAN # scoped only — never a default route
and, when a specific external target must go through the 5G link, a per-target host route:
D=$(readlink -f /sys/class/tty/$(basename $(readlink -f /dev/quectel-at))/device/../..)
QMI=/dev/$(ls $D/*/usbmisc); WWAN=$(ls $D/*/net)
ip route replace 8.8.8.8/32 via 192.168.100.4 dev $WWAN
using the gateway that --wds-get-current-settings reports. Always test with ping -I $WWAN and
iperf3 -B <UE IP>: without them the packets leave by the management interface and prove nothing.
If a data-connection manager has already added a default route through wwan0, demote it rather
than leaving it at metric 0:
D=$(readlink -f /sys/class/tty/$(basename $(readlink -f /dev/quectel-at))/device/../..)
QMI=/dev/$(ls $D/*/usbmisc); WWAN=$(ls $D/*/net)
ip route del default via <gw> dev $WWAN; ip route add default via <gw> dev $WWAN metric 700
If the node is already unreachable, reach it through its control-network address
(ssh root@<ctrl-ip>) from the console and remove the route.
ssh root@<ue-node> 'D=$(readlink -f /sys/class/tty/$(basename $(readlink -f /dev/quectel-at))/device/../..); QMI=/dev/$(ls $D/*/usbmisc); WWAN=$(ls $D/*/net); qmicli -d $QMI --wds-stop-network=disable-autoconnect; ip addr flush dev $WWAN'
ssh root@sdr1-in3 'systemctl start ModemManager'
The bundle teardowns do the same and hand the modem back to ModemManager.
Work down the list; each step is more disruptive than the one before. Before any modem step,
confirm the cell is really on air (the gNB log shows the cell in service and, for a 7.2 rig, the
fronthaul is receiving) — do not keep re-configuring a modem while the cell is down.
LIMSRV,LTE or searching after an earlier run hasmode_pref or has a band mask from a different cell. Run the helper's configure--wds-start-network keeps returning CallFailed, or theAT+COPS=2, wait, AT+COPS=1,2,"00101",12, then start a fresh data call. A plainAT+CFUN=0/AT+CFUN=1 does not release it.NOCONN, strong RSRP)AT+QMBNCFG="List": a profile with flags ,1,1, is active. Deactivate it after the last radiowwan0, and every packet lost withip -s link show $WWAN (next section). The fix is apython3 ocudu-72_modem.py usbheal (the helper's configure does itwwan0 shows transmit errors).qmicli reports a CID allocation failure with ModemManagerrmmod qmi_wwan; modprobe qmi_wwan, then retry--wds-start-network.Integrity check failedInitial Context Setup Failure in the gNB log, typically after the core restarted: theAT+QPRTPARA=3 resets the/root/ue_nvreset.py runs it); back upAT+QPRTPARA=1. RAT, band and APN return to defaults, so re-run step 1 once theAT+CIMI answers). AT+CFUN=1,1 does not clear this state./sys/class/net/wwan0 is missing but /sys/class/wwan/wwan0 exists,CFUN=1,1 cycles have moved the modem from the qmi_wwan networkecho Y > /sys/class/net/wwan0/qmi/raw_ip then fails.wwan0.The modem's control ports and its data endpoint are separate USB interfaces, so the AT port can
answer normally while the data endpoint is stuck. The symptom is complete: registration, PDU
session and IP address all succeed, and no user packet ever reaches the modem. The gNB sees no
scheduling request, the core sees no GTP-U traffic, and the fault looks like a RAN bug on every gNB
stack.
D=$(readlink -f /sys/class/tty/$(basename $(readlink -f /dev/quectel-at))/device/../..)
QMI=/dev/$(ls $D/*/usbmisc); WWAN=$(ls $D/*/net)
ip -s link show $WWAN # TX "errors" rising by one per packet, TX "packets" 0 = broken USB data path
With usbnet dynamic debugging on, the kernel logs qmi_wwan … wwan0: tx throttle -71: the USB
bulk-OUT endpoint returns -EPROTO on every packet. Unbinding and rebinding qmi_wwan creates a
fresh wwan0 that fails the same way; a USB device reset (USBDEVFS_RESET on the modem's
/dev/bus/usb/… node, about 15 s, the modem firmware is not restarted) clears it, after which the
QMI data call is started again. A healthy data path has 0 transmit errors on wwan0, which is why
the COSMOS helpers check it before every configuration.
| Symptom | Cause | Fix |
|---|---|---|
Modem stuck SEARCH, no SSB seen |
the cell is not radiating | check the gNB/O-RU side first (cell in service, fronthaul receiving) |
Modem detects the cell (AT+QSCAN) but will not camp |
nr5g_disable_mode=1 disables SA on RM520N-GL |
set nr5g_disable_mode to 0; lock AT+COPS=1,2,"00101",12 |
Camped (+QENG: "servingcell","NOCONN"…) but +C5GREG: 0,0 for minutes; the core's AMF log shows Registration reject [23] with Integrity[0x0] cannot be bypassed with NIA0 (Open5GS) |
Cold start. The modem still holds a 5G-GUTI from a core that has since restarted (an aborted run, a killed gNB), sends a mobility registration without its security capabilities, is rejected, and after five rejects waits out T3502 (12 min) without trying | AT+CFUN=1,1 (a full reboot; a CFUN=0/1 cycle does not end T3502). The next attempt is an initial registration and succeeds. The tutorial bundles' modem helper does this on its own when registration stalls |
AT+COPS=1,2,"00101",12 → +CME ERROR: 30, modem in SEARCH while the cell is up |
the NR band mask excludes the cell's band (for example 77:78 against an n41 cell) |
AT+QNWPREFCFG="nr5g_band",41:77:78 |
| n78 7.2 cell at 3.75 GHz never seen with a band-78 lock | 3.75 GHz enumerates as band 77 | band mask 77:78 |
Camps (NOCONN, strong RSRP), never registers, gNB sees 0 preambles |
active carrier MBN profile | AT+QMBNCFG="AutoSel",0, AT+QMBNCFG="Deactivate" after the last radio cycle, then lock the PLMN |
| Camps on a healthy cell without registering after many attach cycles | the test PLMN is in the SIM's forbidden list (EF_FPLMN) | AT+CRSM=214,28539,0,0,12,"FFFFFFFFFFFFFFFFFFFFFFFF" (the helpers do it) |
PLMN lock returns +CME ERROR: 13 |
sent before the SIM was ready after a radio cycle | wait for +CPIN: READY, then lock |
Modem camps commercial LTE (LIMSRV,LTE) |
mode_pref drifted to AUTO (after COPS=0 or a data call) |
re-assert mode_pref=NR5G and COPS=1,2,"00101",12 |
Core: Unknown DNN reject |
APN differs from the core's DNN | set AT+CGDCONT to the core's DNN (oai for OAI CN5G, internet for Open5GS, COSMOS-O for the OCUDU 7.2 bundle); clear the back-off timer with AT+CFUN=1,1 |
AMF Authentication failure(MAC failure) on a fresh attempt |
the core's key for this IMSI does not match the SIM | re-provision the core from cosmos-sim-db |
Registered, no data; gNB Integrity check failed |
stale NAS security context in the modem | AT+QPRTPARA=3 (recovery step 6), then reconfigure |
qmicli … CID allocation failed |
ModemManager holds the QMI device, or a stuck channel | systemctl stop ModemManager; rmmod qmi_wwan; modprobe qmi_wwan |
--wds-start-network → CallFailed |
transient after a gNB restart, or a stale PDU session in the network | retry once; then AT+COPS=2, AT+COPS=1,2,"00101",12, retry |
Registered, IP on wwan0, 100 % loss, gNB sees no SR/BSR |
USB bulk-OUT endpoint stuck (-EPROTO) |
USB device reset (usbheal); a qmi_wwan rebind does not help |
echo Y > …/qmi/raw_ip: No such file or directory |
modem moved to the kernel WWAN subsystem | reboot the UE host |
ping: Network is unreachable |
wwan0 has no IP yet |
wait for --wds-get-current-settings to return an address, then ip addr add |
| Node management SSH lost after the data call | a default route via wwan0 |
use only the scoped route 192.168.100.0/22 dev wwan0 |
| AT query returns nothing | another process holds the AT port | stop the other script (or ModemManager) and retry |
qmicli.Author(s): COSMOS team · Last reviewed: 2026-09-24 · Tags: ue, cots, quectel, rm520n-gl, modem, at-commands, qmi, qmicli, sim, sa, registration, pdu-session, routing, troubleshooting, concepts