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 的 Device ;mmcli已 registered 不等于能 ping,还须 NMconnection up。
目录
- [ModemManager 是什么](#ModemManager 是什么)
- [为什么需要 ModemManager](#为什么需要 ModemManager)
- [在 Linux 蜂窝链路中的位置](#在 Linux 蜂窝链路中的位置)
- 核心对象模型
- 插件(Plugin)机制
- 设备发现与端口探测流程
- 端口类型:tty、cdc-wdm、net
- [QMI、MBIM 与 AT 命令](#QMI、MBIM 与 AT 命令)
- [与 udev 的协作](#与 udev 的协作)
- [mmcli 常用命令速查](#mmcli 常用命令速查)
- [与 NetworkManager 的协作边界](#与 NetworkManager 的协作边界)
- 探测时序与「认不出模组」
- 常见踩坑与反模式
- 故障排查思路
- 小结
(可先读文首 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 | 哪个插件驱动(如 quectel、generic) |
| 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 用 插件 适配不同厂商与协议栈。插件分两类,不要混为一维:
| 类型 | 插件示例 | 说明 |
|---|---|---|
| 按厂商/模组 | quectel、sierra |
识别标准 VID/PID,探测路径短 |
| 按协议 | mbim、qmi(部分版本) |
按 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 会:
- 收集该 USB 设备下所有 interface 对应的
/dev节点 - 对每个 候选端口 发探测(AT 同步、QMI client 等)
- 确定 primary AT 口 、QMI/MBIM 口 、net 口
- 创建 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 QCDM、unhandled 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 0 的 Ports 段可看到完整映射。
八、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 -L为 No 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 配置。
实践要点:
- 理解 Modem / Bearer / SIM 对象模型与 tty vs cdc-wdm vs wwan 分工。
- generic 插件 + 多 tty 探测 (单口约 2~5 秒超时)决定热插拔识别往往 不是秒级。
- 时序问题(驱动绑定与 MM 抢跑)是「有 cdc-wdm 但 mmcli 空」的首 suspect。
- 用
mmcli -L+journalctl -u ModemManager排查,而非反复拔插或盲目 restart。 - MM 创建 Modem 后 NM 才有 gsm Device ;
registered后还须 NMconnection up才有 IP。 - 生产拨号 优先 NetworkManager GSM Connection ;旧系统注意 MM 版本(5G/MBIM 宜 1.18+)。
弄清 MM 的边界与节奏,才能在 USB 4G dongle、M.2 模组、工业网关等场景下 可预期地 完成蜂窝联网集成。