Linux ModemManager 蜂窝调制解调器管理详解

Linux ModemManager 蜂窝调制解调器管理详解

ModemManager(MM) 是 Linux 上管理 3G/4G/5G USB 模组、M.2 模组、内置蜂窝模块 的守护进程。它屏蔽 AT、QMI、MBIM 等协议差异,向 NetworkManager 和其他客户端提供统一的 D-Bus modem 对象 。本文从架构、端口探测、插件机制到 mmcli 实战与常见时序问题,系统梳理 MM 的工作原理与工程用法。

TL;DR(太长不看)

MM 管什么: 认模组、端口探测、AT/QMI/MBIM、注册基站、建 Bearer(PDP)。不管什么: 系统路由 metric、WiFi、Connection 配置------那是 NetworkManager。认不出先查什么: 驱动与 cdc-wdm → 等 15~30 秒看 mmcli -L → 查 udev 是否 ID_MM_DEVICE_IGNORE → 绑定完成后再 restart MM(勿反复 restart)。MM 创建 Modem 对象后,NM 才会出现 type=gsm 的 Devicemmcli 已 registered 不等于能 ping,还须 NM connection up


目录

  1. [ModemManager 是什么](#ModemManager 是什么)
  2. [为什么需要 ModemManager](#为什么需要 ModemManager)
  3. [在 Linux 蜂窝链路中的位置](#在 Linux 蜂窝链路中的位置)
  4. 核心对象模型
  5. 插件(Plugin)机制
  6. 设备发现与端口探测流程
  7. 端口类型:tty、cdc-wdm、net
  8. [QMI、MBIM 与 AT 命令](#QMI、MBIM 与 AT 命令)
  9. [与 udev 的协作](#与 udev 的协作)
  10. [mmcli 常用命令速查](#mmcli 常用命令速查)
  11. [与 NetworkManager 的协作边界](#与 NetworkManager 的协作边界)
  12. 探测时序与「认不出模组」
  13. 常见踩坑与反模式
  14. 故障排查思路
  15. 小结

(可先读文首 TL;DR,再按下方目录深读。)


一、ModemManager 是什么

ModemManager 由 OpenMobileStack 社区维护,与 NetworkManager 同属 Red Hat 生态,几乎总是与 NM 一起出现在桌面 Linux 发行版中。

职责 说明
硬件发现 监听 USB/PCI 热插拔,识别调制解调器
端口探测 确定哪个 /dev/ttyUSBX 是 AT 口、哪个 /dev/cdc-wdmX 是 QMI 口
协议对话 发 AT、QMI、MBIM 命令,查询信号、注册、SIM、APN
Bearer 管理 建立数据承载(PDP 上下文),把 IP/DNS 交给 NM
统一 D-Bus API 上层不必关心底层是 Quectel 还是 Sierra

MM 不负责 长期维护系统路由表、WiFi 切换策略------那是 NetworkManager 的职责。


二、为什么需要 ModemManager

蜂窝 USB 模组通常 一次枚举多个接口

text 复制代码
If 0~3  →  USB Serial  →  /dev/ttyUSB0~3  (AT、诊断、GPS...)
If 4    →  QMI/WWAN     →  /dev/cdc-wdm0 + wwan0  (数据)

没有 MM 时,应用需自己:

  • 猜哪个 tty 口能响应 AT
  • qmicli / uqmi 手动发 QMI 拨号
  • ip 手工配地址路由

有 MM 时 ,上述细节被封装为 一个 modem 对象 + 标准 D-Bus 方法 ,NetworkManager 只需 type=gsm 连接即可拨号。

方案 优点 缺点
ModemManager + NM 桌面集成好、多模组支持、可脚本 探测链路长、日志复杂
quectel-CM / uqmi 直连 可控、延迟可能更低 需自己处理 VID/PID、端口配对、路由
Ofono 部分嵌入式栈使用 桌面生态小于 MM

三、在 Linux 蜂窝链路中的位置

text 复制代码
┌──────────────────────────────────────────────────────────┐
│  NetworkManager:GSM Connection、IP、路由、DNS          │
└────────────────────────────┬─────────────────────────────┘
                             │ D-Bus(Bearer、IP 配置)
┌────────────────────────────▼─────────────────────────────┐
│  ModemManager:modem 对象、插件、端口探测、QMI/AT         │
└────────────────────────────┬─────────────────────────────┘
                             │ /dev/ttyUSB* / /dev/cdc-wdm*
┌────────────────────────────▼─────────────────────────────┐
│  内核驱动:option/usbserial、qmi_wwan、cdc_mbim ...         │
└────────────────────────────┬─────────────────────────────┘
                             │ USB / PCIe
┌────────────────────────────▼─────────────────────────────┐
│  蜂窝模组硬件(如 Quectel EG25-G、Sierra EM7455 ...)       │
└──────────────────────────────────────────────────────────┘

四、核心对象模型

ModemManager 通过 D-Bus 暴露层次化对象(路径示意):

text 复制代码
/org/freedesktop/ModemManager1
    /Modem/0          ← 一块物理模组
        /Bearers/0    ← 一条数据承载(一次 PDP)
        /SIM/0        ← SIM 卡信息
        /SMS/...      ← 短信(若支持)

4.1 Modem

代表 一整块蜂窝模组,聚合多个端口:

bash 复制代码
mmcli -L
# /org/freedesktop/ModemManager1/Modem/0 [vendor] model
bash 复制代码
mmcli -m 0

常见输出字段:

字段 含义
state disabled / enabled / registered / connected ...
plugin 哪个插件驱动(如 quectelgeneric
ports 模组暴露的 tty、cdc-wdm、net 口列表
signal RSSI、RSRP 等
operator 当前注册运营商

4.2 Bearer

Bearer 是一次 分组数据连接(PDP Context),包含:

  • IPv4/IPv6 地址、前缀
  • 网关、DNS
  • 关联的 net 端口(如 wwan0

NetworkManager 激活 GSM 连接时,实质是请求 MM 创建或激活 Bearer,再把返回的 L3 信息配到内核。

4.3 SIM

SIM 对象描述 ICCID、IMSI、是否 PIN 锁定等。无 SIM 或 PIN 错误会导致注册失败。


五、插件(Plugin)机制

MM 用 插件 适配不同厂商与协议栈。插件分两类,不要混为一维

类型 插件示例 说明
按厂商/模组 quectelsierra 识别标准 VID/PID,探测路径短
按协议 mbimqmi(部分版本) MBIM/QMI 控制协议 接管,非某一家厂商专属
兜底 generic 未知或定制 VID,通用 AT+QMI 试探

常见组合对照:

插件 典型模组 说明
quectel Quectel EC25/EG25 等标准 VID 厂商专用,探测快
sierra Sierra Wireless 厂商 AT/QMI 变体
mbim 走 cdc_mbim 的模组 协议插件,与 quectel/sierra 不同维度
generic 未知 VID 或定制 VID 慢但通常仍可用

定制 VID 的模组 (厂商改 USB ID 后)常落入 generic 插件------功能通常仍可用,但 端口探测更慢 、日志里更多 unhandled port type 警告。这不等于不能上网,NetworkManager 仍可通过 generic modem 拨号。

版本提示: 较旧 MM(如发行版自带 1.6 以前 )对 MBIM 支持不完整;部分 5G 模组 建议 ModemManager 1.18+ (Ubuntu 18.04 等旧仓库可能偏老,需 backport 或新发行版)。查版本:mmcli --version / ModemManager --version

插件选择逻辑(简化):

text 复制代码
USB 插入 → MM 读 idVendor/idProduct
         → 各插件声明是否 support
         → 优先级最高的插件接管
         → 开始端口探测

六、设备发现与端口探测流程

6.1 发现触发

MM 通过 udev 感知 USB/PCI 设备插入,并订阅相关端口的 add 事件。插入后 MM 的 base-manager 会:

  1. 收集该 USB 设备下所有 interface 对应的 /dev 节点
  2. 对每个 候选端口 发探测(AT 同步、QMI client 等)
  3. 确定 primary AT 口QMI/MBIM 口net 口
  4. 创建 Modem 对象

模组 ModemManager 内核 udev 模组 ModemManager 内核 udev #mermaid-svg-jysgFkXVFlxeHDhT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jysgFkXVFlxeHDhT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jysgFkXVFlxeHDhT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jysgFkXVFlxeHDhT .error-icon{fill:#552222;}#mermaid-svg-jysgFkXVFlxeHDhT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jysgFkXVFlxeHDhT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jysgFkXVFlxeHDhT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jysgFkXVFlxeHDhT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jysgFkXVFlxeHDhT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jysgFkXVFlxeHDhT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jysgFkXVFlxeHDhT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jysgFkXVFlxeHDhT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jysgFkXVFlxeHDhT .marker.cross{stroke:#333333;}#mermaid-svg-jysgFkXVFlxeHDhT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jysgFkXVFlxeHDhT p{margin:0;}#mermaid-svg-jysgFkXVFlxeHDhT .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jysgFkXVFlxeHDhT text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-jysgFkXVFlxeHDhT .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jysgFkXVFlxeHDhT .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-jysgFkXVFlxeHDhT .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-jysgFkXVFlxeHDhT .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-jysgFkXVFlxeHDhT #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-jysgFkXVFlxeHDhT .sequenceNumber{fill:white;}#mermaid-svg-jysgFkXVFlxeHDhT #sequencenumber{fill:#333;}#mermaid-svg-jysgFkXVFlxeHDhT #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-jysgFkXVFlxeHDhT .messageText{fill:#333;stroke:none;}#mermaid-svg-jysgFkXVFlxeHDhT .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jysgFkXVFlxeHDhT .labelText,#mermaid-svg-jysgFkXVFlxeHDhT .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-jysgFkXVFlxeHDhT .loopText,#mermaid-svg-jysgFkXVFlxeHDhT .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-jysgFkXVFlxeHDhT .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jysgFkXVFlxeHDhT .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-jysgFkXVFlxeHDhT .noteText,#mermaid-svg-jysgFkXVFlxeHDhT .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-jysgFkXVFlxeHDhT .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jysgFkXVFlxeHDhT .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jysgFkXVFlxeHDhT .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jysgFkXVFlxeHDhT .actorPopupMenu{position:absolute;}#mermaid-svg-jysgFkXVFlxeHDhT .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-jysgFkXVFlxeHDhT .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jysgFkXVFlxeHDhT .actor-man circle,#mermaid-svg-jysgFkXVFlxeHDhT line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-jysgFkXVFlxeHDhT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 错口超时、QCDM 失败属常见日志 USB add 2ca3:4006 usbserial → ttyUSB0~3 qmi_wwan → cdc-wdm0 + wwan0 AT 探测 ttyUSB0 AT 探测 ttyUSB1 QMI 打开 cdc-wdm0 创建 Modem/0(plugin=generic)

图意: udev 上报插入 → 内核先 ttyUSB 后 cdc-wdm → MM 逐口 AT/QMI 试探 → 最终创建 Modem 对象。

6.2 为什么探测慢

多接口模组 逐个试端口 是 MM 慢的主要原因之一:

因素 影响
多个 ttyUSB 单口 AT 探测超时通常 约 2~5 秒/口 (因插件与口类型而异);4 个 tty 仅错口试探就可能 ~10~20 秒
错协议口 日志 failed to parse QCDMunhandled port type
generic 插件 无厂商捷径,全靠试探
与驱动绑定 抢跑 cdc-wdm 尚未就绪时 MM 已开始 → 第一次失败,数十秒后 内部重试

40~60 秒才出现在设置里 在复杂 USB 模组上并不少见,不代表硬件损坏。


七、端口类型:tty、cdc-wdm、net

MM 为每个端口标注 类型用途

端口类型 设备节点示例 用途
tty /dev/ttyUSB0 AT 命令、状态查询、部分拨号
qmi /dev/cdc-wdm0 QMI 协议控制通道
mbim /dev/cdc-wmbb0 MBIM 协议
net wwan0 数据面网络接口(IP 走这里)

数据路径:

text 复制代码
控制面:MM ──QMI/AT──► cdc-wdm / ttyUSB
数据面:IP 包 ──► wwan0 ──► 运营商核心网

mmcli -m 0Ports 段可看到完整映射。


八、QMI、MBIM 与 AT 命令

协议 特点 常见硬件
AT 文本命令,历史悠久 virtually 所有模组
QMI 高通主导的二进制 RPC,功能全 Quectel、Sierra(QMI 模式)
MBIM USB-IF 标准,Windows 友好 不少 5G 模组

MM 内部按插件选择 主控制协议 。NetworkManager 激活 Bearer 时,MM 用 QMI 或 AT 发起 Start Network,拿到 IP 后交给 NM。

手动 QMI 工具(MM 之外):

bash 复制代码
qmicli -d /dev/cdc-wdm0 --dms-get-model
qmicli -d /dev/cdc-wdm0 --nas-get-signal-strength

qmicli 能通 只说明驱动与 cdc-wdm 正常,不等于 MM 已创建 modem------MM 还有独立的端口枚举与插件逻辑。


九、与 udev 的协作

MM 自带 udev 规则(通常在 /usr/lib/udev/rules.d/),用于:

  • 识别 modem 设备
  • 设置端口权限(dialout 组)
  • 标记 ID_MM_* 属性

ID_MM_DEVICE_IGNORE

若某 udev 规则对设备设置 ENV{ID_MM_DEVICE_IGNORE}="1",MM 会 完全忽略 该设备------mmcli -L 永远为空。排查 unrecognized 模组时应 grep 所有 udev 规则

与自定义 bind 脚本的关系:

对非标准 VID 模组,常需 先于 MM 完成 qmi_wwan 绑定(new_id、只保留正确 interface)。若 MM 在 cdc-wdm 出现 之前 就完成一轮探测,会失败并进入 长时间重试 ;这是热插拔场景的经典时序问题,解法是在驱动就绪 之后 再让 MM 探测(例如绑定脚本完成后再 restart ModemManager,且避免在 MM 已成功时反复 restart)。


十、mmcli 常用命令速查

10.1 列表与详情

bash 复制代码
mmcli -L                    # 列出所有 modem
mmcli -m 0                  # modem 0 详情
mmcli -m 0 --signal-get     # 信号强度
mmcli -m 0 --location-get   # 基站/位置(若支持)
mmcli -m 0 --sim            # SIM 信息

10.2 状态控制

bash 复制代码
mmcli -m 0 --enable         # 上电启用
mmcli -m 0 --disable        # 禁用
mmcli -m 0 --reset          # 重置模组(慎用,会断连)

10.3 简单连接(不经过 NM,调试用)

bash 复制代码
mmcli -m 0 --simple-connect="apn=cmnet"
mmcli -m 0 --simple-disconnect

生产环境 推荐仍用 NetworkManager 管理 GSM Connection,便于路由、DNS、autoconnect 与 WiFi 共存。

10.4 监控

bash 复制代码
mmcli -m 0 --signal-setup=5 --signal-get   # 每 5 秒上报信号
journalctl -u ModemManager -f              # 守护进程日志

十一、与 NetworkManager 的协作边界

层次 ModemManager NetworkManager
模组上电/注册 ---
信号/SIM 查询 ---
PDP / Bearer ✓ 建立 ✓ 触发、消费 IP 信息
wwan0 地址 提供参数 ✓ 写入内核
默认路由 metric ---
WiFi / 有线 ---
桌面「移动宽带」UI 提供 modem 提供 gsm Device + Connection

分工口诀: MM 管 modem 与 Bearer ,NM 管 连接配置与系统网络策略

双向关系(与 NM 侧对照): MM 在 D-Bus 上 创建 Modem 对象 后,NetworkManager 才会将其映射为 type=gsm 的 Device (如 cdc-wdm0);在此之前 NM 设置里不会出现移动宽带。反之,NM 的 connection up 会经 D-Bus 请求 MM 建立 Bearer------两层都 OK 才能 ping 通

典型联调检查:

bash 复制代码
mmcli -L                           # ① 有 modem
nmcli device status | grep gsm     # ② 有 gsm Device
nmcli connection up 4G-Mobile      # ③ 拨号
ip route show default              # ④ 路由是否符合预期

十二、探测时序与「认不出模组」

12.1 典型现象

  • ls /dev/cdc-wdm0 存在,qmicli 正常
  • mmcli -LNo modems were found
  • 或短暂出现后 modem is unusable

12.2 常见原因

原因 机制
cdc-wdm 晚于 ttyUSB 出现 MM 整机探测结束时 cdc-wdm 才 add → 日志 additional port ... after probing has already finished
与驱动绑定抢跑 MM 在 qmi_wwan 未 bind 时探测 → unexpected port subsystem
ID_MM_DEVICE_IGNORE udev 显式忽略
fatal 后未重试 第一次失败后需 等待 MM 内部重试restart MM(绑定完成后)
反复 restart MM 正在成功探测时被 kill → 前功尽弃

12.3 推荐处理顺序

bash 复制代码
# 1. 确认驱动:仅数据 interface 绑 qmi_wwan
lsusb -t
ls /dev/cdc-wdm*

# 2. 确认 MM 服务
systemctl is-active ModemManager

# 3. 等待 15~30 秒(勿急于拔插)
mmcli -L

# 4. 仍为空:在驱动绑定完成后 restart 一次 MM,再等 ~20 秒
sudo systemctl restart ModemManager
sleep 20
mmcli -L

不要 在 MM 日志已出现 creating modem / registered 时再次 restart。


十三、常见踩坑与反模式

13.1 把 qmicli 成功等同于 MM 正常

驱动层 QMI 通道可用 ≠ MM 已完成端口拓扑识别。两者独立。

13.2 对非标准 VID 期望 quectel 插件

定制 USB ID 走 generic 是常态,不影响 NetworkManager 拨号,只是探测更慢。

13.3 热插拔后立即看设置

MM 完整探测 20~60 秒 都可能有;应用 mmcli -L 判断,而非盯 UI。

13.4 多 interface 模组错误绑定 qmi_wwan

多 USB interface 模组错误执行 new_id 后,内核可能给 多个 interface(如 1.1~1.4)都绑上 qmi_wwan ,从而出现多个 wwanX 网卡与多个 cdc-wdm,MM/QMI 行为混乱。

注意: 这里是 多个 interface 误绑同一驱动 ,不是「一块模组天然有 4 张 wwan 网卡」。应 只保留数据 interface (常见为 :1.4)在 qmi_wwan 上,其余解绑。

13.5 生产环境混用 mmcli --simple-connect 与 NM

两套路由可能冲突。选 一条栈 为主。

13.6 stop ModemManager 当「刷新」

restart 会打断进行中的探测。仅在 驱动已稳定、mmcli 长期为空 时使用。


十四、故障排查思路

bash 复制代码
# 硬件与驱动
lsusb
lsusb -t
ls /dev/ttyUSB* /dev/cdc-wdm* 2>/dev/null
dmesg | tail -50

# MM 状态
systemctl status ModemManager
mmcli -L
mmcli -m 0

# 日志关键词
journalctl -u ModemManager --since "10 min ago" | grep -iE "modem|error|warn|probe|cdc-wdm"

# udev 是否 ignore
grep -r "ID_MM_DEVICE_IGNORE" /etc/udev/rules.d/ /usr/lib/udev/rules.d/ 2>/dev/null

日志片段与含义:

日志 含义
creating modem with plugin 'generic' 即将成功
modem is unusable 探测失败,常需等待或 restart
additional port cdc-wdm after probing finished 时序问题,cdc-wdm 太晚
unhandled port type generic 试探错口,通常可忽略
3GPP registration state ... home 已注册运营商

十五、小结

ModemManager 是 Linux 蜂窝连接的 modem 抽象层 :通过 插件 + 端口探测 把复杂的 USB 多接口模组统一为 D-Bus Modem 对象 ,再与 NetworkManager 配合完成拨号与 IP 配置。

实践要点:

  1. 理解 Modem / Bearer / SIM 对象模型与 tty vs cdc-wdm vs wwan 分工。
  2. generic 插件 + 多 tty 探测 (单口约 2~5 秒超时)决定热插拔识别往往 不是秒级
  3. 时序问题(驱动绑定与 MM 抢跑)是「有 cdc-wdm 但 mmcli 空」的首 suspect。
  4. mmcli -L + journalctl -u ModemManager 排查,而非反复拔插或盲目 restart。
  5. MM 创建 Modem 后 NM 才有 gsm Deviceregistered 后还须 NM connection up 才有 IP。
  6. 生产拨号 优先 NetworkManager GSM Connection ;旧系统注意 MM 版本(5G/MBIM 宜 1.18+)。

弄清 MM 的边界与节奏,才能在 USB 4G dongle、M.2 模组、工业网关等场景下 可预期地 完成蜂窝联网集成。