Android GSMMUX驱动实战:GSM 0710多路复用与tty对接
简介GSMMUX驱动是Android内核层用于创建和管理Mux复用器的软件组件本资源面向Android底层开发与硬件适配工程师帮助理解Mux如何让多个组件共享同一物理通道、优化GPIO等硬件资源的使用。压缩包共10个文件约220KB以C语言源码与头文件为核心包含gsm0710协议实现、缓冲区管理、JNI接口及Android.mk、Makefile构建脚本另附PDF技术文档、README说明与备份文件便于对照阅读驱动注册、硬件探测、I/O读写、中断处理、同步互斥、用户空间接口、电源管理及设备树配置等关键知识点。已有185人学习下载适合希望深入掌握Android内核驱动实现细节、提升底层开发与硬件适配能力的读者参考。1. GSMMUX driver for Android多路复用到底在解决什么现场问题如果你做过工业手持终端、车载 T-Box 或者带 4G 模组的 Android 设备大概率遇到过这个场景一个物理串口上同时要跑 AT 指令、PPP 拨号、GPS NMEA 数据上报、短信收发甚至还要留一路给模组固件升级。传统做法是给每路业务分配独立串口但硬件上往往只有一个 UART 连到模组这时候 GSMMUX driver for Android 就是绕不开的一环。它做的事情说白了就是在一条物理串口上虚拟出多条逻辑通道每条通道独立收发互不干扰。Android 侧要做的是把内核里的 GSM 0710 多路复用协议驱动跑通再通过 tty 设备节点暴露给上层。适合谁看做嵌入式 Android 系统集成、Modem 适配、工业物联网网关的工程师尤其是那些被“串口不够用”逼到墙角的团队。这一章先把概念立住后面再拆实现。2. GSM 0710 协议与 Android tty 子系统的对接逻辑2.1 为什么是 GSM 0710 而不是自己写一个分包协议GSM 0710 是 3GPP 定义的标准协议全称是“Terminal Equipment to Mobile Station multiplexer protocol”。它把一条物理串口划分成多个 DLCIData Link Connection Identifier每个 DLCI 对应一个逻辑通道。DLCI 0 是控制通道DLCI 1 通常给 AT 指令DLCI 2 给 PPPDLCI 3 给 GPS以此类推。自己写分包协议当然可以但你会面临帧同步、流控、错误恢复、与模组厂商兼容性等一系列问题。模组厂商的固件里已经内置了 GSM 0710 的从端实现你只要在 Android 主机侧把主端驱动跑起来双方就能按标准握手。常见做法是直接复用内核的drivers/tty/serial/gsm0710.c或者n_gsm.c线路规程而不是从零造轮子。2.2 Android 侧 tty 设备节点的生成路径Android 虽然基于 Linux 内核但它的用户空间和标准 Linux 发行版差别很大。GSM 0710 驱动加载后会在/dev下生成一系列 tty 设备节点比如/dev/gsmtty1、/dev/gsmtty2。这些节点要能被上层 HAL 或 RIL 访问需要满足几个条件内核配置里打开CONFIG_N_GSM设备树里正确描述 UART 控制器和模组电源、复位引脚init.rc 里给设备节点设置正确的权限和 SELinux 标签。很多人在这一步翻车是因为只编译了驱动模块却忘了在ueventd.rc里加规则导致上层进程打不开设备节点。2.3 从物理 UART 到虚拟通道的初始化流程初始化流程可以拆成四步。第一步内核启动时 UART 驱动探测到模组注册 tty 设备。第二步加载n_gsm线路规程通过ioctl把物理 tty 切换到 GSM 0710 模式。第三步驱动与模组交换 SABMSet Asynchronous Balanced Mode帧建立 DLCI 通道。第四步每个 DLCI 注册一个虚拟 tty 设备上层通过标准 tty 操作读写。下面是一个典型的内核模块加载和初始化脚本片段。# 加载 n_gsm 线路规程模块 modprobe n_gsm # 假设物理串口是 /dev/ttyS1将其配置为 GSM 0710 主端 # 使用 ldattach 工具附加线路规程 ldattach -s 115200 GSM0710 /dev/ttyS1 # 查看生成的虚拟 tty 设备 ls -l /dev/gsmtty* # 设置权限让上层 RIL 可访问 chmod 0660 /dev/gsmtty1 chown radio radio /dev/gsmtty1逻辑说明modprobe n_gsm加载内核模块ldattach是 util-linux 提供的工具负责把线路规程绑定到指定 tty 并设置波特率。参数-s 115200是物理串口波特率必须与模组侧一致否则 SABM 帧会超时。GSM0710是线路规程名称对应内核里的n_gsm。生成的gsmtty*设备节点数量取决于模组固件支持的 DLCI 数量常见是 4 到 8 个。权限设置这一步在 Android 上尤其关键因为 SELinux 默认会拦截非系统域进程访问 tty 设备。2.4 内核配置与设备树的关键参数在 Android 的 kernel config 里必须确保以下选项是开启的配置项作用常见值CONFIG_N_GSM启用 GSM 0710 线路规程y 或 mCONFIG_SERIAL_8250启用标准 8250 串口驱动yCONFIG_SERIAL_8250_DMA启用 DMA 传输降低 CPU 占用yCONFIG_TTYtty 子系统基础支持y设备树里UART 节点要正确配置pinctrl、clock、dmas。如果模组有独立的电源使能引脚和复位引脚也要在设备树里描述否则驱动加载时模组可能还没上电导致 SABM 握手失败。我一般会在设备树里加一个modem-power的 GPIO 控制节点在驱动 probe 之前先拉高使能。3. 在 Android 上跑通 GSMMUX 的最小实操步骤3.1 确认内核版本与驱动源码位置不同 Android 内核版本n_gsm的源码位置和 API 可能有差异。常见位置是kernel/drivers/tty/n_gsm.c。先确认你的内核版本# 查看内核版本 uname -r # 在内核源码目录里查找 n_gsm 相关文件 find . -name *gsm* -path */tty/*逻辑说明uname -r输出内核版本号比如 4.19、5.10、5.15。Android 常见的内核版本是 4.19 和 5.10。find命令用来定位驱动源码如果找不到n_gsm.c说明内核配置里没有启用该模块或者源码被裁剪了。这时候需要从对应内核版本的官方源码里把文件补回来并修改 Kconfig 和 Makefile。3.2 编译并加载 n_gsm 模块如果n_gsm被编译成模块CONFIG_N_GSMm需要把.ko文件推到设备里加载。如果是内置CONFIG_N_GSMy则直接在内核启动时生效。# 在内核源码目录编译模块 make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- modules -j8 # 找到生成的 n_gsm.ko find . -name n_gsm.ko # 推送到设备并加载 adb push n_gsm.ko /data/local/tmp/ adb shell su -c insmod /data/local/tmp/n_gsm.ko逻辑说明ARCHarm64指定目标架构CROSS_COMPILE指定交叉编译工具链前缀。-j8表示并行编译任务数按主机 CPU 核心数调整。insmod加载模块后可以用dmesg | grep gsm查看内核日志确认驱动是否 probe 成功。如果看到gsm0710: line discipline registered之类的输出说明模块加载正常。3.3 用 ldattach 绑定物理串口并验证 DLCI 通道模块加载后下一步是把物理串口切换到 GSM 0710 模式。Android 上可能没有ldattach命令需要自己交叉编译 util-linux 或者用ioctl直接操作。// 用 ioctl 手动设置线路规程的示例 #include linux/tty.h #include linux/gsmmux.h int fd open(/dev/ttyS1, O_RDWR | O_NOCTTY); struct gsm_config config; config.adaption 1; // 自适应波特率 config.encapsulation 0; // 基本模式 config.initiator 1; // 主机侧作为发起者 config.t1 10; // 定时器 T1单位 10ms config.t2 34; // 定时器 T2 config.n2 3; // 最大重传次数 ioctl(fd, GSMIOC_SETCONF, config); ioctl(fd, TIOCSETD, ldisc); // ldisc N_GSM0710逻辑说明GSMIOC_SETCONF用来设置 GSM 0710 的参数。adaption1表示自适应波特率适合模组波特率不确定的场景。initiator1表示 Android 侧作为主端发起 SABM 握手。t1、t2、n2是协议定时器和重传参数默认值通常够用但在高延迟链路上可能需要调大t1。TIOCSETD把线路规程切换到N_GSM0710之后内核就会开始与模组握手。握手成功后/dev/gsmtty1等节点会出现。3.4 上层 RIL 与虚拟串口的对接虚拟串口就绪后Android 的 RILRadio Interface Layer需要打开对应的gsmtty节点。常见做法是在init.rc里创建符号链接或者修改 RIL 的配置文件把 AT 通道指向/dev/gsmtty1PPP 通道指向/dev/gsmtty2。# 在 init.rc 里添加设备节点权限和符号链接 on boot chmod 0660 /dev/gsmtty1 chown radio radio /dev/gsmtty1 symlink /dev/gsmtty1 /dev/at0 chmod 0660 /dev/gsmtty2 chown radio radio /dev/gsmtty2 symlink /dev/gsmtty2 /dev/ppp0逻辑说明chmod和chown确保 radio 用户组可以读写设备节点。symlink创建固定名称的符号链接方便上层配置里写死路径。注意 SELinux 标签也要同步设置否则即使权限对了访问仍会被拒绝。可以在file_contexts里加一行/dev/gsmtty[0-9]* u:object_r:radio_device:s0。4. 避坑指南GSMMUX 在 Android 上最容易翻车的五个点4.1 现象驱动加载成功但 /dev/gsmtty* 不出现原因物理串口没有正确切换到 GSM 0710 模式或者模组侧没有响应 SABM 握手。常见情况是波特率不匹配或者模组固件里多路复用功能默认关闭需要用 AT 指令手动开启。解决先用stty -F /dev/ttyS1 115200确认物理串口波特率再用cat /dev/ttyS1看是否有模组主动上报的数据。如果模组支持 AT 指令先发ATCMUX1开启多路复用再重新绑定线路规程。检查内核日志dmesg | grep gsm看是否有 SABM 超时或帧错误。4.2 现象虚拟串口能打开但读写数据丢包或乱序原因流控没有正确配置。GSM 0710 支持软件流控XON/XOFF和硬件流控RTS/CTS。如果物理串口没有启用硬件流控高速数据传输时缓冲区溢出会导致丢包。解决在gsm_config里设置flow_control参数通常设为 1 表示硬件流控。同时确认设备树里 UART 的rts和cts引脚已正确配置。如果硬件不支持流控降低波特率或减小单次写入的数据量。4.3 现象SELinux 拒绝访问 gsmtty 设备原因Android 的 SELinux 策略默认不允许非系统域进程访问新生成的 tty 设备。即使chmod 0660设置了权限avc: denied日志仍会出现。解决在file_contexts里给/dev/gsmtty*打上正确的标签比如radio_device。然后在radio.te里添加allow radio radio_device:chr_file { open read write ioctl };。编译 sepolicy 后刷入设备用dmesg | grep avc确认没有新的拒绝日志。4.4 现象PPP 拨号成功但网络不稳定频繁断线原因GSM 0710 的定时器参数t1和n2设置过小在弱信号或高延迟场景下SABM 帧重传次数不够导致 DLCI 通道被误判为断开。解决把t1从默认的 10100ms调到 20 或 30n2从 3 调到 5。同时检查模组侧的定时器配置是否与主机侧匹配。如果模组固件不支持调整考虑在 PPP 层增加lcp-echo-interval和lcp-echo-failure参数让 PPP 自己检测链路状态。4.5 现象多路并发时 CPU 占用率飙升原因物理串口没有启用 DMA每次数据收发都靠 CPU 中断搬运。GSM 0710 多路复用本身会增加帧封装和解封装的开销如果再加上中断风暴CPU 很容易跑满。解决在设备树里给 UART 节点添加dmas和dma-names属性启用 DMA 传输。内核配置里打开CONFIG_SERIAL_8250_DMA。如果模组支持把波特率从 115200 提高到 921600减少单帧传输时间。用top -H查看内核线程的 CPU 占用确认瓶颈在 UART 中断还是 GSM 0710 的帧处理。5. 进阶技巧用 sysfs 调试 GSMMUX 与动态调整 DLCI5.1 通过 sysfs 查看 DLCI 通道状态n_gsm驱动在 sysfs 里暴露了一些调试接口可以查看每个 DLCI 的状态、帧计数和错误统计。路径通常是/sys/class/tty/gsmtty1/device/或者/sys/kernel/debug/gsm/。具体路径取决于内核版本和驱动实现。# 查看 DLCI 通道状态 cat /sys/kernel/debug/gsm/dlci_state # 查看帧统计 cat /sys/kernel/debug/gsm/frame_stats # 查看错误计数 cat /sys/kernel/debug/gsm/error_count逻辑说明dlci_state显示每个 DLCI 的当前状态比如OPEN、CLOSED、NEGOTIATING。frame_stats显示发送和接收的帧数、字节数。error_count显示 CRC 错误、帧格式错误等。这些信息在排查丢包和断线问题时非常有用。如果 debugfs 没有挂载先执行mount -t debugfs none /sys/kernel/debug。5.2 动态添加和删除 DLCI 通道有些场景下你不需要一开始就建立所有 DLCI 通道而是按需动态添加。n_gsm驱动支持通过ioctl动态创建和销毁 DLCI。// 动态添加一个 DLCI 通道 struct gsm_dlci_config dlci_cfg; dlci_cfg.dlci 5; // 指定 DLCI 编号 dlci_cfg.type 1; // 1 表示数据通道 dlci_cfg.mtu 1500; // 最大传输单元 ioctl(fd, GSMIOC_ADD_DLCI, dlci_cfg); // 删除 DLCI 通道 ioctl(fd, GSMIOC_DEL_DLCI, dlci_cfg.dlci);逻辑说明GSMIOC_ADD_DLCI在运行时创建一个新的 DLCI 通道内核会分配对应的gsmtty设备节点。GSMIOC_DEL_DLCI销毁指定通道。动态添加适合按需启动 GPS 或短信业务的场景可以节省资源。注意 DLCI 编号不能与已存在的通道冲突通常 0 是控制通道1 到 4 是固定业务5 以上可以动态分配。5.3 用 tcpdump 抓取 GSM 0710 帧做协议分析如果怀疑是协议层的问题可以在物理串口上抓取原始数据用 Wireshark 的 GSM 0710 解析器分析。Android 上可以用tcpdump抓取 tty 设备的数据但需要内核支持CONFIG_TTY_PRINTK或者用strace跟踪read/write调用。# 在主机侧用 tcpdump 抓取串口数据需要 USB 转串口 tcpdump -i /dev/ttyUSB0 -w gsm0710.pcap # 用 Wireshark 打开过滤 gsm0710 协议 wireshark gsm0710.pcap逻辑说明tcpdump直接抓取串口设备的数据保存为 pcap 格式。Wireshark 内置了 GSM 0710 解析器可以显示每个帧的 DLCI、类型、长度和载荷。如果看到大量 SABM 重传或 DMDisconnected Mode帧说明握手或链路维持有问题。这个方法在排查模组兼容性问题时特别有效因为你可以直接看到双方交换的原始帧。5.4 一个我踩过的坑波特率自适应不是万能的早期我在一个项目里图省事把adaption设为 1让驱动自动适配模组波特率。结果模组在上电后会先输出一段乱码驱动误判波特率导致后续所有帧都解析失败。后来改成固定波特率并在驱动 probe 之前先给模组足够的上电稳定时间问题才消失。血泪经验是如果模组手册里写明了默认波特率就老老实实写死别依赖自适应。自适应只适合模组波特率确实未知且无法通过 AT 指令查询的场景。5.5 验证 GSMMUX 稳定性的一个土办法最后分享一个验证稳定性的土办法写一个脚本同时打开所有gsmtty节点每个节点持续发送随机长度的数据跑 24 小时然后统计每个通道的收发字节数和错误计数。如果某个通道的错误计数持续增长说明该 DLCI 的流控或定时器参数需要调整。这个办法虽然土但能暴露很多在短时间测试里发现不了的问题。我一般会在项目量产前跑一轮确认没有通道在长时间压力下掉线。希望帮到你。本文还有配套的精品资源点击获取