This commit is contained in:
zhongluofeng
2026-08-21 18:28:33 +08:00
commit 9e478f1591
76 changed files with 27707 additions and 0 deletions
+1
Submodule 5G-Modem-Support added at b1d9a12ac4
+190
View File
@@ -0,0 +1,190 @@
# LEDE 5G 固件构建与分析记录
> 日期:2026-08-21
> 仓库:https://github.com/Atieei/ledefork 自 coolsnowwolf/lede
> 提交:`a09ade8` add 5G modem support (RM520N-GL MBIM) with luci management and CI build
> 目标:飞牛OS (fnOS) KVM 虚拟机 + PCIe 直通 RM520N-GL,编译带自写管理界面的精简稳定版 LEDE 固件
---
## 一、实测经验核心结论(固件设计依据)
以下结论来自 RM520N-GL 在 fnOS KVM + PCIe 直通环境下的实测,是整个固件方案的基石:
| 结论 | 详情 |
|---|---|
| MBIM 是唯一可行模式 | RM520N-GL 在 PCIe/MHI 下 QMI 通道不可用,驱动必须以 `mhi_mbim_enabled=1` 加载 |
| 拨号命令禁用 -i 参数 | MBIM 模式下 `quectel-CM -s <APN>` 即可,传 `-i rmnet_mhi0` 会导致异常 |
| 驱动版本 | pcie_mhi V1.3.8(源码来自 Siriling/5G-Modem-Support 的 quectel_MHI |
| 拨号工具 | quectel-CM V1.6.8QConnectManagerMBIM 路径实测通过) |
| AT 通道与拨号并存 | `/dev/mhi_DUN`sms_tool AT)与 `/dev/mhi_MBIM` 拨号可同时使用 |
| SYS_ERR 故障 | 模块失联时 VM 内 reboot 无法恢复(不切断 PCIe 供电),需阶梯式自愈或宿主机断电 |
| 数据接口 | 拨号成功后 IP 出现在 `rmnet_mhi0.1` |
---
## 二、固件包结构(package/5g-modem/
```
package/5g-modem/
├── pcie-mhi/ # 内核驱动 kmod-pcie-mhi (V1.3.8)
│ ├── Makefile # 外部内核模块编译 (kbuild, 产出 pcie_mhi.ko)
│ ├── src/ # 驱动源码 (core/controllers/devices)
│ └── files/etc/modules.d/90-pcie_mhi # 开机自动加载: pcie_mhi mhi_mbim_enabled=1
├── quectel-cm/ # 拨号工具 quectel-cm (V1.6.8)
│ ├── Makefile # musl-gcc 单命令编译 (与实测通过的编译命令一致)
│ └── src/ # QConnectManager 源码
├── sms-tool/ # AT 命令工具 (obsy/sms_tool + 中文编码修正补丁)
└── luci-app-5gmodem/ # 自写 LuCI 管理应用
├── luasrc/controller/5gmodem.lua # 菜单 + API (status/dial/at)
├── luasrc/model/cbi/5gmodem/settings.lua # 设置页 (APN/认证/PIN/守护开关)
├── luasrc/view/5gmodem/status.htm # 状态页 (轮询+控制+AT控制台)
└── root/
├── etc/config/5gmodem # UCI 默认配置
├── etc/init.d/5gmodem # procd 拨号服务 (START=95)
├── usr/bin/5gmodem-guard # SYS_ERR 自愈守护
└── usr/libexec/5gmodem-status.sh # 状态采集 (JSON 输出)
```
依赖声明:`luci-app-5gmodem``+kmod-pcie-mhi +quectel-cm +sms-tool +luci-compat`
---
## 三、LuCI 管理界面功能
菜单:网络 → 5G 拨号(状态 / 拨号设置)
**状态页(10 秒轮询)**
- 服务与驱动:quectel-CM 运行状态/PID、守护状态、pcie_mhi 加载、MBIM/DUN 通道就绪、PCI 设备地址
- 连接信息:`rmnet_mhi0.1` 的 IP、上下行流量(自动格式化 B/KB/MB/GB
- 模块信息:型号/制造商/固件/IMEI/IMSI/SIM 状态/运营商(PLMN 数字映射中文名)/网络模式/频段信道
- 信号质量:RSRP/SINR/RSRQ/RSSI,带优/中/差彩色分级标签
- 拨号控制:启动/停止/重启(POST + token,防 CSRF
- AT 控制台:自由输入 + 15 条常用命令快捷下拉(QCSQ/QNWINFO/QENG="servingcell"/QTEMP 等),输出区滚动显示
- 拨号日志:/tmp/quectel_cm.log 尾部 12 行实时展示
**设置页**
- 开机自动拨号、APN(默认 wonet)、PAP/CHAP 用户名密码(明文显示便于调试)、认证方式、PIN 码、AT 串口路径、自愈守护开关
**API(控制器)**
- `api_status`GET 状态 JSON
- `api_dial`POST start/stop/restart/reload(白名单校验)
- `api_at`:POST AT 命令,正则白名单校验(`^AT` 开头,允许字母数字空格 `+ " = % ? , . : / # * ( ) -`,禁单引号保证 shell 拼接安全),串口路径二次校验 + 存在性检查
---
## 四、SYS_ERR 自愈守护(5gmodem-guard
基于实测故障经验设计,procd 托管、每 15 秒巡检,恢复阶梯:
1. **阶段 1**`echo <bdf> > /sys/bus/pci/drivers/mhi_q/bind`(实测有效的手动唤醒法,每 30s 重试)
2. **阶段 2**PCI remove + rescan,重走 BHI 固件下载(故障 120s 后,每 60s 一次)
3. **阶段 3**rmmod pcie_mhi + 重加载 MBIM 模式(故障 300s 后,每 90s 一次)
4. **阶段 4**:日志提示需宿主机断电(故障 600s 后,每 300s 提示;VM 内无法切断 PCIe 供电属物理限制)
附加:设备在但 quectel-CM 进程死亡超过 60s 时自动 restart 拨号服务。日志写入 `/tmp/5gmodem_guard.log`(超 200 行自动截半)。
设备识别:扫描 `/sys/bus/pci/devices/*/vendor` 匹配移远 `0x17cb`/`0x1eac`
---
## 五、兼容性验证记录(关键分析)
### 5.1 LuCI 分支兼容性(openwrt-25.12
feeds.conf.default 使用 `coolsnowwolf/luci;openwrt-25.12` 分支,该分支 ucode dispatcher 为新架构。逐项读源码实证:
1. **Lua controller 支持**`modules/luci-base/ucode/dispatcher.uc``glob('/usr/lib/lua/luci/controller/*.lua')` 扫描并通过 `runtime.call('luci.dispatcher', 'process_lua_controller', file)` 桥接处理 → 旧式 controller 完全可用
2. **依赖链完整**`luci-compat` (Makefile: `LUCI_DEPENDS:=+luci-lua-runtime`) → `luci-lua-runtime` 模块存在(含 dispatcher.lua/template.lua 等),luci-base 依赖 `ucode-mod-*` 全套
3. **模板桥接**`luci-lua-runtime/luasrc/template.lua``include()` 在 Lua 视图目录找不到时回退 `L.include(name)`ucode 主题模板),因此 `<%+header%>`/`<%+footer%>` 正确渲染主题框架;bootstrap 主题提供 `ucode/template/themes/bootstrap/header.ut|footer.ut`
4. **同模式先例**:该分支有 68 个 luasrc/controller 文件、150 个 CBI 文件(docker/diskman/lucky/nfs 等应用均为旧式 Lua 写法)
5. **CBI Flag 类**`luci-compat/luasrc/cbi.lua` Flag 构造器初始化 `enabled="1"/disabled="0"`,设置页默认值逻辑正确
### 5.2 内核驱动 6.12/6.18 兼容性
- pcie_mhi 源码版本守护完善:42 处 `LINUX_VERSION_CODE` 条件编译
- 关键破坏点 `class_create` 已适配:`mhi_uci.c:954``mhi_init.c:2678` 均有 `>= 6.4.0` 单参数版本分支
- `mhi_netdev_quectel.c` 有 6.1 分界适配(netdev/dm/timer API
### 5.3 重要发现与修正:virtio 配置冗余
原计划在 `target/linux/x86/config-6.12` base 配置添加 KVM guest 支持。复查发现 **`target/linux/x86/64/config-6.12`(6.18 同)子目标配置已内建全套支持**(`HYPERVISOR_GUEST`/`KVM_GUEST`/`PARAVIRT`/`PARAVIRT_CLOCK`/`VIRTIO`/`VIRTIO_PCI`/`VIRTIO_NET`/`VIRTIO_BLK`/`VIRTIO_BALLOON`/`SCSI_VIRTIO`/`HW_RANDOM_VIRTIO`/`DRM_VIRTIO_GPU` 等全为 y,且子目标配置覆盖合并 base)。
**处置**:回退 base config 修改。fork 相对上游 diff 仅剩纯新增的 3 个目录(`.github/workflows/``configs/``package/5g-modem/`),后续同步上游零冲突。
### 5.4 内核竞争配置检查
确认内核主线 MHI 相关配置(`CONFIG_MHI_BUS` 等)未启用,与外部 pcie_mhi 驱动无符号竞争。
---
## 六、本地校验记录(提交前)
| 项目 | 方法 | 结果 |
|---|---|---|
| 4 个 shell 脚本 | Git Bash `sh -n`status.sh / guard / init.d / modules.d | 全部通过 |
| status.htm 内嵌 JS145 行) | Node `new Function()` 语法检查(LuCI 模板占位符替换后) | 通过 |
| 2 个 Lua 文件 | 自写块结构平衡检查器(剥离注释/字符串后统计 function/if/do/end 配对) | 通过 |
| 行尾检查 | 逐文件统计 CRLF | 仅 sms-tool/Makefile 为 CRLF,已规范化为 LF |
| 执行位 | `git update-index --add --chmod=+x` 三个脚本 | 索引中 100755 确认 |
| 行尾入库保障 | `package/5g-modem/.gitattributes`*.sh/*.lua/*.htm/Makefile 强制 LF | 已设置 |
---
## 七、Git 提交与推送
- 克隆采用 `--depth=1 --filter=blob:none` + sparse-checkout(仅 target/linux/x86 等),提交前将新目录加入 sparse 规则
- 提交 `a09ade8`80 个文件,45600 行插入
- 已推送 `master``origin/master`81636b7..a09ade8
- 三个可执行脚本(init.d/5gmodem、5gmodem-guard、5gmodem-status.sh)以 100755 模式入库(Linux 上 procd 直接执行必需)
---
## 八、新镜像构建与产物
### 构建方法(手动触发)
1. 打开 https://github.com/Atieei/lede/actions
2. 左侧选 **Build 5G-Modem LEDE (x86_64)**
3. **Run workflow** → 直接 Run(首编约 2-3 小时)
4. 可选输入:勾选"忽略缓存全量重编"(首次构建或缓存异常时使用)
### 工作流特性
- 运行环境 ubuntu-22.04lede 官方 CI 同款依赖安装 + jlumbroso/free-disk-space 磁盘清理
- toolchain/dl 缓存(key 含 feeds.conf.default 与种子配置哈希),重编大幅提速
- `.config``configs/x86_64-5gmodem.config` 种子 + `make defconfig` 生成,并强校验 5G 五个包必须被选中(否则立即失败)
- 编译失败自动重试 `make -j1 V=s` 并产出日志 artifact
### 产物(3 个 artifact
| Artifact | 内容 |
|---|---|
| lede-5gmodem-firmware-x86_64 | 固件镜像(BIOS+EFI 双启动 squashfs+gzip,位于 bin/targets/x86/64/ |
| lede-5gmodem-packages | 全部 .ipk(可装进现有系统调试,无需重刷固件) |
| lede-5gmodem-buildinfo | *.buildinfo / *.manifest(精确还原配置) |
### 精简配置说明(configs/x86_64-5gmodem.config
- 关闭:CPU 微码、声卡全套、物理显卡(amdgpu/i915)、物理网卡(igb/i40e/mlx5/r8125 等 17 种)、USB 网卡、f2fs/mmc/传感器/自动挂载
- 保留:e100/e1000/e1000eKVM 常见网卡)+ virtio(内核内建)+ PCIe 直通 + LuCI 中文界面 + 5G 全套包
---
## 九、部署指南
1. 下载固件 artifact,解压取 `*-x86-64-generic-squashfs-combined-efi.img.gz`fnOS KVM 用 EFI
2. `gunzip` 后作为磁盘导入 KVM 虚拟机,vCPU/内存按需,PCIe 直通 RM520N-GL
3. 首次启动后 LAN 口(默认 192.168.1.1)访问 LuCI → 网络 → 5G 拨号
4. 设置页填 APN(联通 wonet)保存即自动拨号;或状态页手动"启动拨号"
5. 多 WAN 需手动配置(固件按需求不预置策略路由)
## 十、故障排查速查
| 现象 | 排查 |
|---|---|
| 状态页全灰 | `lsmod | grep pcie_mhi``ls -l /dev/mhi_*``cat /tmp/5gmodem_guard.log` |
| 拨号无 IP | `cat /tmp/quectel_cm.log`(完整日志)、AT 控制台发 `AT+CPIN?` 查 SIM |
| 自愈不成功 | 看守护日志阶段提示;600s 仍未恢复需宿主机断电(物理限制) |
| AT 控制台无响应 | 确认 `/dev/mhi_DUN` 存在;串口路径在设置页可改 |
| ipk 调试 | artifacts 的 packages 里直接 `opkg install` 对应 ipk 到现有系统 |
+21
View File
@@ -0,0 +1,21 @@
# RM520N-GL 5G 模块 MBIM 拨号成功路线打包
## 内容
- 在线参考 https://blog.siriling.com:81/2023/03/18/openwrt-5g-modem/
- 在线参考 https://github.com/Siriling/5G-Modem-Support
- **文档/**:完整成功路线说明(主文档 `RM520N-MBIM拨号成功路线.md`
- **文件/binaries/**musl 静态编译的 Quectel-CM V1.6.8 及 proxy 工具(部署到 /usr/bin 用)
- **文件/source/**V1.6.8 完整源码(含官方 PDF 文档)
- **文件/configs/**iStoreOS 上的配置(modem 配置、驱动参数、开机脚本)
- **文件/logs/**:成功拨号日志 + 移远官方参考日志
## 快速开始
1. 切换驱动到 MBIM`insmod pcie_mhi.ko mhi_mbim_enabled=1`
2. 部署 `binaries/quectel-CM``/usr/bin/`
3. 拨号:`/usr/bin/quectel-CM -s wonet`
4. 开机自启:复制 `configs/init.d_modem5g``/etc/init.d/` 并 enable
详见 `文档/RM520N-MBIM拨号成功路线.md`
## 关键结论
**RM520N-GL 在 PCIe 直通 + pcie_mhi 驱动下,QMI 模式不通(模块不响应),MBIM 是正解。**
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
pcie_mhi mhi_mbim_enabled=1
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh /etc/rc.common
# RM520N-GL PCIe MBIM 拨号自启 (2026-08-19)
# 依赖: /etc/rc.local 的 mhi_q 绑定修复先跑, /dev/mhi_MBIM 出现后拨号
START=95
STOP=15
start() {
echo "modem5g: 等待 /dev/mhi_MBIM ..."
local i=0
while [ $i -lt 30 ]; do
[ -c /dev/mhi_MBIM ] && break
sleep 2
i=$((i+1))
done
if [ ! -c /dev/mhi_MBIM ]; then
echo "modem5g: /dev/mhi_MBIM 未出现, 放弃拨号"
return 1
fi
echo "modem5g: 开始 MBIM 拨号 (wonet)"
start-stop-daemon -S -b -m -p /var/run/quectel_cm.pid \
-x /usr/bin/quectel-CM -- -s wonet -f /tmp/quectel_cm.log
echo "modem5g: 已启动 quectel-CM"
}
stop() {
echo "modem5g: 停止拨号"
start-stop-daemon -K -p /var/run/quectel_cm.pid 2>/dev/null
return 0
}
+25
View File
@@ -0,0 +1,25 @@
config global 'global'
option enable_dial '1'
option modem_number '1'
option usb_scan '1'
option pci_scan '1'
option manual_configuration '1'
option network_interface_alias '1'
config modem-device 'modem0'
option data_interface 'pci'
option path '/sys/devices/pci0000:00/0000:00:0a.0'
option driver 'driver'
option name 'qualcomm modem'
option manufacturer 'quectel'
option define_connect '1'
option platform 'quectel'
list modes 'qmi'
list modes 'gobinet'
list modes 'ecm'
list modes 'mbim'
list modes 'rndis'
list modes 'ncm'
option at_port '/dev/mhi_DUN'
+12
View File
@@ -0,0 +1,12 @@
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.
# ===== RM520N-GL 说明 (2026-08-20 更新) =====
# pcie_mhi 驱动由 /etc/modules.d/90-pcie_mhi 开机自动加载(mhi_mbim_enabled=1)。
# 原生流程即可自启拨号(S95modem5g 等待 /dev/mhi_MBIM 后起 quectel-CM)。
# 无需在此手动 bind mhi_q —— 手动强绑会抢设备导致 pcie_mhi 失联(SYS_ERR)。
# 开机自启链路: 90-pcie_mhi(驱动) -> S95modem5g(拨号) -> S96wan5g-route(策略路由+NAT)
# 诊断: dmesg | grep -iE "mhi|pcie_mhi"; ls /dev/mhi_*
# 故障处理: 若模块失联且无 /dev/mhi_DUN, 先飞牛断电重启; 若无效再手动:
# echo 0000:00:0a.0 > /sys/bus/pci/drivers/mhi_q/bind # 唤醒(临时)
exit 0
@@ -0,0 +1,30 @@
[08-19_13:00:23:606] HardwareInfo: RM520N-GL
[08-19_13:00:23:607] mbim_device_services_query()
[08-19_13:00:23:613] mbim_set_radio_state( 1 )
[08-19_13:00:23:615] HwRadioState: 1, SwRadioState: 1
[08-19_13:00:23:615] mbim_subscriber_status_query()
[08-19_13:00:23:621] SubscriberId: 460092810130406
[08-19_13:00:23:621] SimIccId: 89860125801671811893
[08-19_13:00:23:621] SubscriberReadyState NotInitialized -> Initialized
[08-19_13:00:23:621] mbim_register_state_query()
[08-19_13:00:23:628] RegisterState Unknown -> Home
[08-19_13:00:23:628] mbim_packet_service_query()
[08-19_13:00:23:632] PacketServiceState Unknown -> Attached
[08-19_13:00:23:632] CurrentDataClass = Custom
[08-19_13:00:23:632] mbim_query_connect(sessionID=0)
[08-19_13:00:23:635] ActivationState Unknown -> Deactivated
[08-19_13:00:23:635] ip link set dev rmnet_mhi0 down
[08-19_13:00:23:640] ip addr flush dev rmnet_mhi0.1
[08-19_13:00:23:644] ip link set dev rmnet_mhi0.1 down
[08-19_13:00:23:649] mbim_register_state_query()
[08-19_13:00:23:652] mbim_packet_service_query()
[08-19_13:00:23:657] CurrentDataClass = Custom
[08-19_13:00:23:657] mbim_set_connect(onoff=1, sessionID=0)
[08-19_13:00:23:916] ActivationState Deactivated -> Activated
[08-19_13:00:23:916] mbim_ip_config(sessionID=0)
[08-19_13:00:23:921] < SessionId = 0
[08-19_13:00:23:921] < IPv4ConfigurationAvailable = 0xf
[08-19_13:00:23:921] < IPv6ConfigurationAvailable = 0x0
[08-19_13:00:23:921] < IPv4AddressCount = 0x1
[08-19_13:00:23:921] < IPv4AddressOffset = 0x3c
[08-19_13:00:23:921] < IPv6AddressCount = 0x
@@ -0,0 +1,80 @@
root@OpenWrt:/# lspci
00:00.0 Class 0604: 17cb:1001
01:00.0 Class ff00: 17cb:0306
root@OpenWrt:/# insmod pcie_mhi.ko mhi_mbim_enabled=1
[ 63.094154] mhi_init Quectel_Linux_PCIE_MHI_Driver_V1.3.0.17
[ 63.094739] mhi_pci_probe pci_dev->name = 0000:01:00.0, domain=0, bus=1, slot=0, vendor=17CB, device=0306
[ 63.099373] mhi_q 0000:01:00.0: BAR 0: assigned [mem 0x48000000-0x48000fff 64bit]
[ 63.108476] mhi_q 0000:01:00.0: enabling device (0140 -> 0142)
[ 63.293451] [I][mhi_netdev_enable_iface] Prepare the channels for transfer
[ 63.324757] [I][mhi_netdev_enable_iface] Exited.
[ 63.326265] rmnet_vnd_register_device(rmnet_mhi0.1)=0
root@OpenWrt:/# quectel-CM -s cment &
[04-13_09:25:23:910] Quectel_QConnectManager_Linux_V1.6.0.25
[04-13_09:25:23:912] network interface '' or qmidev '' is not exist
[04-13_09:25:23:912] netcard driver = pcie_mhi, driver version = V1.3.0.17
[04-13_09:25:23:913] mbim_qmap_mode = 1, vlan_id = 0x00, qmap_netcard = rmnet_mhi0.1
[04-13_09:25:23:913] Modem works in MBIM mode
[04-13_09:25:23:965] cdc_wdm_fd = 7
[04-13_09:25:23:965] mbim_open_device()
[04-13_09:25:24:549] mbim_device_caps_query()
[04-13_09:25:24:575] DeviceId: 869710030002905
[04-13_09:25:24:575] FirmwareInfo: RM500QGLABR10A03M4G_01.001.03
[04-13_09:25:24:575] HardwareInfo: RM500QGL_VH
[04-13_09:25:24:576] mbim_device_services_query()
[04-13_09:25:24:585] mbim_set_radio_state( 1 )
[04-13_09:25:24:588] HwRadioState: 1, SwRadioState: 1
[04-13_09:25:24:588] mbim_subscriber_status_query()
[04-13_09:25:24:612] SubscriberId: 460028563800461
[04-13_09:25:24:612] SimIccId: 89860015120716380461
[04-13_09:25:24:613] SubscriberReadyState NotInitialized -> Initialized
[04-13_09:25:24:613] mbim_register_state_query()
[04-13_09:25:24:617] RegisterState Unknown -> Home
[04-13_09:25:24:617] mbim_packet_service_query()
[04-13_09:25:24:619] PacketServiceState Unknown -> Attached
[04-13_09:25:24:619] CurrentDataClass = 5G_NSA
[04-13_09:25:24:620] mbim_query_connect(sessionID=0)
[04-13_09:25:24:631] ActivationState Unknown -> Deactivated
[04-13_09:25:24:631] ifconfig rmnet_mhi0 down
[04-13_09:25:24:657] ifconfig rmnet_mhi0.1 0.0.0.0
ifconfig: SIOCSIFFLAGS: Network is down
[04-13_09:25:24:681] ifconfig rmnet_mhi0.1 down
[04-13_09:25:24:705] mbim_register_state_query()
[04-13_09:25:24:709] mbim_packet_service_query()
[04-13_09:25:24:713] CurrentDataClass = 5G_NSA
[04-13_09:25:24:713] mbim_set_connect(onoff=1, sessionID=0)
[04-13_09:25:25:096] ActivationState Deactivated -> Activated
[04-13_09:25:25:097] mbim_ip_config(sessionID=0)
[04-13_09:25:25:100] < SessionId = 0
[04-13_09:25:25:100] < IPv4ConfigurationAvailable = 0xf
[04-13_09:25:25:100] < IPv6ConfigurationAvailable = 0x0
[04-13_09:25:25:101] < IPv4AddressCount = 0x1
[04-13_09:25:25:101] < IPv4AddressOffset = 0x3c
[04-13_09:25:25:101] < IPv6AddressCount = 0x0
[04-13_09:25:25:102] < IPv6AddressOffset = 0x0
[04-13_09:25:25:102] < IPv4 = 10.190.166.229/30
[04-13_09:25:25:103] < gw = 10.190.166.230
[04-13_09:25:25:103] < dns1 = 211.138.180.2
[04-13_09:25:25:103] < dns2 = 211.138.180.3
[04-13_09:25:25:104] < ipv4 mtu = 1500
[04-13_09:25:25:112] ifconfig rmnet_mhi0 up
[04-13_09:25:25:141] ifconfig rmnet_mhi0.1 up
[04-13_09:25:25:170] ip -4 address flush dev rmnet_mhi0.1
[04-13_09:25:25:190] ip -4 address add 10.190.166.229/30 dev rmnet_mhi0.1
[04-13_09:25:25:213] ip -4 route add default via 10.190.166.230 dev rmnet_mhi0.1
root@OpenWrt:/# ifconfig rmnet_mhi0.1
rmnet_mhi0.1 Link encap:Ethernet HWaddr 02:50:F4:00:00:00
inet addr:10.190.166.229 Mask:255.255.255.252
inet6 addr: fe80::50:f4ff:fe00:0/64 Scope:Link
UP RUNNING NOARP MTU:1500 Metric:1
RX packets:19 errors:0 dropped:0 overruns:0 frame:0
TX packets:29 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:2326 (2.2 KiB) TX bytes:2991 (2.9 KiB)
root@OpenWrt:/# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=52 time=278.561 ms
+36
View File
@@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 2.4)
project(quectel-CM)
add_definitions(-Wall -Wextra -Werror -O1)
option(USE_QRTR "Enable QRTR" OFF)
set( QL_CM_SRC
QmiWwanCM.c GobiNetCM.c main.c QCQMUX.c QMIThread.c util.c qmap_bridge_mode.c mbim-cm.c device.c
atc.c atchannel.c at_tok.c
udhcpc.c
)
if(USE_QRTR)
add_definitions(-DCONFIG_QRTR)
set( QRTR_SRC qrtr.c rmnetctl.c)
endif()
add_executable(quectel-CM ${QL_CM_SRC} ${QRTR_SRC})
target_link_libraries(quectel-CM PUBLIC pthread)
install (TARGETS quectel-CM DESTINATION bin)
add_executable(quectel-qmi-proxy quectel-qmi-proxy.c)
target_link_libraries(quectel-qmi-proxy PUBLIC pthread)
install (TARGETS quectel-qmi-proxy DESTINATION bin)
add_executable(quectel-mbim-proxy quectel-mbim-proxy.c)
target_link_libraries(quectel-mbim-proxy PUBLIC pthread)
install (TARGETS quectel-mbim-proxy DESTINATION bin)
add_executable(quectel-atc-proxy quectel-atc-proxy.c atchannel.c at_tok.c util.c)
target_link_libraries(quectel-atc-proxy PUBLIC pthread)
install (TARGETS quectel-atc-proxy DESTINATION bin)
#add_executable(quectel-qrtr-proxy quectel-qrtr-proxy.c)
#target_link_libraries(quectel-qrtr-proxy PUBLIC pthread)
#install (TARGETS quectel-qrtr-proxy DESTINATION bin)
+242
View File
@@ -0,0 +1,242 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <stdio.h>
#include <ctype.h>
#include "QMIThread.h"
#ifdef CONFIG_GOBINET
static int qmiclientId[QMUX_TYPE_ALL];
// IOCTL to generate a client ID for this service type
#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1
// IOCTL to get the VIDPID of the device
#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2
// IOCTL to get the MEID of the device
#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3
static int GobiNetSendQMI(PQCQMIMSG pRequest) {
int ret, fd;
fd = qmiclientId[pRequest->QMIHdr.QMIType];
pRequest->QMIHdr.ClientId = (fd&0xFF) ? fd&0xFF : pRequest->QMIHdr.QMIType;
if (fd <= 0) {
dbg_time("%s QMIType: %d has no clientID", __func__, pRequest->QMIHdr.QMIType);
return -ENODEV;
}
// Always ready to write
if (1 == 1) {
ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR);
ret = write(fd, &pRequest->MUXMsg, nwrites);
if (ret == nwrites) {
ret = 0;
} else {
dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno));
}
} else {
dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno));
}
return ret;
}
static int GobiNetGetClientID(const char *qcqmi, UCHAR QMIType) {
int ClientId;
ClientId = cm_open_dev(qcqmi);
if (ClientId == -1) {
dbg_time("failed to open %s, errno: %d (%s)", qcqmi, errno, strerror(errno));
return 0;
}
if (ioctl(ClientId, IOCTL_QMI_GET_SERVICE_FILE, QMIType) != 0) {
dbg_time("failed to get ClientID for 0x%02x errno: %d (%s)", QMIType, errno, strerror(errno));
close(ClientId);
ClientId = 0;
}
switch (QMIType) {
case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break;
case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break;
case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break;
case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break;
case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break;
case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break;
case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break;
case QMUX_TYPE_COEX: dbg_time("Get clientCOEX = %d", ClientId); break;
case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId);
break;
default: break;
}
return ClientId;
}
static int GobiNetDeInit(void) {
unsigned int i;
for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
{
if (qmiclientId[i] != 0)
{
close(qmiclientId[i]);
qmiclientId[i] = 0;
}
}
return 0;
}
static void * GobiNetThread(void *pData) {
PROFILE_T *profile = (PROFILE_T *)pData;
const char *qcqmi = (const char *)profile->qmichannel;
int wait_for_request_quit = 0;
qmiclientId[QMUX_TYPE_WDS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS);
if (profile->enable_ipv6)
qmiclientId[QMUX_TYPE_WDS_IPV6] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS);
qmiclientId[QMUX_TYPE_DMS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_DMS);
qmiclientId[QMUX_TYPE_NAS] = GobiNetGetClientID(qcqmi, QMUX_TYPE_NAS);
qmiclientId[QMUX_TYPE_UIM] = GobiNetGetClientID(qcqmi, QMUX_TYPE_UIM);
#ifdef CONFIG_COEX_WWAN_STATE
qmiclientId[QMUX_TYPE_COEX] = GobiNetGetClientID(qcqmi, QMUX_TYPE_COEX);
#endif
if (profile->qmap_mode == 0 || profile->loopback_state) {//when QMAP enabled, set data format in GobiNet Driver
qmiclientId[QMUX_TYPE_WDS_ADMIN] = GobiNetGetClientID(qcqmi, QMUX_TYPE_WDS_ADMIN);
profile->wda_client = qmiclientId[QMUX_TYPE_WDS_ADMIN];
}
//donot check clientWDA, there is only one client for WDA, if quectel-CM is killed by SIGKILL, i cannot get client ID for WDA again!
if (qmiclientId[QMUX_TYPE_WDS] == 0) /*|| (clientWDA == -1)*/ {
GobiNetDeInit();
dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, qcqmi, errno, strerror(errno));
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
pthread_exit(NULL);
return NULL;
}
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED);
while (1) {
struct pollfd pollfds[16] = {{qmidevice_control_fd[1], POLLIN, 0}};
int ne, ret, nevents = 1;
unsigned int i;
for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
{
if (qmiclientId[i] != 0)
{
pollfds[nevents].fd = qmiclientId[i];
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents = 0;
nevents++;
}
}
do {
ret = poll(pollfds, nevents, wait_for_request_quit ? 1000: -1);
} while ((ret < 0) && (errno == EINTR));
if (ret == 0 && wait_for_request_quit) {
QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI()
continue;
}
if (ret <= 0) {
dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno));
break;
}
for (ne = 0; ne < nevents; ne++) {
int fd = pollfds[ne].fd;
short revents = pollfds[ne].revents;
if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
dbg_time("%s poll err/hup/inval", __func__);
dbg_time("epoll fd = %d, events = 0x%04x", fd, revents);
if (fd == qmidevice_control_fd[1]) {
} else {
}
if (revents & (POLLERR | POLLHUP | POLLNVAL))
goto __GobiNetThread_quit;
}
if ((revents & POLLIN) == 0)
continue;
if (fd == qmidevice_control_fd[1]) {
int triger_event;
if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) {
//DBG("triger_event = 0x%x", triger_event);
switch (triger_event) {
case RIL_REQUEST_QUIT:
goto __GobiNetThread_quit;
break;
case SIG_EVENT_STOP:
wait_for_request_quit = 1;
break;
default:
break;
}
}
continue;
}
{
ssize_t nreads;
PQCQMIMSG pResponse = (PQCQMIMSG)cm_recv_buf;
nreads = read(fd, &pResponse->MUXMsg, sizeof(cm_recv_buf) - sizeof(QCQMI_HDR));
if (nreads <= 0)
{
dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno));
break;
}
for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
{
if (qmiclientId[i] == fd)
{
pResponse->QMIHdr.QMIType = i;
}
}
pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
pResponse->QMIHdr.Length = cpu_to_le16(nreads + sizeof(QCQMI_HDR) - 1);
pResponse->QMIHdr.CtlFlags = 0x00;
pResponse->QMIHdr.ClientId = (fd&0xFF) ? fd&0xFF : pResponse->QMIHdr.QMIType;;
QmiThreadRecvQMI(pResponse);
}
}
}
__GobiNetThread_quit:
GobiNetDeInit();
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI()
dbg_time("%s exit", __func__);
pthread_exit(NULL);
return NULL;
}
const struct qmi_device_ops gobi_qmidev_ops = {
.deinit = GobiNetDeInit,
.send = GobiNetSendQMI,
.read = GobiNetThread,
};
#endif
+7
View File
@@ -0,0 +1,7 @@
This program is totally open souce code, and public domain software for customers of Quectel company.
The APIs of QMI WWAMN interfaces are defined by Qualcomm. And this program complies with Qualcomm QMI WWAN interfaces specification.
Customers are free to modify the source codes and redistribute them.
For those who is not Quectel's customer, all rights are closed, and any copying and commercial development over this progrma is not allowed.
+389
View File
@@ -0,0 +1,389 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#ifndef QCQCTL_H
#define QCQCTL_H
#include "QCQMI.h"
#pragma pack(push, 1)
// ================= QMICTL ==================
// QMICTL Control Flags
#define QMICTL_CTL_FLAG_CMD 0x00
#define QMICTL_CTL_FLAG_RSP 0x01
#define QMICTL_CTL_FLAG_IND 0x02
#if 0
typedef struct _QMICTL_TRANSACTION_ITEM
{
LIST_ENTRY List;
UCHAR TransactionId; // QMICTL transaction id
PVOID Context; // Adapter or IocDev
PIRP Irp;
} QMICTL_TRANSACTION_ITEM, *PQMICTL_TRANSACTION_ITEM;
#endif
typedef struct _QCQMICTL_MSG_HDR
{
UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind
UCHAR TransactionId;
USHORT QMICTLType;
USHORT Length;
} __attribute__ ((packed)) QCQMICTL_MSG_HDR, *PQCQMICTL_MSG_HDR;
#define QCQMICTL_MSG_HDR_SIZE sizeof(QCQMICTL_MSG_HDR)
typedef struct _QCQMICTL_MSG_HDR_RESP
{
UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind
UCHAR TransactionId;
USHORT QMICTLType;
USHORT Length;
UCHAR TLVType; // 0x02 - result code
USHORT TLVLength; // 4
USHORT QMUXResult; // QMI_RESULT_SUCCESS
// QMI_RESULT_FAILURE
USHORT QMUXError; // QMI_ERR_INVALID_ARG
// QMI_ERR_NO_MEMORY
// QMI_ERR_INTERNAL
// QMI_ERR_FAULT
} __attribute__ ((packed)) QCQMICTL_MSG_HDR_RESP, *PQCQMICTL_MSG_HDR_RESP;
typedef struct _QCQMICTL_MSG
{
UCHAR CtlFlags; // 00-cmd, 01-rsp, 10-ind
UCHAR TransactionId;
USHORT QMICTLType;
USHORT Length;
UCHAR Payload;
} __attribute__ ((packed)) QCQMICTL_MSG, *PQCQMICTL_MSG;
// TLV Header
typedef struct _QCQMICTL_TLV_HDR
{
UCHAR TLVType;
USHORT TLVLength;
} __attribute__ ((packed)) QCQMICTL_TLV_HDR, *PQCQMICTL_TLV_HDR;
#define QCQMICTL_TLV_HDR_SIZE sizeof(QCQMICTL_TLV_HDR)
// QMICTL Type
#define QMICTL_SET_INSTANCE_ID_REQ 0x0020
#define QMICTL_SET_INSTANCE_ID_RESP 0x0020
#define QMICTL_GET_VERSION_REQ 0x0021
#define QMICTL_GET_VERSION_RESP 0x0021
#define QMICTL_GET_CLIENT_ID_REQ 0x0022
#define QMICTL_GET_CLIENT_ID_RESP 0x0022
#define QMICTL_RELEASE_CLIENT_ID_REQ 0x0023
#define QMICTL_RELEASE_CLIENT_ID_RESP 0x0023
#define QMICTL_REVOKE_CLIENT_ID_IND 0x0024
#define QMICTL_INVALID_CLIENT_ID_IND 0x0025
#define QMICTL_SET_DATA_FORMAT_REQ 0x0026
#define QMICTL_SET_DATA_FORMAT_RESP 0x0026
#define QMICTL_SYNC_REQ 0x0027
#define QMICTL_SYNC_RESP 0x0027
#define QMICTL_SYNC_IND 0x0027
#define QMI_MESSAGE_CTL_INTERNAL_PROXY_OPEN 0xFF00
#define QMICTL_FLAG_REQUEST 0x00
#define QMICTL_FLAG_RESPONSE 0x01
#define QMICTL_FLAG_INDICATION 0x02
// QMICTL Message Definitions
typedef struct _QMICTL_SET_INSTANCE_ID_REQ_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_REQUEST
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_REQ
USHORT Length; // 4
UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLVLength; // 1
UCHAR Value; // Host-unique QMI instance for this device driver
} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_REQ_MSG, *PQMICTL_SET_INSTANCE_ID_REQ_MSG;
typedef struct _QMICTL_SET_INSTANCE_ID_RESP_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_SET_INSTANCE_ID_RESP
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE
USHORT TLVLength; // 0x0004
USHORT QMIResult;
USHORT QMIError;
UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLV2Length; // 0x0002
USHORT QMI_ID; // Upper byte is assigned by MSM,
// lower assigned by host
} __attribute__ ((packed)) QMICTL_SET_INSTANCE_ID_RESP_MSG, *PQMICTL_SET_INSTANCE_ID_RESP_MSG;
typedef struct _QMICTL_GET_VERSION_REQ_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_REQUEST
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_GET_VERSION_REQ
USHORT Length; // 0
UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLVLength; // var
UCHAR QMUXTypes; // List of one byte QMUX_TYPE values
// 0xFF returns a list of versions for all
// QMUX_TYPEs implemented on the device
} __attribute__ ((packed)) QMICTL_GET_VERSION_REQ_MSG, *PQMICTL_GET_VERSION_REQ_MSG;
typedef struct _QMUX_TYPE_VERSION_STRUCT
{
UCHAR QMUXType;
USHORT MajorVersion;
USHORT MinorVersion;
} __attribute__ ((packed)) QMUX_TYPE_VERSION_STRUCT, *PQMUX_TYPE_VERSION_STRUCT;
typedef struct _ADDENDUM_VERSION_PREAMBLE
{
UCHAR LabelLength;
UCHAR Label;
} __attribute__ ((packed)) ADDENDUM_VERSION_PREAMBLE, *PADDENDUM_VERSION_PREAMBLE;
#define QMICTL_GET_VERSION_RSP_TLV_TYPE_VERSION 0x01
#define QMICTL_GET_VERSION_RSP_TLV_TYPE_ADD_VERSION 0x10
typedef struct _QMICTL_GET_VERSION_RESP_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_GET_VERSION_RESP
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE
USHORT TLVLength; // 0x0004
USHORT QMIResult;
USHORT QMIError;
UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLV2Length; // var
UCHAR NumElements; // Num of QMUX_TYPE_VERSION_STRUCT
QMUX_TYPE_VERSION_STRUCT TypeVersion[0];
} __attribute__ ((packed)) QMICTL_GET_VERSION_RESP_MSG, *PQMICTL_GET_VERSION_RESP_MSG;
typedef struct _QMICTL_GET_CLIENT_ID_REQ_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_REQUEST
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_REQ
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLVLength; // 1
UCHAR QMIType; // QMUX type
} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_REQ_MSG, *PQMICTL_GET_CLIENT_ID_REQ_MSG;
typedef struct _QMICTL_GET_CLIENT_ID_RESP_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_GET_CLIENT_ID_RESP
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE
USHORT TLVLength; // 0x0004
USHORT QMIResult; // result code
USHORT QMIError; // error code
UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLV2Length; // 2
UCHAR QMIType;
UCHAR ClientId;
} __attribute__ ((packed)) QMICTL_GET_CLIENT_ID_RESP_MSG, *PQMICTL_GET_CLIENT_ID_RESP_MSG;
typedef struct _QMICTL_RELEASE_CLIENT_ID_REQ_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_REQUEST
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_REQ
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLVLength; // 0x0002
UCHAR QMIType;
UCHAR ClientId;
} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_REQ_MSG, *PQMICTL_RELEASE_CLIENT_ID_REQ_MSG;
typedef struct _QMICTL_RELEASE_CLIENT_ID_RESP_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_RELEASE_CLIENT_ID_RESP
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE
USHORT TLVLength; // 0x0004
USHORT QMIResult; // result code
USHORT QMIError; // error code
UCHAR TLV2Type; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLV2Length; // 2
UCHAR QMIType;
UCHAR ClientId;
} __attribute__ ((packed)) QMICTL_RELEASE_CLIENT_ID_RESP_MSG, *PQMICTL_RELEASE_CLIENT_ID_RESP_MSG;
typedef struct _QMICTL_REVOKE_CLIENT_ID_IND_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_INDICATION
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLVLength; // 0x0002
UCHAR QMIType;
UCHAR ClientId;
} __attribute__ ((packed)) QMICTL_REVOKE_CLIENT_ID_IND_MSG, *PQMICTL_REVOKE_CLIENT_ID_IND_MSG;
typedef struct _QMICTL_INVALID_CLIENT_ID_IND_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_INDICATION
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLVLength; // 0x0002
UCHAR QMIType;
UCHAR ClientId;
} __attribute__ ((packed)) QMICTL_INVALID_CLIENT_ID_IND_MSG, *PQMICTL_INVALID_CLIENT_ID_IND_MSG;
typedef struct _QMICTL_SET_DATA_FORMAT_REQ_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_REQUEST
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_REQ
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_REQUIRED_PARAMETER
USHORT TLVLength; // 1
UCHAR DataFormat; // 0-default; 1-QoS hdr present
} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_REQ_MSG, *PQMICTL_SET_DATA_FORMAT_REQ_MSG;
#ifdef QC_IP_MODE
#define SET_DATA_FORMAT_TLV_TYPE_LINK_PROTO 0x10
#define SET_DATA_FORMAT_LINK_PROTO_ETH 0x0001
#define SET_DATA_FORMAT_LINK_PROTO_IP 0x0002
typedef struct _QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT
{
UCHAR TLVType; // Link-Layer Protocol
USHORT TLVLength; // 2
USHORT LinkProt; // 0x1: ETH; 0x2: IP
} QMICTL_SET_DATA_FORMAT_TLV_LINK_PROT, *PQMICTL_SET_DATA_FORMAT_TLV_LINK_PROT;
#ifdef QCMP_UL_TLP
#define SET_DATA_FORMAT_TLV_TYPE_UL_TLP 0x11
typedef struct _QMICTL_SET_DATA_FORMAT_TLV_UL_TLP
{
UCHAR TLVType; // 0x11, Uplink TLP Setting
USHORT TLVLength; // 1
UCHAR UlTlpSetting; // 0x0: Disable; 0x01: Enable
} QMICTL_SET_DATA_FORMAT_TLV_UL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_UL_TLP;
#endif // QCMP_UL_TLP
#ifdef QCMP_DL_TLP
#define SET_DATA_FORMAT_TLV_TYPE_DL_TLP 0x13
typedef struct _QMICTL_SET_DATA_FORMAT_TLV_DL_TLP
{
UCHAR TLVType; // 0x11, Uplink TLP Setting
USHORT TLVLength; // 1
UCHAR DlTlpSetting; // 0x0: Disable; 0x01: Enable
} QMICTL_SET_DATA_FORMAT_TLV_DL_TLP, *PQMICTL_SET_DATA_FORMAT_TLV_DL_TLP;
#endif // QCMP_DL_TLP
#endif // QC_IP_MODE
#ifdef MP_QCQOS_ENABLED
#define SET_DATA_FORMAT_TLV_TYPE_QOS_SETTING 0x12
typedef struct _QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING
{
UCHAR TLVType; // 0x12, QoS setting
USHORT TLVLength; // 1
UCHAR QosSetting; // 0x0: Disable; 0x01: Enable
} QMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING, *PQMICTL_SET_DATA_FORMAT_TLV_QOS_SETTING;
#endif // MP_QCQOS_ENABLED
typedef struct _QMICTL_SET_DATA_FORMAT_RESP_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_RESP
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE
USHORT TLVLength; // 0x0004
USHORT QMIResult; // result code
USHORT QMIError; // error code
} __attribute__ ((packed)) QMICTL_SET_DATA_FORMAT_RESP_MSG, *PQMICTL_SET_DATA_FORMAT_RESP_MSG;
typedef struct _QMICTL_SYNC_REQ_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_REQUEST
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_CTL_SYNC_REQ
USHORT Length; // 0
} __attribute__ ((packed)) QMICTL_SYNC_REQ_MSG, *PQMICTL_SYNC_REQ_MSG;
typedef struct _QMICTL_SYNC_RESP_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_CTL_SYNC_RESP
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE
USHORT TLVLength; // 0x0004
USHORT QMIResult;
USHORT QMIError;
} __attribute__ ((packed)) QMICTL_SYNC_RESP_MSG, *PQMICTL_SYNC_RESP_MSG;
typedef struct _QMICTL_SYNC_IND_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_INDICATION
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_REVOKE_CLIENT_ID_IND
USHORT Length;
} __attribute__ ((packed)) QMICTL_SYNC_IND_MSG, *PQMICTL_SYNC_IND_MSG;
typedef struct _QMICTL_LIBQMI_PROXY_OPEN_MSG
{
UCHAR CtlFlags; // QMICTL_FLAG_RESPONSE
UCHAR TransactionId;
USHORT QMICTLType; // QMICTL_SET_DATA_FORMAT_RESP
USHORT Length;
UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE
USHORT TLVLength; // 0x0004
char device_path[0]; // result code
} __attribute__ ((packed)) QMICTL_LIBQMI_PROXY_OPEN_MSG, *PQMICTL_LIBQMI_PROXY_OPEN_MSG;
typedef struct _QMICTL_MSG
{
union
{
// Message Header
QCQMICTL_MSG_HDR QMICTLMsgHdr;
QCQMICTL_MSG_HDR_RESP QMICTLMsgHdrRsp;
// QMICTL Message
QMICTL_SET_INSTANCE_ID_REQ_MSG SetInstanceIdReq;
QMICTL_SET_INSTANCE_ID_RESP_MSG SetInstanceIdRsp;
QMICTL_GET_VERSION_REQ_MSG GetVersionReq;
QMICTL_GET_VERSION_RESP_MSG GetVersionRsp;
QMICTL_GET_CLIENT_ID_REQ_MSG GetClientIdReq;
QMICTL_GET_CLIENT_ID_RESP_MSG GetClientIdRsp;
QMICTL_RELEASE_CLIENT_ID_REQ_MSG ReleaseClientIdReq;
QMICTL_RELEASE_CLIENT_ID_RESP_MSG ReleaseClientIdRsp;
QMICTL_REVOKE_CLIENT_ID_IND_MSG RevokeClientIdInd;
QMICTL_INVALID_CLIENT_ID_IND_MSG InvalidClientIdInd;
QMICTL_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq;
QMICTL_SET_DATA_FORMAT_RESP_MSG SetDataFormatRsp;
QMICTL_SYNC_REQ_MSG SyncReq;
QMICTL_SYNC_RESP_MSG SyncRsp;
QMICTL_SYNC_IND_MSG SyncInd;
QMICTL_LIBQMI_PROXY_OPEN_MSG LibQmiProxyOpenReq;
};
} __attribute__ ((packed)) QMICTL_MSG, *PQMICTL_MSG;
#pragma pack(pop)
#endif //QCQCTL_H
+316
View File
@@ -0,0 +1,316 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#ifndef USBQMI_H
#define USBQMI_H
typedef uint8_t uint8;
typedef int8_t int8;
typedef uint16_t uint16;
typedef int16_t int16;
typedef uint32_t uint32;
typedef uint64_t uint64;
typedef signed char CHAR;
typedef unsigned char UCHAR;
typedef short SHORT;
typedef unsigned short USHORT;
typedef int INT;
typedef unsigned int UINT;
typedef long LONG;
typedef unsigned int ULONG;
typedef unsigned long long ULONG64;
typedef signed char *PCHAR;
typedef unsigned char *PUCHAR;
typedef int *PINT;
typedef int BOOL;
#define TRUE (1 == 1)
#define FALSE (1 != 1)
#define QMICTL_SUPPORTED_MAJOR_VERSION 1
#define QMICTL_SUPPORTED_MINOR_VERSION 0
#pragma pack(push, 1)
// ========= USB Control Message ==========
#define USB_CTL_MSG_TYPE_QMI 0x01
// USB Control Message
typedef struct _QCUSB_CTL_MSG_HDR
{
UCHAR IFType;
} __attribute__ ((packed)) QCUSB_CTL_MSG_HDR, *PQCUSB_CTL_MSG_HDR;
#define QCUSB_CTL_MSG_HDR_SIZE sizeof(QCUSB_CTL_MSG_HDR)
typedef struct _QCUSB_CTL_MSG
{
UCHAR IFType;
UCHAR Message;
} __attribute__ ((packed)) QCUSB_CTL_MSG, *PQCUSB_CTL_MSG;
#define QCTLV_TYPE_REQUIRED_PARAMETER 0x01
#define QCTLV_TYPE_RESULT_CODE 0x02
// ================= QMI ==================
// Define QMI Type
typedef enum _QMI_SERVICE_TYPE
{
QMUX_TYPE_CTL = 0x00,
QMUX_TYPE_WDS = 0x01,
QMUX_TYPE_DMS = 0x02,
QMUX_TYPE_NAS = 0x03,
QMUX_TYPE_QOS = 0x04,
QMUX_TYPE_WMS = 0x05,
QMUX_TYPE_PDS = 0x06,
QMUX_TYPE_UIM = 0x0B,
QMUX_TYPE_WDS_IPV6 = 0x11,
QMUX_TYPE_WDS_ADMIN = 0x1A,
QMUX_TYPE_COEX = 0x22,
QMUX_TYPE_MAX = 0xFF,
QMUX_TYPE_ALL = 0xFF
} QMI_SERVICE_TYPE;
typedef enum _QMI_RESULT_CODE_TYPE
{
QMI_RESULT_SUCCESS = 0x0000,
QMI_RESULT_FAILURE = 0x0001
} QMI_RESULT_CODE_TYPE;
typedef enum _QMI_ERROR_CODE_TYPE
{
QMI_ERR_NONE = 0x0000
,QMI_ERR_MALFORMED_MSG = 0x0001
,QMI_ERR_NO_MEMORY = 0x0002
,QMI_ERR_INTERNAL = 0x0003
,QMI_ERR_ABORTED = 0x0004
,QMI_ERR_CLIENT_IDS_EXHAUSTED = 0x0005
,QMI_ERR_UNABORTABLE_TRANSACTION = 0x0006
,QMI_ERR_INVALID_CLIENT_ID = 0x0007
,QMI_ERR_NO_THRESHOLDS = 0x0008
,QMI_ERR_INVALID_HANDLE = 0x0009
,QMI_ERR_INVALID_PROFILE = 0x000A
,QMI_ERR_INVALID_PINID = 0x000B
,QMI_ERR_INCORRECT_PIN = 0x000C
,QMI_ERR_NO_NETWORK_FOUND = 0x000D
,QMI_ERR_CALL_FAILED = 0x000E
,QMI_ERR_OUT_OF_CALL = 0x000F
,QMI_ERR_NOT_PROVISIONED = 0x0010
,QMI_ERR_MISSING_ARG = 0x0011
,QMI_ERR_ARG_TOO_LONG = 0x0013
,QMI_ERR_INVALID_TX_ID = 0x0016
,QMI_ERR_DEVICE_IN_USE = 0x0017
,QMI_ERR_OP_NETWORK_UNSUPPORTED = 0x0018
,QMI_ERR_OP_DEVICE_UNSUPPORTED = 0x0019
,QMI_ERR_NO_EFFECT = 0x001A
,QMI_ERR_NO_FREE_PROFILE = 0x001B
,QMI_ERR_INVALID_PDP_TYPE = 0x001C
,QMI_ERR_INVALID_TECH_PREF = 0x001D
,QMI_ERR_INVALID_PROFILE_TYPE = 0x001E
,QMI_ERR_INVALID_SERVICE_TYPE = 0x001F
,QMI_ERR_INVALID_REGISTER_ACTION = 0x0020
,QMI_ERR_INVALID_PS_ATTACH_ACTION = 0x0021
,QMI_ERR_AUTHENTICATION_FAILED = 0x0022
,QMI_ERR_PIN_BLOCKED = 0x0023
,QMI_ERR_PIN_PERM_BLOCKED = 0x0024
,QMI_ERR_SIM_NOT_INITIALIZED = 0x0025
,QMI_ERR_MAX_QOS_REQUESTS_IN_USE = 0x0026
,QMI_ERR_INCORRECT_FLOW_FILTER = 0x0027
,QMI_ERR_NETWORK_QOS_UNAWARE = 0x0028
,QMI_ERR_INVALID_QOS_ID = 0x0029
,QMI_ERR_INVALID_ID = 0x0029
,QMI_ERR_REQUESTED_NUM_UNSUPPORTED = 0x002A
,QMI_ERR_INTERFACE_NOT_FOUND = 0x002B
,QMI_ERR_FLOW_SUSPENDED = 0x002C
,QMI_ERR_INVALID_DATA_FORMAT = 0x002D
,QMI_ERR_GENERAL = 0x002E
,QMI_ERR_UNKNOWN = 0x002F
,QMI_ERR_INVALID_ARG = 0x0030
,QMI_ERR_INVALID_INDEX = 0x0031
,QMI_ERR_NO_ENTRY = 0x0032
,QMI_ERR_DEVICE_STORAGE_FULL = 0x0033
,QMI_ERR_DEVICE_NOT_READY = 0x0034
,QMI_ERR_NETWORK_NOT_READY = 0x0035
,QMI_ERR_CAUSE_CODE = 0x0036
,QMI_ERR_MESSAGE_NOT_SENT = 0x0037
,QMI_ERR_MESSAGE_DELIVERY_FAILURE = 0x0038
,QMI_ERR_INVALID_MESSAGE_ID = 0x0039
,QMI_ERR_ENCODING = 0x003A
,QMI_ERR_AUTHENTICATION_LOCK = 0x003B
,QMI_ERR_INVALID_TRANSITION = 0x003C
,QMI_ERR_NOT_A_MCAST_IFACE = 0x003D
,QMI_ERR_MAX_MCAST_REQUESTS_IN_USE = 0x003E
,QMI_ERR_INVALID_MCAST_HANDLE = 0x003F
,QMI_ERR_INVALID_IP_FAMILY_PREF = 0x0040
,QMI_ERR_SESSION_INACTIVE = 0x0041
,QMI_ERR_SESSION_INVALID = 0x0042
,QMI_ERR_SESSION_OWNERSHIP = 0x0043
,QMI_ERR_INSUFFICIENT_RESOURCES = 0x0044
,QMI_ERR_DISABLED = 0x0045
,QMI_ERR_INVALID_OPERATION = 0x0046
,QMI_ERR_INVALID_QMI_CMD = 0x0047
,QMI_ERR_TPDU_TYPE = 0x0048
,QMI_ERR_SMSC_ADDR = 0x0049
,QMI_ERR_INFO_UNAVAILABLE = 0x004A
,QMI_ERR_SEGMENT_TOO_LONG = 0x004B
,QMI_ERR_SEGMENT_ORDER = 0x004C
,QMI_ERR_BUNDLING_NOT_SUPPORTED = 0x004D
,QMI_ERR_OP_PARTIAL_FAILURE = 0x004E
,QMI_ERR_POLICY_MISMATCH = 0x004F
,QMI_ERR_SIM_FILE_NOT_FOUND = 0x0050
,QMI_ERR_EXTENDED_INTERNAL = 0x0051
,QMI_ERR_ACCESS_DENIED = 0x0052
,QMI_ERR_HARDWARE_RESTRICTED = 0x0053
,QMI_ERR_ACK_NOT_SENT = 0x0054
,QMI_ERR_INJECT_TIMEOUT = 0x0055
,QMI_ERR_INCOMPATIBLE_STATE = 0x005A
,QMI_ERR_FDN_RESTRICT = 0x005B
,QMI_ERR_SUPS_FAILURE_CAUSE = 0x005C
,QMI_ERR_NO_RADIO = 0x005D
,QMI_ERR_NOT_SUPPORTED = 0x005E
,QMI_ERR_NO_SUBSCRIPTION = 0x005F
,QMI_ERR_CARD_CALL_CONTROL_FAILED = 0x0060
,QMI_ERR_NETWORK_ABORTED = 0x0061
,QMI_ERR_MSG_BLOCKED = 0x0062
,QMI_ERR_INVALID_SESSION_TYPE = 0x0064
,QMI_ERR_INVALID_PB_TYPE = 0x0065
,QMI_ERR_NO_SIM = 0x0066
,QMI_ERR_PB_NOT_READY = 0x0067
,QMI_ERR_PIN_RESTRICTION = 0x0068
,QMI_ERR_PIN2_RESTRICTION = 0x0069
,QMI_ERR_PUK_RESTRICTION = 0x006A
,QMI_ERR_PUK2_RESTRICTION = 0x006B
,QMI_ERR_PB_ACCESS_RESTRICTED = 0x006C
,QMI_ERR_PB_DELETE_IN_PROG = 0x006D
,QMI_ERR_PB_TEXT_TOO_LONG = 0x006E
,QMI_ERR_PB_NUMBER_TOO_LONG = 0x006F
,QMI_ERR_PB_HIDDEN_KEY_RESTRICTION = 0x0070
} QMI_ERROR_CODE_TYPE;
#define QCQMI_CTL_FLAG_SERVICE 0x80
#define QCQMI_CTL_FLAG_CTL_POINT 0x00
typedef struct _QCQMI_HDR
{
UCHAR IFType;
USHORT Length;
UCHAR CtlFlags; // reserved
UCHAR QMIType;
UCHAR ClientId;
} __attribute__ ((packed)) QCQMI_HDR, *PQCQMI_HDR;
#define QCQMI_HDR_SIZE (sizeof(QCQMI_HDR)-1)
typedef struct _QCQMI
{
UCHAR IFType;
USHORT Length;
UCHAR CtlFlags; // reserved
UCHAR QMIType;
UCHAR ClientId;
UCHAR SDU;
} __attribute__ ((packed)) QCQMI, *PQCQMI;
typedef struct _QMI_SERVICE_VERSION
{
USHORT Major;
USHORT Minor;
USHORT AddendumMajor;
USHORT AddendumMinor;
} __attribute__ ((packed)) QMI_SERVICE_VERSION, *PQMI_SERVICE_VERSION;
// ================= QMUX ==================
#define QMUX_MSG_OVERHEAD_BYTES 4 // Type(USHORT) Length(USHORT) -- header
#define QMUX_BROADCAST_CID 0xFF
typedef struct _QCQMUX_HDR
{
UCHAR CtlFlags; // 0: single QMUX Msg; 1:
USHORT TransactionId;
} __attribute__ ((packed)) QCQMUX_HDR, *PQCQMUX_HDR;
typedef struct _QCQMUX
{
UCHAR CtlFlags; // 0: single QMUX Msg; 1:
USHORT TransactionId;
UCHAR Message; // Type(2), Length(2), Value
} __attribute__ ((packed)) QCQMUX, *PQCQMUX;
#define QCQMUX_HDR_SIZE sizeof(QCQMUX_HDR)
typedef struct _QCQMUX_MSG_HDR
{
USHORT Type;
USHORT Length;
} __attribute__ ((packed)) QCQMUX_MSG_HDR, *PQCQMUX_MSG_HDR;
#define QCQMUX_MSG_HDR_SIZE sizeof(QCQMUX_MSG_HDR)
typedef struct _QCQMUX_MSG_HDR_RESP
{
USHORT Type;
USHORT Length;
UCHAR TLVType; // 0x02 - result code
USHORT TLVLength; // 4
USHORT QMUXResult; // QMI_RESULT_SUCCESS
// QMI_RESULT_FAILURE
USHORT QMUXError; // QMI_ERR_INVALID_ARG
// QMI_ERR_NO_MEMORY
// QMI_ERR_INTERNAL
// QMI_ERR_FAULT
} __attribute__ ((packed)) QCQMUX_MSG_HDR_RESP, *PQCQMUX_MSG_HDR_RESP;
typedef struct _QCQMUX_TLV
{
UCHAR Type;
USHORT Length;
UCHAR Value;
} __attribute__ ((packed)) QCQMUX_TLV, *PQCQMUX_TLV;
typedef struct _QMI_TLV_HDR
{
UCHAR TLVType;
USHORT TLVLength;
} __attribute__ ((packed)) QMI_TLV_HDR, *PQMI_TLV_HDR;
typedef struct _QMI_TLV
{
UCHAR TLVType;
USHORT TLVLength;
union {
int8_t s8;
uint8_t u8;
int16_t s16;
uint16_t u16;
int32_t s32;
uint32_t u32;
uint64_t u64;
};
} __attribute__ ((packed)) QMI_TLV, *PQMI_TLV;
// QMUX Message Definitions -- QMI SDU
#define QMUX_CTL_FLAG_SINGLE_MSG 0x00
#define QMUX_CTL_FLAG_COMPOUND_MSG 0x01
#define QMUX_CTL_FLAG_TYPE_CMD 0x00
#define QMUX_CTL_FLAG_TYPE_RSP 0x02
#define QMUX_CTL_FLAG_TYPE_IND 0x04
#define QMUX_CTL_FLAG_MASK_COMPOUND 0x01
#define QMUX_CTL_FLAG_MASK_TYPE 0x06 // 00-cmd, 01-rsp, 10-ind
#pragma pack(pop)
#endif // USBQMI_H
+472
View File
@@ -0,0 +1,472 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include "QMIThread.h"
static char line[1024];
static pthread_mutex_t dumpQMIMutex = PTHREAD_MUTEX_INITIALIZER;
#undef dbg
#define dbg( format, arg... ) do {if (strlen(line) < sizeof(line)) snprintf(&line[strlen(line)], sizeof(line) - strlen(line), format, ## arg);} while (0)
PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType);
typedef struct {
UINT type;
const char *name;
} QMI_NAME_T;
#define qmi_name_item(type) {type, #type}
#if 0
static const QMI_NAME_T qmi_IFType[] = {
{USB_CTL_MSG_TYPE_QMI, "USB_CTL_MSG_TYPE_QMI"},
};
static const QMI_NAME_T qmi_CtlFlags[] = {
qmi_name_item(QMICTL_CTL_FLAG_CMD),
qmi_name_item(QCQMI_CTL_FLAG_SERVICE),
};
static const QMI_NAME_T qmi_QMIType[] = {
qmi_name_item(QMUX_TYPE_CTL),
qmi_name_item(QMUX_TYPE_WDS),
qmi_name_item(QMUX_TYPE_DMS),
qmi_name_item(QMUX_TYPE_NAS),
qmi_name_item(QMUX_TYPE_QOS),
qmi_name_item(QMUX_TYPE_WMS),
qmi_name_item(QMUX_TYPE_PDS),
qmi_name_item(QMUX_TYPE_WDS_ADMIN),
qmi_name_item(QMUX_TYPE_COEX),
};
static const QMI_NAME_T qmi_ctl_CtlFlags[] = {
qmi_name_item(QMICTL_FLAG_REQUEST),
qmi_name_item(QMICTL_FLAG_RESPONSE),
qmi_name_item(QMICTL_FLAG_INDICATION),
};
#endif
static const QMI_NAME_T qmux_ctl_QMICTLType[] = {
// QMICTL Type
qmi_name_item(QMICTL_SET_INSTANCE_ID_REQ), // 0x0020
qmi_name_item(QMICTL_SET_INSTANCE_ID_RESP), // 0x0020
qmi_name_item(QMICTL_GET_VERSION_REQ), // 0x0021
qmi_name_item(QMICTL_GET_VERSION_RESP), // 0x0021
qmi_name_item(QMICTL_GET_CLIENT_ID_REQ), // 0x0022
qmi_name_item(QMICTL_GET_CLIENT_ID_RESP), // 0x0022
qmi_name_item(QMICTL_RELEASE_CLIENT_ID_REQ), // 0x0023
qmi_name_item(QMICTL_RELEASE_CLIENT_ID_RESP), // 0x0023
qmi_name_item(QMICTL_REVOKE_CLIENT_ID_IND), // 0x0024
qmi_name_item(QMICTL_INVALID_CLIENT_ID_IND), // 0x0025
qmi_name_item(QMICTL_SET_DATA_FORMAT_REQ), // 0x0026
qmi_name_item(QMICTL_SET_DATA_FORMAT_RESP), // 0x0026
qmi_name_item(QMICTL_SYNC_REQ), // 0x0027
qmi_name_item(QMICTL_SYNC_RESP), // 0x0027
qmi_name_item(QMICTL_SYNC_IND), // 0x0027
};
static const QMI_NAME_T qmux_CtlFlags[] = {
qmi_name_item(QMUX_CTL_FLAG_TYPE_CMD),
qmi_name_item(QMUX_CTL_FLAG_TYPE_RSP),
qmi_name_item(QMUX_CTL_FLAG_TYPE_IND),
};
static const QMI_NAME_T qmux_wds_Type[] = {
qmi_name_item(QMIWDS_SET_EVENT_REPORT_REQ), // 0x0001
qmi_name_item(QMIWDS_SET_EVENT_REPORT_RESP), // 0x0001
qmi_name_item(QMIWDS_EVENT_REPORT_IND), // 0x0001
qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_REQ), // 0x0020
qmi_name_item(QMIWDS_START_NETWORK_INTERFACE_RESP), // 0x0020
qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_REQ), // 0x0021
qmi_name_item(QMIWDS_STOP_NETWORK_INTERFACE_RESP), // 0x0021
qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_REQ), // 0x0022
qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_RESP), // 0x0022
qmi_name_item(QMIWDS_GET_PKT_SRVC_STATUS_IND), // 0x0022
qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ), // 0x0023
qmi_name_item(QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP), // 0x0023
qmi_name_item(QMIWDS_GET_PKT_STATISTICS_REQ), // 0x0024
qmi_name_item(QMIWDS_GET_PKT_STATISTICS_RESP), // 0x0024
qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ), // 0x0028
qmi_name_item(QMIWDS_MODIFY_PROFILE_SETTINGS_RESP), // 0x0028
qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_REQ), // 0x002B
qmi_name_item(QMIWDS_GET_PROFILE_SETTINGS_RESP), // 0x002BD
qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_REQ), // 0x002C
qmi_name_item(QMIWDS_GET_DEFAULT_SETTINGS_RESP), // 0x002C
qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_REQ), // 0x002D
qmi_name_item(QMIWDS_GET_RUNTIME_SETTINGS_RESP), // 0x002D
qmi_name_item(QMIWDS_GET_MIP_MODE_REQ), // 0x002F
qmi_name_item(QMIWDS_GET_MIP_MODE_RESP), // 0x002F
qmi_name_item(QMIWDS_GET_DATA_BEARER_REQ), // 0x0037
qmi_name_item(QMIWDS_GET_DATA_BEARER_RESP), // 0x0037
qmi_name_item(QMIWDS_DUN_CALL_INFO_REQ), // 0x0038
qmi_name_item(QMIWDS_DUN_CALL_INFO_RESP), // 0x0038
qmi_name_item(QMIWDS_DUN_CALL_INFO_IND), // 0x0038
qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ), // 0x004D
qmi_name_item(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP), // 0x004D
qmi_name_item(QMIWDS_SET_AUTO_CONNECT_REQ), // 0x0051
qmi_name_item(QMIWDS_SET_AUTO_CONNECT_RESP), // 0x0051
qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_REQ), // 0x00A2
qmi_name_item(QMIWDS_BIND_MUX_DATA_PORT_RESP), // 0x00A2
};
static const QMI_NAME_T qmux_dms_Type[] = {
// ======================= DMS ==============================
qmi_name_item(QMIDMS_SET_EVENT_REPORT_REQ), // 0x0001
qmi_name_item(QMIDMS_SET_EVENT_REPORT_RESP), // 0x0001
qmi_name_item(QMIDMS_EVENT_REPORT_IND), // 0x0001
qmi_name_item(QMIDMS_GET_DEVICE_CAP_REQ), // 0x0020
qmi_name_item(QMIDMS_GET_DEVICE_CAP_RESP), // 0x0020
qmi_name_item(QMIDMS_GET_DEVICE_MFR_REQ), // 0x0021
qmi_name_item(QMIDMS_GET_DEVICE_MFR_RESP), // 0x0021
qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_REQ), // 0x0022
qmi_name_item(QMIDMS_GET_DEVICE_MODEL_ID_RESP), // 0x0022
qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_REQ), // 0x0023
qmi_name_item(QMIDMS_GET_DEVICE_REV_ID_RESP), // 0x0023
qmi_name_item(QMIDMS_GET_MSISDN_REQ), // 0x0024
qmi_name_item(QMIDMS_GET_MSISDN_RESP), // 0x0024
qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ), // 0x0025
qmi_name_item(QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP), // 0x0025
qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_REQ), // 0x0027
qmi_name_item(QMIDMS_UIM_SET_PIN_PROTECTION_RESP), // 0x0027
qmi_name_item(QMIDMS_UIM_VERIFY_PIN_REQ), // 0x0028
qmi_name_item(QMIDMS_UIM_VERIFY_PIN_RESP), // 0x0028
qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_REQ), // 0x0029
qmi_name_item(QMIDMS_UIM_UNBLOCK_PIN_RESP), // 0x0029
qmi_name_item(QMIDMS_UIM_CHANGE_PIN_REQ), // 0x002A
qmi_name_item(QMIDMS_UIM_CHANGE_PIN_RESP), // 0x002A
qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_REQ), // 0x002B
qmi_name_item(QMIDMS_UIM_GET_PIN_STATUS_RESP), // 0x002B
qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_REQ), // 0x002C
qmi_name_item(QMIDMS_GET_DEVICE_HARDWARE_REV_RESP), // 0x002C
qmi_name_item(QMIDMS_GET_OPERATING_MODE_REQ), // 0x002D
qmi_name_item(QMIDMS_GET_OPERATING_MODE_RESP), // 0x002D
qmi_name_item(QMIDMS_SET_OPERATING_MODE_REQ), // 0x002E
qmi_name_item(QMIDMS_SET_OPERATING_MODE_RESP), // 0x002E
qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_REQ), // 0x0031
qmi_name_item(QMIDMS_GET_ACTIVATED_STATUS_RESP), // 0x0031
qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_REQ), // 0x0032
qmi_name_item(QMIDMS_ACTIVATE_AUTOMATIC_RESP), // 0x0032
qmi_name_item(QMIDMS_ACTIVATE_MANUAL_REQ), // 0x0033
qmi_name_item(QMIDMS_ACTIVATE_MANUAL_RESP), // 0x0033
qmi_name_item(QMIDMS_UIM_GET_ICCID_REQ), // 0x003C
qmi_name_item(QMIDMS_UIM_GET_ICCID_RESP), // 0x003C
qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_REQ), // 0x0040
qmi_name_item(QMIDMS_UIM_GET_CK_STATUS_RESP), // 0x0040
qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_REQ), // 0x0041
qmi_name_item(QMIDMS_UIM_SET_CK_PROTECTION_RESP), // 0x0041
qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_REQ), // 0x0042
qmi_name_item(QMIDMS_UIM_UNBLOCK_CK_RESP), // 0x0042
qmi_name_item(QMIDMS_UIM_GET_IMSI_REQ), // 0x0043
qmi_name_item(QMIDMS_UIM_GET_IMSI_RESP), // 0x0043
qmi_name_item(QMIDMS_UIM_GET_STATE_REQ), // 0x0044
qmi_name_item(QMIDMS_UIM_GET_STATE_RESP), // 0x0044
qmi_name_item(QMIDMS_GET_BAND_CAP_REQ), // 0x0045
qmi_name_item(QMIDMS_GET_BAND_CAP_RESP), // 0x0045
};
static const QMI_NAME_T qmux_qos_Type[] = {
qmi_name_item( QMI_QOS_SET_EVENT_REPORT_REQ), // 0x0001
qmi_name_item( QMI_QOS_SET_EVENT_REPORT_RESP), // 0x0001
qmi_name_item( QMI_QOS_SET_EVENT_REPORT_IND), // 0x0001
qmi_name_item( QMI_QOS_BIND_DATA_PORT_REQ), // 0x002B
qmi_name_item( QMI_QOS_BIND_DATA_PORT_RESP), // 0x002B
qmi_name_item( QMI_QOS_INDICATION_REGISTER_REQ), // 0x002F
qmi_name_item( QMI_QOS_INDICATION_REGISTER_RESP), // 0x002F
qmi_name_item( QMI_QOS_GLOBAL_QOS_FLOW_IND), // 0x0031
qmi_name_item( QMI_QOS_GET_QOS_INFO_REQ), // 0x0033
qmi_name_item( QMI_QOS_GET_QOS_INFO_RESP), // 0x0033
};
static const QMI_NAME_T qmux_nas_Type[] = {
// ======================= NAS ==============================
qmi_name_item(QMINAS_SET_EVENT_REPORT_REQ), // 0x0002
qmi_name_item(QMINAS_SET_EVENT_REPORT_RESP), // 0x0002
qmi_name_item(QMINAS_EVENT_REPORT_IND), // 0x0002
qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_REQ), // 0x0020
qmi_name_item(QMINAS_GET_SIGNAL_STRENGTH_RESP), // 0x0020
qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_REQ), // 0x0021
qmi_name_item(QMINAS_PERFORM_NETWORK_SCAN_RESP), // 0x0021
qmi_name_item(QMINAS_INITIATE_NW_REGISTER_REQ), // 0x0022
qmi_name_item(QMINAS_INITIATE_NW_REGISTER_RESP), // 0x0022
qmi_name_item(QMINAS_INITIATE_ATTACH_REQ), // 0x0023
qmi_name_item(QMINAS_INITIATE_ATTACH_RESP), // 0x0023
qmi_name_item(QMINAS_GET_SERVING_SYSTEM_REQ), // 0x0024
qmi_name_item(QMINAS_GET_SERVING_SYSTEM_RESP), // 0x0024
qmi_name_item(QMINAS_SERVING_SYSTEM_IND), // 0x0024
qmi_name_item(QMINAS_GET_HOME_NETWORK_REQ), // 0x0025
qmi_name_item(QMINAS_GET_HOME_NETWORK_RESP), // 0x0025
qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_REQ), // 0x0026
qmi_name_item(QMINAS_GET_PREFERRED_NETWORK_RESP), // 0x0026
qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_REQ), // 0x0027
qmi_name_item(QMINAS_SET_PREFERRED_NETWORK_RESP), // 0x0027
qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_REQ), // 0x0028
qmi_name_item(QMINAS_GET_FORBIDDEN_NETWORK_RESP), // 0x0028
qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_REQ), // 0x0029
qmi_name_item(QMINAS_SET_FORBIDDEN_NETWORK_RESP), // 0x0029
qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_REQ), // 0x002A
qmi_name_item(QMINAS_SET_TECHNOLOGY_PREF_RESP), // 0x002A
qmi_name_item(QMINAS_GET_RF_BAND_INFO_REQ), // 0x0031
qmi_name_item(QMINAS_GET_RF_BAND_INFO_RESP), // 0x0031
qmi_name_item(QMINAS_GET_CELL_LOCATION_INFO_REQ),
qmi_name_item(QMINAS_GET_CELL_LOCATION_INFO_RESP),
qmi_name_item(QMINAS_GET_PLMN_NAME_REQ), // 0x0044
qmi_name_item(QMINAS_GET_PLMN_NAME_RESP), // 0x0044
qmi_name_item(QUECTEL_PACKET_TRANSFER_START_IND), // 0X100
qmi_name_item(QUECTEL_PACKET_TRANSFER_END_IND), // 0X101
qmi_name_item(QMINAS_GET_SYS_INFO_REQ), // 0x004D
qmi_name_item(QMINAS_GET_SYS_INFO_RESP), // 0x004D
qmi_name_item(QMINAS_SYS_INFO_IND), // 0x004D
qmi_name_item(QMINAS_GET_SIG_INFO_REQ),
qmi_name_item(QMINAS_GET_SIG_INFO_RESP),
};
static const QMI_NAME_T qmux_wms_Type[] = {
// ======================= WMS ==============================
qmi_name_item(QMIWMS_SET_EVENT_REPORT_REQ), // 0x0001
qmi_name_item(QMIWMS_SET_EVENT_REPORT_RESP), // 0x0001
qmi_name_item(QMIWMS_EVENT_REPORT_IND), // 0x0001
qmi_name_item(QMIWMS_RAW_SEND_REQ), // 0x0020
qmi_name_item(QMIWMS_RAW_SEND_RESP), // 0x0020
qmi_name_item(QMIWMS_RAW_WRITE_REQ), // 0x0021
qmi_name_item(QMIWMS_RAW_WRITE_RESP), // 0x0021
qmi_name_item(QMIWMS_RAW_READ_REQ), // 0x0022
qmi_name_item(QMIWMS_RAW_READ_RESP), // 0x0022
qmi_name_item(QMIWMS_MODIFY_TAG_REQ), // 0x0023
qmi_name_item(QMIWMS_MODIFY_TAG_RESP), // 0x0023
qmi_name_item(QMIWMS_DELETE_REQ), // 0x0024
qmi_name_item(QMIWMS_DELETE_RESP), // 0x0024
qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_REQ), // 0x0030
qmi_name_item(QMIWMS_GET_MESSAGE_PROTOCOL_RESP), // 0x0030
qmi_name_item(QMIWMS_LIST_MESSAGES_REQ), // 0x0031
qmi_name_item(QMIWMS_LIST_MESSAGES_RESP), // 0x0031
qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_REQ), // 0x0034
qmi_name_item(QMIWMS_GET_SMSC_ADDRESS_RESP), // 0x0034
qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_REQ), // 0x0035
qmi_name_item(QMIWMS_SET_SMSC_ADDRESS_RESP), // 0x0035
qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_REQ), // 0x0036
qmi_name_item(QMIWMS_GET_STORE_MAX_SIZE_RESP), // 0x0036
};
static const QMI_NAME_T qmux_wds_admin_Type[] = {
qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ), // 0x0020
qmi_name_item(QMIWDS_ADMIN_SET_DATA_FORMAT_RESP), // 0x0020
qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_REQ), // 0x0021
qmi_name_item(QMIWDS_ADMIN_GET_DATA_FORMAT_RESP), // 0x0021
qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ), // 0x002B
qmi_name_item(QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP), // 0x002B
qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ), // 0x002C
qmi_name_item(QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP), // 0x002C
qmi_name_item(QMI_WDA_SET_LOOPBACK_CONFIG_REQ), // 0x002F
qmi_name_item(QMI_WDA_SET_LOOPBACK_CONFIG_RESP), // 0x002F
qmi_name_item(QMI_WDA_SET_LOOPBACK_CONFIG_IND), // 0x002F
};
static const QMI_NAME_T qmux_uim_Type[] = {
qmi_name_item( QMIUIM_READ_TRANSPARENT_REQ), // 0x0020
qmi_name_item( QMIUIM_READ_TRANSPARENT_RESP), // 0x0020
qmi_name_item( QMIUIM_READ_TRANSPARENT_IND), // 0x0020
qmi_name_item( QMIUIM_READ_RECORD_REQ), // 0x0021
qmi_name_item( QMIUIM_READ_RECORD_RESP), // 0x0021
qmi_name_item( QMIUIM_READ_RECORD_IND), // 0x0021
qmi_name_item( QMIUIM_WRITE_TRANSPARENT_REQ), // 0x0022
qmi_name_item( QMIUIM_WRITE_TRANSPARENT_RESP), // 0x0022
qmi_name_item( QMIUIM_WRITE_TRANSPARENT_IND), // 0x0022
qmi_name_item( QMIUIM_WRITE_RECORD_REQ), // 0x0023
qmi_name_item( QMIUIM_WRITE_RECORD_RESP), // 0x0023
qmi_name_item( QMIUIM_WRITE_RECORD_IND), // 0x0023
qmi_name_item( QMIUIM_SET_PIN_PROTECTION_REQ), // 0x0025
qmi_name_item( QMIUIM_SET_PIN_PROTECTION_RESP), // 0x0025
qmi_name_item( QMIUIM_SET_PIN_PROTECTION_IND), // 0x0025
qmi_name_item( QMIUIM_VERIFY_PIN_REQ), // 0x0026
qmi_name_item( QMIUIM_VERIFY_PIN_RESP), // 0x0026
qmi_name_item( QMIUIM_VERIFY_PIN_IND), // 0x0026
qmi_name_item( QMIUIM_UNBLOCK_PIN_REQ), // 0x0027
qmi_name_item( QMIUIM_UNBLOCK_PIN_RESP), // 0x0027
qmi_name_item( QMIUIM_UNBLOCK_PIN_IND), // 0x0027
qmi_name_item( QMIUIM_CHANGE_PIN_REQ), // 0x0028
qmi_name_item( QMIUIM_CHANGE_PIN_RESP), // 0x0028
qmi_name_item( QMIUIM_CHANGE_PIN_IND), // 0x0028
qmi_name_item( QMIUIM_DEPERSONALIZATION_REQ), // 0x0029
qmi_name_item( QMIUIM_DEPERSONALIZATION_RESP), // 0x0029
qmi_name_item( QMIUIM_EVENT_REG_REQ), // 0x002E
qmi_name_item( QMIUIM_EVENT_REG_RESP), // 0x002E
qmi_name_item( QMIUIM_GET_CARD_STATUS_REQ), // 0x002F
qmi_name_item( QMIUIM_GET_CARD_STATUS_RESP), // 0x002F
qmi_name_item( QMIUIM_STATUS_CHANGE_IND), // 0x0032
};
static const QMI_NAME_T qmux_coex_Type[] = {
qmi_name_item(QMI_COEX_GET_WWAN_STATE_REQ), // 0x0022
qmi_name_item(QMI_COEX_GET_WWAN_STATE_RESP), // 0x0022
};
static const char * qmi_name_get(const QMI_NAME_T *table, size_t size, int type, const char *tag) {
static char unknow[40];
size_t i;
if (qmux_CtlFlags == table) {
if (!strcmp(tag, "_REQ"))
tag = "_CMD";
else if (!strcmp(tag, "_RESP"))
tag = "_RSP";
}
for (i = 0; i < size; i++) {
if (table[i].type == (UINT)type) {
if (!tag || (strstr(table[i].name, tag)))
return table[i].name;
}
}
sprintf(unknow, "unknow_%x", type);
return unknow;
}
#define QMI_NAME(table, type) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, 0)
#define QMUX_NAME(table, type, tag) qmi_name_get(table, sizeof(table) / sizeof(table[0]), type, tag)
void dump_tlv(PQCQMUX_MSG_HDR pQMUXMsgHdr) {
int TLVFind = 0;
int i;
//dbg("QCQMUX_TLV-----------------------------------\n");
//dbg("{Type,\tLength,\tValue}\n");
while (1) {
PQMI_TLV_HDR TLVHdr = GetTLV(pQMUXMsgHdr, 0x1000 + (++TLVFind));
if (TLVHdr == NULL)
break;
//if ((TLVHdr->TLVType == 0x02) && ((USHORT *)(TLVHdr+1))[0])
{
dbg("{%02x,\t%04x,\t", TLVHdr->TLVType, le16_to_cpu(TLVHdr->TLVLength));
for (i = 0; i < le16_to_cpu(TLVHdr->TLVLength); i++) {
dbg("%02x ", ((UCHAR *)(TLVHdr+1))[i]);
}
dbg("}\n");
}
} // while
}
void dump_ctl(PQCQMICTL_MSG_HDR CTLHdr) {
const char *tag;
//dbg("QCQMICTL_MSG--------------------------------------------\n");
//dbg("CtlFlags: %02x\t\t%s\n", CTLHdr->CtlFlags, QMI_NAME(qmi_ctl_CtlFlags, CTLHdr->CtlFlags));
dbg("TransactionId: %02x\n", CTLHdr->TransactionId);
switch (CTLHdr->CtlFlags) {
case QMICTL_FLAG_REQUEST: tag = "_REQ"; break;
case QMICTL_FLAG_RESPONSE: tag = "_RESP"; break;
case QMICTL_FLAG_INDICATION: tag = "_IND"; break;
default: tag = 0; break;
}
dbg("QMICTLType: %04x\t%s\n", le16_to_cpu(CTLHdr->QMICTLType),
QMUX_NAME(qmux_ctl_QMICTLType, le16_to_cpu(CTLHdr->QMICTLType), tag));
dbg("Length: %04x\n", le16_to_cpu(CTLHdr->Length));
dump_tlv((PQCQMUX_MSG_HDR)(&CTLHdr->QMICTLType));
}
int dump_qmux(QMI_SERVICE_TYPE serviceType, PQCQMUX_HDR QMUXHdr) {
PQCQMUX_MSG_HDR QMUXMsgHdr = (PQCQMUX_MSG_HDR) (QMUXHdr + 1);
const char *tag;
//dbg("QCQMUX--------------------------------------------\n");
switch (QMUXHdr->CtlFlags&QMUX_CTL_FLAG_MASK_TYPE) {
case QMUX_CTL_FLAG_TYPE_CMD: tag = "_REQ"; break;
case QMUX_CTL_FLAG_TYPE_RSP: tag = "_RESP"; break;
case QMUX_CTL_FLAG_TYPE_IND: tag = "_IND"; break;
default: tag = 0; break;
}
//dbg("CtlFlags: %02x\t\t%s\n", QMUXHdr->CtlFlags, QMUX_NAME(qmux_CtlFlags, QMUXHdr->CtlFlags, tag));
dbg("TransactionId: %04x\n", le16_to_cpu(QMUXHdr->TransactionId));
//dbg("QCQMUX_MSG_HDR-----------------------------------\n");
switch (serviceType) {
case QMUX_TYPE_DMS:
dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type),
QMUX_NAME(qmux_dms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag));
break;
case QMUX_TYPE_NAS:
dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type),
QMUX_NAME(qmux_nas_Type, le16_to_cpu(QMUXMsgHdr->Type), tag));
break;
case QMUX_TYPE_WDS:
case QMUX_TYPE_WDS_IPV6:
dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type),
QMUX_NAME(qmux_wds_Type, le16_to_cpu(QMUXMsgHdr->Type), tag));
break;
case QMUX_TYPE_WMS:
dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type),
QMUX_NAME(qmux_wms_Type, le16_to_cpu(QMUXMsgHdr->Type), tag));
break;
case QMUX_TYPE_WDS_ADMIN:
dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type),
QMUX_NAME(qmux_wds_admin_Type, le16_to_cpu(QMUXMsgHdr->Type), tag));
break;
case QMUX_TYPE_UIM:
dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type),
QMUX_NAME(qmux_uim_Type, le16_to_cpu(QMUXMsgHdr->Type), tag));
break;
case QMUX_TYPE_PDS:
case QMUX_TYPE_QOS:
dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type),
QMUX_NAME(qmux_qos_Type, le16_to_cpu(QMUXMsgHdr->Type), tag));
break;
case QMUX_TYPE_COEX:
dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type),
QMUX_NAME(qmux_coex_Type, le16_to_cpu(QMUXMsgHdr->Type), tag));
break;
case QMUX_TYPE_CTL:
default:
dbg("Type: %04x\t%s\n", le16_to_cpu(QMUXMsgHdr->Type), "PDS/QOS/CTL/unknown!");
break;
}
dbg("Length: %04x\n", le16_to_cpu(QMUXMsgHdr->Length));
dump_tlv(QMUXMsgHdr);
return 0;
}
void dump_qmi(void *dataBuffer, int dataLen)
{
PQCQMI_HDR QMIHdr = (PQCQMI_HDR)dataBuffer;
PQCQMUX_HDR QMUXHdr = (PQCQMUX_HDR) (QMIHdr + 1);
PQCQMICTL_MSG_HDR CTLHdr = (PQCQMICTL_MSG_HDR) (QMIHdr + 1);
int i;
if (!debug_qmi)
return;
pthread_mutex_lock(&dumpQMIMutex);
line[0] = 0;
for (i = 0; i < dataLen; i++) {
dbg("%02x ", ((unsigned char *)dataBuffer)[i]);
}
dbg_time("%s", line);
line[0] = 0;
//dbg("QCQMI_HDR-----------------------------------------");
//dbg("IFType: %02x\t\t%s", QMIHdr->IFType, QMI_NAME(qmi_IFType, QMIHdr->IFType));
//dbg("Length: %04x", le16_to_cpu(QMIHdr->Length));
//dbg("CtlFlags: %02x\t\t%s", QMIHdr->CtlFlags, QMI_NAME(qmi_CtlFlags, QMIHdr->CtlFlags));
//dbg("QMIType: %02x\t\t%s", QMIHdr->QMIType, QMI_NAME(qmi_QMIType, QMIHdr->QMIType));
//dbg("ClientId: %02x", QMIHdr->ClientId);
if (QMIHdr->QMIType == QMUX_TYPE_CTL) {
dump_ctl(CTLHdr);
} else {
dump_qmux(QMIHdr->QMIType, QMUXHdr);
}
dbg_time("%s", line);
pthread_mutex_unlock(&dumpQMIMutex);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+467
View File
@@ -0,0 +1,467 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#ifndef __QMI_THREAD_H__
#define __QMI_THREAD_H__
#define CONFIG_GOBINET
#define CONFIG_QMIWWAN
#define CONFIG_SIM
#define CONFIG_APN
#define CONFIG_VERSION
//#define CONFIG_SIGNALINFO
//#define CONFIG_CELLINFO
//#define CONFIG_COEX_WWAN_STATE
#define CONFIG_DEFAULT_PDP 1
//#define CONFIG_IMSI_ICCID
#define QUECTEL_UL_DATA_AGG
//#define QUECTEL_QMI_MERGE
//#define REBOOT_SIM_CARD_WHEN_APN_CHANGE
//#define REBOOT_SIM_CARD_WHEN_LONG_TIME_NO_PS 60 //unit is seconds
//#define CONFIG_QRTR
//#define CONFIG_ENABLE_QOS
//#define CONFIG_REG_QOS_IND
//#define CONFIG_GET_QOS_INFO
//#define CONFIG_GET_QOS_DATA_RATE
#if (defined(CONFIG_REG_QOS_IND) || defined(CONFIG_GET_QOS_INFO) || defined(CONFIG_GET_QOS_DATA_RATE))
#ifndef CONFIG_REG_QOS_IND
#define CONFIG_REG_QOS_IND
#endif
#ifndef CONFIG_ENABLE_QOS
#define CONFIG_ENABLE_QOS
#endif
#endif
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <stddef.h>
#include "qendian.h"
#include "QCQMI.h"
#include "QCQCTL.h"
#include "QCQMUX.h"
#include "util.h"
#define DEVICE_CLASS_UNKNOWN 0
#define DEVICE_CLASS_CDMA 1
#define DEVICE_CLASS_GSM 2
#define WWAN_DATA_CLASS_NONE 0x00000000
#define WWAN_DATA_CLASS_GPRS 0x00000001
#define WWAN_DATA_CLASS_EDGE 0x00000002 /* EGPRS */
#define WWAN_DATA_CLASS_UMTS 0x00000004
#define WWAN_DATA_CLASS_HSDPA 0x00000008
#define WWAN_DATA_CLASS_HSUPA 0x00000010
#define WWAN_DATA_CLASS_LTE 0x00000020
#define WWAN_DATA_CLASS_5G_NSA 0x00000040
#define WWAN_DATA_CLASS_5G_SA 0x00000080
#define WWAN_DATA_CLASS_1XRTT 0x00010000
#define WWAN_DATA_CLASS_1XEVDO 0x00020000
#define WWAN_DATA_CLASS_1XEVDO_REVA 0x00040000
#define WWAN_DATA_CLASS_1XEVDV 0x00080000
#define WWAN_DATA_CLASS_3XRTT 0x00100000
#define WWAN_DATA_CLASS_1XEVDO_REVB 0x00200000 /* for future use */
#define WWAN_DATA_CLASS_UMB 0x00400000
#define WWAN_DATA_CLASS_CUSTOM 0x80000000
struct wwan_data_class_str {
ULONG class;
const char *str;
};
#pragma pack(push, 1)
typedef struct __IPV4 {
uint32_t Address;
uint32_t Gateway;
uint32_t SubnetMask;
uint32_t DnsPrimary;
uint32_t DnsSecondary;
uint32_t Mtu;
} IPV4_T;
typedef struct __IPV6 {
UCHAR Address[16];
UCHAR Gateway[16];
UCHAR SubnetMask[16];
UCHAR DnsPrimary[16];
UCHAR DnsSecondary[16];
UCHAR PrefixLengthIPAddr;
UCHAR PrefixLengthGateway;
ULONG Mtu;
} IPV6_T;
typedef struct {
UINT size;
UINT rx_urb_size;
UINT ep_type;
UINT iface_id;
UINT MuxId;
UINT ul_data_aggregation_max_datagrams; //0x17
UINT ul_data_aggregation_max_size ;//0x18
UINT dl_minimum_padding; //0x1A
} QMAP_SETTING;
//Configured downlink data aggregationprotocol
#define WDA_DL_DATA_AGG_DISABLED (0x00) //DL data aggregation is disabled (default)
#define WDA_DL_DATA_AGG_TLP_ENABLED (0x01) // DL TLP is enabled
#define WDA_DL_DATA_AGG_QC_NCM_ENABLED (0x02) // DL QC_NCM isenabled
#define WDA_DL_DATA_AGG_MBIM_ENABLED (0x03) // DL MBIM isenabled
#define WDA_DL_DATA_AGG_RNDIS_ENABLED (0x04) // DL RNDIS is enabled
#define WDA_DL_DATA_AGG_QMAP_ENABLED (0x05) // DL QMAP isenabled
#define WDA_DL_DATA_AGG_QMAP_V2_ENABLED (0x06) // DL QMAP V2 is enabled
#define WDA_DL_DATA_AGG_QMAP_V3_ENABLED (0x07) // DL QMAP V3 is enabled
#define WDA_DL_DATA_AGG_QMAP_V4_ENABLED (0x08) // DL QMAP V4 is enabled
#define WDA_DL_DATA_AGG_QMAP_V5_ENABLED (0x09) // DL QMAP V5 is enabled
typedef struct {
unsigned int size;
unsigned int rx_urb_size;
unsigned int ep_type;
unsigned int iface_id;
unsigned int qmap_mode;
unsigned int qmap_version;
unsigned int dl_minimum_padding;
char ifname[8][16];
unsigned char mux_id[8];
} RMNET_INFO;
#define IpFamilyV4 (0x04)
#define IpFamilyV6 (0x06)
struct __PROFILE;
struct qmi_device_ops {
int (*init)(struct __PROFILE *profile);
int (*deinit)(void);
int (*send)(PQCQMIMSG pRequest);
void* (*read)(void *pData);
};
#ifdef CONFIG_QRTR
extern const struct qmi_device_ops qrtr_qmidev_ops;
#endif
extern const struct qmi_device_ops gobi_qmidev_ops;
extern const struct qmi_device_ops qmiwwan_qmidev_ops;
extern const struct qmi_device_ops mbim_dev_ops;
extern const struct qmi_device_ops atc_dev_ops;
extern int (*qmidev_send)(PQCQMIMSG pRequest);
struct usb_device_info {
int idVendor;
int idProduct;
int busnum;
int devnum;
int bNumInterfaces;
};
struct usb_interface_info {
int bNumEndpoints;
int bInterfaceClass;
int bInterfaceSubClass;
int bInterfaceProtocol;
char driver[32];
};
#define LIBQMI_PROXY "qmi-proxy" //src/libqmi-glib/qmi-proxy.h
#define LIBMBIM_PROXY "mbim-proxy"
#define QUECTEL_QMI_PROXY "quectel-qmi-proxy"
#define QUECTEL_MBIM_PROXY "quectel-mbim-proxy"
#define QUECTEL_ATC_PROXY "quectel-atc-proxy"
#define QUECTEL_QRTR_PROXY "quectel-qrtr-proxy"
#ifndef bool
#define bool uint8_t
#endif
struct request_ops;
typedef struct __PROFILE {
//user input start
const char *apn;
const char *user;
const char *pd;
int auth;
int iptype;
const char *pincode;
char proxy[32];
int pdp;//pdp_context
int profile_index;//profile_index
int enable_bridge;
bool enable_ipv4;
bool enable_ipv6;
bool no_dhcp;
const char *logfile;
const char *usblogfile;
char expect_adapter[32];
int kill_pdp;
int replication_factor;
//user input end
char qmichannel[32];
char usbnet_adapter[32];
char qmapnet_adapter[32];
char driver_name[32];
int qmap_mode;
int qmap_size;
int qmap_version;
int curIpFamily;
int rawIP;
int muxid;
#ifdef CONFIG_ENABLE_QOS
UINT qos_id;
#endif
int wda_client;
uint32_t udhcpc_ip;
IPV4_T ipv4;
IPV6_T ipv6;
UINT PCSCFIpv4Addr1;
UINT PCSCFIpv4Addr2;
UCHAR PCSCFIpv6Addr1[16];
UCHAR PCSCFIpv6Addr2[16];
bool reattach_flag;
int hardware_interface;
int software_interface;
struct usb_device_info usb_dev;
struct usb_interface_info usb_intf;
int usbmon_fd;
FILE *usbmon_logfile_fp;
bool loopback_state;
char BaseBandVersion[64];
char old_apn[64];
char old_user[64];
char old_pd[64];
int old_auth;
int old_iptype;
const struct qmi_device_ops *qmi_ops;
const struct request_ops *request_ops;
RMNET_INFO rmnet_info;
BOOL bring_up_by_apn_name;
BOOL bring_up_by_apn_type;
int apn_type;
} PROFILE_T;
#ifdef QUECTEL_QMI_MERGE
#define MERGE_PACKET_IDENTITY 0x2c7c
#define MERGE_PACKET_VERSION 0x0001
#define MERGE_PACKET_MAX_PAYLOAD_SIZE 56
typedef struct __QMI_MSG_HEADER {
uint16_t idenity;
uint16_t version;
uint16_t cur_len;
uint16_t total_len;
} QMI_MSG_HEADER;
typedef struct __QMI_MSG_PACKET {
QMI_MSG_HEADER header;
uint16_t len;
char buf[4096];
} QMI_MSG_PACKET;
#endif
typedef enum {
SIM_ABSENT = 0,
SIM_NOT_READY = 1,
SIM_READY = 2, /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */
SIM_PIN = 3,
SIM_PUK = 4,
SIM_NETWORK_PERSONALIZATION = 5,
SIM_BAD = 6,
} SIM_Status;
#pragma pack(pop)
#define WDM_DEFAULT_BUFSIZE 256
#define RIL_REQUEST_QUIT 0x1000
#define RIL_INDICATE_DEVICE_CONNECTED 0x1002
#define RIL_INDICATE_DEVICE_DISCONNECTED 0x1003
#define RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED 0x1004
#define RIL_UNSOL_DATA_CALL_LIST_CHANGED 0x1005
#define MODEM_REPORT_RESET_EVENT 0x1006
#define RIL_UNSOL_LOOPBACK_CONFIG_IND 0x1007
#ifdef CONFIG_REG_QOS_IND
#define RIL_UNSOL_GLOBAL_QOS_FLOW_IND_QOS_ID 0x1008
#endif
extern pthread_mutex_t cm_command_mutex;
extern pthread_cond_t cm_command_cond;
extern unsigned int cm_recv_buf[1024];
extern int cm_open_dev(const char *dev);
extern int cm_open_proxy(const char *name);
extern int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs);
extern int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs, const char *funcname);
#define QmiThreadSendQMI(pRequest, ppResponse) QmiThreadSendQMITimeout(pRequest, ppResponse, 30 * 1000, __func__)
extern void QmiThreadRecvQMI(PQCQMIMSG pResponse);
extern void udhcpc_start(PROFILE_T *profile);
extern void udhcpc_stop(PROFILE_T *profile);
extern void ql_set_driver_link_state(PROFILE_T *profile, int link_state);
extern void ql_set_driver_qmap_setting(PROFILE_T *profile, QMAP_SETTING *qmap_settings);
extern void ql_get_driver_rmnet_info(PROFILE_T *profile, RMNET_INFO *rmnet_info);
extern void dump_qmi(void *dataBuffer, int dataLen);
extern void qmidevice_send_event_to_main(int triger_event);
extern void qmidevice_send_event_to_main_ext(int triger_event, void *data, unsigned len);
extern uint8_t qmi_over_mbim_get_client_id(uint8_t QMIType);
extern uint8_t qmi_over_mbim_release_client_id(uint8_t QMIType, uint8_t ClientId);
#ifdef CONFIG_REG_QOS_IND
extern UCHAR ql_get_global_qos_flow_ind_qos_id(PQCQMIMSG pResponse, UINT *qos_id);
#endif
#ifdef CONFIG_GET_QOS_DATA_RATE
extern UCHAR ql_get_global_qos_flow_ind_data_rate(PQCQMIMSG pResponse, void *max_data_rate);
#endif
struct request_ops {
int (*requestBaseBandVersion)(PROFILE_T *profile);
int (*requestSetEthMode)(PROFILE_T *profile);
int (*requestSetLoopBackState)(UCHAR loopback_state, ULONG replication_factor);
int (*requestGetSIMStatus)(SIM_Status *pSIMStatus);
int (*requestEnterSimPin)(const char *pPinCode);
int (*requestSetProfile)(PROFILE_T *profile); // 1 ~ success and apn change, 0 ~ success and no apn change, -1 ~ fail
int (*requestGetProfile)(PROFILE_T *profile);
int (*requestRegistrationState)(UCHAR *pPSAttachedState);
int (*requestSetupDataCall)(PROFILE_T *profile, int curIpFamily);
int (*requestQueryDataCall)(UCHAR *pConnectionStatus, int curIpFamily);
int (*requestDeactivateDefaultPDP)(PROFILE_T *profile, int curIpFamily);
int (*requestGetIPAddress)(PROFILE_T *profile, int curIpFamily);
int (*requestGetSignalInfo)(void);
int (*requestGetCellInfoList)(void);
int (*requestGetICCID)(void);
int (*requestGetIMSI)(void);
int (*requestRadioPower)(int state);
int (*requestRegisterQos)(PROFILE_T *profile);
int (*requestGetQosInfo)(PROFILE_T *profile);
int (*requestGetCoexWWANState)(void);
};
extern const struct request_ops qmi_request_ops;
extern const struct request_ops mbim_request_ops;
extern const struct request_ops atc_request_ops;
extern int get_driver_type(PROFILE_T *profile);
extern BOOL qmidevice_detect(char *qmichannel, char *usbnet_adapter, unsigned bufsize, PROFILE_T *profile);
int mhidevice_detect(char *qmichannel, char *usbnet_adapter, PROFILE_T *profile);
int atdevice_detect(char *atchannel, char *usbnet_adapter, PROFILE_T *profile);
extern int ql_bridge_mode_detect(PROFILE_T *profile);
extern int ql_enable_qmi_wwan_rawip_mode(PROFILE_T *profile);
extern int ql_qmap_mode_detect(PROFILE_T *profile);
#ifdef CONFIG_QRTR
extern int rtrmnet_ctl_new_vnd(char *devname, char *vndname, uint8_t muxid,
uint32_t qmap_version, uint32_t ul_agg_cnt, uint32_t ul_agg_size);
extern int rtrmnet_ctl_get_vnd(char *vndname, int *muxid,
int *qmap_version);
#endif
#define qmidev_is_gobinet(_qmichannel) (strncmp(_qmichannel, "/dev/qcqmi", strlen("/dev/qcqmi")) == 0)
#define qmidev_is_qmiwwan(_qmichannel) (strncmp(_qmichannel, "/dev/cdc-wdm", strlen("/dev/cdc-wdm")) == 0)
#define qmidev_is_pciemhi(_qmichannel) (strncmp(_qmichannel, "/dev/mhi_", strlen("/dev/mhi_")) == 0)
#define driver_is_qmi(_drv_name) (strncasecmp(_drv_name, "qmi_wwan", strlen("qmi_wwan")) == 0)
#define driver_is_mbim(_drv_name) (strncasecmp(_drv_name, "cdc_mbim", strlen("cdc_mbim")) == 0)
extern FILE *logfilefp;
extern int debug_qmi;
extern int qmidevice_control_fd[2];
extern int g_donot_exit_when_modem_hangup;
extern void update_resolv_conf(int iptype, const char *ifname, const char *dns1, const char *dns2);
void update_ipv4_address(const char *ifname, const char *ip, const char *gw, unsigned prefix);
void update_ipv6_address(const char *ifname, const char *ip, const char *gw, unsigned prefix);
int reattach_driver(PROFILE_T *profile);
extern void no_trunc_strncpy(char *dest, const char *src, size_t dest_size);
enum
{
DRV_INVALID,
SOFTWARE_QMI,
SOFTWARE_MBIM,
SOFTWARE_ECM_RNDIS_NCM,
SOFTWARE_QRTR,
HARDWARE_PCIE,
HARDWARE_USB,
HARDWARE_IPA,
};
#ifdef USE_IPC_MSG_STATUS_IND
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSG_FILE "/etc/passwd"
#define MSG_TYPE_IPC 1
#define MSGBUFFSZ 16
struct message
{
long mtype;
char mtext[MSGBUFFSZ];
};
enum
{
QCM_PARA_CONFIG_QUERY = 1,
QCM_NETWORK_IPV4_CONNECTED,
QCM_NETWORK_IPV6_CONNECTED,
QCM_NETWORK_IPV4_DISCONNECTED,
QCM_NETWORK_IPV6_DISCONNECTED,
QCM_PREPARE_START_DAIL,
QCM_PREPARE_DAIL_FIAL,
QCM_PREPARE_DAIL_SUCC,
QCM_PREPARE_RESTART_DAIL,
QCM_PROCESS_EXIT,
};
#endif
enum
{
SIG_EVENT_START,
SIG_EVENT_CHECK,
SIG_EVENT_STOP,
};
typedef enum
{
DMS_OP_MODE_ONLINE,
DMS_OP_MODE_LOW_POWER,
DMS_OP_MODE_FACTORY_TEST_MODE,
DMS_OP_MODE_OFFLINE,
DMS_OP_MODE_RESETTING,
DMS_OP_MODE_SHUTTING_DOWN,
DMS_OP_MODE_PERSISTENT_LOW_POWER,
DMS_OP_MODE_MODE_ONLY_LOW_POWER,
DMS_OP_MODE_NET_TEST_GW,
}Device_operating_mode;
#ifdef CM_DEBUG
#define dbg_time(fmt, args...) do { \
fprintf(stdout, "[%15s-%04d: %s] " fmt "\n", __FILE__, __LINE__, get_time(), ##args); \
fflush(stdout);\
if (logfilefp) fprintf(logfilefp, "[%s-%04d: %s] " fmt "\n", __FILE__, __LINE__, get_time(), ##args); \
} while(0)
#else
#define dbg_time(fmt, args...) do { \
fprintf(stdout, "[%s] " fmt "\n", get_time(), ##args); \
fflush(stdout);\
if (logfilefp) fprintf(logfilefp, "[%s] " fmt "\n", get_time(), ##args); \
} while(0)
#endif
#endif
+454
View File
@@ -0,0 +1,454 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <stdio.h>
#include <ctype.h>
#include "QMIThread.h"
#ifdef CONFIG_QMIWWAN
static int cdc_wdm_fd = -1;
static UCHAR qmiclientId[QMUX_TYPE_ALL];
static UCHAR GetQCTLTransactionId(void) {
static int TransactionId = 0;
if (++TransactionId > 0xFF)
TransactionId = 1;
return TransactionId;
}
typedef USHORT (*CUSTOMQCTL)(PQMICTL_MSG pCTLMsg, void *arg);
static PQCQMIMSG ComposeQCTLMsg(USHORT QMICTLType, CUSTOMQCTL customQctlMsgFunction, void *arg) {
UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE];
PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf;
int Length;
pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
pRequest->QMIHdr.CtlFlags = 0x00;
pRequest->QMIHdr.QMIType = QMUX_TYPE_CTL;
pRequest->QMIHdr.ClientId= 0x00;
pRequest->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST;
pRequest->CTLMsg.QMICTLMsgHdr.TransactionId = GetQCTLTransactionId();
pRequest->CTLMsg.QMICTLMsgHdr.QMICTLType = cpu_to_le16(QMICTLType);
if (customQctlMsgFunction)
pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(customQctlMsgFunction(&pRequest->CTLMsg, arg) - sizeof(QCQMICTL_MSG_HDR));
else
pRequest->CTLMsg.QMICTLMsgHdr.Length = cpu_to_le16(0x0000);
pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMICTL_MSG_HDR) + sizeof(QCQMI_HDR) - 1);
Length = le16_to_cpu(pRequest->QMIHdr.Length) + 1;
pRequest = (PQCQMIMSG)malloc(Length);
if (pRequest == NULL) {
dbg_time("%s fail to malloc", __func__);
} else {
memcpy(pRequest, QMIBuf, Length);
}
return pRequest;
}
static USHORT CtlGetVersionReq(PQMICTL_MSG QCTLMsg, void *arg)
{
(void)arg;
QCTLMsg->GetVersionReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER;
QCTLMsg->GetVersionReq.TLVLength = cpu_to_le16(0x0001);
QCTLMsg->GetVersionReq.QMUXTypes = QMUX_TYPE_ALL;
return sizeof(QMICTL_GET_VERSION_REQ_MSG);
}
static USHORT CtlGetClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) {
QCTLMsg->GetClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER;
QCTLMsg->GetClientIdReq.TLVLength = cpu_to_le16(0x0001);
QCTLMsg->GetClientIdReq.QMIType = ((UCHAR *)arg)[0];
return sizeof(QMICTL_GET_CLIENT_ID_REQ_MSG);
}
static USHORT CtlReleaseClientIdReq(PQMICTL_MSG QCTLMsg, void *arg) {
QCTLMsg->ReleaseClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER;
QCTLMsg->ReleaseClientIdReq.TLVLength = cpu_to_le16(0x0002);
QCTLMsg->ReleaseClientIdReq.QMIType = ((UCHAR *)arg)[0];
QCTLMsg->ReleaseClientIdReq.ClientId = ((UCHAR *)arg)[1] ;
return sizeof(QMICTL_RELEASE_CLIENT_ID_REQ_MSG);
}
static USHORT CtlLibQmiProxyOpenReq(PQMICTL_MSG QCTLMsg, void *arg)
{
(void)arg;
const char *device_path = (const char *)(arg);
QCTLMsg->LibQmiProxyOpenReq.TLVType = 0x01;
QCTLMsg->LibQmiProxyOpenReq.TLVLength = cpu_to_le16(strlen(device_path));
//strcpy(QCTLMsg->LibQmiProxyOpenReq.device_path, device_path);
//__builtin___strcpy_chk
memcpy(QCTLMsg->LibQmiProxyOpenReq.device_path, device_path, strlen(device_path));
return sizeof(QMICTL_LIBQMI_PROXY_OPEN_MSG) + (strlen(device_path));
}
static int libqmi_proxy_open(const char *cdc_wdm) {
int ret;
PQCQMIMSG pResponse;
ret = QmiThreadSendQMI(ComposeQCTLMsg(QMI_MESSAGE_CTL_INTERNAL_PROXY_OPEN,
CtlLibQmiProxyOpenReq, (void *)cdc_wdm), &pResponse);
if (!ret && pResponse
&& pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult == 0
&& pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError == 0) {
ret = 0;
}
else {
return -1;
}
if (pResponse)
free(pResponse);
return ret;
}
static int QmiWwanSendQMI(PQCQMIMSG pRequest) {
struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}};
int ret;
if (cdc_wdm_fd == -1) {
dbg_time("%s cdc_wdm_fd = -1", __func__);
return -ENODEV;
}
if (pRequest->QMIHdr.QMIType != QMUX_TYPE_CTL) {
pRequest->QMIHdr.ClientId = qmiclientId[pRequest->QMIHdr.QMIType];
if (pRequest->QMIHdr.ClientId == 0) {
dbg_time("QMIType %d has no clientID", pRequest->QMIHdr.QMIType);
return -ENODEV;
}
if (pRequest->QMIHdr.QMIType == QMUX_TYPE_WDS_IPV6)
pRequest->QMIHdr.QMIType = QMUX_TYPE_WDS;
}
do {
ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000);
} while ((ret < 0) && (errno == EINTR));
if (pollfds[0].revents & POLLOUT) {
ssize_t nwrites = le16_to_cpu(pRequest->QMIHdr.Length) + 1;
ret = write(cdc_wdm_fd, pRequest, nwrites);
if (ret == nwrites) {
ret = 0;
} else {
dbg_time("%s write=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno));
}
} else {
dbg_time("%s poll=%d, revents = 0x%x, errno: %d (%s)", __func__, ret, pollfds[0].revents, errno, strerror(errno));
}
return ret;
}
static UCHAR QmiWwanGetClientID(UCHAR QMIType) {
PQCQMIMSG pResponse;
QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_CLIENT_ID_REQ, CtlGetClientIdReq, &QMIType), &pResponse);
if (pResponse) {
USHORT QMUXResult = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult); // QMI_RESULT_SUCCESS
USHORT QMUXError = cpu_to_le16(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError); // QMI_ERR_INVALID_ARG
//UCHAR QMIType = pResponse->CTLMsg.GetClientIdRsp.QMIType;
UCHAR ClientId = pResponse->CTLMsg.GetClientIdRsp.ClientId;
if (!QMUXResult && !QMUXError && (QMIType == pResponse->CTLMsg.GetClientIdRsp.QMIType)) {
switch (QMIType) {
case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break;
case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break;
case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break;
case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break;
case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break;
case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break;
case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break;
case QMUX_TYPE_COEX: dbg_time("Get clientCOEX = %d", ClientId); break;
case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId);
break;
default: break;
}
return ClientId;
}
}
return 0;
}
static int QmiWwanReleaseClientID(QMI_SERVICE_TYPE QMIType, UCHAR ClientId) {
UCHAR argv[] = {QMIType, ClientId};
QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_RELEASE_CLIENT_ID_REQ, CtlReleaseClientIdReq, argv), NULL);
return 0;
}
static int QmiWwanInit(PROFILE_T *profile) {
unsigned i;
int ret;
PQCQMIMSG pResponse;
if (profile->proxy[0] && !strcmp(profile->proxy, LIBQMI_PROXY)) {
ret = libqmi_proxy_open(profile->qmichannel);
if (ret)
return ret;
}
if (!profile->proxy[0]) {
for (i = 0; i < 10; i++) {
ret = QmiThreadSendQMITimeout(ComposeQCTLMsg(QMICTL_SYNC_REQ, NULL, NULL), NULL, 1 * 1000, __func__);
if (ret != ETIMEDOUT)
break;
sleep(1);
}
if (ret)
return ret;
}
QmiThreadSendQMI(ComposeQCTLMsg(QMICTL_GET_VERSION_REQ, CtlGetVersionReq, NULL), &pResponse);
if (profile->qmap_mode) {
if (pResponse) {
if (pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXResult == 0 && pResponse->CTLMsg.QMICTLMsgHdrRsp.QMUXError == 0) {
uint8_t NumElements = 0;
for (NumElements = 0; NumElements < pResponse->CTLMsg.GetVersionRsp.NumElements; NumElements++) {
#if 0
dbg_time("QMUXType = %02x Version = %d.%d",
pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].QMUXType,
pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MajorVersion,
pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MinorVersion);
#endif
if (pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].QMUXType == QMUX_TYPE_WDS_ADMIN)
profile->qmap_version = (pResponse->CTLMsg.GetVersionRsp.TypeVersion[NumElements].MinorVersion > 16);
}
}
}
}
if (pResponse) free(pResponse);
qmiclientId[QMUX_TYPE_WDS] = QmiWwanGetClientID(QMUX_TYPE_WDS);
if (profile->enable_ipv6)
qmiclientId[QMUX_TYPE_WDS_IPV6] = QmiWwanGetClientID(QMUX_TYPE_WDS);
qmiclientId[QMUX_TYPE_DMS] = QmiWwanGetClientID(QMUX_TYPE_DMS);
qmiclientId[QMUX_TYPE_NAS] = QmiWwanGetClientID(QMUX_TYPE_NAS);
qmiclientId[QMUX_TYPE_UIM] = QmiWwanGetClientID(QMUX_TYPE_UIM);
qmiclientId[QMUX_TYPE_WDS_ADMIN] = QmiWwanGetClientID(QMUX_TYPE_WDS_ADMIN);
#ifdef CONFIG_COEX_WWAN_STATE
qmiclientId[QMUX_TYPE_COEX] = QmiWwanGetClientID(QMUX_TYPE_COEX);
#endif
#ifdef CONFIG_ENABLE_QOS
qmiclientId[QMUX_TYPE_QOS] = QmiWwanGetClientID(QMUX_TYPE_QOS);
#endif
profile->wda_client = qmiclientId[QMUX_TYPE_WDS_ADMIN];
return 0;
}
static int QmiWwanDeInit(void) {
unsigned int i;
for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
{
if (qmiclientId[i] != 0)
{
QmiWwanReleaseClientID((QMUX_TYPE_WDS_IPV6 == i ? QMUX_TYPE_WDS : i), qmiclientId[i]);
qmiclientId[i] = 0;
}
}
return 0;
}
static ssize_t qmi_proxy_read (int fd, void *buf, size_t size) {
ssize_t nreads;
PQCQMI_HDR pHdr = (PQCQMI_HDR)buf;
nreads = read(fd, pHdr, sizeof(QCQMI_HDR));
if (nreads == sizeof(QCQMI_HDR) && le16_to_cpu(pHdr->Length) < size) {
nreads += read(fd, pHdr+1, le16_to_cpu(pHdr->Length) + 1 - sizeof(QCQMI_HDR));
}
return nreads;
}
#ifdef QUECTEL_QMI_MERGE
static int QmiWwanMergeQmiRsp(void *buf, ssize_t *src_size) {
static QMI_MSG_PACKET s_QMIPacket;
QMI_MSG_HEADER *header = NULL;
ssize_t size = *src_size;
if((uint16_t)size < sizeof(QMI_MSG_HEADER))
return -1;
header = (QMI_MSG_HEADER *)buf;
if(le16_to_cpu(header->idenity) != MERGE_PACKET_IDENTITY || le16_to_cpu(header->version) != MERGE_PACKET_VERSION || le16_to_cpu(header->cur_len) > le16_to_cpu(header->total_len))
return -1;
if(le16_to_cpu(header->cur_len) == le16_to_cpu(header->total_len)) {
*src_size = le16_to_cpu(header->total_len);
memcpy(buf, buf + sizeof(QMI_MSG_HEADER), *src_size);
s_QMIPacket.len = 0;
return 0;
}
memcpy(s_QMIPacket.buf + s_QMIPacket.len, buf + sizeof(QMI_MSG_HEADER), le16_to_cpu(header->cur_len));
s_QMIPacket.len += le16_to_cpu(header->cur_len);
if (le16_to_cpu(header->cur_len) < MERGE_PACKET_MAX_PAYLOAD_SIZE || s_QMIPacket.len >= le16_to_cpu(header->total_len)) {
memcpy(buf, s_QMIPacket.buf, s_QMIPacket.len);
*src_size = s_QMIPacket.len;
s_QMIPacket.len = 0;
return 0;
}
return -1;
}
#endif
static void * QmiWwanThread(void *pData) {
PROFILE_T *profile = (PROFILE_T *)pData;
const char *cdc_wdm = (const char *)profile->qmichannel;
int wait_for_request_quit = 0;
char num = cdc_wdm[strlen(cdc_wdm)-1];
if (profile->proxy[0]) {
if (!strncmp(profile->proxy, QUECTEL_QMI_PROXY, strlen(QUECTEL_QMI_PROXY))) {
snprintf(profile->proxy, sizeof(profile->proxy), "%s%c", QUECTEL_QMI_PROXY, num);
}
}
else if (!strncmp(cdc_wdm, "/dev/mhi_IPCR", strlen("/dev/mhi_IPCR"))) {
snprintf(profile->proxy, sizeof(profile->proxy), "%s%c", QUECTEL_QRTR_PROXY, num);
}
else if (profile->qmap_mode > 1) {
snprintf(profile->proxy, sizeof(profile->proxy), "%s%c", QUECTEL_QMI_PROXY, num);
}
if (profile->proxy[0])
cdc_wdm_fd = cm_open_proxy(profile->proxy);
else
cdc_wdm_fd = cm_open_dev(cdc_wdm);
if (cdc_wdm_fd == -1) {
dbg_time("%s Failed to open %s, errno: %d (%s)", __func__, cdc_wdm, errno, strerror(errno));
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
pthread_exit(NULL);
return NULL;
}
dbg_time("cdc_wdm_fd = %d", cdc_wdm_fd);
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED);
while (1) {
struct pollfd pollfds[] = {{qmidevice_control_fd[1], POLLIN, 0}, {cdc_wdm_fd, POLLIN, 0}};
int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]);
do {
ret = poll(pollfds, nevents, wait_for_request_quit ? 1000 : -1);
} while ((ret < 0) && (errno == EINTR));
if (ret == 0 && wait_for_request_quit) {
QmiThreadRecvQMI(NULL);
continue;
}
if (ret <= 0) {
dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno));
break;
}
for (ne = 0; ne < nevents; ne++) {
int fd = pollfds[ne].fd;
short revents = pollfds[ne].revents;
//dbg_time("{%d, %x, %x}", pollfds[ne].fd, pollfds[ne].events, pollfds[ne].revents);
if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
dbg_time("%s poll err/hup/inval", __func__);
dbg_time("poll fd = %d, events = 0x%04x", fd, revents);
if (fd == cdc_wdm_fd) {
} else {
}
if (revents & (POLLHUP | POLLNVAL)) //EC20 bug, Can get POLLERR
goto __QmiWwanThread_quit;
}
if ((revents & POLLIN) == 0)
continue;
if (fd == qmidevice_control_fd[1]) {
int triger_event;
if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) {
//DBG("triger_event = 0x%x", triger_event);
switch (triger_event) {
case RIL_REQUEST_QUIT:
goto __QmiWwanThread_quit;
break;
case SIG_EVENT_STOP:
wait_for_request_quit = 1;
break;
default:
break;
}
}
}
if (fd == cdc_wdm_fd) {
ssize_t nreads;
PQCQMIMSG pResponse = (PQCQMIMSG)cm_recv_buf;
if (!profile->proxy[0])
nreads = read(fd, cm_recv_buf, sizeof(cm_recv_buf));
else
nreads = qmi_proxy_read(fd, cm_recv_buf, sizeof(cm_recv_buf));
//dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno));
if (nreads <= 0) {
dbg_time("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno));
break;
}
#ifdef QUECTEL_QMI_MERGE
if((profile->qmap_mode == 0 || profile->qmap_mode == 1) && QmiWwanMergeQmiRsp(cm_recv_buf, &nreads))
continue;
#endif
if (nreads != (le16_to_cpu(pResponse->QMIHdr.Length) + 1)) {
dbg_time("%s nreads=%d, pQCQMI->QMIHdr.Length = %d", __func__, (int)nreads, le16_to_cpu(pResponse->QMIHdr.Length));
continue;
}
QmiThreadRecvQMI(pResponse);
}
}
}
__QmiWwanThread_quit:
if (cdc_wdm_fd != -1) { close(cdc_wdm_fd); cdc_wdm_fd = -1; }
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI()
dbg_time("%s exit", __func__);
pthread_exit(NULL);
return NULL;
}
const struct qmi_device_ops qmiwwan_qmidev_ops = {
.init = QmiWwanInit,
.deinit = QmiWwanDeInit,
.send = QmiWwanSendQMI,
.read = QmiWwanThread,
};
uint8_t qmi_over_mbim_get_client_id(uint8_t QMIType) {
return QmiWwanGetClientID(QMIType);
}
uint8_t qmi_over_mbim_release_client_id(uint8_t QMIType, uint8_t ClientId) {
return QmiWwanReleaseClientID(QMIType, ClientId);
}
#endif
+344
View File
@@ -0,0 +1,344 @@
Release Notes
[V1.6.8]
Date: 3/4/2025
enhancement:
1. add QMI dialing status report
fix:
1. fix some bugs
[V1.6.7]
Date: 5/17/2024
enhancement:
1. optimize -a parameter
fix:
1. fix the multiple dialing issue on the SDX35 platform
[V1.6.6]
Date: 1/12/2024
enhancement:
1. support SDX35 platform
[V1.6.4]
Date: 9/7/2022
enhancement:
1. set cflags as -Wall -Wextra -Werror -O1, and fix compile errors
2. some code refactoring
3. add quectel-qrtr-proxy
fix:
1. netmask error when use ifconfig on little endian cpu
[V1.6.2]
Date: 11/18/2021
enhancement:
1. support 'LTE && WiFi Coexistence Solution via QMI'.
If customer want use this feature, need enable CONFIG_COEX_WWAN_STATE in QMIThread.h
[V1.6.1]
Date: 7/20/2021
enhancement:
1. add requestGetCellInfoList requestRadioPower
2. add QMI OVER MBIM
3. support qrtr and rmnet
4. support RG500U PCIE
5. add qos service && get qos flow data_rate_max func
fix:
1. mbim: increase mbim open timeout to 3 seconds. some modem take long time for open cmd.
2. support MsChapV2
3. mbim: invalid memory access when only get one DNS
4. some bug fix for use AT Command to setup data call
[V1.6.0.26]
Date: 4/22/2021
enhancement:
1. add lots of log file to show how to use this tool
2. support pcie mhi multiple call
3. at command: support EC200U/EC200T/EC200S/RG801H/RG500U/
fix:
1. mbim-proxy: fix errors on big endian cpu, ignore mbim open/close cmd from quectel-cm
[V1.6.0.25]
Date: 4/8/2021
enhancement:
fix:
1. fix compile error when use gcc 9.3.0
2. fix yocto 'QA Issue: No GNU_HASH in the ELF binary'
[V1.6.0.24]
Date: 3/9/2021
enhancement:
1. '-p [quectel-][qmi|mbim]-proxy', can connect to quectel/libqmi/libmbim's proxy, even only one data
2. set variable s_9x07 as 1 (from 0), most of modems are base on MDM90x7 and later QCOM chip.
fix:
1. define CHAR as signed char
2. mofidy Makefile to generate more compile warnnings and fix them
[V1.6.0.23]
Date: 2/26/2021
enhancement:
1. support 'AT+QNETDEVCTL' (not release)
fix:
1. modify help/usage
2. fix some memroy access error in mbim-cm.c
[V1.6.0.22]
Date: 2/4/2021
enhancement:
1. support connect to libqmi's qmi-proxy
2. only allow ' 0/1/2/none/pap/chap' for auth of '-s'
3. '-m iface-idx' bind QMAP data call to wwan0_<iface_idx>
fix:
[V1.6.0.21]
Date: 1/28/2021
enhancement:
1. print 5G signal
fix:
1. fix compile errors: -Werror=format-truncation=
[V1.6.0.20]
Date: 12/29/2020
enhancement:
1. Code refactoring
2. support 'AT+QNETDEVCTL' (not release)
fix:
[V1.6.0.19]
Date: 12/4/2020
enhancement:
1. if 'udhcpc's default.script' missed, directy set ip/dns/route by 'ip' co,mand
fix:
[V1.6.0.18]
Date: 12/4/2020
enhancement:
1. Code refactoring
fix:
[V1.6.0.17]
Date: 8/25/2020
enhancement:
1. support MBIM multi-call
2. support unisoc RG500U mbim
3. QUECTEL_QMI_MERGE: some SOC can not read more then 64 bytes (QMI)data via USB Endpoint 0
fix:
[V1.6.0.15]
Date: 7/24/2020
enhancement:
fix:
1. QMAP multi-call, AT+CFUN=4 then AT+CFUN=1, only one call can obtain IP by DHCP
[V1.6.0.14]
Date: 6/10/2020
enhancement:
1. support X55's GobiNet LOOPBACK
fix:
1. very old uclib do not support htole32 and pthread_condattr_setclock
2. pthread_cond_wait tv_nsec >= 1000000000U is wrong
3. do not close socket in udhcpc.c ifc_get_addr()
[V1.6.0.13]
Date: 6/9/2020
enhancement:
1. add some example for openwrt, marco 'QL_OPENWER_NETWORK_SETUP'
fix:
[V1.6.0.12]
Date: 5/29/2020
enhancement:
fix:
1. some EM12's usb-net-qmi/mbim interface is at 8 (not 4)
[V1.6.0.11]
Date: 5/28/2020
enhancement:
fix:
1. fix mbim debug on Big Endian CPU
[V1.6.0.10]
Date: 5/25/2020
enhancement:
fix:
1. set QMAP .ul_data_aggregation_max_datagrams to 11 (from 16)
[V1.6.0.9]
Date: 5/22/2020
enhancement:
fix:
1. dial fail when register to 5G-SA
[V1.6.0.8]
Date: 4/30/2020
enhancement:
1. support '-b' to seletc brige mode
fix:
[V1.6.0.7]
Date: 4/29/2020
enhancement:
1. support QMAP multi-call for qmi_wwan_q and pcie_mhi 's rmnet driver
fix:
[V1.6.0.6]
Date: 4/20/2020
enhancement:
1. support '-k pdn_idx' to hangup call '-n pdn_idx'
fix:
1. fix set dl_minimum_padding as 0, modems do not support this featrue
[V1.6.0.5]
Date: 4/10/2020
enhancement:
1. support X55's QMAPV5 for PCIE
fix:
[V1.6.0.3]
Date: 4/8/2020
enhancement:
1. support multi-modems all use multi-data-calls
fix:
[V1.6.0.2]
Date: 4/7/2020
enhancement:
1. support X55's QMAPV5 for USB
fix:
[V1.6.0.1]
Date: 4/1/2020
enhancement:
1. support QMAP UL AGG (multi data call)
fix:
1. some EM12's usb-net-qmi/mbim interface is at 8 (not 4)
[V1.5.9]
Date: 3/4/2020
enhancement:
1. support pcie mhi multi-APN data call
3. support QMAP UL AGG (single data call)
fix:
1. set 4 bytes aligned for mbim parameters, or cause dial mbim call fail
[V1.5.8]
Date: 2/18/2020
enhancement:
1. support '-l 14' X55's loopback function
fix:
[V1.5.7]
Date: 2/6/2020
enhancement:
1. support '-u usbmon_log_file' to catch usbmon log
fix:
[V1.5.6]
Date: 1/20/202
enhancement:
1. show driver name and version
2. support PCSCF
3. support bridge in mbim
fix:
[V1.5.5]
Date: 12/31/2019
enhancement:
fix:
1. fix some memory access bug in mbim-cm.c
[WCDMA&LTE_QConnectManager_Linux&Android_V1.5.4]
Date: 12/17/2019
enhancement:
1. Add copyright
2. auto detect pcie mhi /dev/mhi*
fix:
[WCDMA&LTE_QConnectManager_Linux&Android_V1.5.3]
Date: 2019/12/11
enhancement:
1. support show SignalInfo, controlled by macro CONFIG_SIGNALINFO
2. support show 5G_NSA/5G_NA
3. support Microsoft Extend MBIM message
fix:
1. quectel-qmi-proxy bugs on Big-Endian CPU
[WCDMA&LTE_QConnectManager_Linux&Android_V1.5.2]
Date: 12/2/2019
enhancement:
1. support requestGetSignalInfo()
fix:
[WCDMA&LTE_QConnectManager_Linux&Android_V1.4.1]
Date: 10/23/2019
enhancement:
1. support QMI_CTL_REVOKE_CLIENT_ID_IND (Quectel define QMI)
2. add copyright
fix:
1. remove SIGUSR
[WCDMA&LTE_QConnectManager_Linux&Android_V1.3.10]
Date: 10/14/2019
enhancement:
1. increase retry interval
fix:
[WCDMA&LTE_QConnectManager_Linux&Android_V1.2.1]
Date: 2019/02/26
enhancement:
1. Implement help message.
root@ubuntu:# ./quectel-CM -h
[02-26_10:39:21:353] Usage: ./quectel-CM [options]
[02-26_10:39:21:353] -s [apn [user password auth]] Set apn/user/password/auth get from your network provider
[02-26_10:39:21:353] -p pincode Verify sim card pin if sim card is locked
[02-26_10:39:21:353] -f logfilename Save log message of this program to file
[02-26_10:39:21:353] -i interface Specify network interface(default auto-detect)
[02-26_10:39:21:353] -4 IPv4 protocol
[02-26_10:39:21:353] -6 IPv6 protocol
[02-26_10:39:21:353] -m muxID Specify muxid when set multi-pdn data connection.
[02-26_10:39:21:353] -n channelID Specify channelID when set multi-pdn data connection(default 1).
[02-26_10:39:21:353] [Examples]
[02-26_10:39:21:353] Example 1: ./quectel-CM
[02-26_10:39:21:353] Example 2: ./quectel-CM -s 3gnet
[02-26_10:39:21:353] Example 3: ./quectel-CM -s 3gnet carl 1234 0 -p 1234 -f gobinet_log.txt
root@ubuntu:#
2. Support bridge mode when set multi-pdn data connections.
3. Host device can access network in bridge mode.
[WCDMA&LTE_QConnectManager_Linux&Android_V1.1.46]
Date: 2019/02/18
enhancement:
1. support only IPV6 data call. quectel-CM now support three dialing methods: IPV4 only, IPV6 only, IPV4V6.
./quectel-CM -4(or no argument) only IPV4
-6 only IPV6
-4 -6 IPV4 && IPV6
[WCDMA&LTE_QConnectManager_Linux&Android_V1.1.45]
Date: 2018/09/13
enhancement:
1. support EG12 PCIE interface
[WCDMA&LTE_QConnectManager_Linux&Android_V1.1.44]
Date: 2018/09/10
enhancement:
1. support setup IPV4&IPV6 data call.
[WCDMA&LTE_QConnectManager_Linux&Android_V1.1.43]
[WCDMA&LTE_QConnectManager_Linux&Android_V1.1.42]
Date: 2018/08/29
enhancement:
1. support QMI_WWAN's QMAP fucntion and bridge mode, please contact Quectel FAE to get qmi_wwan.c patch.
when enable QMI_WWAN's QMAP IP Mux function, must run 'quectel-qmi-proxy -d /dev/cdc-wdmX' before quectel-CM
[WCDMA&LTE_QConnectManager_Linux&Android_V1.1.41]
Date: 2018/05/24
enhancement:
1. fix a cdma data call error
[WCDMA&LTE_QConnectManager_Linux&Android_V1.1.40]
Date: 2018/05/12
enhancement:
1. support GobiNet's QMAP fucntion and bridge mode.
'Quectel_WCDMA&LTE_Linux&Android_GobiNet_Driver_V1.3.5' and later version is required to use QMAP and bridge mode.
for detail, please refer to GobiNet Driver
+283
View File
@@ -0,0 +1,283 @@
/* //device/system/reference-ril/at_tok.c
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#include "at_tok.h"
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
/**
* Starts tokenizing an AT response string
* returns -1 if this is not a valid response string, 0 on success.
* updates *p_cur with current position
*/
int at_tok_start(char **p_cur)
{
if (*p_cur == NULL) {
return -1;
}
// skip prefix
// consume "^[^:]:"
*p_cur = strchr(*p_cur, ':');
if (*p_cur == NULL) {
return -1;
}
(*p_cur)++;
return 0;
}
static void skipWhiteSpace(char **p_cur)
{
if (*p_cur == NULL) return;
while (**p_cur != '\0' && isspace(**p_cur)) {
(*p_cur)++;
}
}
static void skipNextComma(char **p_cur)
{
if (*p_cur == NULL) return;
while (**p_cur != '\0' && **p_cur != ',') {
(*p_cur)++;
}
if (**p_cur == ',') {
(*p_cur)++;
}
}
static char * nextTok(char **p_cur)
{
char *ret = NULL;
skipWhiteSpace(p_cur);
if (*p_cur == NULL) {
ret = NULL;
} else if (**p_cur == '"') {
(*p_cur)++;
ret = strsep(p_cur, "\"");
skipNextComma(p_cur);
} else {
ret = strsep(p_cur, ",");
}
return ret;
}
/**
* Parses the next integer in the AT response line and places it in *p_out
* returns 0 on success and -1 on fail
* updates *p_cur
* "base" is the same as the base param in strtol
*/
static int at_tok_nextint_base(char **p_cur, int *p_out, int base, int uns)
{
char *ret;
if (*p_cur == NULL) {
return -1;
}
ret = nextTok(p_cur);
if (ret == NULL) {
return -1;
} else {
long l;
char *end;
if (uns)
l = strtoul(ret, &end, base);
else
l = strtol(ret, &end, base);
*p_out = (int)l;
if (end == ret) {
return -1;
}
}
return 0;
}
/**
* Parses the next base 10 integer in the AT response line
* and places it in *p_out
* returns 0 on success and -1 on fail
* updates *p_cur
*/
int at_tok_nextint(char **p_cur, int *p_out)
{
return at_tok_nextint_base(p_cur, p_out, 10, 0);
}
/**
* Parses the next base 16 integer in the AT response line
* and places it in *p_out
* returns 0 on success and -1 on fail
* updates *p_cur
*/
int at_tok_nexthexint(char **p_cur, int *p_out)
{
return at_tok_nextint_base(p_cur, p_out, 16, 1);
}
int at_tok_nextbool(char **p_cur, char *p_out)
{
int ret;
int result;
ret = at_tok_nextint(p_cur, &result);
if (ret < 0) {
return -1;
}
// booleans should be 0 or 1
if (!(result == 0 || result == 1)) {
return -1;
}
if (p_out != NULL) {
*p_out = (char)result;
}
return ret;
}
int at_tok_nextstr(char **p_cur, char **p_out)
{
if (*p_cur == NULL) {
return -1;
}
*p_out = nextTok(p_cur);
return 0;
}
/** returns 1 on "has more tokens" and 0 if no */
int at_tok_hasmore(char **p_cur)
{
return ! (*p_cur == NULL || **p_cur == '\0');
}
int at_tok_count(const char *in_line)
{
int commas = 0;
const char *p;
if (!in_line)
return 0;
for (p = in_line; *p != '\0' ; p++) {
if (*p == ',') commas++;
}
return commas;
}
//fmt: d ~ int, x ~ hexint, b ~ bool, s ~ str
int at_tok_scanf(const char *in_line, const char *fmt, ...)
{
int n = 0;
int err;
va_list ap;
const char *p = fmt;
void *d;
void *dump;
static char s_line[1024];
char *line = s_line;
if (!in_line)
return 0;
strncpy(s_line, in_line, sizeof(s_line) - 1);
va_start(ap, fmt);
err = at_tok_start(&line);
if (err < 0) goto error;
for (; *p; p++) {
if (*p == ',' || *p == ' ')
continue;
if (*p != '%') {
goto error;
}
p++;
d = va_arg(ap, void *);
if (!d)
d = &dump;
if (!at_tok_hasmore(&line))
break;
if (*line == '-' && *(line + 1) == ',') {
line += 2;
n++;
if (*p == 'd')
*(int *)d = -1;
continue;
}
switch(*p) {
case 'd':
err = at_tok_nextint(&line, (int *)d);
if (err < 0) goto error;
break;
case 'x':
err = at_tok_nexthexint(&line, (int *)d);
if (err < 0) goto error;
break;
case 'b':
err = at_tok_nextbool(&line, (char *)d);
if (err < 0) goto error;
break;
case 's':
err = at_tok_nextstr(&line, (char **)d); //if strdup(line), here return free memory to caller
if (err < 0) goto error;
break;
default:
goto error;
break;
}
n++;
}
va_end(ap);
error:
//free(line);
return n;
}
+33
View File
@@ -0,0 +1,33 @@
/* //device/system/reference-ril/at_tok.h
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#ifndef AT_TOK_H
#define AT_TOK_H 1
int at_tok_start(char **p_cur);
int at_tok_nextint(char **p_cur, int *p_out);
int at_tok_nexthexint(char **p_cur, int *p_out);
int at_tok_nextbool(char **p_cur, char *p_out);
int at_tok_nextstr(char **p_cur, char **out);
int at_tok_hasmore(char **p_cur);
int at_tok_count(const char *in_line);
int at_tok_scanf(const char *line, const char *fmt, ...);
#endif /*AT_TOK_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+152
View File
@@ -0,0 +1,152 @@
/* //device/system/reference-ril/atchannel.h
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#ifndef ATCHANNEL_H
#define ATCHANNEL_H 1
#ifdef __cplusplus
extern "C" {
#endif
/* define AT_DEBUG to send AT traffic to /tmp/radio-at.log" */
#define AT_DEBUG 0
#if AT_DEBUG
extern void AT_DUMP(const char* prefix, const char* buff, int len);
#else
#define AT_DUMP(prefix,buff,len) do{}while(0)
#endif
#define AT_ERROR_GENERIC -1
#define AT_ERROR_COMMAND_PENDING -2
#define AT_ERROR_CHANNEL_CLOSED -3
#define AT_ERROR_TIMEOUT -4
#define AT_ERROR_INVALID_THREAD -5 /* AT commands may not be issued from
reader thread (or unsolicited response
callback */
#define AT_ERROR_INVALID_RESPONSE -6 /* eg an at_send_command_singleline that
did not get back an intermediate
response */
typedef enum {
NO_RESULT, /* no intermediate response expected */
NUMERIC, /* a single intermediate response starting with a 0-9 */
SINGLELINE, /* a single intermediate response starting with a prefix */
MULTILINE /* multiple line intermediate response
starting with a prefix */
} ATCommandType;
/** a singly-lined list of intermediate responses */
typedef struct ATLine {
struct ATLine *p_next;
char *line;
} ATLine;
/** Free this with at_response_free() */
typedef struct {
int success; /* true if final response indicates
success (eg "OK") */
char *finalResponse; /* eg OK, ERROR */
ATLine *p_intermediates; /* any intermediate responses */
} ATResponse;
/**
* a user-provided unsolicited response handler function
* this will be called from the reader thread, so do not block
* "s" is the line, and "sms_pdu" is either NULL or the PDU response
* for multi-line TS 27.005 SMS PDU responses (eg +CMT:)
*/
typedef void (*ATUnsolHandler)(const char *s, const char *sms_pdu);
int at_open(int fd, ATUnsolHandler h, int proxy);
void at_close();
/* This callback is invoked on the command thread.
You should reset or handshake here to avoid getting out of sync */
void at_set_on_timeout(void (*onTimeout)(void));
/* This callback is invoked on the reader thread (like ATUnsolHandler)
when the input stream closes before you call at_close
(not when you call at_close())
You should still call at_close()
It may also be invoked immediately from the current thread if the read
channel is already closed */
void at_set_on_reader_closed(void (*onClose)(void));
int at_send_command_singleline (const char *command,
const char *responsePrefix,
ATResponse **pp_outResponse);
int at_send_command_numeric (const char *command,
ATResponse **pp_outResponse);
int at_send_command_multiline (const char *command,
const char *responsePrefix,
ATResponse **pp_outResponse);
int at_send_command_raw (const char *command,
const char *raw_data, unsigned int raw_len,
const char *responsePrefix,
ATResponse **pp_outResponse);
int at_handshake();
int at_send_command (const char *command, ATResponse **pp_outResponse);
int at_send_command_sms (const char *command, const char *pdu,
const char *responsePrefix,
ATResponse **pp_outResponse);
void at_response_free(ATResponse *p_response);
int strStartsWith(const char *line, const char *prefix);
typedef enum {
CME_ERROR_NON_CME = -1,
CME_SUCCESS = 0,
CME_OPERATION_NOT_ALLOWED = 3,
CME_OPERATION_NOT_SUPPORTED = 4,
CME_PH_SIM_PIN= 5,
CME_PH_FSIM_PIN = 6,
CME_PH_FSIM_PUK = 7,
CME_SIM_NOT_INSERTED =10,
CME_SIM_PIN_REQUIRED = 11,
CME_SIM_PUK_REQUIRED = 12,
CME_FAILURE = 13,
CME_SIM_BUSY = 14,
CME_SIM_WRONG = 15,
CME_INCORRECT_PASSWORD = 16,
CME_SIM_PIN2_REQUIRED = 17,
CME_SIM_PUK2_REQUIRED = 18,
CME_MEMORY_FULL = 20,
CME_INVALID_INDEX = 21,
CME_NOT_FOUND = 22,
CME_MEMORY_FAILURE = 23,
CME_STRING_TO_LONG = 24,
CME_INVALID_CHAR = 25,
CME_DIALSTR_TO_LONG = 26,
CME_INVALID_DIALCHAR = 27,
} AT_CME_Error;
AT_CME_Error at_get_cme_error(const ATResponse *p_response);
#ifdef __cplusplus
}
#endif
#endif /*ATCHANNEL_H*/
+756
View File
@@ -0,0 +1,756 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <strings.h>
#include <stdlib.h>
#include <limits.h>
#include <linux/usbdevice_fs.h>
#include <linux/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <time.h>
#include <pthread.h>
#include "QMIThread.h"
#include "ethtool-copy.h"
#define USB_CLASS_VENDOR_SPEC 0xff
#define USB_CLASS_COMM 2
#define USB_CDC_SUBCLASS_ACM 0x02
#define USB_CDC_SUBCLASS_ETHERNET 0x06
#define USB_CDC_SUBCLASS_NCM 0x0d
#define USB_CDC_SUBCLASS_MBIM 0x0e
#define USB_CLASS_WIRELESS_CONTROLLER 0xe0
#define CM_MAX_PATHLEN 256
#define CM_INVALID_VAL (~((int)0))
static int RDNIS_MODEL = 0;
/* get first line from file 'fname'
* And convert the content into a hex number, then return this number */
static int file_get_value(const char *fname, int base)
{
FILE *fp = NULL;
long num;
char buff[32 + 1] = {'\0'};
char *endptr = NULL;
fp = fopen(fname, "r");
if (!fp) goto error;
if (fgets(buff, sizeof(buff), fp) == NULL)
goto error;
fclose(fp);
num = (int)strtol(buff, &endptr, base);
if (errno == ERANGE && (num == LONG_MAX || num == LONG_MIN))
goto error;
/* if there is no digit in buff */
if (endptr == buff)
goto error;
if (debug_qmi)
dbg_time("(%s) = %lx", fname, num);
return (int)num;
error:
if (fp) fclose(fp);
return CM_INVALID_VAL;
}
/*
* This function will search the directory 'dirname' and return the first child.
* '.' and '..' is ignored by default
*/
static int dir_get_child(const char *dirname, char *buff, unsigned bufsize, const char *prefix)
{
struct dirent *entptr = NULL;
DIR *dirptr;
buff[0] = 0;
dirptr = opendir(dirname);
if (!dirptr)
return -1;
while ((entptr = readdir(dirptr))) {
if (entptr->d_name[0] == '.')
continue;
if (prefix && strlen(prefix) && strncmp(entptr->d_name, prefix, strlen(prefix)))
continue;
snprintf(buff, bufsize, "%.31s", entptr->d_name);
break;
}
closedir(dirptr);
return 0;
}
static int conf_get_val(const char *fname, const char *key)
{
char buff[128] = {'\0'};
FILE *fp = fopen(fname, "r");
if (!fp)
return CM_INVALID_VAL;
while (fgets(buff, sizeof(buff)-1, fp)) {
char prefix[128] = {'\0'};
char tail[128] = {'\0'};
/* To eliminate cppcheck warnning: Assume string length is no more than 15 */
sscanf(buff, "%15[^=]=%15s", prefix, tail);
if (!strncasecmp(prefix, key, strlen(key))) {
fclose(fp);
return atoi(tail);
}
}
fclose(fp);
return CM_INVALID_VAL;
}
static void query_usb_device_info(char *path, struct usb_device_info *p) {
size_t offset = strlen(path);
memset(p, 0, sizeof(*p));
path[offset] = '\0';
strcat(path, "/idVendor");
p->idVendor = file_get_value(path, 16);
if (p->idVendor == CM_INVALID_VAL)
return;
path[offset] = '\0';
strcat(path, "/idProduct");
p->idProduct = file_get_value(path, 16);
path[offset] = '\0';
strcat(path, "/busnum");
p->busnum = file_get_value(path, 10);
path[offset] = '\0';
strcat(path, "/devnum");
p->devnum = file_get_value(path, 10);
path[offset] = '\0';
strcat(path, "/bNumInterfaces");
p->bNumInterfaces = file_get_value(path, 10);
path[offset] = '\0';
}
static void query_usb_interface_info(char *path, struct usb_interface_info *p) {
char driver[128];
size_t offset = strlen(path);
int n;
memset(p, 0, sizeof(*p));
path[offset] = '\0';
strcat(path, "/bNumEndpoints");
p->bInterfaceClass = file_get_value(path, 16);
path[offset] = '\0';
strcat(path, "/bInterfaceClass");
p->bInterfaceClass = file_get_value(path, 16);
path[offset] = '\0';
strcat(path, "/bInterfaceSubClass");
p->bInterfaceSubClass = file_get_value(path, 16);
path[offset] = '\0';
strcat(path, "/bInterfaceProtocol");
p->bInterfaceProtocol = file_get_value(path, 16);
path[offset] = '\0';
strcat(path, "/driver");
n = readlink(path, driver, sizeof(driver));
if (n > 0) {
driver[n] = 0;
if (debug_qmi) dbg_time("driver -> %s", driver);
n = strlen(driver);
while (n > 0) {
if (driver[n] == '/')
break;
n--;
}
strncpy(p->driver, &driver[n+1], sizeof(p->driver) - 1);
}
path[offset] = '\0';
}
static int detect_path_cdc_wdm_or_qcqmi(char *path, char *devname, size_t bufsize)
{
size_t offset = strlen(path);
char tmp[32];
devname[0] = 0;
if (access(path, R_OK))
return -1;
path[offset] = '\0';
strcat(path, "/GobiQMI");
if (!access(path, R_OK))
goto step_1;
path[offset] = '\0';
strcat(path, "/usbmisc");
if (!access(path, R_OK))
goto step_1;
path[offset] = '\0';
strcat(path, "/usb");
if (!access(path, R_OK))
goto step_1;
return -1;
step_1:
/* get device(qcqmiX|cdc-wdmX) */
if (debug_qmi) dbg_time("%s", path);
dir_get_child(path, tmp, sizeof(tmp), NULL);
if (tmp[0] == '\0')
return -1;
/* There is a chance that, no device(qcqmiX|cdc-wdmX) is generated. We should warn user about that! */
snprintf(devname, bufsize, "/dev/%s", tmp);
if (access(devname, R_OK | F_OK) && errno == ENOENT)
{
int major, minor;
dbg_time("access %s failed, errno: %d (%s)", devname, errno, strerror(errno));
strcat(path, "/");
strcat(path, tmp);
strcat(path, "/uevent");
major = conf_get_val(path, "MAJOR");
minor = conf_get_val(path, "MINOR");
if(major == CM_INVALID_VAL || minor == CM_INVALID_VAL)
dbg_time("get major and minor failed");
else if (mknod(devname, S_IFCHR|0666, (((major & 0xfff) << 8) | (minor & 0xff) | ((minor & 0xfff00) << 12))))
dbg_time("please mknod %s c %d %d", devname, major, minor);
}
return 0;
}
/* To detect the device info of the modem.
* return:
* FALSE -> fail
* TRUE -> ok
*/
BOOL qmidevice_detect(char *qmichannel, char *usbnet_adapter, unsigned bufsize, PROFILE_T *profile) {
struct dirent* ent = NULL;
DIR *pDir;
const char *rootdir = "/sys/bus/usb/devices";
struct {
char path[255*2];
} *pl;
pl = (typeof(pl)) malloc(sizeof(*pl));
memset(pl, 0x00, sizeof(*pl));
pDir = opendir(rootdir);
if (!pDir) {
dbg_time("opendir %s failed: %s", rootdir, strerror(errno));
goto error;
}
while ((ent = readdir(pDir)) != NULL) {
char netcard[32+1] = {'\0'};
char devname[32+5] = {'\0'}; //+strlen("/dev/")
int netIntf;
int driver_type;
if (ent->d_name[0] == 'u')
continue;
snprintf(pl->path, sizeof(pl->path), "%s/%s", rootdir, ent->d_name);
query_usb_device_info(pl->path, &profile->usb_dev);
if (profile->usb_dev.idVendor == CM_INVALID_VAL)
continue;
if (profile->usb_dev.idVendor == 0x2c7c || profile->usb_dev.idVendor == 0x05c6) {
dbg_time("Find %s/%s idVendor=0x%x idProduct=0x%x, bus=0x%03x, dev=0x%03x",
rootdir, ent->d_name, profile->usb_dev.idVendor, profile->usb_dev.idProduct,
profile->usb_dev.busnum, profile->usb_dev.devnum);
}
/* get network interface */
/* NOTICE: there is a case that, bNumberInterface=6, but the net interface is 8 */
/* toolchain-mips_24kc_gcc-5.4.0_musl donot support GLOB_BRACE */
/* RG500U's MBIM is at inteface 0 */
for (netIntf = 0; netIntf < (profile->usb_dev.bNumInterfaces + 8); netIntf++) {
snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d/net", rootdir, ent->d_name, netIntf);
dir_get_child(pl->path, netcard, sizeof(netcard), NULL);
if (netcard[0])
break;
}
if (netcard[0] == '\0') { //for centos 2.6.x
const char *n= "usb0";
const char *c = "qcqmi0";
snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/net:%s", rootdir, ent->d_name, n);
if (!access(pl->path, F_OK)) {
snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4/GobiQMI:%s", rootdir, ent->d_name, c);
if (!access(pl->path, F_OK)) {
snprintf(qmichannel, bufsize, "/dev/%s", c);
snprintf(usbnet_adapter, bufsize, "%s", n);
snprintf(pl->path, sizeof(pl->path), "%s/%s:1.4", rootdir, ent->d_name);
query_usb_interface_info(pl->path, &profile->usb_intf);
break;
}
}
}
if (netcard[0] == '\0')
continue;
/* not '-i iface' */
if (usbnet_adapter[0] && strcmp(usbnet_adapter, netcard))
continue;
snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d", rootdir, ent->d_name, netIntf);
query_usb_interface_info(pl->path, &profile->usb_intf);
driver_type = get_driver_type(profile);
if (driver_type == SOFTWARE_QMI || driver_type == SOFTWARE_MBIM) {
detect_path_cdc_wdm_or_qcqmi(pl->path, devname, sizeof(devname));
}
else if (driver_type == SOFTWARE_ECM_RNDIS_NCM)
{
int atIntf = -1;
if (profile->usb_dev.idVendor == 0x2c7c) { //Quectel
switch (profile->usb_dev.idProduct) { //EC200U
case 0x0901: //EC200U
case 0x0902: //EC200D
case 0x8101: //RG801H
atIntf = 2;
break;
case 0x0900: //RG500U
atIntf = 4;
break;
case 0x6026: //EC200T
case 0x6005: //EC200A
case 0x6002: //EC200S
case 0x6001: //EC100Y
atIntf = 3;
break;
case 0x6007: //EG915Q\EG800Q
if(RDNIS_MODEL == 1)
atIntf = 5;
else
atIntf = 3;
break;
default:
dbg_time("unknow at interface for USB idProduct:%04x\n", profile->usb_dev.idProduct);
break;
}
}
if (atIntf != -1) {
snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d", rootdir, ent->d_name, atIntf);
dir_get_child(pl->path, devname, sizeof(devname), "tty");
if (devname[0] && !strcmp(devname, "tty")) {
snprintf(pl->path, sizeof(pl->path), "%s/%s:1.%d/tty", rootdir, ent->d_name, atIntf);
dir_get_child(pl->path, devname, sizeof(devname), "tty");
}
}
}
if (netcard[0] && devname[0]) {
if (devname[0] == '/')
snprintf(qmichannel, bufsize, "%s", devname);
else
snprintf(qmichannel, bufsize, "/dev/%s", devname);
snprintf(usbnet_adapter, bufsize, "%s", netcard);
dbg_time("Auto find qmichannel = %s", qmichannel);
dbg_time("Auto find usbnet_adapter = %s", usbnet_adapter);
break;
}
}
closedir(pDir);
if (qmichannel[0] == '\0' || usbnet_adapter[0] == '\0') {
dbg_time("network interface '%s' or qmidev '%s' is not exist", usbnet_adapter, qmichannel);
goto error;
}
free(pl);
return TRUE;
error:
free(pl);
return FALSE;
}
int mhidevice_detect(char *qmichannel, char *usbnet_adapter, PROFILE_T *profile) {
struct dirent* ent = NULL;
DIR *pDir;
const char *rootdir_mhi[] = {"/sys/bus/mhi_q/devices", "/sys/bus/mhi/devices", NULL};
int i = 0;
char path[256];
int find = 0;
while (rootdir_mhi[i]) {
const char *rootdir = rootdir_mhi[i++];
pDir = opendir(rootdir);
if (!pDir) {
if (errno != ENOENT)
dbg_time("opendir %s failed: %s", rootdir, strerror(errno));
continue;
}
while ((ent = readdir(pDir)) != NULL) {
char netcard[32] = {'\0'};
char devname[32] = {'\0'};
int software_interface = SOFTWARE_QMI;
char *pNode = NULL;
pNode = strstr(ent->d_name, "_IP_HW0"); //0306_00.01.00_IP_HW0
if (!pNode)
continue;
snprintf(path, sizeof(path), "%s/%.32s/net", rootdir, ent->d_name);
dir_get_child(path, netcard, sizeof(netcard), NULL);
if (!netcard[0])
continue;
if (usbnet_adapter[0] && strcmp(netcard, usbnet_adapter)) //not '-i x'
continue;
if (!strcmp(rootdir, "/sys/bus/mhi/devices")) {
snprintf(path, sizeof(path), "%s/%.13s_IPCR", rootdir, ent->d_name); // 13 is sizeof(0306_00.01.00)
if (!access(path, F_OK)) {
/* we also need 'cat /dev/mhi_0306_00.01.00_pipe_14' to enable rmnet as like USB's DTR
or will get error 'requestSetEthMode QMUXResult = 0x1, QMUXError = 0x46' */
sprintf(usbnet_adapter, "%s", netcard);
sprintf(qmichannel, "qrtr-%d", 3); // 3 is sdx modem's node id
profile->software_interface = SOFTWARE_QRTR;
find = 1;
break;
}
continue;
}
snprintf(path, sizeof(path), "%s/%.13s_IPCR", rootdir, ent->d_name);
if (access(path, F_OK)) {
snprintf(path, sizeof(path), "%s/%.13s_QMI0", rootdir, ent->d_name);
if (access(path, F_OK)) {
snprintf(path, sizeof(path), "%s/%.13s_MBIM", rootdir, ent->d_name);
if (!access(path, F_OK))
software_interface = SOFTWARE_MBIM;
}
}
if (access(path, F_OK))
continue;
strncat(path, "/mhi_uci_q", sizeof(path)-1);
dir_get_child(path, devname, sizeof(devname), NULL);
if (!devname[0])
continue;
sprintf(usbnet_adapter, "%s", netcard);
sprintf(qmichannel, "/dev/%s", devname);
profile->software_interface = software_interface;
find = 1;
break;
}
closedir(pDir);
}
return find;
}
int atdevice_detect(char *atchannel, char *usbnet_adapter, PROFILE_T *profile) {
if (!access("/sys/class/net/sipa_dummy0", F_OK)) {
strcpy(usbnet_adapter, "sipa_dummy0");
snprintf(profile->qmapnet_adapter, sizeof(profile->qmapnet_adapter), "%s%d", "pcie", profile->pdp - 1);
}
else {
dbg_time("atdevice_detect failed");
goto error;
}
if (!access("/dev/stty_nr31", F_OK)) {
strcpy(atchannel, "/dev/stty_nr31");
profile->software_interface = SOFTWARE_ECM_RNDIS_NCM;
}
else {
goto error;
}
return 1;
error:
return 0;
}
int get_driver_type(PROFILE_T *profile)
{
/* QMI_WWAN */
if (profile->usb_intf.bInterfaceClass == USB_CLASS_VENDOR_SPEC) {
return SOFTWARE_QMI;
}
else if (profile->usb_intf.bInterfaceClass == USB_CLASS_COMM) {
switch (profile->usb_intf.bInterfaceSubClass) {
case USB_CDC_SUBCLASS_MBIM:
return SOFTWARE_MBIM;
break;
case USB_CDC_SUBCLASS_ETHERNET:
case USB_CDC_SUBCLASS_NCM:
return SOFTWARE_ECM_RNDIS_NCM;
break;
default:
break;
}
}
else if (profile->usb_intf.bInterfaceClass == USB_CLASS_WIRELESS_CONTROLLER) {
if (profile->usb_intf.bInterfaceSubClass == 1 && profile->usb_intf.bInterfaceProtocol == 3) {
RDNIS_MODEL = 1;//Some modules in RNDIS mode the usb port definition is different, adding a marker bit to distinguish.
return SOFTWARE_ECM_RNDIS_NCM;
}
}
dbg_time("%s unknow bInterfaceClass=%d, bInterfaceSubClass=%d", __func__,
profile->usb_intf.bInterfaceClass, profile->usb_intf.bInterfaceSubClass);
return DRV_INVALID;
}
struct usbfs_getdriver
{
unsigned int interface;
char driver[255 + 1];
};
struct usbfs_ioctl
{
int ifno; /* interface 0..N ; negative numbers reserved */
int ioctl_code; /* MUST encode size + direction of data so the
* macros in <asm/ioctl.h> give correct values */
void *data; /* param buffer (in, or out) */
};
#define IOCTL_USBFS_DISCONNECT _IO('U', 22)
#define IOCTL_USBFS_CONNECT _IO('U', 23)
int usbfs_is_kernel_driver_alive(int fd, int ifnum)
{
struct usbfs_getdriver getdrv;
getdrv.interface = ifnum;
if (ioctl(fd, USBDEVFS_GETDRIVER, &getdrv) < 0) {
dbg_time("%s ioctl USBDEVFS_GETDRIVER failed, kernel driver may be inactive", __func__);
return 0;
}
dbg_time("%s find interface %d has match the driver %s", __func__, ifnum, getdrv.driver);
return 1;
}
void usbfs_detach_kernel_driver(int fd, int ifnum)
{
struct usbfs_ioctl operate;
operate.data = NULL;
operate.ifno = ifnum;
operate.ioctl_code = IOCTL_USBFS_DISCONNECT;
if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) {
dbg_time("%s detach kernel driver failed", __func__);
} else {
dbg_time("%s detach kernel driver success", __func__);
}
}
void usbfs_attach_kernel_driver(int fd, int ifnum)
{
struct usbfs_ioctl operate;
operate.data = NULL;
operate.ifno = ifnum;
operate.ioctl_code = IOCTL_USBFS_CONNECT;
if (ioctl(fd, USBDEVFS_IOCTL, &operate) < 0) {
dbg_time("%s detach kernel driver failed", __func__);
} else {
dbg_time("%s detach kernel driver success", __func__);
}
}
int reattach_driver(PROFILE_T *profile)
{
int ifnum = 4;
int fd;
char devpath[128] = {'\0'};
snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d/%03d", profile->usb_dev.busnum, profile->usb_dev.devnum);
fd = open(devpath, O_RDWR | O_NOCTTY);
if (fd < 0)
{
dbg_time("%s fail to open %s", __func__, devpath);
return -1;
}
usbfs_detach_kernel_driver(fd, ifnum);
usbfs_attach_kernel_driver(fd, ifnum);
close(fd);
return 0;
}
#define SIOCETHTOOL 0x8946
int ql_get_netcard_driver_info(const char *devname)
{
int fd = -1;
struct ethtool_drvinfo drvinfo;
struct ifreq ifr; /* ifreq suitable for ethtool ioctl */
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, devname);
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
dbg_time("Cannot get control socket: errno(%d)(%s)", errno, strerror(errno));
return -1;
}
drvinfo.cmd = ETHTOOL_GDRVINFO;
ifr.ifr_data = (void *)&drvinfo;
if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) {
dbg_time("ioctl() error: errno(%d)(%s)", errno, strerror(errno));
close(fd);
return -1;
}
dbg_time("netcard driver = %s, driver version = %s", drvinfo.driver, drvinfo.version);
close(fd);
return 0;
}
int ql_get_netcard_carrier_state(const char *devname)
{
int fd = -1;
struct ethtool_value edata;
struct ifreq ifr; /* ifreq suitable for ethtool ioctl */
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, devname);
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
dbg_time("Cannot get control socket: errno(%d)(%s)", errno, strerror(errno));
return -1;
}
edata.cmd = ETHTOOL_GLINK;
edata.data = 0;
ifr.ifr_data = (void *)&edata;
if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) {
dbg_time("ioctl('%s') error: errno(%d)(%s)", devname, errno, strerror(errno));
return -1;
}
if (!edata.data)
dbg_time("netcard carrier = %d", edata.data);
close(fd);
return edata.data;
}
static void *catch_log(void *arg)
{
PROFILE_T *profile = (PROFILE_T *)arg;
int nreads = 0;
char tbuff[256+32];
char filter[32];
size_t tsize = strlen(get_time()) + 1;
snprintf(filter, sizeof(filter), ":%d:%03d:", profile->usb_dev.busnum, profile->usb_dev.devnum);
while(1) {
nreads = read(profile->usbmon_fd, tbuff + tsize, sizeof(tbuff) - tsize - 1);
if (nreads <= 0) {
if (nreads == -1 && errno == EINTR)
continue;
break;
}
tbuff[tsize+nreads] = '\0'; // printf("%s", buff);
if (!strstr(tbuff+tsize, filter))
continue;
snprintf(tbuff, sizeof(tbuff), "%s", get_time());
tbuff[tsize-1] = ' ';
fwrite(tbuff, strlen(tbuff), 1, profile->usbmon_logfile_fp);
}
return NULL;
}
int ql_capture_usbmon_log(PROFILE_T *profile, const char *log_path)
{
char usbmon_path[256];
pthread_t pt;
pthread_attr_t attr;
if (access("/sys/module/usbmon", F_OK)) {
dbg_time("usbmon is not load, please execute \"modprobe usbmon\" or \"insmod usbmon.ko\"");
return -1;
}
if (access("/sys/kernel/debug/usb", F_OK)) {
dbg_time("debugfs is not mount, please execute \"mount -t debugfs none_debugs /sys/kernel/debug\"");
return -1;
}
snprintf(usbmon_path, sizeof(usbmon_path), "/sys/kernel/debug/usb/usbmon/%du", profile->usb_dev.busnum);
profile->usbmon_fd = open(usbmon_path, O_RDONLY);
if (profile->usbmon_fd < 0) {
dbg_time("open %s error(%d) (%s)", usbmon_path, errno, strerror(errno));
return -1;
}
snprintf(usbmon_path, sizeof(usbmon_path), "cat /sys/kernel/debug/usb/devices >> %s", log_path);
if (system(usbmon_path) == -1) {};
profile->usbmon_logfile_fp = fopen(log_path, "wb");
if (!profile->usbmon_logfile_fp) {
dbg_time("open %s error(%d) (%s)", log_path, errno, strerror(errno));
close(profile->usbmon_fd);
profile->usbmon_fd = -1;
return -1;
}
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&pt, &attr, catch_log, (void *)profile);
return 0;
}
void ql_stop_usbmon_log(PROFILE_T *profile) {
if (profile->usbmon_fd > 0)
close(profile->usbmon_fd);
if (profile->usbmon_logfile_fp)
fclose(profile->usbmon_logfile_fp);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+63
View File
@@ -0,0 +1,63 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#ifndef __QUECTEL_ENDIAN_H__
#define __QUECTEL_ENDIAN_H__
#include <endian.h>
#ifndef htole32
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define htole16(x) (uint16_t)(x)
#define le16toh(x) (uint16_t)(x)
#define letoh16(x) (uint16_t)(x)
#define htole32(x) (uint32_t)(x)
#define le32toh(x) (uint32_t)(x)
#define letoh32(x) (uint32_t)(x)
#define htole64(x) (uint64_t)(x)
#define le64toh(x) (uint64_t)(x)
#define letoh64(x) (uint64_t)(x)
#else
static __inline uint16_t __bswap16(uint16_t __x) {
return (__x<<8) | (__x>>8);
}
static __inline uint32_t __bswap32(uint32_t __x) {
return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24);
}
static __inline uint64_t __bswap64(uint64_t __x) {
return (__bswap32(__x)+0ULL<<32) | (__bswap32(__x>>32));
}
#define htole16(x) __bswap16(x)
#define le16toh(x) __bswap16(x)
#define letoh16(x) __bswap16(x)
#define htole32(x) __bswap32(x)
#define le32toh(x) __bswap32(x)
#define letoh32(x) __bswap32(x)
#define htole64(x) __bswap64(x)
#define le64toh(x) __bswap64(x)
#define letoh64(x) __bswap64(x)
#endif
#endif
#define le16_to_cpu(x) le16toh((uint16_t)(x))
#define le32_to_cpu(x) le32toh((uint32_t)(x))
#define le64_to_cpu(x) le64toh((uint64_t)(x))
#define cpu_to_le16(x) htole16((uint16_t)(x))
#define cpu_to_le32(x) htole32((uint32_t)(x))
#define cpu_to_le64(x) htole64((uint64_t)(x))
static __inline uint32_t ql_swap32(uint32_t __x) {
return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24);
}
#endif //__QUECTEL_ENDIAN_H__
+49
View File
@@ -0,0 +1,49 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#ifndef __QUECTEL_LIST_H__
#define __QUECTEL_LIST_H__
struct qlistnode
{
struct qlistnode *next;
struct qlistnode *prev;
};
#define qnode_to_item(node, container, member) \
(container *) (((char*) (node)) - offsetof(container, member))
#define qlist_for_each(node, list) \
for (node = (list)->next; node != (list); node = node->next)
#define qlist_empty(list) ((list) == (list)->next)
#define qlist_head(list) ((list)->next)
#define qlist_tail(list) ((list)->prev)
static void qlist_init(struct qlistnode *node)
{
node->next = node;
node->prev = node;
}
static void qlist_add_tail(struct qlistnode *head, struct qlistnode *item)
{
item->next = head;
item->prev = head->prev;
head->prev->next = item;
head->prev = item;
}
static void qlist_remove(struct qlistnode *item)
{
item->next->prev = item->prev;
item->prev->next = item->next;
}
#endif
+455
View File
@@ -0,0 +1,455 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include "QMIThread.h"
static size_t ql_fread(const char *filename, void *buf, size_t size) {
FILE *fp = fopen(filename , "r");
size_t n = 0;
memset(buf, 0x00, size);
if (fp) {
n = fread(buf, 1, size, fp);
if (n <= 0 || n == size) {
dbg_time("warnning: fail to fread(%s), fread=%zu, buf_size=%zu: (%s)", filename, n, size, strerror(errno));
}
fclose(fp);
}
return n > 0 ? n : 0;
}
static size_t ql_fwrite(const char *filename, const void *buf, size_t size) {
FILE *fp = fopen(filename , "w");
size_t n = 0;
if (fp) {
n = fwrite(buf, 1, size, fp);
if (n != size) {
dbg_time("warnning: fail to fwrite(%s), fwrite=%zu, buf_size=%zu: (%s)", filename, n, size, strerror(errno));
}
fclose(fp);
}
return n > 0 ? n : 0;
}
int ql_bridge_mode_detect(PROFILE_T *profile) {
const char *ifname = profile->qmapnet_adapter[0] ? profile->qmapnet_adapter : profile->usbnet_adapter;
const char *driver;
char bridge_mode[128];
char bridge_ipv4[128];
char ipv4[128];
char buf[64];
size_t n;
int in_bridge = 0;
driver = profile->driver_name;
snprintf(bridge_mode, sizeof(bridge_mode), "/sys/class/net/%s/bridge_mode", ifname);
snprintf(bridge_ipv4, sizeof(bridge_ipv4), "/sys/class/net/%s/bridge_ipv4", ifname);
if (access(bridge_ipv4, R_OK)) {
if (errno != ENOENT) {
dbg_time("fail to access %s, errno: %d (%s)", bridge_mode, errno, strerror(errno));
return 0;
}
snprintf(bridge_mode, sizeof(bridge_mode), "/sys/module/%s/parameters/bridge_mode", driver);
snprintf(bridge_ipv4, sizeof(bridge_ipv4), "/sys/module/%s/parameters/bridge_ipv4", driver);
if (access(bridge_mode, R_OK)) {
if (errno != ENOENT) {
dbg_time("fail to access %s, errno: %d (%s)", bridge_mode, errno, strerror(errno));
}
return 0;
}
}
n = ql_fread(bridge_mode, buf, sizeof(buf));
if (n > 0) {
in_bridge = (buf[0] != '0');
}
if (!in_bridge)
return 0;
memset(ipv4, 0, sizeof(ipv4));
if (strstr(bridge_ipv4, "/sys/class/net/") || profile->qmap_mode == 0 || profile->qmap_mode == 1) {
snprintf(ipv4, sizeof(ipv4), "0x%x", profile->ipv4.Address);
dbg_time("echo '%s' > %s", ipv4, bridge_ipv4);
ql_fwrite(bridge_ipv4, ipv4, strlen(ipv4));
}
else {
snprintf(ipv4, sizeof(ipv4), "0x%x:%d", profile->ipv4.Address, profile->muxid);
dbg_time("echo '%s' > %s", ipv4, bridge_ipv4);
ql_fwrite(bridge_ipv4, ipv4, strlen(ipv4));
}
return in_bridge;
}
int ql_enable_qmi_wwan_rawip_mode(PROFILE_T *profile) {
char filename[256];
char buf[4];
size_t n;
FILE *fp;
if (!qmidev_is_qmiwwan(profile->qmichannel))
return 0;
snprintf(filename, sizeof(filename), "/sys/class/net/%s/qmi/rawip", profile->usbnet_adapter);
n = ql_fread(filename, buf, sizeof(buf));
if (n == 0)
return 0;
if (buf[0] == '1' || buf[0] == 'Y')
return 0;
fp = fopen(filename , "w");
if (fp == NULL) {
dbg_time("Fail to fopen(%s, \"w\"), errno: %d (%s)", filename, errno, strerror(errno));
return 1;
}
buf[0] = 'Y';
n = fwrite(buf, 1, 1, fp);
if (n != 1) {
dbg_time("Fail to fwrite(%s), errno: %d (%s)", filename, errno, strerror(errno));
fclose(fp);
return 1;
}
fclose(fp);
return 0;
}
int ql_driver_type_detect(PROFILE_T *profile) {
if (qmidev_is_gobinet(profile->qmichannel)) {
profile->qmi_ops = &gobi_qmidev_ops;
}
else {
profile->qmi_ops = &qmiwwan_qmidev_ops;
}
qmidev_send = profile->qmi_ops->send;
return 0;
}
static void ql_set_driver_bridge_mode(PROFILE_T *profile) {
char enable[16];
char filename[256];
if(profile->qmap_mode)
snprintf(filename, sizeof(filename), "/sys/class/net/%s/bridge_mode", profile->qmapnet_adapter);
else
snprintf(filename, sizeof(filename), "/sys/class/net/%s/bridge_mode", profile->usbnet_adapter);
snprintf(enable, sizeof(enable), "%02d\n", profile->enable_bridge);
ql_fwrite(filename, enable, sizeof(enable));
}
#ifdef CONFIG_QRTR
static int ql_create_or_detect_rmnet_data(PROFILE_T *profile) {
char tmp[128];
int muxid = 0, qmap_version = 0;
if (access("/sys/module/rmnet_data", F_OK) != 0 /* spf11.x */
&& access("/sys/module/rmnet_core", F_OK) != 0 /* spf12.x */) {
dbg_time("rmnet_data/rmnet_core driver is not support!\n");
return -ENOENT;
}
snprintf(tmp, sizeof(tmp), "/sys/class/net/%s", profile->qmapnet_adapter);
if (access(tmp, F_OK) && errno == ENOENT) {
uint32_t ul_agg_cnt = 0, ul_agg_size = 0;
if (profile->hardware_interface == HARDWARE_USB) {
ul_agg_cnt = 11;
ul_agg_size = 4096; //sdx modem may only support 4096
}
rtrmnet_ctl_new_vnd(profile->usbnet_adapter, profile->qmapnet_adapter,
profile->muxid, profile->qmap_version, ul_agg_cnt, ul_agg_size);
}
if (access(tmp, F_OK) && errno == ENOENT) {
dbg_time("Fail to detect %s", profile->qmapnet_adapter);
return -1;
}
rtrmnet_ctl_get_vnd(profile->qmapnet_adapter, &muxid, &qmap_version);
if (qmap_version != profile->qmap_version || muxid != profile->muxid) {
dbg_time("wrong qmap_version: %d - %d, muxid: %d - %d",
qmap_version, profile->qmap_version, muxid, profile->muxid);
return -1;
}
return 0;
}
#endif
static int ql_qmi_qmap_mode_detect(PROFILE_T *profile) {
char buf[128];
int n;
struct {
char filename[255 * 2];
char linkname[255 * 2];
} *pl;
pl = (typeof(pl)) malloc(sizeof(*pl));
snprintf(pl->linkname, sizeof(pl->linkname), "/sys/class/net/%s/device/driver", profile->usbnet_adapter);
n = readlink(pl->linkname, pl->filename, sizeof(pl->filename));
pl->filename[n] = '\0';
while (pl->filename[n] != '/')
n--;
strncpy(profile->driver_name, &pl->filename[n+1], sizeof(profile->driver_name) - 1);
ql_get_driver_rmnet_info(profile, &profile->rmnet_info);
if (profile->rmnet_info.size) {
profile->qmap_mode = profile->rmnet_info.qmap_mode;
if (profile->qmap_mode) {
int offset_id = (profile->muxid == 0)? profile->pdp - 1 : profile->muxid - 0x81;
if (profile->qmap_mode == 1)
offset_id = 0;
profile->muxid = profile->rmnet_info.mux_id[offset_id];
strncpy(profile->qmapnet_adapter, profile->rmnet_info.ifname[offset_id], sizeof(profile->qmapnet_adapter) - 1);
profile->qmap_size = profile->rmnet_info.rx_urb_size;
profile->qmap_version = profile->rmnet_info.qmap_version;
}
#ifdef CONFIG_QRTR
if (!strcmp(profile->qmapnet_adapter, "use_rmnet_data")) {
snprintf(profile->qmapnet_adapter, sizeof(profile->qmapnet_adapter), "%.16s_%d",
profile->usbnet_adapter, profile->pdp);
ql_create_or_detect_rmnet_data(profile);
}
#endif
goto _out;
}
snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/qmap_mode", profile->usbnet_adapter);
if (access(pl->filename, R_OK)) {
if (errno != ENOENT) {
dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno));
goto _out;
}
snprintf(pl->filename, sizeof(pl->filename), "/sys/module/%s/parameters/qmap_mode", profile->driver_name);
if (access(pl->filename, R_OK)) {
if (errno != ENOENT) {
dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno));
goto _out;
}
snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/device/driver/module/parameters/qmap_mode", profile->usbnet_adapter);
if (access(pl->filename, R_OK)) {
if (errno != ENOENT) {
dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno));
goto _out;
}
}
}
}
if (!access(pl->filename, R_OK)) {
n = ql_fread(pl->filename, buf, sizeof(buf));
if (n > 0) {
profile->qmap_mode = atoi(buf);
if (profile->qmap_mode > 1) {
if(!profile->muxid)
profile->muxid = profile->pdp + 0x80; //muxis is 0x8X for PDN-X
snprintf(profile->qmapnet_adapter, sizeof(profile->qmapnet_adapter),
"%.16s.%d", profile->usbnet_adapter, profile->muxid - 0x80);
} if (profile->qmap_mode == 1) {
profile->muxid = 0x81;
strncpy(profile->qmapnet_adapter, profile->usbnet_adapter, sizeof(profile->qmapnet_adapter));
}
}
}
else if (qmidev_is_qmiwwan(profile->qmichannel)) {
snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/qmimux%d", profile->pdp - 1);
if (access(pl->filename, R_OK)) {
if (errno != ENOENT) {
dbg_time("fail to access %s, errno: %d (%s)", pl->filename, errno, strerror(errno));
}
goto _out;
}
//upstream Kernel Style QMAP qmi_wwan.c
snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/qmi/add_mux", profile->usbnet_adapter);
n = ql_fread(pl->filename, buf, sizeof(buf));
if (n >= 5) {
dbg_time("If use QMAP by /sys/class/net/%s/qmi/add_mux", profile->usbnet_adapter);
#if 1
dbg_time("Please set mtu of wwan0 >= max dl qmap packet size");
#else
dbg_time("File:%s Line:%d Please make sure add next patch to qmi_wwan.c", __func__, __LINE__);
/*
diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
index 74bebbd..db8a777 100644
--- a/drivers/net/usb/qmi_wwan.c
+++ b/drivers/net/usb/qmi_wwan.c
@@ -379,6 +379,24 @@ static ssize_t add_mux_store(struct device *d, struct device_attribute *attr, c
if (!ret) {
info->flags |= QMI_WWAN_FLAG_MUX;
ret = len;
+#if 1 //Add by Quectel
+ if (le16_to_cpu(dev->udev->descriptor.idVendor) == 0x2c7c) {
+ int idProduct = le16_to_cpu(dev->udev->descriptor.idProduct);
+
+ if (idProduct == 0x0121 || idProduct == 0x0125 || idProduct == 0x0435) //MDM9x07
+ dev->rx_urb_size = 4*1024;
+ else if (idProduct == 0x0306) //MDM9x40
+ dev->rx_urb_size = 16*1024;
+ else if (idProduct == 0x0512) //SDX20
+ dev->rx_urb_size = 32*1024;
+ else if (idProduct == 0x0620) //SDX24
+ dev->rx_urb_size = 32*1024;
+ else if (idProduct == 0x0800) //SDX55
+ dev->rx_urb_size = 32*1024;
+ else
+ dev->rx_urb_size = 32*1024;
+ }
+#endif
}
err:
rtnl_unlock();
*/
#endif
profile->qmap_mode = n/5; //0x11\n0x22\n0x33\n
if (profile->qmap_mode > 1) {
//PDN-X map to qmimux-X
if(!profile->muxid) {
profile->muxid = (buf[5*(profile->pdp - 1) + 2] - '0')*16 + (buf[5*(profile->pdp - 1) + 3] - '0');
snprintf(profile->qmapnet_adapter, sizeof(profile->qmapnet_adapter), "qmimux%d", profile->pdp - 1);
} else {
profile->muxid = (buf[5*(profile->muxid - 0x81) + 2] - '0')*16 + (buf[5*(profile->muxid - 0x81) + 3] - '0');
snprintf(profile->qmapnet_adapter, sizeof(profile->qmapnet_adapter), "qmimux%d", profile->muxid - 0x81);
}
} else if (profile->qmap_mode == 1) {
profile->muxid = (buf[5*0 + 2] - '0')*16 + (buf[5*0 + 3] - '0');
snprintf(profile->qmapnet_adapter, sizeof(profile->qmapnet_adapter),
"qmimux%d", 0);
}
}
}
_out:
if (profile->qmap_mode) {
if (profile->qmap_size == 0) {
profile->qmap_size = 16*1024;
snprintf(pl->filename, sizeof(pl->filename), "/sys/class/net/%s/qmap_size", profile->usbnet_adapter);
if (!access(pl->filename, R_OK)) {
size_t n;
char buf[32];
n = ql_fread(pl->filename, buf, sizeof(buf));
if (n > 0) {
profile->qmap_size = atoi(buf);
}
}
}
if (profile->qmap_version == 0) {
profile->qmap_version = WDA_DL_DATA_AGG_QMAP_ENABLED;
}
dbg_time("qmap_mode = %d, qmap_version = %d, qmap_size = %d, muxid = 0x%02x, qmap_netcard = %s",
profile->qmap_mode, profile->qmap_version, profile->qmap_size, profile->muxid, profile->qmapnet_adapter);
}
ql_set_driver_bridge_mode(profile);
free(pl);
return 0;
}
static int ql_mbim_usb_vlan_mode_detect(PROFILE_T *profile) {
char tmp[128];
snprintf(tmp, sizeof(tmp), "/sys/class/net/%s.%d", profile->usbnet_adapter, profile->pdp);
if (!access(tmp, F_OK)) {
profile->qmap_mode = 4;
profile->muxid = profile->pdp;
no_trunc_strncpy(profile->qmapnet_adapter, tmp + strlen("/sys/class/net/"), sizeof(profile->qmapnet_adapter) - 1);
dbg_time("mbim_qmap_mode = %d, vlan_id = 0x%02x, qmap_netcard = %s",
profile->qmap_mode, profile->muxid, profile->qmapnet_adapter);
}
return 0;
}
static int ql_mbim_mhi_qmap_mode_detect(PROFILE_T *profile) {
ql_get_driver_rmnet_info(profile, &profile->rmnet_info);
if (profile->rmnet_info.size) {
profile->qmap_mode = profile->rmnet_info.qmap_mode;
if (profile->qmap_mode) {
int offset_id = profile->pdp - 1;
if (profile->qmap_mode == 1)
offset_id = 0;
profile->muxid = profile->pdp;
strcpy(profile->qmapnet_adapter, profile->rmnet_info.ifname[offset_id]);
profile->qmap_size = profile->rmnet_info.rx_urb_size;
profile->qmap_version = profile->rmnet_info.qmap_version;
dbg_time("mbim_qmap_mode = %d, vlan_id = 0x%02x, qmap_netcard = %s",
profile->qmap_mode, profile->muxid, profile->qmapnet_adapter);
}
goto _out;
}
_out:
return 0;
}
int ql_qmap_mode_detect(PROFILE_T *profile) {
if (profile->software_interface == SOFTWARE_MBIM) {
if (profile->hardware_interface == HARDWARE_USB)
return ql_mbim_usb_vlan_mode_detect(profile);
else if (profile->hardware_interface == HARDWARE_PCIE)
return ql_mbim_mhi_qmap_mode_detect(profile);
} else if (profile->software_interface == SOFTWARE_QMI) {
return ql_qmi_qmap_mode_detect(profile);
}
#ifdef CONFIG_QRTR
else if (profile->software_interface == SOFTWARE_QRTR) {
profile->qmap_mode = 1;
profile->qmap_version = WDA_DL_DATA_AGG_QMAP_V5_ENABLED;
profile->qmap_size = 15*1024;
profile->muxid = 0x80 | profile->pdp;
if (!strcmp(profile->driver_name, "mhi_netdev")) {
char linkname[64];
char filename[128];
int n;
snprintf(linkname, sizeof(linkname), "/sys/class/net/%s/device", profile->usbnet_adapter);
n = readlink(linkname, filename, sizeof(filename));
if (n > 0) {
//../../../0308_00.01.00_IP_HW0
filename[n] = '\0';
if (strstr(filename, "_IP_HW0") && strstr(filename+n, "0303_")) //EG18
profile->qmap_version = WDA_DL_DATA_AGG_QMAP_ENABLED;
}
}
snprintf(profile->qmapnet_adapter, sizeof(profile->qmapnet_adapter), "rmnet_data%d",
profile->pdp);
return ql_create_or_detect_rmnet_data(profile);
}
#endif
return 0;
}
+652
View File
@@ -0,0 +1,652 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <stdio.h>
#include <ctype.h>
#include "QMIThread.h"
typedef struct {
uint32_t service;
uint32_t version;
uint32_t instance;
uint32_t node;
uint32_t port;
} QrtrService;
#define QRTR_MAX (QMUX_TYPE_WDS_ADMIN + 1)
static QrtrService service_list[QRTR_MAX];
static int qmiclientId[QRTR_MAX];
static int get_client(UCHAR QMIType);
static uint32_t node_modem = 3; //IPQ ~ 3, QCM ~ 0
#ifdef USE_LINUX_MSM_IPC
#include <linux/msm_ipc.h>
struct xport_ipc_router_server_addr {
uint32_t service;
uint32_t instance;
uint32_t node_id;
uint32_t port_id;
};
union ctl_msg {
uint32_t cmd;
struct {
uint32_t cmd;
uint32_t service;
uint32_t instance;
uint32_t node_id;
uint32_t port_id;
} srv;
struct {
uint32_t cmd;
uint32_t node_id;
uint32_t port_id;
} cli;
};
#define CTL_CMD_NEW_SERVER 4
#define CTL_CMD_REMOVE_SERVER 5
#define VERSION_MASK 0xff
#define GET_VERSION(x) (x & 0xff)
#define GET_XPORT_SVC_INSTANCE(x) GET_VERSION(x)
#define GET_INSTANCE(x) ((x & 0xff00) >> 8)
static int msm_ipc_socket(const char *name)
{
int sock;
int flags;
sock = socket(AF_MSM_IPC, SOCK_DGRAM, 0);
if (sock < 0) {
dbg_time("%s(%s) errno: %d (%s)\n", __func__, name, errno, strerror(errno));
return -1;
}
fcntl(sock, F_SETFD, FD_CLOEXEC);
flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
return sock;
}
static uint32_t xport_lookup
(
int lookup_sock_fd,
uint32_t service_id,
uint32_t version
)
{
uint32_t num_servers_found = 0;
uint32_t num_entries_to_fill = 4;
struct server_lookup_args *lookup_arg;
int i;
lookup_arg = (struct server_lookup_args *)malloc(sizeof(*lookup_arg)
+ (num_entries_to_fill * sizeof(struct msm_ipc_server_info)));
if (!lookup_arg)
{
dbg_time("%s: Malloc failed\n", __func__);
return 0;
}
lookup_arg->port_name.service = service_id;
lookup_arg->port_name.instance = GET_XPORT_SVC_INSTANCE(version);
lookup_arg->num_entries_in_array = num_entries_to_fill;
lookup_arg->lookup_mask = VERSION_MASK;
lookup_arg->num_entries_found = 0;
if (ioctl(lookup_sock_fd, IPC_ROUTER_IOCTL_LOOKUP_SERVER, lookup_arg) < 0)
{
dbg_time("%s: Lookup failed for %08x: %08x\n", __func__, service_id, version);
free(lookup_arg);
return 0;
}
dbg_time("%s: num_entries_found %d for type=%d instance=%d", __func__,
lookup_arg->num_entries_found, service_id, version);
num_servers_found = 0;
for (i = 0; ((i < (int)num_entries_to_fill) && (i < lookup_arg->num_entries_found)); i++)
{
QrtrService service_info[1];
if (lookup_arg->srv_info[i].node_id != node_modem)
continue;
num_servers_found++;
service_info[0].service = lookup_arg->srv_info[i].service;
service_info[0].version = GET_VERSION(lookup_arg->srv_info[i].instance);
service_info[0].instance = GET_INSTANCE(lookup_arg->srv_info[i].instance);
service_info[0].node = lookup_arg->srv_info[i].node_id;
service_info[0].port = lookup_arg->srv_info[i].port_id;
service_list[service_id] = service_info[0];
qmiclientId[service_id] = get_client(service_id);
}
free(lookup_arg);
return num_servers_found;
}
static int xport_send(int sock, uint32_t node, uint32_t port, const void *data, unsigned int sz)
{
struct sockaddr_msm_ipc addr = {};
int rc;
addr.family = AF_MSM_IPC;
addr.address.addrtype = MSM_IPC_ADDR_ID;
addr.address.addr.port_addr.node_id = node;
addr.address.addr.port_addr.port_id = port;
rc = sendto(sock, data, sz, MSG_DONTWAIT, (void *)&addr, sizeof(addr));
if (rc < 0) {
dbg_time("xport_send errno: %d (%s)\n", errno, strerror(errno));
return -1;
}
return 0;
}
static int xport_recv(int sock, void *data, unsigned int sz, uint32_t *node, uint32_t *port)
{
struct sockaddr_msm_ipc addr = {};
socklen_t addr_size = sizeof(struct sockaddr_msm_ipc);
int rc;
rc = recvfrom(sock, data, sz, MSG_DONTWAIT, (void *)&addr, &addr_size);
if (rc < 0) {
dbg_time("xport_recv errno: %d (%s)\n", errno, strerror(errno));
}
else if (addr.address.addrtype != MSM_IPC_ADDR_ID) {
dbg_time("xport_recv addrtype is NOT MSM_IPC_ADDR_ID\n");
rc = -1;
}
*node = addr.address.addr.port_addr.node_id;
*port = addr.address.addr.port_addr.port_id;
return rc;
}
#define qmi_recv xport_recv
static int xport_ctrl_init(void)
{
int ctrl_sock;
int rc;
uint32_t instance = 1; //modem
uint32_t version;
ctrl_sock = msm_ipc_socket("ctrl_port");
if (ctrl_sock == -1)
return -1;
rc = ioctl(ctrl_sock, IPC_ROUTER_IOCTL_GET_VERSION, &version);
if (rc < 0) {
dbg_time("%s: failed to get ipc version\n", __func__);
goto init_close_ctrl_fd;
}
dbg_time("%s ipc_version = %d", __func__, version);
rc = ioctl(ctrl_sock, IPC_ROUTER_IOCTL_BIND_CONTROL_PORT, NULL);
if (rc < 0) {
dbg_time("%s: failed to bind as control port\n", __func__);
goto init_close_ctrl_fd;
}
//cat /sys/kernel/debug/msm_ipc_router/dump_servers
rc = 0;
rc += xport_lookup(ctrl_sock, QMUX_TYPE_WDS, instance);
if (service_list[QMUX_TYPE_WDS].port) {
qmiclientId[QMUX_TYPE_WDS_IPV6] = get_client(QMUX_TYPE_WDS);
}
rc += xport_lookup(ctrl_sock, QMUX_TYPE_NAS, instance);
rc += xport_lookup(ctrl_sock, QMUX_TYPE_UIM, instance);
rc += xport_lookup(ctrl_sock, QMUX_TYPE_DMS, instance);
rc += xport_lookup(ctrl_sock, QMUX_TYPE_WDS_ADMIN, instance);
if (rc == 0) {
dbg_time("%s: failed to lookup qmi service\n", __func__);
goto init_close_ctrl_fd;
}
return ctrl_sock;
init_close_ctrl_fd:
close(ctrl_sock);
return -1;
}
static void handle_ctrl_pkt(int sock) {
union ctl_msg pkt;
uint32_t type;
int rc;
rc = recvfrom(sock, &pkt, sizeof(pkt), 0, NULL, NULL);
if (rc < 0)
return;
type = le32toh(pkt.cmd);
if (CTL_CMD_NEW_SERVER == type || CTL_CMD_REMOVE_SERVER == type) {
QrtrService s;
s.service = le32toh(pkt.srv.service);
s.version = le32toh(pkt.srv.instance) & 0xff;
s.instance = le32toh(pkt.srv.instance) >> 8;
s.node = le32toh(pkt.srv.node_id);
s.port = le32toh(pkt.srv.port_id);
if (debug_qmi)
dbg_time ("[qrtr] %s server on %u:%u -> service %u, version %u, instance %u",
CTL_CMD_NEW_SERVER == type ? "add" : "remove",
s.node, s.port, s.service, s.version, s.instance);
if (CTL_CMD_NEW_SERVER == type) {
if (s.service < QRTR_MAX) {
service_list[s.service] = s;
}
}
else if (CTL_CMD_REMOVE_SERVER == type) {
if (s.service < QRTR_MAX) {
memset(&service_list[s.service], 0, sizeof(QrtrService));
}
}
}
}
#else
#include <linux/socket.h>
#include <linux/qrtr.h>
#endif
static int qrtr_socket(void)
{
struct sockaddr_qrtr sq;
socklen_t sl = sizeof(sq);
int sock;
int rc;
sock = socket(AF_QIPCRTR, SOCK_DGRAM, 0);
if (sock < 0) {
dbg_time("qrtr_socket errno: %d (%s)\n", errno, strerror(errno));
return -1;
}
rc = getsockname(sock, (void *)&sq, &sl);
if (rc || sq.sq_family != AF_QIPCRTR || sl != sizeof(sq)) {
dbg_time("getsockname: %d (%s)\n", errno, strerror(errno));
close(sock);
return -1;
}
return sock;
}
static int qrtr_send(int sock, uint32_t node, uint32_t port, const void *data, unsigned int sz)
{
struct sockaddr_qrtr sq;
int rc;
sq.sq_family = AF_QIPCRTR;
sq.sq_node = node;
sq.sq_port = port;
rc = sendto(sock, data, sz, MSG_DONTWAIT, (void *)&sq, sizeof(sq));
if (rc < 0) {
dbg_time("sendto errno: %d (%s)\n", errno, strerror(errno));
return -1;
}
return 0;
}
static int qrtr_recv(int sock, void *data, unsigned int sz, uint32_t *node, uint32_t *port)
{
struct sockaddr_qrtr sq;
socklen_t sl = sizeof(sq);
int rc;
rc = recvfrom(sock, data, sz, MSG_DONTWAIT, (void *)&sq, &sl);
if (rc < 0) {
dbg_time("qrtr_recv errno: %d (%s)\n", errno, strerror(errno));
}
*node = sq.sq_node;
*port = sq.sq_port;
return rc;
}
#define qmi_recv qrtr_recv
static int qrtr_ctrl_init(void) {
int sock;
int rc;
struct qrtr_ctrl_pkt pkt;
struct sockaddr_qrtr sq;
socklen_t sl = sizeof(sq);
sock = qrtr_socket();
if (sock == -1)
return -1;
memset(&pkt, 0, sizeof(pkt));
pkt.cmd = htole32(QRTR_TYPE_NEW_LOOKUP);
getsockname(sock, (void *)&sq, &sl);
rc = qrtr_send(sock, sq.sq_node, QRTR_PORT_CTRL, &pkt, sizeof(pkt));
if (rc == -1) {
dbg_time("qrtr_send errno: %d (%s)\n", errno, strerror(errno));
close(sock);
return -1;
}
return sock;
}
static void handle_server_change(uint32_t type, struct qrtr_ctrl_pkt *ppkt) {
struct qrtr_ctrl_pkt pkt = *ppkt;
QrtrService s;
s.service = le32toh(pkt.server.service);
s.version = le32toh(pkt.server.instance) & 0xff;
s.instance = le32toh(pkt.server.instance) >> 8;
s.node = le32toh(pkt.server.node);
s.port = le32toh(pkt.server.port);
if (debug_qmi)
dbg_time ("[qrtr] %s server on %u:%u -> service %u, version %u, instance %u",
QRTR_TYPE_NEW_SERVER == type ? "add" : "remove",
s.node, s.port, s.service, s.version, s.instance);
if (s.node != node_modem)
return; //we only care modem
if (QRTR_TYPE_NEW_SERVER == type) {
if (s.service < QRTR_MAX) {
service_list[s.service] = s;
}
}
else if (QRTR_TYPE_DEL_SERVER == type) {
if (s.service < QRTR_MAX) {
memset(&service_list[s.service], 0, sizeof(QrtrService));
}
}
}
static void handle_ctrl_pkt(int sock) {
struct qrtr_ctrl_pkt pkt;
struct sockaddr_qrtr sq;
socklen_t sl = sizeof(sq);
uint32_t type;
int rc;
rc = recvfrom(sock, &pkt, sizeof(pkt), 0, (void *)&sq, &sl);
if (rc < 0)
return;
type = le32toh(pkt.cmd);
if (debug_qmi)
dbg_time("type %u, node %u, sq.port %x, len: %d", type, sq.sq_node, sq.sq_port, rc);
if (sq.sq_port != QRTR_PORT_CTRL)
return;
if (QRTR_TYPE_NEW_SERVER == type || QRTR_TYPE_DEL_SERVER == type) {
handle_server_change(type, &pkt);
}
}
static int get_client(UCHAR QMIType) {
int ClientId;
QrtrService *s = &service_list[QMIType];
if (!s ->service) {
dbg_time("%s service: %d for QMIType: %d", __func__, s ->service, QMIType);
return -ENODEV;
}
#ifdef USE_LINUX_MSM_IPC
ClientId = msm_ipc_socket("xport");
#else
ClientId = qrtr_socket();
#endif
if (ClientId == -1) {
return 0;
}
switch (QMIType) {
case QMUX_TYPE_WDS: dbg_time("Get clientWDS = %d", ClientId); break;
case QMUX_TYPE_DMS: dbg_time("Get clientDMS = %d", ClientId); break;
case QMUX_TYPE_NAS: dbg_time("Get clientNAS = %d", ClientId); break;
case QMUX_TYPE_QOS: dbg_time("Get clientQOS = %d", ClientId); break;
case QMUX_TYPE_WMS: dbg_time("Get clientWMS = %d", ClientId); break;
case QMUX_TYPE_PDS: dbg_time("Get clientPDS = %d", ClientId); break;
case QMUX_TYPE_UIM: dbg_time("Get clientUIM = %d", ClientId); break;
case QMUX_TYPE_WDS_ADMIN: dbg_time("Get clientWDA = %d", ClientId);
break;
default: break;
}
return ClientId;
}
static void handle_alloc_client(PROFILE_T *profile) {
int srv_list[] = {QMUX_TYPE_WDS, QMUX_TYPE_NAS, QMUX_TYPE_UIM, QMUX_TYPE_DMS, QMUX_TYPE_WDS_ADMIN};
size_t i = 0, srv_ready = 0;
static int report = -1;
if (report != -1)
return;
for(i = 0; i < sizeof(srv_list)/sizeof(srv_list[0]); i++) {
int srv = srv_list[i];
if (service_list[srv].service)
srv_ready++;
else
continue;
if (qmiclientId[srv] == 0) {
qmiclientId[srv] = get_client(srv);
if (qmiclientId[srv] != 0) {
if (srv == QMUX_TYPE_WDS) {
qmiclientId[QMUX_TYPE_WDS_IPV6] = get_client(QMUX_TYPE_WDS);
}
else if (srv == QMUX_TYPE_WDS_ADMIN) {
profile->wda_client = qmiclientId[QMUX_TYPE_WDS_ADMIN];
}
}
}
}
if (srv_ready == sizeof(srv_list)/sizeof(srv_list[0])) {
if (qmiclientId[QMUX_TYPE_WDS]) {
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED);
} else {
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
}
report = 1;
}
}
static int qmi_send(PQCQMIMSG pRequest) {
uint8_t QMIType = pRequest->QMIHdr.QMIType;
int sock;
QrtrService *s = &service_list[QMIType == QMUX_TYPE_WDS_IPV6 ? QMUX_TYPE_WDS: QMIType];
sock = qmiclientId[QMIType];
pRequest->QMIHdr.ClientId = 0xaa;
if (!s ->service || !sock) {
dbg_time("%s service: %d, sock: %d for QMIType: %d", __func__, s ->service, sock, QMIType);
return -ENODEV;
}
#ifdef USE_LINUX_MSM_IPC
return xport_send(sock, s->node, s->port, &pRequest->MUXMsg,
le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR));
#else
return qrtr_send(sock, s->node, s->port, &pRequest->MUXMsg,
le16_to_cpu(pRequest->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR));
#endif
}
static int qmi_deinit(void) {
unsigned int i;
for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
{
if (qmiclientId[i] != 0)
{
close(qmiclientId[i]);
qmiclientId[i] = 0;
}
}
return 0;
}
static void * qmi_read(void *pData) {
PROFILE_T *profile = (PROFILE_T *)pData;
int ctrl_sock;
int wait_for_request_quit = 0;
#ifdef USE_LINUX_MSM_IPC
ctrl_sock = xport_ctrl_init();
if (ctrl_sock != -1)
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED);
#else
ctrl_sock = qrtr_ctrl_init();
#endif
if (ctrl_sock == -1)
goto _quit;
while (1) {
struct pollfd pollfds[16] = {{qmidevice_control_fd[1], POLLIN, 0}, {ctrl_sock, POLLIN, 0}};
int ne, ret, nevents = 2;
unsigned int i;
for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
{
if (qmiclientId[i] != 0)
{
pollfds[nevents].fd = qmiclientId[i];
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents = 0;
nevents++;
}
}
do {
ret = poll(pollfds, nevents, wait_for_request_quit ? 1000 : -1);
} while ((ret < 0) && (errno == EINTR));
if (ret == 0 && wait_for_request_quit) {
QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI()
continue;
}
if (ret <= 0) {
dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno));
break;
}
for (ne = 0; ne < nevents; ne++) {
int fd = pollfds[ne].fd;
short revents = pollfds[ne].revents;
if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
dbg_time("%s poll err/hup/inval", __func__);
dbg_time("epoll fd = %d, events = 0x%04x", fd, revents);
if (fd == qmidevice_control_fd[1]) {
} else {
}
if (revents & (POLLERR | POLLHUP | POLLNVAL))
goto _quit;
}
if ((revents & POLLIN) == 0)
continue;
if (fd == qmidevice_control_fd[1]) {
int triger_event;
if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) {
//DBG("triger_event = 0x%x", triger_event);
switch (triger_event) {
case RIL_REQUEST_QUIT:
goto _quit;
break;
case SIG_EVENT_STOP:
wait_for_request_quit = 1;
break;
default:
break;
}
}
}
else if (fd == ctrl_sock) {
handle_ctrl_pkt(ctrl_sock);
handle_alloc_client(profile);
}
else
{
PQCQMIMSG pResponse = (PQCQMIMSG)cm_recv_buf;
int rc;
uint32_t sq_node = 0;
uint32_t sq_port = 0;
rc = qmi_recv(fd, &pResponse->MUXMsg, sizeof(cm_recv_buf) - sizeof(QCQMI_HDR), &sq_node, &sq_port);
if (debug_qmi)
dbg_time("fd %d, node %u, port %x, len: %d", fd, sq_node, sq_port, rc);
if (rc <= 0)
{
dbg_time("%s read=%d errno: %d (%s)", __func__, rc, errno, strerror(errno));
break;
}
for (i = 0; i < sizeof(qmiclientId)/sizeof(qmiclientId[0]); i++)
{
if (qmiclientId[i] == fd)
{
pResponse->QMIHdr.QMIType = i;
if (service_list[i].node != sq_node || service_list[i].port != sq_port) {
continue;
}
}
}
pResponse->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
pResponse->QMIHdr.Length = cpu_to_le16(rc + sizeof(QCQMI_HDR) - 1);
pResponse->QMIHdr.CtlFlags = 0x00;
pResponse->QMIHdr.ClientId = 0xaa;
QmiThreadRecvQMI(pResponse);
}
}
}
_quit:
qmi_deinit();
close(ctrl_sock);
qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED);
QmiThreadRecvQMI(NULL); //main thread may pending on QmiThreadSendQMI()
dbg_time("%s exit", __func__);
pthread_exit(NULL);
return NULL;
}
const struct qmi_device_ops qrtr_qmidev_ops = {
.deinit = qmi_deinit,
.send = qmi_send,
.read = qmi_read,
};
+85
View File
@@ -0,0 +1,85 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _LINUX_QRTR_H
#define _LINUX_QRTR_H
#include <linux/socket.h>
#include <linux/types.h>
#ifndef AF_QIPCRTR
#define AF_QIPCRTR 42
#endif
#define QRTR_NODE_BCAST 0xffffffffu
#define QRTR_PORT_CTRL 0xfffffffeu
struct sockaddr_qrtr {
__kernel_sa_family_t sq_family;
__u32 sq_node;
__u32 sq_port;
};
enum qrtr_pkt_type {
QRTR_TYPE_DATA = 1,
QRTR_TYPE_HELLO = 2,
QRTR_TYPE_BYE = 3,
QRTR_TYPE_NEW_SERVER = 4,
QRTR_TYPE_DEL_SERVER = 5,
QRTR_TYPE_DEL_CLIENT = 6,
QRTR_TYPE_RESUME_TX = 7,
QRTR_TYPE_EXIT = 8,
QRTR_TYPE_PING = 9,
QRTR_TYPE_NEW_LOOKUP = 10,
QRTR_TYPE_DEL_LOOKUP = 11,
};
#define QRTR_TYPE_DEL_PROC 13
struct qrtr_ctrl_pkt {
__le32 cmd;
union {
struct {
__le32 service;
__le32 instance;
__le32 node;
__le32 port;
} server;
struct {
__le32 node;
__le32 port;
} client;
struct {
__le32 rsvd;
__le32 node;
} proc;
};
} __attribute__ ((packed));
#define QRTR_PROTO_VER_1 1
struct qrtr_hdr_v1 {
__le32 version;
__le32 type;
__le32 src_node_id;
__le32 src_port_id;
__le32 confirm_rx;
__le32 size;
__le32 dst_node_id;
__le32 dst_port_id;
} __attribute__ ((packed));
#endif /* _LINUX_QRTR_H */
+502
View File
@@ -0,0 +1,502 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <stddef.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <getopt.h>
#include <poll.h>
#include <sys/time.h>
#include <endian.h>
#include <time.h>
#include <sys/types.h>
#include <limits.h>
#include <inttypes.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <fcntl.h>
#include <pthread.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <linux/un.h>
#include <linux/if.h>
#include <dirent.h>
#include <signal.h>
#include <endian.h>
#include <inttypes.h>
#include "qlist.h"
#include "QMIThread.h"
#include "atchannel.h"
#include "at_tok.h"
#define dprintf(fmt, args...) do { fprintf(stdout, "%s " fmt, get_time(), ##args); } while(0);
#define SYSCHECK(c) do{if((c)<0) {dprintf("%s %d error: '%s' (code: %d)\n", __func__, __LINE__, strerror(errno), errno); return -1;}}while(0)
#define cfmakenoblock(fd) do{fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);}while(0)
#define safe_free(__x) do { if (__x) { free((void *)__x); __x = NULL;}} while(0)
#define safe_at_response_free(__x) { if (__x) { at_response_free(__x); __x = NULL;}}
#define at_response_error(err, p_response) \
(err \
|| p_response == NULL \
|| p_response->finalResponse == NULL \
|| p_response->success == 0)
typedef struct {
struct qlistnode qnode;
int ClientFd;
unsigned AccessTime;
} ATC_PROXY_CONNECTION;
static int atc_proxy_quit = 0;
static pthread_t thread_id = 0;
static int atc_dev_fd = -1;
static int atc_proxy_server_fd = -1;
static struct qlistnode atc_proxy_connection;
static int verbose_debug = 0;
static int modem_reset_flag = 0;
static uint8_t atc_buf[4096];
static int asr_style_atc = 0;
extern int asprintf(char **s, const char *fmt, ...);
static ATC_PROXY_CONNECTION *current_client_fd = NULL;
static void dump_atc(uint8_t *pATC, int fd,int size, const char flag)
{
if (verbose_debug) {
printf("%c %d:\n", flag, fd);
printf("%.*s\n", size, pATC);
}
}
static int send_atc_to_client(int clientFd, uint8_t *pATC, int size) {
struct pollfd pollfds[]= {{clientFd, POLLOUT, 0}};
ssize_t ret = 0;
do {
ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000);
} while (ret == -1 && errno == EINTR && atc_proxy_quit == 0);
if (pollfds[0].revents & POLLOUT) {
ret = write(clientFd, pATC, size);
}
return ret;
}
static void onUnsolicited (const char *s, const char *sms_pdu)
{
struct qlistnode *con_node;
int ret;
char buf[1024];
if(s) {
strcpy(buf, s);
strcat(buf, "\r\n");
}
if(sms_pdu) {
strcat(buf, sms_pdu);
strcat(buf, "\r\n");
}
if(current_client_fd) {
ATC_PROXY_CONNECTION *atc_con = current_client_fd;
ret = send_atc_to_client(atc_con->ClientFd, (uint8_t *)buf, strlen(buf));
if(ret < 0) {
close(atc_con->ClientFd);
qlist_remove(&atc_con->qnode);
free(atc_con);
}
return;
}
qlist_for_each(con_node, &atc_proxy_connection) {
ATC_PROXY_CONNECTION *atc_con = qnode_to_item(con_node, ATC_PROXY_CONNECTION, qnode);
if(atc_con && atc_con->ClientFd > 0) {
ret = send_atc_to_client(atc_con->ClientFd, (uint8_t *)buf, strlen(buf));
if(ret < 0) {
close(atc_con->ClientFd);
con_node = con_node->prev;
qlist_remove(&atc_con->qnode);
free(atc_con);
continue;
}
}
}
}
static void onTimeout(void) {
dprintf("%s", __func__);
//TODO
}
static void onClose(void) {
dprintf("%s", __func__);
}
static int create_local_server(const char *name) {
int sockfd = -1;
int reuse_addr = 1;
struct sockaddr_un sockaddr;
socklen_t alen;
/*Create server socket*/
SYSCHECK(sockfd = socket(AF_LOCAL, SOCK_STREAM, 0));
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sun_family = AF_LOCAL;
sockaddr.sun_path[0] = 0;
memcpy(sockaddr.sun_path + 1, name, strlen(name) );
alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1;
SYSCHECK(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr)));
if(bind(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) {
close(sockfd);
dprintf("bind %s errno: %d (%s)\n", name, errno, strerror(errno));
return -1;
}
dprintf("local server: %s sockfd = %d\n", name, sockfd);
cfmakenoblock(sockfd);
listen(sockfd, 1);
return sockfd;
}
static void accept_atc_connection(int serverfd) {
int clientfd = -1;
unsigned char addr[128];
socklen_t alen = sizeof(addr);
ATC_PROXY_CONNECTION *atc_con;
clientfd = accept(serverfd, (struct sockaddr *)addr, &alen);
atc_con = (ATC_PROXY_CONNECTION *)malloc(sizeof(ATC_PROXY_CONNECTION));
if (atc_con) {
qlist_init(&atc_con->qnode);
atc_con->ClientFd= clientfd;
atc_con->AccessTime = 0;
dprintf("+++ ClientFd=%d\n", atc_con->ClientFd);
qlist_add_tail(&atc_proxy_connection, &atc_con->qnode);
}
cfmakenoblock(clientfd);
}
static void cleanup_atc_connection(int clientfd) {
struct qlistnode *con_node;
qlist_for_each(con_node, &atc_proxy_connection) {
ATC_PROXY_CONNECTION *atc_con = qnode_to_item(con_node, ATC_PROXY_CONNECTION, qnode);
if (atc_con->ClientFd == clientfd) {
dprintf("--- ClientFd=%d\n", atc_con->ClientFd);
close(atc_con->ClientFd);
qlist_remove(&atc_con->qnode);
free(atc_con);
if (current_client_fd == atc_con)
current_client_fd = NULL;
break;
}
}
}
static int atc_proxy_init(void) {
int err;
char *cmd;
ATResponse *p_response = NULL;
err = at_handshake();
if (err) {
dprintf("handshake fail, TODO ... ");
goto exit;
}
at_send_command_singleline("AT+QCFG=\"usbnet\"", "+QCFG:", NULL);
at_send_command_multiline("AT+QNETDEVCTL=?", "+QNETDEVCTL:", NULL);
at_send_command("AT+CGREG=2", NULL); //GPRS Network Registration Status
at_send_command("AT+CEREG=2", NULL); //EPS Network Registration Status
at_send_command("AT+C5GREG=2", NULL); //5GS Network Registration Status
at_send_command_singleline("AT+QNETDEVSTATUS=?", "+QNETDEVSTATUS:", &p_response);
if (at_response_error(err, p_response))
asr_style_atc = 1; //EC200T/EC100Y do not support this AT, but RG801/RG500U support
safe_at_response_free(p_response);
err = at_send_command_singleline("AT+QCFG=\"NAT\"", "+QCFG:", &p_response);
if (!at_response_error(err, p_response)) {
int old_nat, new_nat = asr_style_atc ? 1 : 0;
err = at_tok_scanf(p_response->p_intermediates->line, "%s%d", NULL, &old_nat);
if (err == 2 && old_nat != new_nat) {
safe_at_response_free(p_response);
asprintf(&cmd, "AT+QCFG=\"NAT\",%d", new_nat);
err = at_send_command(cmd, &p_response);
safe_free(cmd);
if (!at_response_error(err, p_response)) {
err = at_send_command("at+cfun=1,1",NULL);
}
safe_at_response_free(p_response);
}
err = 0;
}
safe_at_response_free(p_response);
exit:
return err;
}
static void atc_start_server(const char* servername) {
atc_proxy_server_fd = create_local_server(servername);
dprintf("atc_proxy_server_fd = %d\n", atc_proxy_server_fd);
if (atc_proxy_server_fd == -1) {
dprintf("Failed to create %s, errno: %d (%s)\n", servername, errno, strerror(errno));
}
}
static void atc_close_server(const char* servername) {
if (atc_proxy_server_fd != -1) {
dprintf("%s %s close server\n", __func__, servername);
close(atc_proxy_server_fd);
atc_proxy_server_fd = -1;
}
}
static void *atc_proxy_loop(void *param)
{
uint8_t *pATC = atc_buf;
struct qlistnode *con_node;
ATC_PROXY_CONNECTION *atc_con;
(void)param;
dprintf("%s enter thread_id %p\n", __func__, (void *)pthread_self());
qlist_init(&atc_proxy_connection);
while (atc_dev_fd > 0 && atc_proxy_quit == 0) {
struct pollfd pollfds[2+64];
int ne, ret, nevents = 0;
ssize_t nreads;
pollfds[nevents].fd = atc_dev_fd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
if (atc_proxy_server_fd > 0) {
pollfds[nevents].fd = atc_proxy_server_fd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
}
qlist_for_each(con_node, &atc_proxy_connection) {
atc_con = qnode_to_item(con_node, ATC_PROXY_CONNECTION, qnode);
pollfds[nevents].fd = atc_con->ClientFd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
if (nevents == (sizeof(pollfds)/sizeof(pollfds[0])))
break;
}
do {
ret = poll(pollfds, nevents, (atc_proxy_server_fd > 0) ? -1 : 200);
} while (ret == -1 && errno == EINTR && atc_proxy_quit == 0);
if (ret < 0) {
dprintf("%s poll=%d, errno: %d (%s)\n", __func__, ret, errno, strerror(errno));
goto atc_proxy_loop_exit;
}
for (ne = 0; ne < nevents; ne++) {
int fd = pollfds[ne].fd;
short revents = pollfds[ne].revents;
if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
dprintf("%s poll fd = %d, revents = %04x\n", __func__, fd, revents);
if (fd == atc_dev_fd) {
goto atc_proxy_loop_exit;
} else if(fd == atc_proxy_server_fd) {
} else {
cleanup_atc_connection(fd);
}
continue;
}
if (!(pollfds[ne].revents & POLLIN)) {
continue;
}
if (fd == atc_proxy_server_fd) {
accept_atc_connection(fd);
}
else if (fd == atc_dev_fd) {
usleep(10*1000); //let atchannel.c read at response.
if (modem_reset_flag)
goto atc_proxy_loop_exit;
}
else {
memset(atc_buf, 0x0, sizeof(atc_buf));
nreads = read(fd, pATC, sizeof(atc_buf));
if (nreads <= 0) {
dprintf("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno));
cleanup_atc_connection(fd);
break;
}
dump_atc(pATC, fd, nreads, 'r');
qlist_for_each(con_node, &atc_proxy_connection) {
atc_con = qnode_to_item(con_node, ATC_PROXY_CONNECTION, qnode);
if (atc_con->ClientFd == pollfds[nevents].fd) {
current_client_fd = atc_con;
break;
}
}
at_send_command ((const char *)pATC, NULL);
current_client_fd = NULL;
}
}
}
atc_proxy_loop_exit:
at_close();
while (!qlist_empty(&atc_proxy_connection)) {
ATC_PROXY_CONNECTION *atc_con = qnode_to_item(qlist_head(&atc_proxy_connection), ATC_PROXY_CONNECTION, qnode);
cleanup_atc_connection(atc_con->ClientFd);
}
dprintf("%s exit, thread_id %p\n", __func__, (void *)pthread_self());
return NULL;
}
static void usage(void) {
dprintf(" -d <device_name> A valid atc device\n"
" default /dev/ttyUSB2, but /dev/ttyUSB2 may be invalid\n"
" -i <netcard_name> netcard name\n"
" -v Will show all details\n");
}
static void sig_action(int sig) {
if (atc_proxy_quit == 0) {
atc_proxy_quit = 1;
if (thread_id)
pthread_kill(thread_id, sig);
}
}
int main(int argc, char *argv[]) {
int opt;
char atc_dev[32+1] = "/dev/ttyUSB2";
int retry_times = 0;
char servername[64] = {0};
optind = 1;
signal(SIGINT, sig_action);
while ( -1 != (opt = getopt(argc, argv, "d:i:vh"))) {
switch (opt) {
case 'd':
strcpy(atc_dev, optarg);
break;
case 'v':
verbose_debug = 1;
break;
default:
usage();
return 0;
}
}
if (access(atc_dev, R_OK | W_OK)) {
dprintf("Fail to access %s, errno: %d (%s). break\n", atc_dev, errno, strerror(errno));
return -1;
}
sprintf(servername, "quectel-atc-proxy%c", atc_dev[strlen(atc_dev) - 1]);
dprintf("Will use atc-dev='%s', proxy='%s'\n", atc_dev, servername);
while (atc_proxy_quit == 0) {
if (access(atc_dev, R_OK | W_OK)) {
dprintf("Fail to access %s, errno: %d (%s). continue\n", atc_dev, errno, strerror(errno));
// wait device
sleep(3);
continue;
}
atc_dev_fd = open(atc_dev, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (atc_dev_fd == -1) {
dprintf("Failed to open %s, errno: %d (%s). break\n", atc_dev, errno, strerror(errno));
return -1;
}
cfmakenoblock(atc_dev_fd);
if (at_open(atc_dev_fd, onUnsolicited, 1)) {
close(atc_dev_fd);
atc_dev_fd = -1;
}
at_set_on_timeout(onTimeout);
at_set_on_reader_closed(onClose);
/* no atc_proxy_loop lives, create one */
pthread_create(&thread_id, NULL, atc_proxy_loop, NULL);
/* try to redo init if failed, init function must be successfully */
while (atc_proxy_init() != 0) {
if (retry_times < 5) {
dprintf("fail to init proxy, try again in 2 seconds.\n");
sleep(2);
retry_times++;
} else {
dprintf("has failed too much times, restart the modem and have a try...\n");
break;
}
/* break loop if modem is detached */
if (access(atc_dev, F_OK|R_OK|W_OK))
break;
}
retry_times = 0;
atc_start_server(servername);
if (atc_proxy_server_fd == -1)
pthread_cancel(thread_id);
pthread_join(thread_id, NULL);
/* close local server at last */
atc_close_server(servername);
close(atc_dev_fd);
/* DO RESTART IN 20s IF MODEM RESET ITSELF */
if (modem_reset_flag) {
unsigned int time_to_wait = 20;
while (time_to_wait) {
time_to_wait = sleep(time_to_wait);
}
modem_reset_flag = 0;
}
}
return 0;
}
@@ -0,0 +1,464 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <fcntl.h>
#include <pthread.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <linux/un.h>
#include <linux/in.h>
#include <linux/if.h>
#include <dirent.h>
#include <signal.h>
#include <inttypes.h>
#include <getopt.h>
#include "qendian.h"
#define QUECTEL_MBIM_PROXY "quectel-mbim-proxy"
#define safe_close(_fd) do { if (_fd > 0) { close(_fd); _fd = -1; } } while(0)
#define CM_MAX_CLIENT 32
#define TID_MASK (0xFFFFFF)
#define TID_SHIFT (24)
typedef enum {
MBIM_OPEN_MSG = 1,
MBIM_CLOSE_MSG = 2,
MBIM_OPEN_DONE = 0x80000001,
MBIM_CLOSE_DONE = 0x80000002,
} MBIM_MSG;
typedef struct {
unsigned int MessageType;
unsigned int MessageLength;
unsigned int TransactionId;
} MBIM_MESSAGE_HEADER;
typedef struct {
MBIM_MESSAGE_HEADER MessageHeader;
unsigned int MaxControlTransfer;
} MBIM_OPEN_MSG_T;
typedef struct {
MBIM_MESSAGE_HEADER MessageHeader;
unsigned int Status;
} MBIM_OPEN_DONE_T;
typedef struct {
int client_fd;
int client_idx;
} CM_CLIENT_T;
static unsigned char cm_recv_buffer[4096];
static CM_CLIENT_T cm_clients[CM_MAX_CLIENT];
static int verbose = 0;
const char * get_time(void) {
static char time_buf[128];
struct timeval tv;
time_t time;
suseconds_t millitm;
struct tm *ti;
gettimeofday (&tv, NULL);
time= tv.tv_sec;
millitm = (tv.tv_usec + 500) / 1000;
if (millitm == 1000) {
++time;
millitm = 0;
}
ti = localtime(&time);
sprintf(time_buf, "[%02d-%02d_%02d:%02d:%02d:%03d]", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm);
return time_buf;
}
#define mbim_debug(fmt, args...) do { fprintf(stdout, "%s " fmt, get_time(), ##args); } while(0);
static int non_block_write(int fd, void *data, int len)
{
int ret;
struct pollfd pollfd = {fd, POLLOUT, 0};
ret = poll(&pollfd, 1, 3000);
if (ret <= 0) {
mbim_debug("%s poll ret=%d, errno: %d(%s)\n", __func__, ret, errno, strerror(errno));
}
ret = write (fd, data, len);
if (ret != len)
mbim_debug("%s write ret=%d, errno: %d(%s)\n", __func__, ret, errno, strerror(errno));
return len;
}
static int mbim_send_open_msg(int mbim_dev_fd, uint32_t MaxControlTransfer) {
MBIM_OPEN_MSG_T open_msg;
MBIM_OPEN_MSG_T *pRequest = &open_msg;
pRequest->MessageHeader.MessageType = htole32(MBIM_OPEN_MSG);
pRequest->MessageHeader.MessageLength = htole32(sizeof(MBIM_OPEN_MSG_T));
pRequest->MessageHeader.TransactionId = htole32(1);
pRequest->MaxControlTransfer = htole32(MaxControlTransfer);
mbim_debug("%s()\n", __func__);
return non_block_write(mbim_dev_fd, pRequest, sizeof(MBIM_OPEN_MSG_T));
}
/*
* parameter: proxy name
* return: local proxy server fd or -1
*/
static int proxy_make_server(const char *proxy_name)
{
int len, flag;
struct sockaddr_un sockaddr;
int mbim_server_fd;
mbim_server_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (mbim_server_fd < 0) {
mbim_debug("socket failed: %s\n", strerror(errno));
return -1;
}
if (fcntl(mbim_server_fd, F_SETFL, fcntl(mbim_server_fd, F_GETFL) | O_NONBLOCK) < 0)
mbim_debug("fcntl set server(%d) NONBLOCK attribute failed: %s\n", mbim_server_fd, strerror(errno));
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sun_family = AF_LOCAL;
sockaddr.sun_path[0] = 0;
snprintf(sockaddr.sun_path, UNIX_PATH_MAX, "0%s", proxy_name);
sockaddr.sun_path[0] = '\0'; // string starts with leading '\0'
flag = 1;
if (setsockopt(mbim_server_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) < 0) {
safe_close(mbim_server_fd);
mbim_debug("setsockopt failed\n");
}
len = strlen(proxy_name) + offsetof(struct sockaddr_un, sun_path) + 1;
if (bind(mbim_server_fd, (struct sockaddr*)&sockaddr, len) < 0) {
safe_close(mbim_server_fd);
mbim_debug("bind failed: %s\n", strerror(errno));
return -1;
}
listen(mbim_server_fd, 4);
return mbim_server_fd;
}
static int handle_client_connect(int server_fd)
{
int i, client_fd;
struct sockaddr_in cli_addr;
socklen_t len = sizeof(cli_addr);
client_fd = accept(server_fd, (struct sockaddr *)&cli_addr, &len);
if (client_fd < 0) {
mbim_debug("proxy accept failed: %s\n", strerror(errno));
return -1;
}
if (fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL) | O_NONBLOCK) < 0)
mbim_debug("fcntl set client(%d) NONBLOCK attribute failed: %s\n", client_fd, strerror(errno));
for (i = 0; i < CM_MAX_CLIENT; i++) {
if (cm_clients[i].client_fd <= 0) {
cm_clients[i].client_fd = client_fd;
cm_clients[i].client_idx= i+1;
mbim_debug("%s client_fd=%d, client_idx=%d\n", __func__, cm_clients[i].client_fd, cm_clients[i].client_idx);
return 0;
}
}
close(client_fd);
return -1;
}
static void handle_client_disconnect(int client_fd)
{
int i;
for (i = 0; i < CM_MAX_CLIENT; i++) {
if (cm_clients[i].client_fd == client_fd) {
mbim_debug("%s client_fd=%d, client_idx=%d\n", __func__, cm_clients[i].client_fd, cm_clients[i].client_idx);
safe_close(cm_clients[i].client_fd);
return;
}
}
}
static int handle_client_request(int mbim_dev_fd, int client_fd, void *pdata, int len)
{
int i;
int client_idx = -1;
int ret;
MBIM_MESSAGE_HEADER *pRequest = (MBIM_MESSAGE_HEADER *)pdata;
unsigned int TransactionId = le32toh(pRequest->TransactionId);
for (i = 0; i < CM_MAX_CLIENT; i++) {
if (cm_clients[i].client_fd == client_fd) {
client_idx = cm_clients[i].client_idx;
break;
}
}
if (client_idx == -1) {
goto error;
}
if (le32toh(pRequest->MessageType) == MBIM_OPEN_MSG
|| le32toh(pRequest->MessageType) == MBIM_CLOSE_MSG) {
MBIM_OPEN_DONE_T OpenDone;
OpenDone.MessageHeader.MessageType = htole32(le32toh(pRequest->MessageType) | 0x80000000);
OpenDone.MessageHeader.MessageLength = htole32(sizeof(OpenDone));
OpenDone.MessageHeader.TransactionId = htole32(TransactionId);
OpenDone.Status = htole32(0);
non_block_write (client_fd, &OpenDone, sizeof(OpenDone));
return 0;
}
/* transfer TransicationID to proxy transicationID and record in sender list */
pRequest->TransactionId = htole32(TransactionId | (client_idx << TID_SHIFT));
if (verbose) mbim_debug("REQ client_fd=%d, client_idx=%d, tid=%u\n",
cm_clients[client_idx].client_fd, cm_clients[client_idx].client_idx, TransactionId);
ret = non_block_write (mbim_dev_fd, pRequest, len);
if (ret == len)
return 0;
error:
return -1;
}
/*
* Will read message from device and transfer it to clients/client
* Notice:
* unsocial message will be send to all clients
*/
static int handle_device_response(void *pdata, int len)
{
int i;
MBIM_MESSAGE_HEADER *pResponse = (MBIM_MESSAGE_HEADER *)pdata;
unsigned int TransactionId = le32toh(pResponse->TransactionId);
/* unsocial/function error message */
if (TransactionId == 0) {
for (i = 0; i < CM_MAX_CLIENT; i++) {
if (cm_clients[i].client_fd > 0) {
non_block_write(cm_clients[i].client_fd, pResponse, len);
}
}
}
else {
/* try to find the sender */
int client_idx = (TransactionId >> TID_SHIFT);
for (i = 0; i < CM_MAX_CLIENT; i++) {
if (cm_clients[i].client_idx == client_idx && cm_clients[i].client_fd > 0) {
TransactionId &= TID_MASK;
pResponse->TransactionId = htole32(TransactionId);
if (verbose) mbim_debug("RSP client_fd=%d, client_idx=%d, tid=%u\n",
cm_clients[i].client_fd, cm_clients[i].client_idx, TransactionId);
non_block_write(cm_clients[i].client_fd, pResponse, len);
break;
}
}
if (i == CM_MAX_CLIENT) {
mbim_debug("%s nobody care tid=%u\n", __func__, TransactionId);
}
}
return 0;
}
static int proxy_loop(int mbim_dev_fd)
{
int i;
int mbim_server_fd = -1;
while (mbim_dev_fd > 0) {
struct pollfd pollfds[2+CM_MAX_CLIENT];
int ne, ret, nevents = 0;
pollfds[nevents].fd = mbim_dev_fd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
if (mbim_server_fd > 0) {
pollfds[nevents].fd = mbim_server_fd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
for (i = 0; i < CM_MAX_CLIENT; i++) {
if (cm_clients[i].client_fd > 0) {
pollfds[nevents].fd = cm_clients[i].client_fd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
}
}
}
ret = poll(pollfds, nevents, (mbim_server_fd > 0) ? -1 : (10*1000));
if (ret <= 0) {
goto error;
}
for (ne = 0; ne < nevents; ne++) {
int fd = pollfds[ne].fd;
short revents = pollfds[ne].revents;
if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
mbim_debug("%s poll fd = %d, revents = %04x\n", __func__, fd, revents);
if (fd == mbim_dev_fd) {
goto error;
} else if(fd == mbim_server_fd) {
} else {
handle_client_disconnect(fd);
}
continue;
}
if (!(pollfds[ne].revents & POLLIN)) {
continue;
}
if (fd == mbim_server_fd) {
handle_client_connect(fd);
}
else {
int len = read(fd, cm_recv_buffer, sizeof(cm_recv_buffer));
if (len <= 0) {
mbim_debug("%s read fd=%d, len=%d, errno: %d(%s)\n", __func__, fd, len, errno, strerror(errno));
if (fd == mbim_dev_fd)
goto error;
else
handle_client_disconnect(fd);
return len;
}
if (fd == mbim_dev_fd) {
if (mbim_server_fd == -1) {
MBIM_OPEN_DONE_T *pOpenDone = (MBIM_OPEN_DONE_T *)cm_recv_buffer;
if (le32toh(pOpenDone->MessageHeader.MessageType) == MBIM_OPEN_DONE) {
mbim_debug("receive MBIM_OPEN_DONE, status=%d\n", htole32(pOpenDone->Status));
if (htole32(pOpenDone->Status))
goto error;
mbim_server_fd = proxy_make_server(QUECTEL_MBIM_PROXY);
mbim_debug("mbim_server_fd=%d\n", mbim_server_fd);
}
}
else {
handle_device_response(cm_recv_buffer, len);
}
}
else {
handle_client_request(mbim_dev_fd, fd, cm_recv_buffer, len);
}
}
}
}
error:
safe_close(mbim_server_fd);
for (i = 0; i < CM_MAX_CLIENT; i++) {
safe_close(cm_clients[i].client_fd);
}
mbim_debug("%s exit\n", __func__);
return 0;
}
/*
* How to use this proxy?
* 1. modprobe -a 8021q
* 2. Create network interface for channels:
* ip link add link wwan0 name wwan0.1 type vlan id 1
* ip link add link wwan0 name wwan0.2 type vlan id 2
* 3. Start './mbim-proxy' with -d 'device'
* 4. Start Clients: ./quectel-CM -n id1
* 5. Start Clients: ./quectel-CM -n id2
* ...
* Notice:
* mbim-proxy can work in backgroud as a daemon
* '-n' sessionID
* The modem may not support multi-PDN mode or how many PDN it supports is undefined. It depends!!!
* Besides, some modem also may not support some sessionID. For instance EC20 doesn't support SessionId 1...
*/
int main(int argc, char **argv)
{
int optidx = 0;
int opt;
char *optstr = "d:vh";
const char *device = "/dev/cdc-wdm0";
struct option options[] = {
{"verbose", no_argument, NULL, 'v'},
{"device", required_argument, NULL, 'd'},
{0, 0, 0, 0},
};
while ((opt = getopt_long(argc, argv, optstr, options, &optidx)) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
case 'd':
device = optarg;
break;
case 'h':
mbim_debug("-h Show this message\n");
mbim_debug("-v Verbose\n");
mbim_debug("-d [device] MBIM device\n");
return 0;
default:
mbim_debug("illegal argument\n");
return -1;
}
}
if (!device) {
mbim_debug("Missing parameter: device\n");
return -1;
}
while (1) {
int mbim_dev_fd = open(device, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (mbim_dev_fd < 0) {
mbim_debug("cannot open mbim_device %s: %s\n", device, strerror(errno));
sleep(2);
continue;
}
mbim_debug ("mbim_dev_fd=%d\n", mbim_dev_fd);
memset(cm_clients, 0, sizeof(cm_clients));
mbim_send_open_msg(mbim_dev_fd, sizeof(cm_recv_buffer));
proxy_loop(mbim_dev_fd);
safe_close(mbim_dev_fd);
}
return -1;
}
+696
View File
@@ -0,0 +1,696 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <fcntl.h>
#include <pthread.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <linux/un.h>
#include <linux/if.h>
#include <dirent.h>
#include <signal.h>
#include <inttypes.h>
#include "qendian.h"
#include "qlist.h"
#include "QCQMI.h"
#include "QCQCTL.h"
#include "QCQMUX.h"
#ifndef MIN
#define MIN(a, b) ((a) < (b)? (a): (b))
#endif
const char * get_time(void) {
static char time_buf[128];
struct timeval tv;
time_t time;
suseconds_t millitm;
struct tm *ti;
gettimeofday (&tv, NULL);
time= tv.tv_sec;
millitm = (tv.tv_usec + 500) / 1000;
if (millitm == 1000) {
++time;
millitm = 0;
}
ti = localtime(&time);
sprintf(time_buf, "[%02d-%02d_%02d:%02d:%02d:%03d]", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm);
return time_buf;
}
#define dprintf(fmt, args...) do { fprintf(stdout, "%s " fmt, get_time(), ##args); } while(0);
#define SYSCHECK(c) do{if((c)<0) {dprintf("%s %d error: '%s' (code: %d)\n", __func__, __LINE__, strerror(errno), errno); return -1;}}while(0)
#define cfmakenoblock(fd) do{fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);}while(0)
typedef struct {
struct qlistnode qnode;
int ClientFd;
QCQMIMSG qmi[0];
} QMI_PROXY_MSG;
typedef struct {
struct qlistnode qnode;
uint8_t QMIType;
uint8_t ClientId;
unsigned AccessTime;
} QMI_PROXY_CLINET;
typedef struct {
struct qlistnode qnode;
struct qlistnode client_qnode;
int ClientFd;
unsigned AccessTime;
} QMI_PROXY_CONNECTION;
#ifdef QUECTEL_QMI_MERGE
#define MERGE_PACKET_IDENTITY 0x2c7c
#define MERGE_PACKET_VERSION 0x0001
#define MERGE_PACKET_MAX_PAYLOAD_SIZE 56
typedef struct __QMI_MSG_HEADER {
uint16_t idenity;
uint16_t version;
uint16_t cur_len;
uint16_t total_len;
} QMI_MSG_HEADER;
typedef struct __QMI_MSG_PACKET {
QMI_MSG_HEADER header;
uint16_t len;
char buf[4096];
} QMI_MSG_PACKET;
#endif
static int qmi_proxy_quit = 0;
static pthread_t thread_id = 0;
static int cdc_wdm_fd = -1;
static int qmi_proxy_server_fd = -1;
static struct qlistnode qmi_proxy_connection;
static struct qlistnode qmi_proxy_ctl_msg;
static int verbose_debug = 0;
static int modem_reset_flag = 0;
static int qmi_sync_done = 0;
static uint8_t qmi_buf[4096];
static int send_qmi_to_cdc_wdm(PQCQMIMSG pQMI);
#ifdef QUECTEL_QMI_MERGE
static int merge_qmi_rsp_packet(void *buf, ssize_t *src_size) {
static QMI_MSG_PACKET s_QMIPacket;
QMI_MSG_HEADER *header = NULL;
ssize_t size = *src_size;
if((uint16_t)size < sizeof(QMI_MSG_HEADER))
return -1;
header = (QMI_MSG_HEADER *)buf;
if(le16toh(header->idenity) != MERGE_PACKET_IDENTITY || le16toh(header->version) != MERGE_PACKET_VERSION || le16toh(header->cur_len) > le16toh(header->total_len))
return -1;
if(le16toh(header->cur_len) == le16toh(header->total_len)) {
*src_size = le16toh(header->total_len);
memcpy(buf, buf + sizeof(QMI_MSG_HEADER), *src_size);
s_QMIPacket.len = 0;
return 0;
}
memcpy(s_QMIPacket.buf + s_QMIPacket.len, buf + sizeof(QMI_MSG_HEADER), le16toh(header->cur_len));
s_QMIPacket.len += le16toh(header->cur_len);
if (le16toh(header->cur_len) < MERGE_PACKET_MAX_PAYLOAD_SIZE || s_QMIPacket.len >= le16toh(header->total_len)) {
memcpy(buf, s_QMIPacket.buf, s_QMIPacket.len);
*src_size = s_QMIPacket.len;
s_QMIPacket.len = 0;
return 0;
}
return -1;
}
#endif
static int create_local_server(const char *name) {
int sockfd = -1;
int reuse_addr = 1;
struct sockaddr_un sockaddr;
socklen_t alen;
/*Create server socket*/
SYSCHECK(sockfd = socket(AF_LOCAL, SOCK_STREAM, 0));
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sun_family = AF_LOCAL;
sockaddr.sun_path[0] = 0;
memcpy(sockaddr.sun_path + 1, name, strlen(name) );
alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1;
SYSCHECK(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr)));
if(bind(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) {
dprintf("bind %s errno: %d (%s)\n", name, errno, strerror(errno));
close(sockfd);
return -1;
}
dprintf("local server: %s sockfd = %d\n", name, sockfd);
cfmakenoblock(sockfd);
listen(sockfd, 1);
return sockfd;
}
static void accept_qmi_connection(int serverfd) {
int clientfd = -1;
unsigned char addr[128];
socklen_t alen = sizeof(addr);
QMI_PROXY_CONNECTION *qmi_con;
clientfd = accept(serverfd, (struct sockaddr *)addr, &alen);
qmi_con = (QMI_PROXY_CONNECTION *)malloc(sizeof(QMI_PROXY_CONNECTION));
if (qmi_con) {
qlist_init(&qmi_con->qnode);
qlist_init(&qmi_con->client_qnode);
qmi_con->ClientFd= clientfd;
qmi_con->AccessTime = 0;
dprintf("+++ ClientFd=%d\n", qmi_con->ClientFd);
qlist_add_tail(&qmi_proxy_connection, &qmi_con->qnode);
}
cfmakenoblock(clientfd);
}
static void cleanup_qmi_connection(int clientfd, int clientDisconnect) {
struct qlistnode *con_node, *qmi_node;
qlist_for_each(con_node, &qmi_proxy_connection) {
QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode);
if (qmi_con->ClientFd == clientfd) {
while (!qlist_empty(&qmi_con->client_qnode)) {
QMI_PROXY_CLINET *qmi_client = qnode_to_item(qlist_head(&qmi_con->client_qnode), QMI_PROXY_CLINET, qnode);
if (clientDisconnect) {
int size = 17;
QMI_PROXY_MSG *qmi_msg = malloc(sizeof(QMI_PROXY_MSG) + size);
PQCQMIMSG pQMI = &qmi_msg->qmi[0];
dprintf("xxx ClientFd=%d QMIType=%d ClientId=%d\n", qmi_con->ClientFd, qmi_client->QMIType, qmi_client->ClientId);
qlist_init(&qmi_msg->qnode);
qmi_msg->ClientFd = qmi_proxy_server_fd;
pQMI->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
pQMI->QMIHdr.Length = htole16(16);
pQMI->QMIHdr.CtlFlags = 0x00;
pQMI->QMIHdr.QMIType = QMUX_TYPE_CTL;
pQMI->QMIHdr.ClientId= 0x00;
pQMI->CTLMsg.ReleaseClientIdReq.CtlFlags = QMICTL_FLAG_REQUEST;
pQMI->CTLMsg.ReleaseClientIdReq.TransactionId = 255;
pQMI->CTLMsg.ReleaseClientIdReq.QMICTLType = htole16(QMICTL_RELEASE_CLIENT_ID_REQ);
pQMI->CTLMsg.ReleaseClientIdReq.Length = htole16(5);
pQMI->CTLMsg.ReleaseClientIdReq.TLVType = QCTLV_TYPE_REQUIRED_PARAMETER;
pQMI->CTLMsg.ReleaseClientIdReq.TLVLength = htole16(2);
pQMI->CTLMsg.ReleaseClientIdReq.QMIType = qmi_client->QMIType;
pQMI->CTLMsg.ReleaseClientIdReq.ClientId = qmi_client->ClientId;
if (qlist_empty(&qmi_proxy_ctl_msg))
send_qmi_to_cdc_wdm(pQMI);
qlist_add_tail(&qmi_proxy_ctl_msg, &qmi_msg->qnode);
}
qlist_remove(&qmi_client->qnode);
free(qmi_client);
}
qlist_for_each(qmi_node, &qmi_proxy_ctl_msg) {
QMI_PROXY_MSG *qmi_msg = qnode_to_item(qmi_node, QMI_PROXY_MSG, qnode);
if (qmi_msg->ClientFd == qmi_con->ClientFd) {
qlist_remove(&qmi_msg->qnode);
free(qmi_msg);
break;
}
}
dprintf("--- ClientFd=%d\n", qmi_con->ClientFd);
close(qmi_con->ClientFd);
qlist_remove(&qmi_con->qnode);
free(qmi_con);
break;
}
}
}
static void get_client_id(QMI_PROXY_CONNECTION *qmi_con, PQMICTL_GET_CLIENT_ID_RESP_MSG pClient) {
if (pClient->QMIResult == 0 && pClient->QMIError == 0) {
QMI_PROXY_CLINET *qmi_client = (QMI_PROXY_CLINET *)malloc(sizeof(QMI_PROXY_CLINET));
qlist_init(&qmi_client->qnode);
qmi_client->QMIType = pClient->QMIType;
qmi_client->ClientId = pClient->ClientId;
qmi_client->AccessTime = 0;
dprintf("+++ ClientFd=%d QMIType=%d ClientId=%d\n", qmi_con->ClientFd, qmi_client->QMIType, qmi_client->ClientId);
qlist_add_tail(&qmi_con->client_qnode, &qmi_client->qnode);
}
}
static void release_client_id(QMI_PROXY_CONNECTION *qmi_con, PQMICTL_RELEASE_CLIENT_ID_RESP_MSG pClient) {
struct qlistnode *client_node;
if (pClient->QMIResult == 0 && pClient->QMIError == 0) {
qlist_for_each (client_node, &qmi_con->client_qnode) {
QMI_PROXY_CLINET *qmi_client = qnode_to_item(client_node, QMI_PROXY_CLINET, qnode);
if (pClient->QMIType == qmi_client->QMIType && pClient->ClientId == qmi_client->ClientId) {
dprintf("--- ClientFd=%d QMIType=%d ClientId=%d\n", qmi_con->ClientFd, qmi_client->QMIType, qmi_client->ClientId);
qlist_remove(&qmi_client->qnode);
free(qmi_client);
break;
}
}
}
}
static void dump_qmi(PQCQMIMSG pQMI, int fd, const char flag)
{
if (verbose_debug)
{
unsigned i;
unsigned size = le16toh(pQMI->QMIHdr.Length) + 1;
char buf[128];
int cnt = 0;
cnt += snprintf(buf + cnt, sizeof(buf) - cnt, "%c %d %u: ", flag, fd, size);
for (i = 0; i < size && i < 24; i++)
cnt += snprintf(buf + cnt, sizeof(buf) - cnt, "%02x ", ((uint8_t *)pQMI)[i]);
dprintf("%s\n", buf)
}
}
static int send_qmi_to_cdc_wdm(PQCQMIMSG pQMI) {
struct pollfd pollfds[]= {{cdc_wdm_fd, POLLOUT, 0}};
ssize_t ret = 0;
do {
ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000);
} while (ret == -1 && errno == EINTR && qmi_proxy_quit == 0);
if (pollfds[0].revents & POLLOUT) {
ssize_t size = le16toh(pQMI->QMIHdr.Length) + 1;
ret = write(cdc_wdm_fd, pQMI, size);
dump_qmi(pQMI, cdc_wdm_fd, 'w');
}
return ret;
}
static int send_qmi_to_client(PQCQMIMSG pQMI, int clientFd) {
struct pollfd pollfds[]= {{clientFd, POLLOUT, 0}};
ssize_t ret = 0;
do {
ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000);
} while (ret == -1 && errno == EINTR && qmi_proxy_quit == 0);
if (pollfds[0].revents & POLLOUT) {
ssize_t size = le16toh(pQMI->QMIHdr.Length) + 1;
ret = write(clientFd, pQMI, size);
dump_qmi(pQMI, clientFd, 'w');
}
return ret;
}
static void recv_qmi_from_dev(PQCQMIMSG pQMI) {
struct qlistnode *con_node, *client_node;
if (qmi_proxy_server_fd == -1) {
qmi_sync_done = 1;
}
else if (pQMI->QMIHdr.QMIType == QMUX_TYPE_CTL) {
if (pQMI->CTLMsg.QMICTLMsgHdr.CtlFlags == QMICTL_CTL_FLAG_RSP) {
if (!qlist_empty(&qmi_proxy_ctl_msg)) {
QMI_PROXY_MSG *qmi_msg = qnode_to_item(qlist_head(&qmi_proxy_ctl_msg), QMI_PROXY_MSG, qnode);
if (qmi_msg->qmi[0].CTLMsg.QMICTLMsgHdrRsp.TransactionId != pQMI->CTLMsg.QMICTLMsgHdrRsp.TransactionId
|| qmi_msg->qmi[0].CTLMsg.QMICTLMsgHdrRsp.QMICTLType != pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) {
dprintf("ERROR: ctl rsp tid:%d, type:%d - ctl req %d, %d\n",
pQMI->CTLMsg.QMICTLMsgHdrRsp.TransactionId, pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType,
qmi_msg->qmi[0].CTLMsg.QMICTLMsgHdrRsp.TransactionId, qmi_msg->qmi[0].CTLMsg.QMICTLMsgHdrRsp.QMICTLType);
}
else if (qmi_msg->ClientFd == qmi_proxy_server_fd) {
if (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_RELEASE_CLIENT_ID_RESP) {
dprintf("--- ClientFd=%d QMIType=%d ClientId=%d\n", qmi_proxy_server_fd,
pQMI->CTLMsg.ReleaseClientIdRsp.QMIType, pQMI->CTLMsg.ReleaseClientIdRsp.ClientId);
}
}
else {
qlist_for_each(con_node, &qmi_proxy_connection) {
QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode);
if (qmi_con->ClientFd == qmi_msg->ClientFd) {
send_qmi_to_client(pQMI, qmi_msg->ClientFd);
if (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_GET_CLIENT_ID_RESP) {
get_client_id(qmi_con, &pQMI->CTLMsg.GetClientIdRsp);
}
else if (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_RELEASE_CLIENT_ID_RESP) {
release_client_id(qmi_con, &pQMI->CTLMsg.ReleaseClientIdRsp);
}
else {
}
}
}
}
qlist_remove(&qmi_msg->qnode);
free(qmi_msg);
if (!qlist_empty(&qmi_proxy_ctl_msg)) {
QMI_PROXY_MSG *qmi_msg = qnode_to_item(qlist_head(&qmi_proxy_ctl_msg), QMI_PROXY_MSG, qnode);
send_qmi_to_cdc_wdm(qmi_msg->qmi);
}
}
}
else if (pQMI->QMIHdr.QMIType == QMICTL_CTL_FLAG_IND) {
if (le16toh(pQMI->CTLMsg.QMICTLMsgHdrRsp.QMICTLType) == QMICTL_REVOKE_CLIENT_ID_IND) {
modem_reset_flag = 1;
}
}
}
else {
qlist_for_each(con_node, &qmi_proxy_connection) {
QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode);
qlist_for_each(client_node, &qmi_con->client_qnode) {
QMI_PROXY_CLINET *qmi_client = qnode_to_item(client_node, QMI_PROXY_CLINET, qnode);
if (pQMI->QMIHdr.QMIType == qmi_client->QMIType) {
if (pQMI->QMIHdr.ClientId == 0 || pQMI->QMIHdr.ClientId == qmi_client->ClientId) {
send_qmi_to_client(pQMI, qmi_con->ClientFd);
}
}
}
}
}
}
static int recv_qmi_from_client(PQCQMIMSG pQMI, unsigned size, int clientfd) {
if (qmi_proxy_server_fd == -1)
return -1;
if (pQMI->QMIHdr.QMIType == QMUX_TYPE_CTL) {
QMI_PROXY_MSG *qmi_msg;
if (pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType == QMICTL_SYNC_REQ) {
dprintf("do not allow client send QMICTL_SYNC_REQ\n");
return 0;
}
qmi_msg = malloc(sizeof(QMI_PROXY_MSG) + size);
qlist_init(&qmi_msg->qnode);
qmi_msg->ClientFd = clientfd;
memcpy(qmi_msg->qmi, pQMI, size);
if (qlist_empty(&qmi_proxy_ctl_msg))
send_qmi_to_cdc_wdm(pQMI);
qlist_add_tail(&qmi_proxy_ctl_msg, &qmi_msg->qnode);
}
else {
send_qmi_to_cdc_wdm(pQMI);
}
return 0;
}
static int qmi_proxy_init(unsigned retry) {
unsigned i;
QCQMIMSG _QMI;
PQCQMIMSG pQMI = &_QMI;
dprintf("%s enter\n", __func__);
pQMI->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
pQMI->QMIHdr.CtlFlags = 0x00;
pQMI->QMIHdr.QMIType = QMUX_TYPE_CTL;
pQMI->QMIHdr.ClientId= 0x00;
pQMI->CTLMsg.QMICTLMsgHdr.CtlFlags = QMICTL_FLAG_REQUEST;
qmi_sync_done = 0;
for (i = 0; i < retry; i++) {
pQMI->CTLMsg.SyncReq.TransactionId = i+1;
pQMI->CTLMsg.SyncReq.QMICTLType = htole16(QMICTL_SYNC_REQ);
pQMI->CTLMsg.SyncReq.Length = htole16(0);
pQMI->QMIHdr.Length =
htole16(le16toh(pQMI->CTLMsg.QMICTLMsgHdr.Length) + sizeof(QCQMI_HDR) + sizeof(QCQMICTL_MSG_HDR) - 1);
if (send_qmi_to_cdc_wdm(pQMI) <= 0)
break;
sleep(1);
if (qmi_sync_done)
break;
}
dprintf("%s %s\n", __func__, qmi_sync_done ? "succful" : "fail");
return qmi_sync_done ? 0 : -1;
}
static void *qmi_proxy_loop(void *param)
{
PQCQMIMSG pQMI = (PQCQMIMSG)qmi_buf;
struct qlistnode *con_node;
QMI_PROXY_CONNECTION *qmi_con;
(void)param;
dprintf("%s enter thread_id %p\n", __func__, (void *)pthread_self());
qlist_init(&qmi_proxy_connection);
qlist_init(&qmi_proxy_ctl_msg);
while (cdc_wdm_fd > 0 && qmi_proxy_quit == 0) {
struct pollfd pollfds[2+64];
int ne, ret, nevents = 0;
ssize_t nreads;
pollfds[nevents].fd = cdc_wdm_fd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
if (qmi_proxy_server_fd > 0) {
pollfds[nevents].fd = qmi_proxy_server_fd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
}
qlist_for_each(con_node, &qmi_proxy_connection) {
qmi_con = qnode_to_item(con_node, QMI_PROXY_CONNECTION, qnode);
pollfds[nevents].fd = qmi_con->ClientFd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
if (nevents == (sizeof(pollfds)/sizeof(pollfds[0])))
break;
}
#if 0
dprintf("poll ");
for (ne = 0; ne < nevents; ne++) {
dprintf("%d ", pollfds[ne].fd);
}
dprintf("\n");
#endif
do {
//ret = poll(pollfds, nevents, -1);
ret = poll(pollfds, nevents, (qmi_proxy_server_fd > 0) ? -1 : 200);
} while (ret == -1 && errno == EINTR && qmi_proxy_quit == 0);
if (ret < 0) {
dprintf("%s poll=%d, errno: %d (%s)\n", __func__, ret, errno, strerror(errno));
goto qmi_proxy_loop_exit;
}
for (ne = 0; ne < nevents; ne++) {
int fd = pollfds[ne].fd;
short revents = pollfds[ne].revents;
if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
dprintf("%s poll fd = %d, revents = %04x\n", __func__, fd, revents);
if (fd == cdc_wdm_fd) {
goto qmi_proxy_loop_exit;
} else if(fd == qmi_proxy_server_fd) {
} else {
cleanup_qmi_connection(fd, 1);
}
continue;
}
if (!(pollfds[ne].revents & POLLIN)) {
continue;
}
if (fd == qmi_proxy_server_fd) {
accept_qmi_connection(fd);
}
else if (fd == cdc_wdm_fd) {
nreads = read(fd, pQMI, sizeof(qmi_buf));
if (nreads <= 0) {
dprintf("%s read=%d errno: %d (%s)\n", __func__, (int)nreads, errno, strerror(errno));
goto qmi_proxy_loop_exit;
}
#ifdef QUECTEL_QMI_MERGE
if(merge_qmi_rsp_packet(pQMI, &nreads))
continue;
#endif
if (nreads != (le16toh(pQMI->QMIHdr.Length) + 1)) {
dprintf("%s nreads=%d, pQCQMI->QMIHdr.Length = %d\n", __func__, (int)nreads, le16toh(pQMI->QMIHdr.Length));
continue;
}
dump_qmi(pQMI, fd, 'r');
recv_qmi_from_dev(pQMI);
if (modem_reset_flag)
goto qmi_proxy_loop_exit;
}
else {
nreads = read(fd, pQMI, sizeof(qmi_buf));
if (nreads <= 0) {
dprintf("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno));
cleanup_qmi_connection(fd, 1);
break;
}
if (nreads != (le16toh(pQMI->QMIHdr.Length) + 1)) {
dprintf("%s nreads=%d, pQCQMI->QMIHdr.Length = %d\n", __func__, (int)nreads, le16toh(pQMI->QMIHdr.Length));
continue;
}
dump_qmi(pQMI, fd, 'r');
recv_qmi_from_client(pQMI, nreads, fd);
}
}
}
qmi_proxy_loop_exit:
while (!qlist_empty(&qmi_proxy_connection)) {
QMI_PROXY_CONNECTION *qmi_con = qnode_to_item(qlist_head(&qmi_proxy_connection), QMI_PROXY_CONNECTION, qnode);
cleanup_qmi_connection(qmi_con->ClientFd, 0);
}
dprintf("%s exit, thread_id %p\n", __func__, (void *)pthread_self());
return NULL;
}
static void usage(void) {
dprintf(" -d <device_name> A valid qmi device\n"
" default /dev/cdc-wdm0, but cdc-wdm0 may be invalid\n"
" -i <netcard_name> netcard name\n"
" -v Will show all details\n");
}
static void sig_action(int sig) {
if (qmi_proxy_quit++ == 0) {
if (thread_id)
pthread_kill(thread_id, sig);
}
}
int main(int argc, char *argv[]) {
int opt;
char cdc_wdm[32+1] = "/dev/cdc-wdm0";
char servername[64] = {0};
optind = 1;
signal(SIGINT, sig_action);
while ( -1 != (opt = getopt(argc, argv, "d:i:vh"))) {
switch (opt) {
case 'd':
strcpy(cdc_wdm, optarg);
break;
case 'v':
verbose_debug = 1;
break;
default:
usage();
return 0;
}
}
sprintf(servername, "quectel-qmi-proxy%c", cdc_wdm[strlen(cdc_wdm)-1]);
dprintf("Will use cdc-wdm='%s', proxy='%s'\n", cdc_wdm, servername);
while (qmi_proxy_quit == 0) {
cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (cdc_wdm_fd == -1) {
dprintf("Failed to open %s, errno: %d (%s)\n", cdc_wdm, errno, strerror(errno));
sleep(3);
continue;
}
cfmakenoblock(cdc_wdm_fd);
/* no qmi_proxy_loop lives, create one */
pthread_create(&thread_id, NULL, qmi_proxy_loop, NULL);
if (qmi_proxy_init(60) == 0) {
qmi_proxy_server_fd = create_local_server(servername);
dprintf("qmi_proxy_server_fd = %d\n", qmi_proxy_server_fd);
if (qmi_proxy_server_fd == -1) {
dprintf("Failed to create %s, errno: %d (%s)\n", servername, errno, strerror(errno));
pthread_cancel(thread_id);
}
}
else {
pthread_cancel(thread_id);
}
pthread_join(thread_id, NULL);
thread_id = 0;
if (qmi_proxy_server_fd != -1) {
dprintf("close server %s\n", servername);
close(qmi_proxy_server_fd);
qmi_proxy_server_fd = -1;
}
close(cdc_wdm_fd);
cdc_wdm_fd = -1;
if (qmi_proxy_quit == 0)
sleep(modem_reset_flag ? 30 : 3);
modem_reset_flag = 0;
}
return 0;
}
@@ -0,0 +1,902 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <fcntl.h>
#include <pthread.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <linux/un.h>
#include <linux/if.h>
#include <dirent.h>
#include <signal.h>
#include <inttypes.h>
#include <linux/socket.h>
#include <linux/qrtr.h>
#define QRTR_PROTO_VER_1 1
struct qrtr_hdr_v1 {
__le32 version;
__le32 type;
__le32 src_node_id;
__le32 src_port_id;
__le32 confirm_rx;
__le32 size;
__le32 dst_node_id;
__le32 dst_port_id;
} __attribute__ ((packed));
#include "qendian.h"
#include "qlist.h"
#include "QCQMI.h"
#include "QCQCTL.h"
#include "QCQMUX.h"
static const char * get_time(void) {
static char time_buf[128];
struct timeval tv;
time_t time;
suseconds_t millitm;
struct tm *ti;
gettimeofday (&tv, NULL);
time= tv.tv_sec;
millitm = (tv.tv_usec + 500) / 1000;
if (millitm == 1000) {
++time;
millitm = 0;
}
ti = localtime(&time);
sprintf(time_buf, "[%02d-%02d_%02d:%02d:%02d:%03d]", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm);
return time_buf;
}
#define dprintf(fmt, args...) do { fprintf(stdout, "%s " fmt, get_time(), ##args); } while(0);
#define SYSCHECK(c) do{if((c)<0) {dprintf("%s %d error: '%s' (code: %d)\n", __func__, __LINE__, strerror(errno), errno); return -1;}}while(0)
#define cfmakenoblock(fd) do{fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);}while(0)
#define align_4(_len) (((_len) + 3) & ~3)
typedef struct {
struct qlistnode qnode;
int ClientFd;
QCQMIMSG qrtr[0];
} QRTR_PROXY_MSG;
typedef struct {
struct qlistnode qnode;
uint8_t QMIType;
uint8_t ClientId;
uint32_t node_id;
uint32_t port_id;
unsigned AccessTime;
} QRTR_PROXY_CLINET;
typedef struct {
struct qlistnode qnode;
struct qlistnode client_qnode;
int ClientFd;
unsigned AccessTime;
} QRTR_PROXY_CONNECTION;
typedef struct {
struct qlistnode qnode;
uint32_t service;
uint32_t version;
uint32_t instance;
uint32_t node;
uint32_t port;
__le32 src_node_id;
__le32 src_port_id;
} QRTR_SERVICE;
static int qrtr_proxy_quit = 0;
static pthread_t thread_id = 0;
static int cdc_wdm_fd = -1;
static int qrtr_proxy_server_fd = -1;
static struct qlistnode qrtr_proxy_connection;
static struct qlistnode qrtr_server_list;
static int verbose_debug = 0;
static uint32_t node_modem = 3; //IPQ ~ 3, QCM ~ 0
static uint32_t node_myself = 1;
static QRTR_SERVICE *find_qrtr_service(uint8_t QMIType)
{
struct qlistnode *node;
qlist_for_each (node, &qrtr_server_list) {
QRTR_SERVICE *srv = qnode_to_item(node, QRTR_SERVICE, qnode);
if (srv->service == QMIType)
return srv;
}
return NULL;
}
static uint8_t client_bitmap[0xf0];
static uint8_t port_bitmap[0xff0];
static int alloc_client_id(void) {
int id = 1;
for (id = 1; id < (int)sizeof(client_bitmap); id++) {
if (client_bitmap[id] == 0) {
client_bitmap[id] = id;
return id;
}
}
dprintf("NOT find %s()\n", __func__);
return 0;
}
static void free_client_id(int id) {
if (id < (int)sizeof(client_bitmap) && client_bitmap[id] == id) {
client_bitmap[id] = 0;
return;
}
dprintf("NOT find %s(id=%d)\n", __func__, id);
}
static int alloc_port_id(void) {
int id = 1;
for (id = 1; id < (int)sizeof(port_bitmap); id++) {
if (port_bitmap[id] == 0) {
port_bitmap[id] = id;
return id;
}
}
dprintf("NOT find %s()\n", __func__);
return 0;
}
static void free_port_id(int id) {
if (id < (int)sizeof(port_bitmap) && port_bitmap[id] == id) {
port_bitmap[id] = 0;
return;
}
dprintf("NOT find %s(id=%d)\n", __func__, id);
}
static void dump_qrtr(void *buf, size_t len, char flag)
{
size_t i;
static char printf_buf[1024];
int cnt = 0, limit=1024;
unsigned char *d = (unsigned char *)buf;
struct qrtr_hdr_v1 *hdr = (struct qrtr_hdr_v1 *)buf;
const char *ctrl_pkt_strings[] = {
[QRTR_TYPE_DATA] = "data",
[QRTR_TYPE_HELLO] = "hello",
[QRTR_TYPE_BYE] = "bye",
[QRTR_TYPE_NEW_SERVER] = "new-server",
[QRTR_TYPE_DEL_SERVER] = "del-server",
[QRTR_TYPE_DEL_CLIENT] = "del-client",
[QRTR_TYPE_RESUME_TX] = "resume-tx",
[QRTR_TYPE_EXIT] = "exit",
[QRTR_TYPE_PING] = "ping",
[QRTR_TYPE_NEW_LOOKUP] = "new-lookup",
[QRTR_TYPE_DEL_LOOKUP] = "del-lookup",
};
for (i = 0; i < len && i < 64; i++) {
if (i%4 == 0)
cnt += snprintf(printf_buf+cnt, limit-cnt, " ");
cnt += snprintf(printf_buf+cnt, limit-cnt, "%02x", d[i]);
}
dprintf("%s\n", printf_buf);
dprintf("%c ver=%d, type=%d(%s), %x,%x -> %x,%x, confirm_rx=%d, size=%u\n",
flag,
le32toh(hdr->version), le32toh(hdr->type), ctrl_pkt_strings[le32toh(hdr->type)],
le32toh(hdr->src_node_id), le32toh(hdr->src_port_id), le32toh(hdr->dst_node_id), le32toh(hdr->dst_port_id),
le32toh(hdr->confirm_rx), le32toh(hdr->size));
}
static int send_qmi_to_client(PQCQMIMSG pQMI, int fd) {
struct pollfd pollfds[]= {{fd, POLLOUT, 0}};
ssize_t ret = 0;
ssize_t size = le16toh(pQMI->QMIHdr.Length) + 1;
do {
ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000);
} while (ret == -1 && errno == EINTR && qrtr_proxy_quit == 0);
if (pollfds[0].revents & POLLOUT) {
ret = write(fd, pQMI, size);
}
return ret == size ? 0 : -1;
}
static int send_qrtr_to_dev(struct qrtr_hdr_v1 *hdr, int fd) {
struct pollfd pollfds[]= {{fd, POLLOUT, 0}};
ssize_t ret = 0;
ssize_t size = align_4(le32toh(hdr->size) + sizeof(*hdr));
do {
ret = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), 5000);
} while (ret == -1 && errno == EINTR && qrtr_proxy_quit == 0);
if (pollfds[0].revents & POLLOUT) {
ret = write(fd, hdr, size);
}
return ret == size ? 0 : -1;
}
static int qrtr_node_enqueue(const void *data, size_t len,
int type, struct sockaddr_qrtr *from,
struct sockaddr_qrtr *to, unsigned int confirm_rx)
{
int rc = -1;
size_t size = sizeof(struct qrtr_hdr_v1) + len;
struct qrtr_hdr_v1 *hdr = (struct qrtr_hdr_v1 *)malloc(align_4(size));
if (hdr) {
hdr->version = htole32(QRTR_PROTO_VER_1);
hdr->type = htole32(type);
hdr->src_node_id = htole32(from->sq_node);
hdr->src_port_id = htole32(from->sq_port);
hdr->dst_node_id = htole32(to->sq_node);
hdr->dst_port_id = htole32(to->sq_port);
hdr->size = htole32(len);
hdr->confirm_rx = htole32(!!confirm_rx);
memcpy(hdr + 1, data, len);
dump_qrtr(hdr, size, '>');
send_qrtr_to_dev(hdr, cdc_wdm_fd);
free(hdr);
}
return rc;
}
static int send_ctrl_hello(__u32 sq_node, __u32 sq_port)
{
struct qrtr_ctrl_pkt pkt;
int rc;
struct sockaddr_qrtr to = {AF_QIPCRTR, sq_node, sq_port};
struct sockaddr_qrtr from = {AF_QIPCRTR, node_myself, QRTR_PORT_CTRL};
memset(&pkt, 0, sizeof(pkt));
pkt.cmd = htole32(QRTR_TYPE_HELLO);
rc = qrtr_node_enqueue(&pkt, sizeof(pkt), QRTR_TYPE_HELLO, &from, &to, 0);
if (rc < 0)
return rc;
return 0;
}
static int ctrl_cmd_del_client(__u32 sq_node, __u32 sq_port, uint8_t QMIType)
{
struct qrtr_ctrl_pkt pkt;
int rc;
struct sockaddr_qrtr to = {AF_QIPCRTR, QRTR_NODE_BCAST, QRTR_PORT_CTRL};
struct sockaddr_qrtr from = {AF_QIPCRTR, sq_node, sq_port};
QRTR_SERVICE *srv = find_qrtr_service(QMIType);
if (srv) {
to.sq_node = srv->src_node_id;
}
memset(&pkt, 0, sizeof(pkt));
pkt.cmd = htole32(QRTR_TYPE_DEL_CLIENT);
pkt.client.node = htole32(sq_node);
pkt.client.port = htole32(sq_port);
rc = qrtr_node_enqueue(&pkt, sizeof(pkt), QRTR_TYPE_DATA, &from, &to, 0);
if (rc < 0)
return rc;
return 0;
}
static void handle_server_change(struct qrtr_hdr_v1 *hdr) {
struct qrtr_ctrl_pkt *pkt = (struct qrtr_ctrl_pkt *)(hdr + 1);
QRTR_SERVICE *s;
dprintf ("[qrtr] %s server on %u:%u(%u:%u) -> service %u, instance %x\n",
QRTR_TYPE_NEW_SERVER == hdr->type ? "add" : "remove",
le32toh(pkt->server.node), le32toh(pkt->server.port),
le32toh(hdr->src_node_id), le32toh(hdr->src_port_id),
le32toh(pkt->server.service), le32toh(pkt->server.instance));
if (le32toh(pkt->server.node) != node_modem) {
return; //we only care modem
}
s = (QRTR_SERVICE *)malloc(sizeof(QRTR_SERVICE));
if (!s)
return;
qlist_init(&s->qnode);
s->service = le32toh(pkt->server.service);
s->version = le32toh(pkt->server.instance) & 0xff;
s->instance = le32toh(pkt->server.instance) >> 8;
s->node = le32toh(pkt->server.node);
s->port = le32toh(pkt->server.port);
s->src_node_id = le32toh(hdr->src_node_id);
s->src_port_id = le32toh(hdr->src_port_id);
if (QRTR_TYPE_NEW_SERVER == hdr->type) {
qlist_add_tail(&qrtr_server_list, &s->qnode);
}
else if (QRTR_TYPE_DEL_SERVER == hdr->type) {
qlist_remove(&s->qnode);
}
}
static int create_local_server(const char *name) {
int sockfd = -1;
int reuse_addr = 1;
struct sockaddr_un sockaddr;
socklen_t alen;
/*Create server socket*/
SYSCHECK(sockfd = socket(AF_LOCAL, SOCK_STREAM, 0));
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sun_family = AF_LOCAL;
sockaddr.sun_path[0] = 0;
memcpy(sockaddr.sun_path + 1, name, strlen(name) );
alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1;
SYSCHECK(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr)));
if(bind(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) {
close(sockfd);
dprintf("bind %s errno: %d (%s)\n", name, errno, strerror(errno));
return -1;
}
dprintf("local server: %s sockfd = %d\n", name, sockfd);
cfmakenoblock(sockfd);
listen(sockfd, 1);
return sockfd;
}
static uint8_t alloc_qrtr_client_id(QRTR_PROXY_CONNECTION *qrtr_con, uint8_t QMIType) {
QRTR_PROXY_CLINET *qrtr_client = (QRTR_PROXY_CLINET *)malloc(sizeof(QRTR_PROXY_CLINET));
qlist_init(&qrtr_client->qnode);
qrtr_client->QMIType = QMIType;
qrtr_client->ClientId = alloc_client_id();
qrtr_client->node_id = 1;
qrtr_client->port_id = alloc_port_id();
qrtr_client->AccessTime = 0;
dprintf("+++ ClientFd=%d QMIType=%d ClientId=%d, node_id=%d, port_id=%d\n",
qrtr_con->ClientFd, qrtr_client->QMIType, qrtr_client->ClientId,
qrtr_client->node_id, qrtr_client->port_id);
qlist_add_tail(&qrtr_con->client_qnode, &qrtr_client->qnode);
return qrtr_client->ClientId;
}
static void release_qrtr_client_id(QRTR_PROXY_CONNECTION *qrtr_con, uint8_t QMIType, uint8_t ClientId) {
struct qlistnode *client_node;
int find = 0;
qlist_for_each (client_node, &qrtr_con->client_qnode) {
QRTR_PROXY_CLINET *qrtr_client = qnode_to_item(client_node, QRTR_PROXY_CLINET, qnode);
if (QMIType == qrtr_client->QMIType && ClientId == qrtr_client->ClientId) {
dprintf("--- ClientFd=%d QMIType=%d ClientId=%d, node_id=%d, port_id=%d\n",
qrtr_con->ClientFd, qrtr_client->QMIType, qrtr_client->ClientId,
qrtr_client->node_id, qrtr_client->port_id);
ctrl_cmd_del_client(qrtr_client->node_id, qrtr_client->port_id, qrtr_client->QMIType);
free_client_id(qrtr_client->ClientId);
free_port_id(qrtr_client->port_id);
qlist_remove(&qrtr_client->qnode);
free(qrtr_client);
find++;
break;
}
}
if (!find) {
dprintf("NOT find on %s(ClientFd=%d, QMIType=%d, ClientId=%d)\n",
__func__, qrtr_con->ClientFd, QMIType, ClientId);
}
}
static void accept_qrtr_connection(int serverfd) {
int clientfd = -1;
unsigned char addr[128];
socklen_t alen = sizeof(addr);
QRTR_PROXY_CONNECTION *qrtr_con;
clientfd = accept(serverfd, (struct sockaddr *)addr, &alen);
qrtr_con = (QRTR_PROXY_CONNECTION *)malloc(sizeof(QRTR_PROXY_CONNECTION));
if (qrtr_con) {
qlist_init(&qrtr_con->qnode);
qlist_init(&qrtr_con->client_qnode);
qrtr_con->ClientFd= clientfd;
qrtr_con->AccessTime = 0;
dprintf("+++ ClientFd=%d\n", qrtr_con->ClientFd);
qlist_add_tail(&qrtr_proxy_connection, &qrtr_con->qnode);
}
cfmakenoblock(clientfd);
}
static void cleanup_qrtr_connection(int clientfd) {
struct qlistnode *con_node;
int find = 0;
qlist_for_each(con_node, &qrtr_proxy_connection) {
QRTR_PROXY_CONNECTION *qrtr_con = qnode_to_item(con_node, QRTR_PROXY_CONNECTION, qnode);
if (qrtr_con->ClientFd == clientfd) {
while (!qlist_empty(&qrtr_con->client_qnode)) {
QRTR_PROXY_CLINET *qrtr_client = qnode_to_item(qlist_head(&qrtr_con->client_qnode), QRTR_PROXY_CLINET, qnode);
release_qrtr_client_id(qrtr_con, qrtr_client->QMIType, qrtr_client->ClientId);
}
dprintf("--- ClientFd=%d\n", qrtr_con->ClientFd);
close(qrtr_con->ClientFd);
qlist_remove(&qrtr_con->qnode);
free(qrtr_con);
find = 1;
break;
}
}
if (!find) {
dprintf("NOT find on %s(ClientFd=%d)\n", __func__, clientfd);
}
}
static void recv_qrtr_from_dev(struct qrtr_hdr_v1 *hdr) {
int find = 0;
uint32_t type = le32toh(hdr->type);
if (type == QRTR_TYPE_HELLO) {
send_ctrl_hello(le32toh(hdr->src_node_id), le32toh(hdr->src_port_id));
find++;
}
else if (type == QRTR_TYPE_NEW_SERVER || type == QRTR_TYPE_DEL_SERVER) {
handle_server_change(hdr);
find++;
}
else if (type == QRTR_TYPE_DATA) {
struct qlistnode *con_node, *client_node;
qlist_for_each(con_node, &qrtr_proxy_connection) {
QRTR_PROXY_CONNECTION *qrtr_con = qnode_to_item(con_node, QRTR_PROXY_CONNECTION, qnode);
qlist_for_each(client_node, &qrtr_con->client_qnode) {
QRTR_PROXY_CLINET *qrtr_client = qnode_to_item(client_node, QRTR_PROXY_CLINET, qnode);
if (qrtr_client->node_id == le32toh(hdr->dst_node_id) && qrtr_client->port_id == le32toh(hdr->dst_port_id)) {
PQCQMIMSG pQMI = (PQCQMIMSG)malloc(hdr->size + sizeof(QCQMI_HDR));
if (pQMI) {
pQMI->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
pQMI->QMIHdr.Length = htole16(hdr->size + sizeof(QCQMI_HDR) - 1);
pQMI->QMIHdr.CtlFlags = 0x00;
pQMI->QMIHdr.QMIType = qrtr_client->QMIType;
pQMI->QMIHdr.ClientId = qrtr_client->ClientId;
memcpy(&pQMI->MUXMsg, hdr + 1, hdr->size);
send_qmi_to_client(pQMI, qrtr_con->ClientFd);
free(pQMI);
find++;
}
}
}
}
if (hdr->confirm_rx) {
struct qrtr_ctrl_pkt pkt;
struct sockaddr_qrtr from = {AF_QIPCRTR, le32toh(hdr->dst_node_id), le32toh(hdr->dst_port_id)};
struct sockaddr_qrtr to = {AF_QIPCRTR, le32toh(hdr->src_node_id), le32toh(hdr->src_port_id)};
memset(&pkt, 0, sizeof(pkt));
pkt.cmd = htole32(QRTR_TYPE_RESUME_TX);
pkt.client.node = hdr->dst_node_id;
pkt.client.port = hdr->dst_port_id;
qrtr_node_enqueue(&pkt, sizeof(pkt), QRTR_TYPE_RESUME_TX, &from, &to, 0);
}
}
else if (type == QRTR_TYPE_RESUME_TX) {
}
if (!find) {
dprintf("NOT find on %s()\n", __func__);
}
}
static int recv_qmi_from_client(PQCQMIMSG pQMI, int clientfd) {
QRTR_PROXY_CONNECTION *qrtr_con;
struct qlistnode *con_node, *client_node;
int find = 0;
qlist_for_each(con_node, &qrtr_proxy_connection) {
qrtr_con = qnode_to_item(con_node, QRTR_PROXY_CONNECTION, qnode);
if (qrtr_con->ClientFd == clientfd)
break;
qrtr_con = NULL;
}
if (!qrtr_con) {
return -1;
}
if (le16toh(pQMI->QMIHdr.QMIType) == QMUX_TYPE_CTL) {
if (pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType == QMICTL_SYNC_REQ) {
dprintf("do not allow client send QMICTL_SYNC_REQ\n");
return 0;
}
else if (le16toh(pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType) == QMICTL_GET_CLIENT_ID_REQ) {
uint8_t QMIType = pQMI->CTLMsg.GetClientIdReq.QMIType;
PQCQMIMSG pRsp = (PQCQMIMSG)malloc(256);
if (pRsp) {
uint8_t ClientId = 0;
if (find_qrtr_service(QMIType)) {
ClientId = alloc_qrtr_client_id(qrtr_con, QMIType);
}
pRsp->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
pRsp->QMIHdr.Length = htole16(sizeof(pRsp->CTLMsg.GetClientIdRsp) + sizeof(pRsp->QMIHdr) - 1);
pRsp->QMIHdr.CtlFlags = 0x00;
pRsp->QMIHdr.QMIType = QMUX_TYPE_CTL;
pRsp->QMIHdr.ClientId = 0;
pRsp->CTLMsg.QMICTLMsgHdrRsp.CtlFlags = QMICTL_FLAG_RESPONSE;
pRsp->CTLMsg.QMICTLMsgHdrRsp.TransactionId = pQMI->CTLMsg.QMICTLMsgHdr.TransactionId;
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMICTLType = pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType;
pRsp->CTLMsg.QMICTLMsgHdrRsp.Length = htole16(sizeof(pRsp->CTLMsg.GetClientIdRsp) - sizeof(pRsp->CTLMsg.QMICTLMsgHdr));
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVType = QCTLV_TYPE_RESULT_CODE;
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVLength = htole16(4);
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult = htole16(ClientId ? 0 : QMI_RESULT_FAILURE);
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError = htole16(ClientId ? 0 : QMI_ERR_INTERNAL);
pRsp->CTLMsg.GetClientIdRsp.TLV2Type = QCTLV_TYPE_REQUIRED_PARAMETER;
pRsp->CTLMsg.GetClientIdRsp.TLV2Length = htole16(2);
pRsp->CTLMsg.GetClientIdRsp.QMIType = QMIType;
pRsp->CTLMsg.GetClientIdRsp.ClientId = ClientId;
send_qmi_to_client(pRsp, clientfd);
free(pRsp);
find++;
}
}
else if (le16toh(pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType) == QMICTL_RELEASE_CLIENT_ID_REQ) {
PQCQMIMSG pRsp = (PQCQMIMSG)malloc(256);
release_qrtr_client_id(qrtr_con, pQMI->CTLMsg.ReleaseClientIdReq.QMIType, pQMI->CTLMsg.ReleaseClientIdReq.ClientId);
if (pRsp) {
pRsp->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
pRsp->QMIHdr.Length = htole16(sizeof(pRsp->CTLMsg.ReleaseClientIdRsp) + sizeof(pRsp->QMIHdr) - 1);
pRsp->QMIHdr.CtlFlags = 0x00;
pRsp->QMIHdr.QMIType = QMUX_TYPE_CTL;
pRsp->QMIHdr.ClientId = 0;
pRsp->CTLMsg.QMICTLMsgHdrRsp.CtlFlags = QMICTL_FLAG_RESPONSE;
pRsp->CTLMsg.QMICTLMsgHdrRsp.TransactionId = pQMI->CTLMsg.QMICTLMsgHdr.TransactionId;
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMICTLType = pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType;
pRsp->CTLMsg.QMICTLMsgHdrRsp.Length = htole16(sizeof(pRsp->CTLMsg.ReleaseClientIdRsp) - sizeof(pRsp->CTLMsg.QMICTLMsgHdr));
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVType = QCTLV_TYPE_RESULT_CODE;
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVLength = htole16(4);
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult = htole16(0);
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError = htole16(0);
pRsp->CTLMsg.ReleaseClientIdRsp.TLV2Type = QCTLV_TYPE_REQUIRED_PARAMETER;
pRsp->CTLMsg.ReleaseClientIdRsp.TLV2Length = htole16(2);
pRsp->CTLMsg.ReleaseClientIdRsp.QMIType = pQMI->CTLMsg.ReleaseClientIdReq.QMIType;
pRsp->CTLMsg.ReleaseClientIdRsp.ClientId = pQMI->CTLMsg.ReleaseClientIdReq.ClientId;
send_qmi_to_client(pRsp, clientfd);
free(pRsp);
find++;
}
}
else if (le16toh(pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType) == QMICTL_GET_VERSION_REQ) {
PQCQMIMSG pRsp = (PQCQMIMSG)malloc(256);
if (pRsp) {
pRsp->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI;
pRsp->QMIHdr.Length = htole16(sizeof(pRsp->CTLMsg.GetVersionRsp) + sizeof(pRsp->QMIHdr) - 1);
pRsp->QMIHdr.CtlFlags = 0x00;
pRsp->QMIHdr.QMIType = QMUX_TYPE_CTL;
pRsp->QMIHdr.ClientId = 0;
pRsp->CTLMsg.QMICTLMsgHdrRsp.CtlFlags = QMICTL_FLAG_RESPONSE;
pRsp->CTLMsg.QMICTLMsgHdrRsp.TransactionId = pQMI->CTLMsg.QMICTLMsgHdr.TransactionId;
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMICTLType = pQMI->CTLMsg.QMICTLMsgHdr.QMICTLType;
pRsp->CTLMsg.QMICTLMsgHdrRsp.Length = htole16(sizeof(pRsp->CTLMsg.GetVersionRsp) - sizeof(pRsp->CTLMsg.QMICTLMsgHdr));
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVType = QCTLV_TYPE_RESULT_CODE;
pRsp->CTLMsg.QMICTLMsgHdrRsp.TLVLength = htole16(4);
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXResult = htole16(0);
pRsp->CTLMsg.QMICTLMsgHdrRsp.QMUXError = htole16(0);
pRsp->CTLMsg.GetVersionRsp.TLV2Type = QCTLV_TYPE_REQUIRED_PARAMETER;
pRsp->CTLMsg.GetVersionRsp.TLV2Length = htole16(1);
pRsp->CTLMsg.GetVersionRsp.NumElements = 0;
send_qmi_to_client(pRsp, clientfd);
free(pRsp);
find++;
}
}
}
else {
qlist_for_each (client_node, &qrtr_con->client_qnode) {
QRTR_PROXY_CLINET *qrtr_client = qnode_to_item(client_node, QRTR_PROXY_CLINET, qnode);
if (pQMI->QMIHdr.QMIType == qrtr_client->QMIType && pQMI->QMIHdr.ClientId == qrtr_client->ClientId) {
QRTR_SERVICE *srv = find_qrtr_service(pQMI->QMIHdr.QMIType);
if (srv && srv->service) {
struct sockaddr_qrtr from = {AF_QIPCRTR, qrtr_client->node_id, qrtr_client->port_id};
struct sockaddr_qrtr to = {AF_QIPCRTR, srv->node, srv->port};
qrtr_node_enqueue(&pQMI->MUXMsg, le16toh(pQMI->QMIHdr.Length) + 1 - sizeof(QCQMI_HDR),
QRTR_TYPE_DATA, &from, &to, 0);
find++;
}
break;
}
}
}
if (!find) {
dprintf("NOT find on %s()\n", __func__);
}
return 0;
}
static int qrtr_proxy_init(void) {
unsigned i;
int qrtr_sync_done = 0;
dprintf("%s enter\n", __func__);
send_ctrl_hello(QRTR_NODE_BCAST, QRTR_PORT_CTRL);
for (i = 0; i < 10; i++) {
sleep(1);
qrtr_sync_done = !qlist_empty(&qrtr_server_list);
if (qrtr_sync_done)
break;
}
dprintf("%s %s\n", __func__, qrtr_sync_done ? "succful" : "fail");
return qrtr_sync_done ? 0 : -1;
}
static void qrtr_start_server(const char* servername) {
qrtr_proxy_server_fd = create_local_server(servername);
dprintf("qrtr_proxy_server_fd = %d\n", qrtr_proxy_server_fd);
if (qrtr_proxy_server_fd == -1) {
dprintf("Failed to create %s, errno: %d (%s)\n", servername, errno, strerror(errno));
}
}
static void qrtr_close_server(const char* servername) {
if (qrtr_proxy_server_fd != -1) {
dprintf("%s %s\n", __func__, servername);
close(qrtr_proxy_server_fd);
qrtr_proxy_server_fd = -1;
}
}
static void *qrtr_proxy_loop(void *param)
{
void *rx_buf;
struct qlistnode *con_node;
QRTR_PROXY_CONNECTION *qrtr_con;
(void)param;
dprintf("%s enter thread_id %p\n", __func__, (void *)pthread_self());
rx_buf = malloc(8192);
if (!rx_buf)
return NULL;
while (cdc_wdm_fd > 0 && qrtr_proxy_quit == 0) {
struct pollfd pollfds[32];
int ne, ret, nevents = 0;
ssize_t nreads;
pollfds[nevents].fd = cdc_wdm_fd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
if (qrtr_proxy_server_fd > 0) {
pollfds[nevents].fd = qrtr_proxy_server_fd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
}
qlist_for_each(con_node, &qrtr_proxy_connection) {
qrtr_con = qnode_to_item(con_node, QRTR_PROXY_CONNECTION, qnode);
pollfds[nevents].fd = qrtr_con->ClientFd;
pollfds[nevents].events = POLLIN;
pollfds[nevents].revents= 0;
nevents++;
if (nevents == (sizeof(pollfds)/sizeof(pollfds[0])))
break;
}
do {
//ret = poll(pollfds, nevents, -1);
ret = poll(pollfds, nevents, (qrtr_proxy_server_fd > 0) ? -1 : 200);
} while (ret == -1 && errno == EINTR && qrtr_proxy_quit == 0);
if (ret < 0) {
dprintf("%s poll=%d, errno: %d (%s)\n", __func__, ret, errno, strerror(errno));
goto qrtr_proxy_loop_exit;
}
for (ne = 0; ne < nevents; ne++) {
int fd = pollfds[ne].fd;
short revents = pollfds[ne].revents;
if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
dprintf("%s poll fd = %d, revents = %04x\n", __func__, fd, revents);
if (fd == cdc_wdm_fd) {
goto qrtr_proxy_loop_exit;
}
else if (fd == qrtr_proxy_server_fd) {
}
else {
cleanup_qrtr_connection(fd);
}
continue;
}
if (!(pollfds[ne].revents & POLLIN)) {
continue;
}
if (fd == qrtr_proxy_server_fd) {
accept_qrtr_connection(fd);
}
else if (fd == cdc_wdm_fd) {
struct qrtr_hdr_v1 *hdr = (struct qrtr_hdr_v1 *)rx_buf;
nreads = read(fd, rx_buf, 8192);
if (nreads <= 0) {
dprintf("%s read=%d errno: %d (%s)\n", __func__, (int)nreads, errno, strerror(errno));
goto qrtr_proxy_loop_exit;
}
else if (nreads != (int)align_4(le32toh(hdr->size) + sizeof(*hdr))) {
dprintf("%s nreads=%d, hdr->size = %d\n", __func__, (int)nreads, le32toh(hdr->size));
continue;
}
dump_qrtr(hdr, nreads, '<');
recv_qrtr_from_dev(hdr);
}
else {
PQCQMIMSG pQMI = (PQCQMIMSG)rx_buf;
nreads = read(fd, rx_buf, 8192);
if (nreads <= 0) {
dprintf("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno));
cleanup_qrtr_connection(fd);
break;
}
else if (nreads != (le16toh(pQMI->QMIHdr.Length) + 1)) {
dprintf("%s nreads=%d, pQCQMI->QMIHdr.Length = %d\n", __func__, (int)nreads, le16toh(pQMI->QMIHdr.Length));
continue;
}
recv_qmi_from_client(pQMI, fd);
}
}
}
qrtr_proxy_loop_exit:
while (!qlist_empty(&qrtr_proxy_connection)) {
QRTR_PROXY_CONNECTION *qrtr_con = qnode_to_item(qlist_head(&qrtr_proxy_connection), QRTR_PROXY_CONNECTION, qnode);
cleanup_qrtr_connection(qrtr_con->ClientFd);
}
dprintf("%s exit, thread_id %p\n", __func__, (void *)pthread_self());
free(rx_buf);
return NULL;
}
static void usage(void) {
dprintf(" -d <device_name> A valid qrtr device\n"
" default /dev/mhi_IPCR, but mhi_IPCR may be invalid\n"
" -i <netcard_name> netcard name\n"
" -v Will show all details\n");
}
static void sig_action(int sig) {
if (qrtr_proxy_quit == 0) {
qrtr_proxy_quit = 1;
if (thread_id)
pthread_kill(thread_id, sig);
}
}
int main(int argc, char *argv[]) {
int opt;
char cdc_wdm[32+1] = "/dev/mhi_IPCR";
char servername[64] = {0};
signal(SIGINT, sig_action);
signal(SIGTERM, sig_action);
optind = 1;
while ( -1 != (opt = getopt(argc, argv, "d:i:vh"))) {
switch (opt) {
case 'd':
strcpy(cdc_wdm, optarg);
break;
case 'v':
verbose_debug = 1;
break;
default:
usage();
return 0;
}
}
sprintf(servername, "quectel-qrtr-proxy%c", cdc_wdm[strlen(cdc_wdm)-1]);
dprintf("Will use cdc-wdm='%s', proxy='%s'\n", cdc_wdm, servername);
while (qrtr_proxy_quit == 0) {
cdc_wdm_fd = open(cdc_wdm, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (cdc_wdm_fd == -1) {
dprintf("Failed to open %s, errno: %d (%s)\n", cdc_wdm, errno, strerror(errno));
sleep(5);
continue;
}
cfmakenoblock(cdc_wdm_fd);
qlist_init(&qrtr_proxy_connection);
qlist_init(&qrtr_server_list);
pthread_create(&thread_id, NULL, qrtr_proxy_loop, NULL);
if (qrtr_proxy_init() == 0) {
qrtr_start_server(servername);
pthread_join(thread_id, NULL);
qrtr_close_server(servername);
}
else {
pthread_cancel(thread_id);
pthread_join(thread_id, NULL);
}
close(cdc_wdm_fd);
}
return 0;
}
+508
View File
@@ -0,0 +1,508 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
//https://source.codeaurora.org/quic/la/platform/vendor/qcom-opensource/dataservices/tree/rmnetctl
#include <sys/socket.h>
#include <stdint.h>
#include <linux/netlink.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/rtnetlink.h>
#include <linux/gen_stats.h>
#include <net/if.h>
#include <asm/types.h>
#include <linux/rmnet_data.h>
#include "QMIThread.h"
#undef RMNET_EGRESS_FORMAT_AGGREGATION //QSDK SPF11.4
#define RMNET_EGRESS_FORMAT_AGGREGATION (1U << 31)
#ifndef RMNET_FLAGS_INGRESS_MAP_CKSUMV5
#define RMNET_FLAGS_INGRESS_MAP_CKSUMV5 (1U << 5)
#define RMNET_FLAGS_EGRESS_MAP_CKSUMV5 (1U << 6)
#endif
#define RMNETCTL_SUCCESS 0
#define RMNETCTL_LIB_ERR 1
#define RMNETCTL_KERNEL_ERR 2
#define RMNETCTL_INVALID_ARG 3
#define CONFIG_QRTR_IPQ_NSS_OFFLOAD //defined for ipq nss offload support
enum rmnetctl_error_codes_e {
RMNETCTL_API_SUCCESS = 0,
RMNETCTL_API_FIRST_ERR = 1,
RMNETCTL_API_ERR_MESSAGE_SEND = 3,
RMNETCTL_API_ERR_MESSAGE_RECEIVE = 4,
RMNETCTL_INIT_FIRST_ERR = 5,
RMNETCTL_INIT_ERR_PROCESS_ID = RMNETCTL_INIT_FIRST_ERR,
RMNETCTL_INIT_ERR_NETLINK_FD = 6,
RMNETCTL_INIT_ERR_BIND = 7,
RMNETCTL_API_SECOND_ERR = 9,
RMNETCTL_API_ERR_HNDL_INVALID = RMNETCTL_API_SECOND_ERR,
RMNETCTL_API_ERR_RETURN_TYPE = 13,
RMNETCTL_API_THIRD_ERR = 25,
/* Failed to copy data into netlink message */
RMNETCTL_API_ERR_RTA_FAILURE = RMNETCTL_API_THIRD_ERR,
};
struct rmnetctl_hndl_s {
uint32_t pid;
uint32_t transaction_id;
int netlink_fd;
struct sockaddr_nl src_addr, dest_addr;
};
typedef struct rmnetctl_hndl_s rmnetctl_hndl_t;
#define NLMSG_TAIL(nmsg) \
((struct rtattr *) (((char *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
struct nlmsg {
struct nlmsghdr nl_addr;
struct ifinfomsg ifmsg;
char data[500];
};
#define MIN_VALID_PROCESS_ID 0
#define MIN_VALID_SOCKET_FD 0
#define KERNEL_PROCESS_ID 0
#define UNICAST 0
/* IFLA Attributes for the RT RmNet driver */
enum {
RMNETCTL_IFLA_UNSPEC,
RMNETCTL_IFLA_MUX_ID,
RMNETCTL_IFLA_FLAGS,
RMNETCTL_IFLA_DFC_QOS,
RMNETCTL_IFLA_UPLINK_PARAMS,
RMNETCTL_IFLA_NSS_OFFLOAD,
__RMNETCTL_IFLA_MAX,
};
struct rmnetctl_uplink_params {
uint16_t byte_count;
uint8_t packet_count;
uint8_t features;
uint32_t time_limit;
};
static void rta_parse(struct rtattr **tb, int maxtype, struct rtattr *head,
int len)
{
struct rtattr *rta;
memset(tb, 0, sizeof(struct rtattr *) * maxtype);
for (rta = head; RTA_OK(rta, len);
rta = RTA_NEXT(rta, len)) {
__u16 type = rta->rta_type & NLA_TYPE_MASK;
if (type > 0 && type < maxtype)
tb[type] = rta;
}
}
static struct rtattr *rta_find(struct rtattr *rta, int attrlen, uint16_t type)
{
for (; RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) {
if (rta->rta_type == (type & NLA_TYPE_MASK))
return rta;
}
return NULL;
}
static struct rtattr * rta_put(struct nlmsg *req,int type, int len, void *data)
{
struct rtattr *attrinfo = NLMSG_TAIL(&req->nl_addr);
attrinfo->rta_type = type;
attrinfo->rta_len = RTA_ALIGN(RTA_LENGTH(len));
if (data && len)
memcpy(RTA_DATA(attrinfo), data, len);
req->nl_addr.nlmsg_len = NLMSG_ALIGN(req->nl_addr.nlmsg_len) +
RTA_ALIGN(RTA_LENGTH(len));
return attrinfo;
}
static int rmnet_get_ack(rmnetctl_hndl_t *hndl, uint16_t *error_code)
{
struct nlack {
struct nlmsghdr ackheader;
struct nlmsgerr ackdata;
char data[256];
} ack;
int i;
if (!hndl || !error_code)
return RMNETCTL_INVALID_ARG;
if ((i = recv(hndl->netlink_fd, &ack, sizeof(ack), 0)) < 0) {
*error_code = errno;
return RMNETCTL_API_ERR_MESSAGE_RECEIVE;
}
/*Ack should always be NLMSG_ERROR type*/
if (ack.ackheader.nlmsg_type == NLMSG_ERROR) {
if (ack.ackdata.error == 0) {
*error_code = RMNETCTL_API_SUCCESS;
return RMNETCTL_SUCCESS;
} else {
*error_code = -ack.ackdata.error;
return RMNETCTL_KERNEL_ERR;
}
}
*error_code = RMNETCTL_API_ERR_RETURN_TYPE;
return RMNETCTL_API_FIRST_ERR;
}
static int rtrmnet_ctl_init(rmnetctl_hndl_t **hndl, uint16_t *error_code)
{
struct sockaddr_nl __attribute__((__may_alias__)) *saddr_ptr;
int netlink_fd = -1;
pid_t pid = 0;
if (!hndl || !error_code)
return RMNETCTL_INVALID_ARG;
*hndl = (rmnetctl_hndl_t *)malloc(sizeof(rmnetctl_hndl_t));
if (!*hndl) {
*error_code = RMNETCTL_API_ERR_HNDL_INVALID;
return RMNETCTL_LIB_ERR;
}
memset(*hndl, 0, sizeof(rmnetctl_hndl_t));
pid = getpid();
if (pid < MIN_VALID_PROCESS_ID) {
free(*hndl);
*error_code = RMNETCTL_INIT_ERR_PROCESS_ID;
return RMNETCTL_LIB_ERR;
}
(*hndl)->pid = KERNEL_PROCESS_ID;
netlink_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (netlink_fd < MIN_VALID_SOCKET_FD) {
free(*hndl);
*error_code = RMNETCTL_INIT_ERR_NETLINK_FD;
return RMNETCTL_LIB_ERR;
}
(*hndl)->netlink_fd = netlink_fd;
memset(&(*hndl)->src_addr, 0, sizeof(struct sockaddr_nl));
(*hndl)->src_addr.nl_family = AF_NETLINK;
(*hndl)->src_addr.nl_pid = (*hndl)->pid;
saddr_ptr = &(*hndl)->src_addr;
if (bind((*hndl)->netlink_fd,
(struct sockaddr *)saddr_ptr,
sizeof(struct sockaddr_nl)) < 0) {
close((*hndl)->netlink_fd);
free(*hndl);
*error_code = RMNETCTL_INIT_ERR_BIND;
return RMNETCTL_LIB_ERR;
}
memset(&(*hndl)->dest_addr, 0, sizeof(struct sockaddr_nl));
(*hndl)->dest_addr.nl_family = AF_NETLINK;
(*hndl)->dest_addr.nl_pid = KERNEL_PROCESS_ID;
(*hndl)->dest_addr.nl_groups = UNICAST;
return RMNETCTL_SUCCESS;
}
static int rtrmnet_ctl_deinit(rmnetctl_hndl_t *hndl)
{
if (!hndl)
return RMNETCTL_SUCCESS;
close(hndl->netlink_fd);
free(hndl);
return RMNETCTL_SUCCESS;
}
static int rtrmnet_ctl_newvnd(rmnetctl_hndl_t *hndl, char *devname, char *vndname,
uint16_t *error_code, uint8_t index,
uint32_t flagconfig, uint32_t ul_agg_cnt, uint32_t ul_agg_size)
{
struct rtattr *datainfo, *linkinfo;
struct ifla_vlan_flags flags;
int devindex;
char *kind = "rmnet";
struct nlmsg req;
short id;
if (!hndl || !devname || !vndname || !error_code)
return RMNETCTL_INVALID_ARG;
memset(&req, 0, sizeof(req));
req.nl_addr.nlmsg_type = RTM_NEWLINK;
req.nl_addr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.nl_addr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL |
NLM_F_ACK;
req.nl_addr.nlmsg_seq = hndl->transaction_id;
hndl->transaction_id++;
/* Get index of devname*/
devindex = if_nametoindex(devname);
if (devindex < 0) {
*error_code = errno;
return RMNETCTL_KERNEL_ERR;
}
/* Setup link attr with devindex as data */
rta_put(&req, IFLA_LINK, sizeof(devindex), &devindex);
rta_put(&req, IFLA_IFNAME, strlen(vndname) + 1, vndname);
/* Set up IFLA info kind RMNET that has linkinfo and type */
linkinfo = rta_put(&req, IFLA_LINKINFO, 0, NULL);
rta_put(&req, IFLA_INFO_KIND, strlen(kind), kind);
datainfo = rta_put(&req, IFLA_INFO_DATA, 0, NULL);
id = index;
rta_put(&req, RMNETCTL_IFLA_MUX_ID, sizeof(id), &id);
if (flagconfig != 0) {
flags.mask = flagconfig;
flags.flags = flagconfig;
rta_put(&req, RMNETCTL_IFLA_FLAGS, sizeof(flags), &flags);
}
#ifdef CONFIG_QRTR_IPQ_NSS_OFFLOAD
if (!access("/sys/module/rmnet_nss/", F_OK)) {
uint8_t offload = 1;
rta_put(&req, RMNETCTL_IFLA_NSS_OFFLOAD, sizeof(offload), &offload);
}
#endif
if (ul_agg_cnt > 1) {
struct rmnetctl_uplink_params ul_agg;
ul_agg.byte_count = ul_agg_size;
ul_agg.packet_count = ul_agg_cnt;
ul_agg.features = 0;
ul_agg.time_limit = 3000000;
rta_put(&req, RMNETCTL_IFLA_UPLINK_PARAMS, sizeof(ul_agg), &ul_agg);
}
datainfo->rta_len = (char *)NLMSG_TAIL(&req.nl_addr) - (char *)datainfo;
linkinfo->rta_len = (char *)NLMSG_TAIL(&req.nl_addr) - (char *)linkinfo;
if (send(hndl->netlink_fd, &req, req.nl_addr.nlmsg_len, 0) < 0) {
*error_code = RMNETCTL_API_ERR_MESSAGE_SEND;
return RMNETCTL_LIB_ERR;
}
return rmnet_get_ack(hndl, error_code);
}
int rtrmnet_ctl_new_vnd(char *devname, char *vndname, uint8_t muxid,
uint32_t qmap_version, uint32_t ul_agg_cnt, uint32_t ul_agg_size)
{
struct rmnetctl_hndl_s *handle;
uint16_t error_code;
int return_code;
uint32_t flagconfig = RMNET_FLAGS_INGRESS_DEAGGREGATION;
dbg_time("%s dev: %s, vnd: %s, muxid: %d, qmap_version: %d",
__func__, devname, vndname, muxid, qmap_version);
if (ul_agg_cnt > 1)
flagconfig |= RMNET_EGRESS_FORMAT_AGGREGATION;
if (qmap_version == 9) { //QMAPV5
flagconfig |= RMNET_FLAGS_INGRESS_MAP_CKSUMV5;
flagconfig |= RMNET_FLAGS_EGRESS_MAP_CKSUMV5;
}
else if (qmap_version == 5) { //QMAPV1
}
else {
dbg_time("%s donot support qmap_version %d\n", __func__, qmap_version);
return -1001;
}
return_code = rtrmnet_ctl_init(&handle, &error_code);
if (return_code != RMNETCTL_SUCCESS) {
dbg_time("rtrmnet_ctl_init error_code: %d, return_code: %d, errno: %d (%s)",
error_code, return_code, errno, strerror(errno));
}
return_code = rtrmnet_ctl_newvnd(handle, devname, vndname, &error_code,
muxid, flagconfig, ul_agg_cnt, ul_agg_size);
if (return_code == RMNETCTL_KERNEL_ERR)
dbg_time("rtrmnet_ctl_newvnd RMNETCTL_KERNEL_ERR errno: %d (%s)",
error_code, strerror(error_code));
else if (return_code != RMNETCTL_SUCCESS) {
dbg_time("rtrmnet_ctl_newvnd error_code: %d, return_code: %d",
error_code, return_code);
}
rtrmnet_ctl_deinit(handle);
return return_code;
}
static int rtrmnet_ctl_getvnd(rmnetctl_hndl_t *hndl, char *vndname,
uint16_t *error_code, uint16_t *mux_id,
uint32_t *flagconfig, struct rmnetctl_uplink_params *ul_agg)
{
struct nlmsg req;
struct nlmsghdr *resp;
struct rtattr *attrs, *linkinfo, *datainfo;
struct rtattr *tb[__RMNETCTL_IFLA_MAX];
unsigned int devindex = 0;
int resp_len;
memset(&req, 0, sizeof(req));
if (!hndl || !vndname || !error_code || !(mux_id || flagconfig))
return RMNETCTL_INVALID_ARG;
req.nl_addr.nlmsg_type = RTM_GETLINK;
req.nl_addr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.nl_addr.nlmsg_flags = NLM_F_REQUEST;
req.nl_addr.nlmsg_seq = hndl->transaction_id;
hndl->transaction_id++;
/* Get index of vndname */
devindex = if_nametoindex(vndname);
if (devindex == 0) {
*error_code = errno;
return RMNETCTL_KERNEL_ERR;
}
req.ifmsg.ifi_index = devindex;
if (send(hndl->netlink_fd, &req, req.nl_addr.nlmsg_len, 0) < 0) {
*error_code = RMNETCTL_API_ERR_MESSAGE_SEND;
return RMNETCTL_LIB_ERR;
}
resp_len = recv(hndl->netlink_fd, NULL, 0, MSG_PEEK | MSG_TRUNC);
if (resp_len < 0) {
*error_code = errno;
return RMNETCTL_API_ERR_MESSAGE_RECEIVE;
}
resp = malloc((size_t)resp_len);
if (!resp) {
*error_code = errno;
return RMNETCTL_LIB_ERR;
}
resp_len = recv(hndl->netlink_fd, (char *)resp, (size_t)resp_len, 0);
if (resp_len < 0) {
*error_code = errno;
free(resp);
return RMNETCTL_API_ERR_MESSAGE_RECEIVE;
}
/* Parse out the RT attributes */
attrs = (struct rtattr *)((char *)NLMSG_DATA(resp) + NLMSG_ALIGN(sizeof(req.ifmsg)));
linkinfo = rta_find(attrs, NLMSG_PAYLOAD(resp, sizeof(req.ifmsg)), IFLA_LINKINFO);
if (!linkinfo) {
free(resp);
*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
return RMNETCTL_KERNEL_ERR;
}
datainfo = rta_find(RTA_DATA(linkinfo), RTA_PAYLOAD(linkinfo), IFLA_INFO_DATA);
if (!datainfo) {
free(resp);
*error_code = RMNETCTL_API_ERR_RTA_FAILURE;
return RMNETCTL_KERNEL_ERR;
}
/* Parse all the rmnet-specific information from the kernel */
rta_parse(tb, __RMNETCTL_IFLA_MAX, RTA_DATA(datainfo), RTA_PAYLOAD(datainfo));
if (tb[RMNETCTL_IFLA_MUX_ID] && mux_id) {
*mux_id = *((uint16_t *)RTA_DATA(tb[RMNETCTL_IFLA_MUX_ID]));
dbg_time("\tMux id: %d", *mux_id);
}
if (tb[RMNETCTL_IFLA_FLAGS] && flagconfig) {
struct ifla_vlan_flags *flags;
flags = (struct ifla_vlan_flags *)RTA_DATA(tb[RMNETCTL_IFLA_FLAGS]);
*flagconfig = flags->flags;
dbg_time("\tData format: 0x%x", *flagconfig);
}
if (tb[RMNETCTL_IFLA_UPLINK_PARAMS]) {
*ul_agg = *(struct rmnetctl_uplink_params *)
RTA_DATA(tb[RMNETCTL_IFLA_UPLINK_PARAMS]);
dbg_time("\tUplink Aggregation parameters:");
dbg_time("\t\tPacket limit: %u", ul_agg->packet_count);
dbg_time("\t\tByte limit: %u", ul_agg->byte_count);
dbg_time("\t\tTime limit (ns): %u", ul_agg->time_limit);
dbg_time("\t\tFeatures : 0x%x", ul_agg->features);
}
free(resp);
return RMNETCTL_API_SUCCESS;
}
int rtrmnet_ctl_get_vnd(char *vndname, int *muxid, int *qmap_version)
{
struct rmnetctl_hndl_s *handle;
uint16_t error_code;
int return_code;
uint32_t flagconfig = 0;
uint16_t mux_id = 0;
struct rmnetctl_uplink_params ul_agg;
dbg_time("%s vnd: %s", __func__, vndname);
return_code = rtrmnet_ctl_init(&handle, &error_code);
if (return_code != RMNETCTL_SUCCESS) {
dbg_time("rtrmnet_ctl_init error_code: %d, return_code: %d, errno: %d (%s)",
error_code, return_code, errno, strerror(errno));
return -1;
}
// rmnetcli -n getlink rmnet_data0
return_code = rtrmnet_ctl_getvnd(handle, /*devname, */vndname, &error_code,
&mux_id, &flagconfig, &ul_agg);
if (return_code == RMNETCTL_KERNEL_ERR)
dbg_time("rtrmnet_ctl_uplinkparam RMNETCTL_KERNEL_ERR errno: %d (%s)",
error_code, strerror(error_code));
else if (return_code != RMNETCTL_SUCCESS) {
dbg_time("rtrmnet_ctl_uplinkparam error_code: %d, return_code: %d",
error_code, return_code);
}
rtrmnet_ctl_deinit(handle);
if (return_code == RMNETCTL_SUCCESS) {
*muxid = mux_id;
if (flagconfig&RMNET_FLAGS_INGRESS_DEAGGREGATION) {
if ((flagconfig&RMNET_FLAGS_INGRESS_MAP_CKSUMV5) && (flagconfig&RMNET_FLAGS_EGRESS_MAP_CKSUMV5))
*qmap_version = 9; //QMAPV5
else
*qmap_version = 5; //QMAPV1
}
}
return return_code;
}
+741
View File
@@ -0,0 +1,741 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <net/if.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <endian.h>
#include "util.h"
#include "QMIThread.h"
extern int ql_get_netcard_carrier_state(const char *devname);
static __inline in_addr_t qmi2addr(uint32_t __x) {
return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24);
}
static int ql_system(const char *shell_cmd) {
dbg_time("%s", shell_cmd);
return system(shell_cmd);
}
static void ifc_init_ifr(const char *name, struct ifreq *ifr)
{
memset(ifr, 0, sizeof(struct ifreq));
no_trunc_strncpy(ifr->ifr_name, name, IFNAMSIZ);
ifr->ifr_name[IFNAMSIZ - 1] = 0;
}
static void ql_set_mtu(const char *ifname, int ifru_mtu) {
int inet_sock;
struct ifreq ifr;
inet_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (inet_sock > 0) {
ifc_init_ifr(ifname, &ifr);
if (!ioctl(inet_sock, SIOCGIFMTU, &ifr)) {
if (ifr.ifr_ifru.ifru_mtu != ifru_mtu) {
dbg_time("change mtu %d -> %d", ifr.ifr_ifru.ifru_mtu , ifru_mtu);
ifr.ifr_ifru.ifru_mtu = ifru_mtu;
ioctl(inet_sock, SIOCSIFMTU, &ifr);
}
}
close(inet_sock);
}
}
static int ifc_get_addr(const char *name, in_addr_t *addr)
{
int inet_sock;
struct ifreq ifr;
int ret = 0;
inet_sock = socket(AF_INET, SOCK_DGRAM, 0);
ifc_init_ifr(name, &ifr);
if (addr != NULL) {
ret = ioctl(inet_sock, SIOCGIFADDR, &ifr);
if (ret < 0) {
*addr = 0;
} else {
*addr = ((struct sockaddr_in*) &ifr.ifr_addr)->sin_addr.s_addr;
}
}
close(inet_sock);
return ret;
}
static short ifc_get_flags(const char *ifname)
{
int inet_sock;
struct ifreq ifr;
int ret = 0;
inet_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (inet_sock > 0) {
ifc_init_ifr(ifname, &ifr);
if (!ioctl(inet_sock, SIOCGIFFLAGS, &ifr)) {
ret = ifr.ifr_ifru.ifru_flags;
}
close(inet_sock);
}
return ret;
}
static void ifc_set_state(const char *ifname, int state) {
char shell_cmd[128];
if (!access("/sbin/ip", X_OK)) {
snprintf(shell_cmd, sizeof(shell_cmd), "ip link set dev %s %s", ifname, state ? "up" : "down");
} else {
snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %s", ifname, state ? "up" : "down");
}
ql_system(shell_cmd);
}
static int ql_netcard_ipv4_address_check(const char *ifname, in_addr_t ip) {
in_addr_t addr = 0;
ifc_get_addr(ifname, &addr);
return addr == ip;
}
static int ql_raw_ip_mode_check(const char *ifname, uint32_t ip) {
int fd;
char raw_ip[128];
char mode[2] = "X";
int mode_change = 0;
if (ql_netcard_ipv4_address_check(ifname, qmi2addr(ip)))
return 0;
snprintf(raw_ip, sizeof(raw_ip), "/sys/class/net/%s/qmi/raw_ip", ifname);
if (access(raw_ip, F_OK))
return 0;
fd = open(raw_ip, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd < 0) {
dbg_time("%s %d fail to open(%s), errno:%d (%s)", __FILE__, __LINE__, raw_ip, errno, strerror(errno));
return 0;
}
if (read(fd, mode, 2) == -1) {};
if (mode[0] == '0' || mode[0] == 'N') {
dbg_time("File:%s Line:%d udhcpc fail to get ip address, try next:", __func__, __LINE__);
ifc_set_state(ifname, 0);
dbg_time("echo Y > /sys/class/net/%s/qmi/raw_ip", ifname);
mode[0] = 'Y';
if (write(fd, mode, 2) == -1) {};
mode_change = 1;
ifc_set_state(ifname, 1);
}
close(fd);
return mode_change;
}
static void* udhcpc_thread_function(void* arg) {
FILE * udhcpc_fp;
char *udhcpc_cmd = (char *)arg;
if (udhcpc_cmd == NULL)
return NULL;
dbg_time("%s", udhcpc_cmd);
udhcpc_fp = popen(udhcpc_cmd, "r");
free(udhcpc_cmd);
if (udhcpc_fp) {
char buf[0xff];
buf[sizeof(buf)-1] = '\0';
while((fgets(buf, sizeof(buf)-1, udhcpc_fp)) != NULL) {
if ((strlen(buf) > 1) && (buf[strlen(buf) - 1] == '\n'))
buf[strlen(buf) - 1] = '\0';
dbg_time("%s", buf);
}
pclose(udhcpc_fp);
}
return NULL;
}
//#define USE_DHCLIENT
#ifdef USE_DHCLIENT
static int dhclient_alive = 0;
#endif
static int dibbler_client_alive = 0;
void ql_set_driver_link_state(PROFILE_T *profile, int link_state) {
char link_file[128];
int fd;
int new_state = 0;
snprintf(link_file, sizeof(link_file), "/sys/class/net/%s/link_state", profile->usbnet_adapter);
fd = open(link_file, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd == -1) {
if (errno != ENOENT)
dbg_time("Fail to access %s, errno: %d (%s)", link_file, errno, strerror(errno));
return;
}
if (profile->qmap_mode <= 1)
new_state = !!link_state;
else {
//0x80 means link off this pdp
new_state = (link_state ? 0x00 : 0x80) + (profile->muxid & 0x7F);
}
snprintf(link_file, sizeof(link_file), "%d\n", new_state);
if (write(fd, link_file, sizeof(link_file)) == -1) {};
if (link_state == 0 && profile->qmapnet_adapter[0]
&& strcmp(profile->qmapnet_adapter, profile->usbnet_adapter)) {
size_t rc;
lseek(fd, 0, SEEK_SET);
rc = read(fd, link_file, sizeof(link_file));
if (rc > 1 && (!strncasecmp(link_file, "0\n", 2) || !strncasecmp(link_file, "0x0\n", 4))) {
ifc_set_state(profile->usbnet_adapter, 0);
}
}
close(fd);
}
static const char *ipv4Str(const uint32_t Address) {
static char str[] = {"255.225.255.255"};
uint8_t *ip = (uint8_t *)&Address;
snprintf(str, sizeof(str), "%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]);
return str;
}
static const char *ipv6Str(const UCHAR Address[16]) {
static char str[64];
uint16_t ip[8];
int i;
for (i = 0; i < 8; i++) {
ip[i] = (Address[i*2]<<8) + Address[i*2+1];
}
snprintf(str, sizeof(str), "%x:%x:%x:%x:%x:%x:%x:%x",
ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]);
return str;
}
void update_ipv4_address(const char *ifname, const char *ip, const char *gw, unsigned prefix)
{
char shell_cmd[128];
if (!ifname)
return;
if (!access("/sbin/ip", X_OK)) {
snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address flush dev %s", 4, ifname);
ql_system(shell_cmd);
snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %s/%u dev %s", 4, ip, prefix, ifname);
ql_system(shell_cmd);
//ping6 www.qq.com
snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default via %s dev %s", 4, gw, ifname);
ql_system(shell_cmd);
} else {
unsigned n = (0xFFFFFFFF >> (32 - prefix)) << (32 - prefix);
// n = (n>>24) | (n>>8&0xff00) | (n<<8&0xff0000) | (n<<24);
snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %s netmask %s", ifname, ip, ipv4Str(n));
ql_system(shell_cmd);
//Resetting default routes
snprintf(shell_cmd, sizeof(shell_cmd), "route del default dev %s", ifname);
while(!system(shell_cmd));
snprintf(shell_cmd, sizeof(shell_cmd), "route add default gw %s dev %s", gw, ifname);
ql_system(shell_cmd);
}
}
void update_ipv6_address(const char *ifname, const char *ip, const char *gw, unsigned prefix) {
char shell_cmd[128];
(void)gw;
if (!access("/sbin/ip", X_OK)) {
snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address flush dev %s", 6, ifname);
ql_system(shell_cmd);
snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d address add %s/%u dev %s", 6, ip, prefix, ifname);
ql_system(shell_cmd);
//ping6 www.qq.com
snprintf(shell_cmd, sizeof(shell_cmd), "ip -%d route add default dev %s", 6, ifname);
ql_system(shell_cmd);
} else {
snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s %s/%d", ifname, ip, prefix);
ql_system(shell_cmd);
snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default dev %s", ifname);
ql_system(shell_cmd);
}
}
static void update_ip_address_by_qmi(const char *ifname, const IPV4_T *ipv4, const IPV6_T *ipv6) {
char *d1, *d2;
if (ipv4 && ipv4->Address) {
d1 = strdup(ipv4Str(ipv4->Address));
d2 = strdup(ipv4Str(ipv4->Gateway));
unsigned prefix = 0;
unsigned n = 0;
for (n = 0; n < 32; n++) {
if (ipv4->SubnetMask&((unsigned)1<<n)) {
prefix++;
}
}
update_ipv4_address(ifname, d1, d2, prefix);
free(d1); free(d2);
//Adding DNS
if (ipv4->DnsPrimary) {
d1 = strdup(ipv4Str(ipv4->DnsPrimary));
d2 = strdup(ipv4Str(ipv4->DnsSecondary ? ipv4->DnsSecondary : ipv4->DnsPrimary));
update_resolv_conf(4, ifname, d1, d2);
free(d1); free(d2);
}
}
if (ipv6 && ipv6->Address[0] && ipv6->PrefixLengthIPAddr) {
d1 = strdup(ipv6Str(ipv6->Address));
d2 = strdup(ipv6Str(ipv6->Gateway));
update_ipv6_address(ifname, d1, d2, ipv6->PrefixLengthIPAddr);
free(d1); free(d2);
//Adding DNS
if (ipv6->DnsPrimary[0]) {
d1 = strdup(ipv6Str(ipv6->DnsPrimary));
d2 = strdup(ipv6Str(ipv6->DnsSecondary[0] ? ipv6->DnsSecondary : ipv6->DnsPrimary));
update_resolv_conf(6, ifname, d1, d2);
free(d1); free(d2);
}
}
}
//#define QL_OPENWER_NETWORK_SETUP
#ifdef QL_OPENWER_NETWORK_SETUP
static const char *openwrt_lan = "br-lan";
static const char *openwrt_wan = "wwan0";
static int ql_openwrt_system(const char *cmd) {
int i;
int ret = 1;
char shell_cmd[128];
snprintf(shell_cmd, sizeof(shell_cmd), "%s 2>1 > /dev/null", cmd);
for (i = 0; i < 15; i++) {
dbg_time("%s", cmd);
ret = system(shell_cmd);
if (!ret)
break;
sleep(1);
}
return ret;
}
static int ql_openwrt_is_wan(const char *ifname) {
if (openwrt_lan == NULL) {
system("uci show network.wan.ifname");
}
if (strcmp(ifname, openwrt_wan))
return 0;
return 1;
}
static void ql_openwrt_setup_wan(const char *ifname, const IPV4_T *ipv4) {
FILE *fp = NULL;
char config[64];
snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv4config", ifname);
if (ipv4 == NULL) {
if (ql_openwrt_is_wan(ifname))
ql_openwrt_system("ifdown wan");
return;
}
fp = fopen(config, "w");
if (fp == NULL)
return;
fprintf(fp, "IFNAME=\"%s\"\n", ifname);
fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv4Str(ipv4->Address));
fprintf(fp, "NETMASK=\"%s\"\n", ipv4Str(ipv4->SubnetMask));
fprintf(fp, "GATEWAY=\"%s\"\n", ipv4Str(ipv4->Gateway));
fprintf(fp, "DNSSERVERS=\"%s", ipv4Str(ipv4->DnsPrimary));
if (ipv4->DnsSecondary != 0)
fprintf(fp, " %s", ipv4Str(ipv4->DnsSecondary));
fprintf(fp, "\"\n");
fclose(fp);
if (!ql_openwrt_is_wan(ifname))
return;
ql_openwrt_system("ifup wan");
}
static void ql_openwrt_setup_wan6(const char *ifname, const IPV6_T *ipv6) {
FILE *fp = NULL;
char config[64];
int first_ifup;
snprintf(config, sizeof(config), "/tmp/rmnet_%s_ipv6config", ifname);
if (ipv6 == NULL) {
if (ql_openwrt_is_wan(ifname))
ql_openwrt_system("ifdown wan6");
return;
}
first_ifup = (access(config, F_OK) != 0);
fp = fopen(config, "w");
if (fp == NULL)
return;
fprintf(fp, "IFNAME=\"%s\"\n", ifname);
fprintf(fp, "PUBLIC_IP=\"%s\"\n", ipv6Str(ipv6->Address));
fprintf(fp, "NETMASK=\"%s\"\n", ipv6Str(ipv6->SubnetMask));
fprintf(fp, "GATEWAY=\"%s\"\n", ipv6Str(ipv6->Gateway));
fprintf(fp, "PrefixLength=\"%d\"\n", ipv6->PrefixLengthIPAddr);
fprintf(fp, "DNSSERVERS=\"%s", ipv6Str(ipv6->DnsPrimary));
if (ipv6->DnsSecondary[0])
fprintf(fp, " %s", ipv6Str(ipv6->DnsSecondary));
fprintf(fp, "\"\n");
fclose(fp);
if (!ql_openwrt_is_wan(ifname))
return;
if (first_ifup)
ql_openwrt_system("ifup wan6");
else
ql_openwrt_system("/etc/init.d/network restart"); //make PC to release old IPV6 address, and RS new IPV6 address
#if 1 //TODO? why need this?
if (openwrt_lan) {
int i;
char shell_cmd[128];
UCHAR Address[16] = {0};
ql_openwrt_system(("ifstatus lan"));
for (i = 0; i < (ipv6->PrefixLengthIPAddr/8); i++)
Address[i] = ipv6->Address[i];
snprintf(shell_cmd, sizeof(shell_cmd), "ip route del %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, ifname);
ql_openwrt_system(shell_cmd);
snprintf(shell_cmd, sizeof(shell_cmd), "ip route add %s/%u dev %s", ipv6Str(Address), ipv6->PrefixLengthIPAddr, openwrt_lan);
ql_system(shell_cmd);
}
#endif
}
#endif
void udhcpc_start(PROFILE_T *profile) {
char *ifname = profile->usbnet_adapter;
ql_set_driver_link_state(profile, 1);
if (profile->qmapnet_adapter[0]) {
ifname = profile->qmapnet_adapter;
}
if (profile->rawIP && profile->ipv4.Address && profile->ipv4.Mtu) {
ql_set_mtu(ifname, (profile->ipv4.Mtu));
}
if (strcmp(ifname, profile->usbnet_adapter)) {
ifc_set_state(profile->usbnet_adapter, 1);
if (ifc_get_flags(ifname)&IFF_UP) {
ifc_set_state(ifname, 0);
}
}
ifc_set_state(ifname, 1);
if (profile->ipv4.Address) {
if (profile->PCSCFIpv4Addr1)
dbg_time("pcscf1: %s", ipv4Str(profile->PCSCFIpv4Addr1));
if (profile->PCSCFIpv4Addr2)
dbg_time("pcscf2: %s", ipv4Str(profile->PCSCFIpv4Addr2));
}
if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) {
if (profile->PCSCFIpv6Addr1[0])
dbg_time("pcscf1: %s", ipv6Str(profile->PCSCFIpv6Addr1));
if (profile->PCSCFIpv6Addr2[0])
dbg_time("pcscf2: %s", ipv6Str(profile->PCSCFIpv6Addr2));
}
#if 1 //for bridge mode, only one public IP, so do udhcpc manually
if (ql_bridge_mode_detect(profile)) {
return;
}
#endif
//because must use udhcpc to obtain IP when working on ETH mode,
//so it is better also use udhcpc to obtain IP when working on IP mode.
//use the same policy for all modules
#if 0
if (profile->rawIP != 0) //mdm9x07/ec25,ec20 R2.0
{
update_ip_address_by_qmi(ifname, &profile->ipv4, &profile->ipv6);
return;
}
#endif
if (profile->ipv4.Address == 0)
goto set_ipv6;
if (profile->no_dhcp || profile->usb_dev.idProduct == 0x0316 || profile->request_ops == &mbim_request_ops) { //lots of mbim modem do not support DHCP
update_ip_address_by_qmi(ifname, &profile->ipv4, NULL);
}
else
/* Do DHCP using busybox tools */
{
char udhcpc_cmd[128];
pthread_attr_t udhcpc_thread_attr;
pthread_t udhcpc_thread_id;
pthread_attr_init(&udhcpc_thread_attr);
pthread_attr_setdetachstate(&udhcpc_thread_attr, PTHREAD_CREATE_DETACHED);
#ifdef USE_DHCLIENT
snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -4 -d --no-pid %s", ifname);
dhclient_alive++;
#else
if (access("/usr/share/udhcpc/default.script", X_OK)
&& access("/etc//udhcpc/default.script", X_OK)) {
dbg_time("No default.script found, it should be in '/usr/share/udhcpc/' or '/etc//udhcpc' depend on your udhcpc version!");
}
//-f,--foreground Run in foreground
//-b,--background Background if lease is not obtained
//-n,--now Exit if lease is not obtained
//-q,--quit Exit after obtaining lease
//-t,--retries N Send up to N discover packets (default 3)
snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "busybox udhcpc -f -n -q -t 5 -i %s", ifname);
#endif
#if 1 //for OpenWrt
if (!access("/lib/netifd/dhcp.script", X_OK) && !access("/sbin/ifup", X_OK) && !access("/sbin/ifstatus", X_OK)) {
#if 0 //20210415 do not promot these message
dbg_time("you are use OpenWrt?");
dbg_time("should not calling udhcpc manually?");
dbg_time("should modify /etc/config/network as below?");
dbg_time("config interface wan");
dbg_time("\toption ifname %s", ifname);
dbg_time("\toption proto dhcp");
dbg_time("should use \"/sbin/ifstaus wan\" to check %s 's status?", ifname);
#endif
}
#endif
#ifdef USE_DHCLIENT
pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd));
sleep(1);
#else
pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd));
pthread_join(udhcpc_thread_id, NULL);
if (profile->request_ops == &atc_request_ops) {
profile->udhcpc_ip = 0;
ifc_get_addr(ifname, &profile->udhcpc_ip);
if (profile->udhcpc_ip != profile->ipv4.Address) {
unsigned char *l = (unsigned char *)&profile->udhcpc_ip;
unsigned char *r = (unsigned char *)&profile->ipv4.Address;
dbg_time("ERROR: IP from udhcpc (%d.%d.%d.%d) is different to IP from ATC (%d.%d.%d.%d)!",
l[0], l[1], l[2], l[3], r[0], r[1], r[2], r[3]);
ql_get_netcard_carrier_state(ifname); //miss udhcpc default.script or modem not report usb-net-cdc-linkup
}
}
if (profile->request_ops != &qmi_request_ops) { //only QMI modem support next fixup!
goto set_ipv6;
}
if (ql_raw_ip_mode_check(ifname, profile->ipv4.Address)) {
pthread_create(&udhcpc_thread_id, NULL, udhcpc_thread_function, (void*)strdup(udhcpc_cmd));
pthread_join(udhcpc_thread_id, NULL);
}
if (!ql_netcard_ipv4_address_check(ifname, qmi2addr(profile->ipv4.Address))) {
//no udhcpc's default.script exist, directly set ip and dns
update_ip_address_by_qmi(ifname, &profile->ipv4, NULL);
}
//Add by Demon. check default route
FILE *rt_fp = NULL;
char rt_cmd[128] = {0};
//Check if there is a default route.
snprintf(rt_cmd, sizeof(rt_cmd), "route -n | grep %s | awk '{print $1}' | grep 0.0.0.0", ifname);
rt_fp = popen((const char *)rt_cmd, "r");
if (rt_fp != NULL) {
char buf[20] = {0};
int found_default_rt = 0;
if (fgets(buf, sizeof(buf), rt_fp) != NULL) {
//Find the specified interface
found_default_rt = 1;
}
if (1 == found_default_rt) {
//dbg_time("Route items found for %s", ifname);
}
else {
dbg_time("Warning: No route items found for %s", ifname);
}
pclose(rt_fp);
}
//End by Demon.
#endif
}
#ifdef QL_OPENWER_NETWORK_SETUP
ql_openwrt_setup_wan(ifname, &profile->ipv4);
#endif
set_ipv6:
if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr) {
#if 1
//module do not support DHCPv6, only support 'Router Solicit'
//and it seem if enable /proc/sys/net/ipv6/conf/all/forwarding, Kernel do not send RS
const char *forward_file = "/proc/sys/net/ipv6/conf/all/forwarding";
int forward_fd = open(forward_file, O_RDONLY);
if (forward_fd > 0) {
char forward_state[2];
if (read(forward_fd, forward_state, 2) == -1) {};
if (forward_state[0] == '1') {
//dbg_time("%s enabled, kernel maybe donot send 'Router Solicit'", forward_file);
}
close(forward_fd);
}
update_ip_address_by_qmi(ifname, NULL, &profile->ipv6);
if (profile->ipv6.DnsPrimary[0] || profile->ipv6.DnsSecondary[0]) {
char dns1str[64], dns2str[64];
if (profile->ipv6.DnsPrimary[0]) {
strcpy(dns1str, ipv6Str(profile->ipv6.DnsPrimary));
}
if (profile->ipv6.DnsSecondary[0]) {
strcpy(dns2str, ipv6Str(profile->ipv6.DnsSecondary));
}
update_resolv_conf(6, ifname, profile->ipv6.DnsPrimary[0] ? dns1str : NULL,
profile->ipv6.DnsSecondary[0] != '\0' ? dns2str : NULL);
}
#ifdef QL_OPENWER_NETWORK_SETUP
ql_openwrt_setup_wan6(ifname, &profile->ipv6);
#endif
#else
#ifdef USE_DHCLIENT
snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dhclient -6 -d --no-pid %s", ifname);
dhclient_alive++;
#else
/*
DHCPv6: Dibbler - a portable DHCPv6
1. download from http://klub.com.pl/dhcpv6/
2. cross-compile
2.1 ./configure --host=arm-linux-gnueabihf
2.2 copy dibbler-client to your board
3. mkdir -p /var/log/dibbler/ /var/lib/ on your board
4. create /etc/dibbler/client.conf on your board, the content is
log-mode short
log-level 7
iface wwan0 {
ia
option dns-server
}
5. run "dibbler-client start" to get ipV6 address
6. run "route -A inet6 add default dev wwan0" to add default route
*/
snprintf(shell_cmd, sizeof(shell_cmd), "route -A inet6 add default %s", ifname);
ql_system(shell_cmd);
snprintf(udhcpc_cmd, sizeof(udhcpc_cmd), "dibbler-client run");
dibbler_client_alive++;
#endif
pthread_create(&udhcpc_thread_id, &udhcpc_thread_attr, udhcpc_thread_function, (void*)strdup(udhcpc_cmd));
#endif
}
}
void udhcpc_stop(PROFILE_T *profile) {
char *ifname = profile->usbnet_adapter;
char shell_cmd[128];
ql_set_driver_link_state(profile, 0);
if (profile->qmapnet_adapter[0]) {
ifname = profile->qmapnet_adapter;
}
#ifdef USE_DHCLIENT
if (dhclient_alive) {
system("killall dhclient");
dhclient_alive = 0;
}
#endif
if (dibbler_client_alive) {
if (system("killall dibbler-client")) {};
dibbler_client_alive = 0;
}
profile->udhcpc_ip = 0;
//it seems when call netif_carrier_on(), and netcard 's IP is "0.0.0.0", will cause netif_queue_stopped()
if (!access("/sbin/ip", X_OK))
snprintf(shell_cmd, sizeof(shell_cmd), "ip addr flush dev %s", ifname);
else
snprintf(shell_cmd, sizeof(shell_cmd), "ifconfig %s 0.0.0.0", ifname);
ql_system(shell_cmd);
ifc_set_state(ifname, 0);
#ifdef QL_OPENWER_NETWORK_SETUP
ql_openwrt_setup_wan(ifname, NULL);
ql_openwrt_setup_wan6(ifname, NULL);
#endif
}
+190
View File
@@ -0,0 +1,190 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <net/if.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <endian.h>
#include "libmnl/ifutils.h"
#include "libmnl/dhcp/dhcp.h"
#include "util.h"
#include "QMIThread.h"
static int ql_raw_ip_mode_check(const char *ifname)
{
int fd;
char raw_ip[128];
char mode[2] = "X";
int mode_change = 0;
snprintf(raw_ip, sizeof(raw_ip), "/sys/class/net/%s/qmi/raw_ip", ifname);
if (access(raw_ip, F_OK))
return 0;
fd = open(raw_ip, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd < 0)
{
dbg_time("%s %d fail to open(%s), errno:%d (%s)", __FILE__, __LINE__, raw_ip, errno, strerror(errno));
return 0;
}
read(fd, mode, 2);
if (mode[0] == '0' || mode[0] == 'N')
{
if_link_down(ifname);
dbg_time("echo Y > /sys/class/net/%s/qmi/raw_ip", ifname);
mode[0] = 'Y';
write(fd, mode, 2);
mode_change = 1;
if_link_up(ifname);
}
close(fd);
return mode_change;
}
void ql_set_driver_link_state(PROFILE_T *profile, int link_state)
{
char link_file[128];
int fd;
int new_state = 0;
snprintf(link_file, sizeof(link_file), "/sys/class/net/%s/link_state", profile->usbnet_adapter);
fd = open(link_file, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd == -1)
{
if (errno != ENOENT)
dbg_time("Fail to access %s, errno: %d (%s)", link_file, errno, strerror(errno));
return;
}
if (profile->qmap_mode <= 1)
new_state = !!link_state;
else
{
//0x80 means link off this pdp
new_state = (link_state ? 0x00 : 0x80) + profile->pdp;
}
snprintf(link_file, sizeof(link_file), "%d\n", new_state);
write(fd, link_file, sizeof(link_file));
if (link_state == 0 && profile->qmap_mode > 1)
{
size_t rc;
lseek(fd, 0, SEEK_SET);
rc = read(fd, link_file, sizeof(link_file));
if (rc > 1 && (!strcasecmp(link_file, "0\n") || !strcasecmp(link_file, "0x0\n")))
{
if_link_down(profile->usbnet_adapter);
}
}
close(fd);
}
void udhcpc_start(PROFILE_T *profile)
{
char *ifname = profile->usbnet_adapter;
ql_set_driver_link_state(profile, 1);
ql_raw_ip_mode_check(ifname);
if (profile->qmapnet_adapter)
{
ifname = profile->qmapnet_adapter;
}
if (profile->rawIP && profile->ipv4.Address && profile->ipv4.Mtu)
{
if_set_mtu(ifname, (profile->ipv4.Mtu));
}
if (strcmp(ifname, profile->usbnet_adapter))
{
if_link_up(profile->usbnet_adapter);
}
if_link_up(ifname);
#if 1 //for bridge mode, only one public IP, so do udhcpc manually
if (ql_bridge_mode_detect(profile))
{
return;
}
#endif
// if use DHCP(should make with ${DHCP} src files)
// do_dhcp(ifname);
// return 0;
/* IPv4 Addr Info */
if (profile->ipv4.Address)
{
dbg_time("IPv4 MTU: %d", profile->ipv4.Mtu);
dbg_time("IPv4 Address: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.Address)));
dbg_time("IPv4 Netmask: %d", mask_to_prefix_v4(ntohl(profile->ipv4.SubnetMask)));
dbg_time("IPv4 Gateway: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.Gateway)));
dbg_time("IPv4 DNS1: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.DnsPrimary)));
dbg_time("IPv4 DNS2: %s", ipaddr_to_string_v4(ntohl(profile->ipv4.DnsSecondary)));
if_set_network_v4(ifname, ntohl(profile->ipv4.Address),
mask_to_prefix_v4(profile->ipv4.SubnetMask),
ntohl(profile->ipv4.Gateway),
ntohl(profile->ipv4.DnsPrimary),
ntohl(profile->ipv4.DnsSecondary));
}
if (profile->ipv6.Address[0] && profile->ipv6.PrefixLengthIPAddr)
{
//module do not support DHCPv6, only support 'Router Solicit'
//and it seem if enable /proc/sys/net/ipv6/conf/all/forwarding, Kernel do not send RS
const char *forward_file = "/proc/sys/net/ipv6/conf/all/forwarding";
int forward_fd = open(forward_file, O_RDONLY);
if (forward_fd > 0)
{
char forward_state[2];
read(forward_fd, forward_state, 2);
if (forward_state[0] == '1')
{
dbg_time("%s enabled, kernel maybe donot send 'Router Solicit'", forward_file);
}
close(forward_fd);
}
dbg_time("IPv6 MTU: %d", profile->ipv6.Mtu);
dbg_time("IPv6 Address: %s", ipaddr_to_string_v6(profile->ipv6.Address));
dbg_time("IPv6 Netmask: %d", profile->ipv6.PrefixLengthIPAddr);
dbg_time("IPv6 Gateway: %s", ipaddr_to_string_v6(profile->ipv6.Gateway));
dbg_time("IPv6 DNS1: %s", ipaddr_to_string_v6(profile->ipv6.DnsPrimary));
dbg_time("IPv6 DNS2: %s", ipaddr_to_string_v6(profile->ipv6.DnsSecondary));
if_set_network_v6(ifname, profile->ipv6.Address, profile->ipv6.PrefixLengthIPAddr,
profile->ipv6.Gateway, profile->ipv6.DnsPrimary, profile->ipv6.DnsSecondary);
}
}
void udhcpc_stop(PROFILE_T *profile)
{
char *ifname = profile->usbnet_adapter;
ql_set_driver_link_state(profile, 0);
if (profile->qmapnet_adapter)
{
ifname = profile->qmapnet_adapter;
}
if_link_down(ifname);
if_flush_v4_addr(ifname);
if_flush_v6_addr(ifname);
}
+128
View File
@@ -0,0 +1,128 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <net/if.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <endian.h>
#include "util.h"
#include "QMIThread.h"
#define IFDOWN_SCRIPT "/etc/quectel/ifdown.sh"
#define IFUP_SCRIPT "/etc/quectel/ifup.sh"
static int ql_system(const char *shell_cmd)
{
dbg_time("%s", shell_cmd);
return system(shell_cmd);
}
uint32_t mask_to_prefix_v4(uint32_t mask)
{
uint32_t prefix = 0;
while (mask)
{
mask = mask & (mask - 1);
prefix++;
}
return prefix;
}
uint32_t mask_from_prefix_v4(uint32_t prefix)
{
return ~((1 << (32 - prefix)) - 1);
}
/* mask in int */
uint32_t broadcast_from_mask(uint32_t ip, uint32_t mask)
{
return (ip & mask) | (~mask);
}
const char *ipaddr_to_string_v4(in_addr_t ipaddr, char *buf, size_t size)
{
// static char buf[INET6_ADDRSTRLEN] = {'\0'};
buf[0] = '\0';
uint32_t addr = ipaddr;
return inet_ntop(AF_INET, &addr, buf, size);
}
const char *ipaddr_to_string_v6(uint8_t *ipaddr, char *buf, size_t size)
{
buf[0] = '\0';
return inet_ntop(AF_INET6, ipaddr, buf, size);
}
/**
* For more details see default.script
*
* The main aim of this function is offload ip management to script, CM has not interest in manage IP address.
* just tell script all the info about ip, mask, router, dns...
*/
void udhcpc_start(PROFILE_T *profile)
{
char shell_cmd[1024];
char ip[128];
char subnet[128];
char broadcast[128];
char router[128];
char domain1[128];
char domain2[128];
if (NULL == getenv(IFUP_SCRIPT))
return;
// manage IPv4???
// check rawip ???
snprintf(shell_cmd, sizeof(shell_cmd),
" netiface=%s interface=%s mtu=%u ip=%s subnet=%s broadcast=%s router=%s"
" domain=\"%s %s\" %s",
profile->usbnet_adapter,
profile->qmapnet_adapter ? profile->qmapnet_adapter : profile->usbnet_adapter,
profile->ipv4.Mtu,
ipaddr_to_string_v4(ntohl(profile->ipv4.Address), ip, sizeof(ip)),
ipaddr_to_string_v4(ntohl(profile->ipv4.SubnetMask), subnet, sizeof(subnet)),
ipaddr_to_string_v4(ntohl(broadcast_from_mask(profile->ipv4.Address, profile->ipv4.SubnetMask)),
broadcast, sizeof(broadcast)),
ipaddr_to_string_v4(ntohl(profile->ipv4.Gateway), router, sizeof(router)),
ipaddr_to_string_v4(ntohl(profile->ipv4.DnsPrimary), domain1, sizeof(domain1)),
ipaddr_to_string_v4(ntohl(profile->ipv4.DnsSecondary), domain2, sizeof(domain2)),
getenv(IFUP_SCRIPT));
ql_system(shell_cmd);
// manage IPv6???
}
/**
* For more details see default.script
*
* The main aim of this function is offload ip management to script, CM has not interest in manage IP address.
* just tell script all the info about ip, mask, router, dns...
*/
void udhcpc_stop(PROFILE_T *profile)
{
char shell_cmd[1024];
if (NULL == getenv(IFDOWN_SCRIPT))
return;
snprintf(shell_cmd, sizeof(shell_cmd),
"netiface=%s interface=%s %s",
profile->usbnet_adapter,
profile->qmapnet_adapter ? profile->qmapnet_adapter : profile->usbnet_adapter,
getenv(IFDOWN_SCRIPT));
ql_system(shell_cmd);
}
+356
View File
@@ -0,0 +1,356 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#include <sys/time.h>
#include <net/if.h>
typedef unsigned short sa_family_t;
#include <linux/un.h>
#if defined(__STDC__)
#include <stdarg.h>
#define __V(x) x
#else
#include <varargs.h>
#define __V(x) (va_alist) va_dcl
#define const
#define volatile
#endif
#include <syslog.h>
#include "QMIThread.h"
pthread_mutex_t cm_command_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cm_command_cond = PTHREAD_COND_INITIALIZER;
unsigned int cm_recv_buf[1024];
int cm_open_dev(const char *dev) {
int fd;
fd = open(dev, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd != -1) {
fcntl(fd, F_SETFL, fcntl(fd,F_GETFL) | O_NONBLOCK);
fcntl(fd, F_SETFD, FD_CLOEXEC);
if (!strncmp(dev, "/dev/tty", strlen("/dev/tty")))
{
//disable echo on serial ports
struct termios ios;
memset(&ios, 0, sizeof(ios));
tcgetattr( fd, &ios );
cfmakeraw(&ios);
cfsetispeed(&ios, B115200);
cfsetospeed(&ios, B115200);
tcsetattr( fd, TCSANOW, &ios );
tcflush(fd, TCIOFLUSH);
}
} else {
dbg_time("Failed to open %s, errno: %d (%s)", dev, errno, strerror(errno));
}
return fd;
}
int cm_open_proxy(const char *name) {
int sockfd = -1;
int reuse_addr = 1;
struct sockaddr_un sockaddr;
socklen_t alen;
/*Create server socket*/
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0)
return sockfd;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sun_family = AF_LOCAL;
sockaddr.sun_path[0] = 0;
memcpy(sockaddr.sun_path + 1, name, strlen(name) );
alen = strlen(name) + offsetof(struct sockaddr_un, sun_path) + 1;
if(connect(sockfd, (struct sockaddr *)&sockaddr, alen) < 0) {
close(sockfd);
dbg_time("connect %s errno: %d (%s)", name, errno, strerror(errno));
return -1;
}
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,sizeof(reuse_addr));
fcntl(sockfd, F_SETFL, fcntl(sockfd,F_GETFL) | O_NONBLOCK);
fcntl(sockfd, F_SETFD, FD_CLOEXEC);
dbg_time("connect to %s sockfd = %d", name, sockfd);
return sockfd;
}
static void setTimespecRelative(struct timespec *p_ts, long long msec)
{
struct timeval tv;
gettimeofday(&tv, (struct timezone *) NULL);
/* what's really funny about this is that I know
pthread_cond_timedwait just turns around and makes this
a relative time again */
p_ts->tv_sec = tv.tv_sec + (msec / 1000);
p_ts->tv_nsec = (tv.tv_usec + (msec % 1000) * 1000L ) * 1000L;
if ((unsigned long)p_ts->tv_nsec >= 1000000000UL) {
p_ts->tv_sec += 1;
p_ts->tv_nsec -= 1000000000UL;
}
}
int pthread_cond_timeout_np(pthread_cond_t *cond, pthread_mutex_t * mutex, unsigned msecs) {
if (msecs != 0) {
unsigned i;
unsigned t = msecs/4;
int ret = 0;
if (t == 0)
t = 1;
for (i = 0; i < msecs; i += t) {
struct timespec ts;
setTimespecRelative(&ts, t);
//very old uclibc do not support pthread_condattr_setclock(CLOCK_MONOTONIC)
ret = pthread_cond_timedwait(cond, mutex, &ts); //to advoid system time change
if (ret != ETIMEDOUT) {
if(ret) dbg_time("ret=%d, msecs=%u, t=%u", ret, msecs, t);
break;
}
}
return ret;
} else {
return pthread_cond_wait(cond, mutex);
}
}
const char * get_time(void) {
static char time_buf[128];
struct timeval tv;
time_t time;
suseconds_t millitm;
struct tm *ti;
gettimeofday (&tv, NULL);
time= tv.tv_sec;
millitm = (tv.tv_usec + 500) / 1000;
if (millitm == 1000) {
++time;
millitm = 0;
}
ti = localtime(&time);
sprintf(time_buf, "%02d-%02d_%02d:%02d:%02d:%03d", ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec, (int)millitm);
return time_buf;
}
unsigned long clock_msec(void)
{
struct timespec tm;
clock_gettime( CLOCK_MONOTONIC, &tm);
return (unsigned long)(tm.tv_sec*1000 + (tm.tv_nsec/1000000));
}
FILE *logfilefp = NULL;
void update_resolv_conf(int iptype, const char *ifname, const char *dns1, const char *dns2) {
const char *dns_file = "/etc/resolv.conf";
FILE *dns_fp;
char dns_line[256];
#define MAX_DNS 16
char *dns_info[MAX_DNS];
char dns_tag[64];
int dns_match = 0;
int i;
snprintf(dns_tag, sizeof(dns_tag), "# IPV%d %s", iptype, ifname);
for (i = 0; i < MAX_DNS; i++)
dns_info[i] = NULL;
dns_fp = fopen(dns_file, "r");
if (dns_fp) {
i = 0;
dns_line[sizeof(dns_line)-1] = '\0';
while((fgets(dns_line, sizeof(dns_line)-1, dns_fp)) != NULL) {
if ((strlen(dns_line) > 1) && (dns_line[strlen(dns_line) - 1] == '\n'))
dns_line[strlen(dns_line) - 1] = '\0';
//dbg_time("%s", dns_line);
if (strstr(dns_line, dns_tag)) {
dns_match++;
continue;
}
dns_info[i++] = strdup(dns_line);
if (i == MAX_DNS)
break;
}
fclose(dns_fp);
}
else if (errno != ENOENT) {
dbg_time("fopen %s fail, errno:%d (%s)", dns_file, errno, strerror(errno));
return;
}
if (dns1 == NULL && dns_match == 0)
return;
dns_fp = fopen(dns_file, "w");
if (dns_fp) {
if (dns1)
fprintf(dns_fp, "nameserver %s %s\n", dns1, dns_tag);
if (dns2)
fprintf(dns_fp, "nameserver %s %s\n", dns2, dns_tag);
for (i = 0; i < MAX_DNS && dns_info[i]; i++)
fprintf(dns_fp, "%s\n", dns_info[i]);
fclose(dns_fp);
}
else {
dbg_time("fopen %s fail, errno:%d (%s)", dns_file, errno, strerror(errno));
}
for (i = 0; i < MAX_DNS && dns_info[i]; i++)
free(dns_info[i]);
}
pid_t getpid_by_pdp(int pdp, const char* program_name)
{
glob_t gt;
int ret;
char filter[16];
pid_t pid;
snprintf(filter, sizeof(filter), "-n %d", pdp);
ret = glob("/proc/*/cmdline", GLOB_NOSORT, NULL, &gt);
if (ret != 0) {
dbg_time("glob error, errno = %d(%s)", errno, strerror(errno));
return -1;
} else {
int i = 0, fd = -1;
ssize_t nreads;
char cmdline[512] = {0};
for (i = 0; i < (int)gt.gl_pathc; i++) {
fd = open(gt.gl_pathv[i], O_RDONLY);
if (fd == -1) {
dbg_time("open %s failed, errno = %d(%s)", gt.gl_pathv[i], errno, strerror(errno));
globfree(&gt);
return -1;
}
nreads = read(fd, cmdline, sizeof(cmdline));
if (nreads > 0) {
int pos = 0;
while (pos < nreads-1) {
if (cmdline[pos] == '\0')
cmdline[pos] = ' '; // space
pos++;
}
// printf("%s\n", cmdline);
}
if (strstr(cmdline, program_name) && strstr(cmdline, filter)) {
char path[64] = {0};
char pidstr[64] = {0};
char *p;
dbg_time("%s: %s", gt.gl_pathv[i], cmdline);
strcpy(path, gt.gl_pathv[i]);
p = strstr(gt.gl_pathv[i], "/cmdline");
*p = '\0';
while (*(--p) != '/') ;
strcpy(pidstr, p+1);
pid = atoi(pidstr);
globfree(&gt);
return pid;
}
}
}
globfree(&gt);
return -1;
}
void ql_get_driver_rmnet_info(PROFILE_T *profile, RMNET_INFO *rmnet_info) {
int ifc_ctl_sock;
struct ifreq ifr;
int rc;
int request = 0x89F3;
unsigned char data[512];
memset(rmnet_info, 0x00, sizeof(*rmnet_info));
ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (ifc_ctl_sock <= 0) {
dbg_time("socket() failed: %s\n", strerror(errno));
return;
}
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, profile->usbnet_adapter, IFNAMSIZ);
ifr.ifr_name[IFNAMSIZ - 1] = 0;
ifr.ifr_ifru.ifru_data = (void *)data;
rc = ioctl(ifc_ctl_sock, request, &ifr);
if (rc < 0) {
if (errno != ENOTSUP)
dbg_time("ioctl(0x%x, qmap_settings) errno:%d (%s), rc=%d", request, errno, strerror(errno), rc);
}
else {
memcpy(rmnet_info, data, sizeof(*rmnet_info));
}
close(ifc_ctl_sock);
}
void ql_set_driver_qmap_setting(PROFILE_T *profile, QMAP_SETTING *qmap_settings) {
int ifc_ctl_sock;
struct ifreq ifr;
int rc;
int request = 0x89F2;
ifc_ctl_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (ifc_ctl_sock <= 0) {
dbg_time("socket() failed: %s\n", strerror(errno));
return;
}
memset(&ifr, 0, sizeof(struct ifreq));
strncpy(ifr.ifr_name, profile->usbnet_adapter, IFNAMSIZ);
ifr.ifr_name[IFNAMSIZ - 1] = 0;
ifr.ifr_ifru.ifru_data = (void *)qmap_settings;
rc = ioctl(ifc_ctl_sock, request, &ifr);
if (rc < 0) {
dbg_time("ioctl(0x%x, qmap_settings) failed: %s, rc=%d", request, strerror(errno), rc);
}
close(ifc_ctl_sock);
}
void no_trunc_strncpy(char *dest, const char *src, size_t dest_size)
{
size_t i = 0;
for (i = 0; i < dest_size && *src; i++) {
*dest++ = *src++;
}
*dest = 0;
}
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright 2025 Quectel Wireless Solutions Co.,Ltd
Quectel hereby grants customers of Quectel a license to use, modify,
distribute and publish the Software in binary form provided that
customers shall have no right to reverse engineer, reverse assemble,
decompile or reduce to source code form any portion of the Software.
Under no circumstances may customers modify, demonstrate, use, deliver
or disclose any portion of the Software in source code form.
*/
#ifndef _UTILS_H_
#define _UTILS_H_
#include <stddef.h>
#include <glob.h>
struct listnode
{
struct listnode *next;
struct listnode *prev;
};
#define node_to_item(node, container, member) \
(container *) (((char*) (node)) - offsetof(container, member))
#define list_declare(name) \
struct listnode name = { \
.next = &name, \
.prev = &name, \
}
#define list_for_each(node, list) \
for (node = (list)->next; node != (list); node = node->next)
#define list_for_each_reverse(node, list) \
for (node = (list)->prev; node != (list); node = node->prev)
void list_init(struct listnode *list);
void list_add_tail(struct listnode *list, struct listnode *item);
void list_add_head(struct listnode *head, struct listnode *item);
void list_remove(struct listnode *item);
#define list_empty(list) ((list) == (list)->next)
#define list_head(list) ((list)->next)
#define list_tail(list) ((list)->prev)
int epoll_register(int epoll_fd, int fd, unsigned int events);
int epoll_deregister(int epoll_fd, int fd);
const char * get_time(void);
unsigned long clock_msec(void);
pid_t getpid_by_pdp(int, const char*);
#endif
@@ -0,0 +1 @@
pcie_mhi mhi_mbim_enabled=1
@@ -0,0 +1,57 @@
# 多 WAN 配置说明(eth1 走 5G / eth0 走宽带,双出口)
> 日期:2026-08-19
> 目标:istoreos 上,eth0 走宽带(管理+SSH),eth1+5G 专门给主路由当 wan2 备份上游
## 架构
```
主路由 (wan1=宽带优先, wan2=5G备份)
├── LAN口 → istoreos eth0 = 192.168.110.200/24 (静态) 走宽带, 管理+SSH
└── wan2口 = 192.168.200.2/24, 网关 192.168.200.1
└── istoreos eth1 → br-lan = 192.168.200.1/24 (5G NAT出口)
└── 5G rmnet_mhi0.1 (MBIM拨号, 动态IP) → 主路由wan2备份
```
- **istoreos 自身流量**(从 eth0 发出)→ 走宽带(默认路由 110.1)
- **来自 eth1/192.168.200.x 网段**(即主路由 wan2 侧)→ 走 5G(策略路由表 200)
## 关键配置文件(本目录)
| 文件 | 部署到 istoreos | 作用 |
|---|---|---|
| `network.txt` | `/etc/config/network` | eth0 静态 110.200、eth1/br-lan 200.1 |
| `init.d_wan5g-route` | `/etc/init.d/wan5g-route`(enable) | 策略路由表200 + NAT,开机执行 S96 |
| `init.d_modem5g` | `/etc/init.d/modem5g`(enable) | MBIM 拨号自启 S95 |
| `90-pcie_mhi.txt` | `/etc/modules.d/90-pcie_mhi` | 驱动 MBIM 模式 mhi_mbim_enabled=1 |
| `rc.local` | `/etc/rc.local` | mhi_q 绑定修复(重启失联兜底) |
## 重启后自启顺序
1. `S95modem5g`:等待 /dev/mhi_MBIM → quectel-CM 拨号 wonet
2. `rc.local`MHI 绑定修复(兜底)
3. `S96wan5g-route`:等待 rmnet_mhi0.1 有 IP → 加策略路由 + NAT
### wan5g-route 核心命令(可手动执行)
```bash
grep -q "wan5g" /etc/iproute2/rt_tables || echo "200 wan5g" >> /etc/iproute2/rt_tables
sysctl -w net.ipv4.ip_forward=1
# 等待 rmnet 有 IP 后:
ip rule del from 192.168.200.0/24 lookup wan5g 2>/dev/null
ip rule add from 192.168.200.0/24 lookup wan5g pref 100
ip route replace default dev rmnet_mhi0.1 table 200
iptables -t nat -C POSTROUTING -s 192.168.200.0/24 -o rmnet_mhi0.1 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s 192.168.200.0/24 -o rmnet_mhi0.1 -j MASQUERADE
```
## 验证
```bash
# 模拟主路由 wan2 侧流量走 5G(源 200.x 网段)
ip addr add 192.168.200.2/24 dev br-lan
ping -c 3 -W 3 -I 192.168.200.2 223.5.5.5 # 应通, 延迟高(走5G)
ip addr del 192.168.200.2/24 dev br-lan
# 对比管理流量走宽带
ping -c 2 -W 2 -I 192.168.110.200 223.5.5.5 # 应通, 延迟低(走宽带)
```
## 备注
- **mwan3 已安装**(2.11.16) + luci-app-mwan3,但**未启用**。当前用手写策略路由(更贴合 5G 动态 IP)。如需 wan1/wan2 真负载均衡可后续启用 mwan3。
- 5G 每次拨号 IP 动态,istoreos 默认路由始终走宽带,5G 只服务 eth1(200 网段)。
- istoreos 软件源:mirrors.cernet.edu.cnkmods 源 curl 22 失败,但不影响纯脚本包安装)。
- 备份:原 `/etc/config/network`eth0 曾 DHCP 拿 110.135)可回滚。
@@ -0,0 +1,29 @@
#!/bin/sh /etc/rc.common
# RM520N-GL PCIe MBIM 拨号自启 (2026-08-19)
# 依赖: /etc/rc.local 的 mhi_q 绑定修复先跑, /dev/mhi_MBIM 出现后拨号
START=95
STOP=15
start() {
echo "modem5g: 等待 /dev/mhi_MBIM ..."
local i=0
while [ $i -lt 30 ]; do
[ -c /dev/mhi_MBIM ] && break
sleep 2
i=$((i+1))
done
if [ ! -c /dev/mhi_MBIM ]; then
echo "modem5g: /dev/mhi_MBIM 未出现, 放弃拨号"
return 1
fi
echo "modem5g: 开始 MBIM 拨号 (wonet)"
start-stop-daemon -S -b -m -p /var/run/quectel_cm.pid \
-x /usr/bin/quectel-CM -- -s wonet -f /tmp/quectel_cm.log
echo "modem5g: 已启动 quectel-CM"
}
stop() {
echo "modem5g: 停止拨号"
start-stop-daemon -K -p /var/run/quectel_cm.pid 2>/dev/null
return 0
}
@@ -0,0 +1,49 @@
#!/bin/sh /etc/rc.common
# 5G(rmnet_mhi0.1) 作为 eth1/192.168.200.x 的出口 (2026-08-19)
# 让主路由 wan2 的流量经 eth1 -> 5G 上网; eth0 管理流量走宽带
# 依赖: S95modem5g 先拨号, rmnet_mhi0.1 有 IP
START=96
STOP=14
setup() {
# 确保路由表定义
grep -q "wan5g" /etc/iproute2/rt_tables 2>/dev/null || echo "200 wan5g" >> /etc/iproute2/rt_tables
# 确保 ip_forward
sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1
# 等待 rmnet_mhi0.1 有 IP (拨号需完成)
local i=0
local rmnet_ip=""
while [ $i -lt 60 ]; do
rmnet_ip=$(ip -4 addr show rmnet_mhi0.1 2>/dev/null | sed -n "s/.*inet \([0-9.]*\)\/.*/\1/p" | head -1)
[ -n "$rmnet_ip" ] && break
sleep 2
i=$((i+1))
done
[ -z "$rmnet_ip" ] && { echo "wan5g-route: rmnet_mhi0.1 无IP, 5G未拨号"; return 1; }
echo "wan5g-route: rmnet_mhi0.1 = $rmnet_ip"
# 策略规则 (幂等)
ip rule del from 192.168.200.0/24 lookup wan5g 2>/dev/null
ip rule add from 192.168.200.0/24 lookup wan5g pref 100 2>/dev/null
# 5G 默认路由填入表200
ip route replace default dev rmnet_mhi0.1 table 200 2>/dev/null
# NAT: 200网段 -> 5G出口
iptables -t nat -C POSTROUTING -s 192.168.200.0/24 -o rmnet_mhi0.1 -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -s 192.168.200.0/24 -o rmnet_mhi0.1 -j MASQUERADE
echo "wan5g-route: 策略路由+NAT 就绪"
}
start() { setup; }
reload() { setup; }
restart() { setup; }
stop() {
ip rule del from 192.168.200.0/24 lookup wan5g 2>/dev/null
ip route del default table 200 2>/dev/null
iptables -t nat -D POSTROUTING -s 192.168.200.0/24 -o rmnet_mhi0.1 -j MASQUERADE 2>/dev/null
return 0
}
+34
View File
@@ -0,0 +1,34 @@
config interface 'loopback'
option device 'lo'
option proto 'static'
option ipaddr '127.0.0.1'
option netmask '255.0.0.0'
config globals 'globals'
option ula_prefix 'fdcf:8956:3e66::/48'
config interface 'wan'
option device 'eth0'
option proto 'static'
option ipaddr '192.168.110.200'
option netmask '255.255.255.0'
option gateway '192.168.110.1'
option dns '223.5.5.5'
config interface 'wan6'
option device 'eth0'
option proto 'dhcpv6'
config device
option name 'br-lan'
option type 'bridge'
list ports 'eth1'
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.200.1'
option netmask '255.255.255.0'
option ip6assign '60'
+12
View File
@@ -0,0 +1,12 @@
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.
# ===== RM520N-GL 说明 (2026-08-20 更新) =====
# pcie_mhi 驱动由 /etc/modules.d/90-pcie_mhi 开机自动加载(mhi_mbim_enabled=1)。
# 原生流程即可自启拨号(S95modem5g 等待 /dev/mhi_MBIM 后起 quectel-CM)。
# 无需在此手动 bind mhi_q —— 手动强绑会抢设备导致 pcie_mhi 失联(SYS_ERR)。
# 自启链路: 90-pcie_mhi -> S95modem5g(拨号) -> S96wan5g-route(策略路由+NAT)
# 诊断: dmesg | grep -iE "mhi|pcie_mhi"; ls /dev/mhi_*
# 故障处理: 模块失联无 /dev/mhi_DUN 时, 先断电重启飞牛; 仍无效再:
# echo 0000:00:0a.0 > /sys/bus/pci/drivers/mhi_q/bind # 临时唤醒
exit 0
@@ -0,0 +1,207 @@
# RM520N-GL 5G 模块在 iStoreOS(虚拟化直通)下 MBIM 拨号成功路线
> 日期:2026-08-19
> 环境:宿主飞牛(Fnos) 将移远 RM520N-GL 5G 模块通过 PCIe(vfio-pci) 直通给 iStoreOS 虚拟机
> 目标:让 iStoreOS 能够拨号上网,5G 模块作为宽带备用(wan2)
> 状态:✅ 5G 拨号成功、稳定上网、已固化开机自启
---
## 一、最终成果一句话
在 iStoreOS 里,用 **移远官方 Quectel-CM V1.6.8musl 静态编译)** 通过 **MBIM 模式** 拨号,模块成功拿到运营商内网 IP 并上网(`ping 223.5.5.5` 通)。
**关键结论:RM520N-GL 在 PCIe 直通 + pcie_mhi 驱动下,QMI 模式是死路(模块不响应 QMI),必须用 MBIM 模式。**
---
## 二、环境与拓扑
| 项 | 值 |
|---|---|
| 5G 模块 | 移远 RM520N-GL,固件 `RM520NGLAAR03A04M4G`IMEI 868371050275649 |
| 模块宿主位置 | 飞牛 `06:00.0 17cb:0308` |
| 直通方式 | vfio-pci 直通给 istoreos 虚拟机 |
| 虚拟机内位置 | `00:0a.0 Qualcomm SDX62` |
| iStoreOS IP | 192.168.110.135 |
| iStoreOS 登录 | root / SSH 已开启 |
| 数据通道 | PCIe/RMNET + qmap vlanMBIM 模式走 `rmnet_mhi0.1` |
| AT 通道 | `/dev/mhi_DUN` |
### 关键驱动
- **pcie_mhi 驱动** V1.3.8(移远私有 MHI 驱动)
- 加载参数:`mhi_mbim_enabled=1`**MBIM 模式**
- MBIM 模式设备节点:`/dev/mhi_MBIM`QMI 模式是 `/dev/mhi_QMI0`
---
## 三、为什么是这样(决策记录)
### 走过的弯路
1. **ModemManager 识别不了**:移远 pcie_mhi 驱动把控制通道暴露为私有 `/dev/mhi_QMI0`,不是标准 cdc-wdmmmcli 扫不到。
2. **luci-app-modem 界面管不到**:修好 `at_port=/dev/mhi_DUN` + `manufacturer=quectel` 后,界面能读到信息但不拨号。
3. **QMI 拨号失败(V1.6.7**:系统自带 quectel-CM 1.6.7 只支持 ECM 分支,报 `unsupport software_interface 1`
4. **升级 V1.6.8 仍失败(QMI**V1.6.8 支持 QMI 分支,但 **模块对 QMI 请求从不响应**——手动发 QMI SYNC 能写入但读不到任何响应;换 `qmap_mode`、软重启都不行。判定是 **RM520N-GL + pcie_mhi v1.3.8 的 QMI 硬伤**
5. **✅ 切 MBIM 成功**:驱动 `mhi_mbim_enabled=1``/dev/mhi_MBIM` 出现 → quectel-CM 直接拨号成功。完全复现移远官方日志 `pcie_mhi_mbim.txt` 的流程。
### 核心经验
- **RM520N-GL 在 PCIe/MHI 下能用 MBIMQMI 通道是死的**(至少 pcie_mhi v1.3.8 + 此固件下如此)。
- MBIM 模式**不需要 proxy**`quectel-CM -s <apn>` 直接拨号即可。
- iStoreOS 是 **musl libc**,必须用 **musl-gcc 静态编译** quectel-CMglibc 静态编译的 qmi-proxy 在 musl 上闪退/卡死)。
---
## 四、完整复现步骤
### 步骤 1:确认模块拓扑与直通
```bash
# 宿主(飞牛)
lspci | grep 17cb # 找到 06:00.0 17cb:0308
# 直通给 istoreos VM 后,guest 内
lspci | grep -i qualcomm # 00:0a.0 Qualcomm SDX62
ls /sys/bus/mhi_q/devices/ # 应看到 0308_00.00.0a_DIAG/DUN/EFS/QMI0 等
ls /dev/mhi_DUN /dev/mhi_QMI0 # AT 通道在
```
### 步骤 2:切到 MBIM 模式(关键!)
```bash
# 备份当前配置
cp /etc/modules.d/90-pcie_mhi /etc/modules.d/90-pcie_mhi.bak
# 卸载驱动
rmmod pcie_mhi
# 以 MBIM 模式重新加载
insmod /lib/modules/$(uname -r)/pcie_mhi.ko mhi_mbim_enabled=1
# 验证:/dev/mhi_MBIM 出现,/dev/mhi_QMI0 消失
ls /dev/mhi_MBIM
# 固化(重启自动 MBIM
echo "pcie_mhi mhi_mbim_enabled=1" > /etc/modules.d/90-pcie_mhi
```
### 步骤 3:部署 musl 版 quectel-CM V1.6.8
```bash
# 二进制在 文件/binaries/quectel-CMmusl 静态编译 V1.6.8
# 备份旧版 V1.6.7
cp /usr/bin/quectel-CM /usr/bin/quectel-CM.bak-20260819-167
# 替换
cp quectel-CM /usr/bin/quectel-CM
chmod 755 /usr/bin/quectel-CM
/usr/bin/quectel-CM 2>&1 | head -1 # 应显示 QConnectManager_Linux_V1.6.8
```
### 步骤 4:MBIM 拨号(核心命令)
```bash
# 直接拨号,不需要 proxy
/usr/bin/quectel-CM -s wonet -f /tmp/quectel_cm.log
# 注意:MBIM 模式不要加 -i rmnet_mhi0(会异常);APN 用 wonet(联通卡)
```
成功标志:日志出现 `ActivationState Deactivated -> Activated``rmnet_mhi0.1` 拿到 `inet` IP`ping -I rmnet_mhi0.1 223.5.5.5` 通。
### 步骤 5:开机自启
```bash
# 使用 文件/configs/init.d_modem5g
cp 文件/configs/init.d_modem5g /etc/init.d/modem5g
chmod 755 /etc/init.d/modem5g
/etc/init.d/modem5g enable # 建立 S95modem5g
# 手动测试
/etc/init.d/modem5g start
/etc/init.d/modem5g stop
```
### 步骤 6:开机绑定修复(已确认非必需,默认跳过)
```bash
# 说明 (2026-08-20 更新):
# 原生流程即可自启拨号: 90-pcie_mhi(驱动) -> S95modem5g(拨号) -> S96wan5g-route(策略路由)
# rc.local **不需要**手动 bind mhi_q。
# 教训: 手动强绑`echo 0000:00:0a.0 > /sys/bus/pci/drivers/mhi_q/bind`可能抢设备致 SYS_ERR。
# 若模块失联(无/dev/mhi_DUN),先断电重启飞牛,仍无效才可手动 bind 临时唤醒。
```
---
## 五、已验证的关键事实
### 拨号成功日志(节选,完整见 文件/logs/)
```
[08-19_13:00:xx] Quectel_QConnectManager_Linux_V1.6.8
[08-19_13:00:xx] Modem works in MBIM mode
[08-19_13:00:xx] cdc_wdm_fd = 8 # 成功打开 /dev/mhi_MBIM
[08-19_13:00:xx] mbim_device_caps_query()
[08-19_13:00:xx] DeviceId: 868371050275649
[08-19_13:00:xx] FirmwareInfo: RM520NGLAAR03A04M4G
[08-19_13:00:xx] mbim_subscriber_status_query()
[08-19_13:00:xx] SimIccId: 89860125801671811893
[08-19_13:00:xx] RegisterState Unknown -> Home # 已注册
[08-19_13:00:xx] PacketServiceState Unknown -> Attached
[08-19_13:00:xx] mbim_set_connect(onoff=1, sessionID=0)
[08-19_13:00:xx] ActivationState Deactivated -> Activated # ← 拨号成功!
[08-19_13:00:xx] mbim_ip_config(sessionID=0)
[08-19_13:00:xx] IPv4 = 10.98.xxx.xxx/30 # 拿到内网 IP
```
接口验证:
```
ip addr show rmnet_mhi0.1 # inet 10.98.xxx.xxx/30 ... state UP
ping -c 3 -W 2 -I rmnet_mhi0.1 223.5.5.5 # 2/2 received
```
### 界面管理(MBIM 下 luci 正常)
```
sh /usr/share/modem/modem_info.sh /dev/mhi_DUN quectel quectel 1
# 返回 manufacturer=Quectel, temperature, SIM ready, IMEI/IMSI 正常
```
luci 界面(网络→Modem)可作**状态监控**;但 luci 自动拨号(AT激活)不适用于 PCIe MBIM(数据通道由 quectel-CM 控制)。
---
## 六、目录文件清单
```
RM520N-5G-成功路线/
├── 文档/
│ └── RM520N-MBIM拨号成功路线.md # 本文档
├── 文件/
│ ├── binaries/ # musl 静态编译的 V1.6.8 二进制
│ │ ├── quectel-CM # ★ 拨号主程序(部署到 /usr/bin)
│ │ ├── quectel-qmi-proxy # (备用,QMI 模式才需要)
│ │ ├── quectel-mbim-proxy
│ │ └── quectel-atc-proxy
│ ├── source/ # V1.6.8 完整源码(21 .c + 头文件 + PDF文档)
│ │ └── Quectel_QConnectManager_Linux_User_Guide_V1.0.pdf
│ ├── configs/ # istoreos 配置文件(可直接复用)
│ │ ├── modem_config.txt # /etc/config/modem
│ │ ├── 90-pcie_mhi.txt # /etc/modules.d/90-pcie_mhi (mhi_mbim_enabled=1)
│ │ ├── rc.local # /etc/rc.local (默认无需绑定, 见故障排查文档)
│ │ └── init.d_modem5g # /etc/init.d/modem5g (开机自启拨号)
│ └── logs/
│ ├── quectel_cm_success_log.txt # 本次成功拨号日志(真实)
│ └── 官方参考_pcie_mhi_mbim.txt # 移远官方 MBIM 成功日志(参考)
```
---
## 七、编译说明(如需重编)
iStoreOS 是 musl libc,必须 musl 静态编译:
```bash
# 在 WSL(Ubuntu) 或其他 x86_64 glibc 环境
sudo apt install musl-tools
# musl-gcc 需要 linux 头文件,复制到 musl include
sudo cp -r /usr/include/linux /usr/include/x86_64-linux-musl/
sudo cp -r /usr/include/x86_64-linux-gnu/asm /usr/include/x86_64-linux-musl/
sudo cp -r /usr/include/asm-generic /usr/include/x86_64-linux-musl/
# 编译 quectel-CMV1.6.8QMI 分支)
cd 文件/source
musl-gcc -O1 -static -pthread -o quectel-CM \
QmiWwanCM.c GobiNetCM.c main.c QCQMUX.c QMIThread.c util.c \
qmap_bridge_mode.c mbim-cm.c device.c atc.c atchannel.c at_tok.c udhcpc.c
```
---
## 八、遗留 / 建议
1. **wan2 备份(最终目标)**5G 已能上网,还差把 `rmnet_mhi0.1` 配成 wan2 备份(wan1 宽带优先)。推荐 mwan3 或 OpenWrt 策略路由。
2. **luci 界面**:目前可作状态监控。如需界面控制拨号(启停 modem5g),可在 luci-app-modem 加轻量入口。
3. **QMI 死路提醒**:不要再尝试 QMI 拨号,此模块此驱动下不通,MBIM 是正解。
4. **APN**:本次用 `wonet`(联通)。换卡需改 `/etc/init.d/modem5g` 里的 `-s` 参数。
@@ -0,0 +1,41 @@
# RM520N-GL 5G 模块故障排查与自启链路(2026-08-20 更新)
## 开机自启链路(已验证可靠)
```
/etc/modules.d/90-pcie_mhi (pcie_mhi mhi_mbim_enabled=1) 驱动加载
/etc/rc.d/S95modem5g (等待 /dev/mhi_MBIM 后起 quectel-CM -s wonet)
/etc/rc.d/S96wan5g-route (策略路由 from 200.0/24 lookup wan5g + MASQUERADE)
```
- **无需**在 rc.local 里手动 bind mhi_q(那会抢设备导致 SYS_ERR
- 飞牛断电重启后,原生流程自动恢复拨号(2026-08-20 实测:rmnet_mhi0.1 = 10.xping 223.5.5.5 ≈ 8.9ms
## 故障现象:5G 起不来 / 模块 SYS_ERR
**症状**:无 rmnet_mhi0.1、无 /dev/mhi_DUN|MBIM、只有 /dev/mhi_BHI、无 quectel-CM 进程
**dmesg**`dev_state:SYS_ERR``probe failed error -5/-110`
### 排查顺序(重要教训)
1. **优先断电重启飞牛**(模块真正断电复位;istoreos 内 reboot 不动 PCIe 供电,治不好 SYS_ERR
2. 若重启无效,**临时手动唤醒**
```
echo 0000:00:0a.0 > /sys/bus/pci/drivers/mhi_q/bind
```
mhi_q = pcie_mhi 驱动注册名。bind 后 /dev/mhi_MBIM 出现,rmnet 接口建立)
3. **不要**手动 unbind/rmmod pcie_mhi / rescan 折腾——会把设备弄成"无驱动孤儿",反而更难恢复
### 已确认的死路(勿重复踩)
- 禁用 modules.d 里的内核 mhi 驱动(mhi_pci_generic 等)**无效**:它们是 PCI autoloadmodalias 拉起的),禁 modules.d 拦不住
- 手动 rmmod pcie_mhi + 重 insmod **不唤醒**refcnt 仍 0
## 移远 pcie_mhi 驱动机制
- `/lib/modules/6.6.144/pcie_mhi.ko`alias 为 `MHI_CORE`(非标准 PCI ID 匹配)
- insmod 后 PCI probe 函数注册为驱动名 **mhi_q**dmesg 里 `mhi_q 0000:00:0a.0` 就是 pcie_mhi 在跑)
- 与内核 `mhi_pci_generic`(识别为 qcom-sdx65m,需装高通固件,实际加载失败)竞争同一设备
- 模块正常时 pcie_mhi 首发 probe 能成功;异常时需要 bind mhi_q 手动唤醒
## 5G 出口行为(2026-08-20 实测)
- IPv4 HTTP/HTTPS 国内站点:正常(baidu 200
- 国外明文 HTTP(如 neverssl.com):可能被运营商/防火墙过滤(返回空/卡住)
- 5G 出口 **无 IPv6**curl -6 返回 000
- 主路由双 WAN failover 检测可能因此误判:若主路由用国外站点/双栈做健康检查会误报"外网不通"
@@ -0,0 +1,131 @@
# 硬件结构与网络拓扑(2026-08-19)
> 峰的家用 NAS/软路由硬件拓扑与 RM520N-GL 5G 模块直通结构
> 整理日期:2026-08-19
---
## 一、硬件结构
### 宿主机(飞牛 Fnos
| 项 | 值 |
|---|---|
| 系统 | 飞牛 fnOS(云主机/软路由宿主) |
| CPU | Intel Core i5-8250U @ 1.60GHz4核8线程) |
| 内存 | 15 GiB |
| 网卡 | 4 × Intel I226-Venp1s0 / enp2s0 / enp3s0 / enp4s0 |
| 网卡现状 | enp1s0 = UPenp4s0 = UPenp2s0 / enp3s0 = 未接(DOWN |
| 虚拟化 | KVM/QEMUlibvirt`pc-i440fx-7.2`),需用 `vfio` 直通 |
### 5G 模块(移远 RM520N-GL
| 项 | 值 |
|---|---|
| 型号 | Quectel RM520N-GL |
| PCI 位置(宿主) | `06:00.0`PCI_ID `17cb:0308`Qualcomm Device 0308 |
| 物理接口 | PCIe / M.2 转接卡(M.2→PCIE ×1 |
| 直通方式 | vfio-pci 直通给 istoreos 虚拟机 |
| 固件 | RM520NGLAAR03A04M4G |
| IMEI | 868371050275649 |
| SIM | 联通(ICCID 89860125801671811893IMSI 460092810130406 |
### istoreos 虚拟机(5G 路由器)
| 项 | 值 |
|---|---|
| VM 名称 | adwi9jj4libvirt domain-1 |
| 系统 | iStoreOSOpenWrt 定制版,musl libc |
| 登录 IP | 192.168.110.200rootSSH |
| 虚拟网卡 | 2 × virtio-net-pcitap 桥接) |
| 直通 | vfio-pci hostdev `0000:06:00.0` → guest PCI `00:0a.0` |
---
## 二、虚拟网卡映射(宿主 ↔ istoreos)
| 宿主网卡 | istoreos 虚拟网卡 | guest 接口 | MAC | 网段/用途 |
|---|---|---|---|---|
| enp1s0 (UP) | net0 (virtio, tap) | **eth0** | dc:f9:90:7a:e3:d8 | 192.168.110.200 → 走宽带(管理+SSH |
| enp4s0 (UP) | net1 (virtio, tap) | **eth1** | ec:45:6d:3b:62:ef | 192.168.200.1 → 5G 出口(给主路由 wan2 |
> 注:宿主网卡通过 OVS/veth`enp1s0-ovs`、`enp4s0-ovs` 等)连接。
---
## 三、网络拓扑(逻辑)
```
┌─────────────────────────────────────────┐
│ 主路由 (OpenWrt) │
│ wan1 = 192.168.1.2 → 光猫宽带 (优先) │
│ wan2 = 192.168.200.2 → istoreos eth1 │
│ lan = 192.168.110.1 │
└──────┬────────────────────┬──────────────┘
│ │
(宽带) │ │ (5G 备份出口)
│ │
┌────────────▼──────┐ ┌─────────▼──────────┐
│ istoreos eth0 │ │ istoreos eth1 │
│ 192.168.110.200 │ │ 192.168.200.1 │
│ (管理/SSH) │ │ (NAT → 5G) │
└────────┬──────────┘ └─────────┬──────────┘
│ │
(直通vfio) │
┌───────────▼──────────┐ │
│ RM520N-GL 5G 模块 │◄────────────┘
│ 06:00.0 → 00:0a.0 │
│ MBIM → rmnet_mhi0.1 │
└──────────────────────┘
```
### 数据流向
| 流量 | 路径 | 出口 |
|---|---|---|
| istoreos 自身管理 | eth0 → 默认路由 110.1 | **宽带**(主路由 wan1 |
| 主路由 wan2 侧 (200.x) | eth1 → 策略路由表 200 → 5G NAT | **5G** |
| 主路由正常上网 | wan1 宽带(192.168.1.2 | **宽带**(优先) |
| 宽带断时 | 主路由自动切 wan2 → istoreos 5G | **5G**(备份) |
---
## 四、关键驱动与配置
### 驱动
- **pcie_mhi** V1.3.8(移远私有 MHI 驱动),参数 `mhi_mbim_enabled=1`**MBIM 模式**
- MBIM 设备节点:`/dev/mhi_MBIM`(控制通道)、`/dev/mhi_DUN`AT 通道)
- 数据接口:`rmnet_mhi0`(主)、`rmnet_mhi0.1`(实际拨号数据面,qmap vlan)
### istoreos 关键接口
| 接口 | IP | 作用 |
|---|---|---|
| eth0 (wan) | 192.168.110.200/24,网关 110.1 | 宽带/管理 |
| eth1 (br-lan) | 192.168.200.1/24 | 主路由 wan2 网关 |
| rmnet_mhi0.1 | 运营商动态 IP (10.x) | 5G 数据面 |
| docker0 / br-lan | — | 系统默认 |
### 开机自启链(重启自动恢复)
1. **/etc/modules.d/90-pcie_mhi**`pcie_mhi mhi_mbim_enabled=1` 驱动加载
2. **S95modem5g**:等待 `/dev/mhi_MBIM``quectel-CM -s wonet` 拨号(MBIM
3. **S96wan5g-route**:等待 `rmnet_mhi0.1` 有 IP → 加策略路由表 200 + NAT
> 注:rc.local **不需要**手动 bind mhi_q(手动强绑会抢设备致 SYS_ERR)。模块失联时先断电重启飞牛,仍无效再手动 `echo 0000:00:0a.0 > /sys/bus/pci/drivers/mhi_q/bind` 临时唤醒。详见《RM520N-故障排查与自启链路.md》
---
## 五、当前 IP 网段一览
| 网段 | 用途 | 关键设备 |
|---|---|---|
| 192.168.1.0/24 | 光猫宽带 | 主路由 wan1 = 192.168.1.2 |
| 192.168.110.0/24 | 主路由 LAN | 主路由 110.1istoreos eth0 = 110.200 |
| 192.168.200.0/24 | 主路由 wan2 ↔ istoreos eth1 | 主路由 wan2 = 200.2istoreos eth1 = 200.1 |
| 10.x.x.x | 5G 运营商内网 | istoreos rmnet_mhi0.1 |
---
## 六、备份与归档
- 完整成功路线归档于本目录上级:`/vol2/1000/OS/RM520N-5G-成功路线/`
- `文件/configs/` 基础拨号配置
- `文件/多WAN配置/` 双出口(eth1→5G / eth0→宽带)
- `文件/binaries/` musl 编译的 Quectel-CM V1.6.8
- `文件/source/` V1.6.8 源码
- 归档压缩包:`/vol2/1000/OS/RM520N-5G-成功路线.tar.gz`
Submodule
+1
Submodule lede added at a09ade85b3
+1
Submodule luci-feed-inspect added at ee9eee0767