commit 9e478f1591aa460c02b0605e4e5c707793c962f2 Author: zhongluofeng Date: Fri Aug 21 18:28:33 2026 +0800 init diff --git a/5G-Modem-Support b/5G-Modem-Support new file mode 160000 index 0000000..b1d9a12 --- /dev/null +++ b/5G-Modem-Support @@ -0,0 +1 @@ +Subproject commit b1d9a12ac4c209b82fb9db533b067c3fec95a9a6 diff --git a/README.md b/README.md new file mode 100644 index 0000000..851e37c --- /dev/null +++ b/README.md @@ -0,0 +1,190 @@ +# LEDE 5G 固件构建与分析记录 + +> 日期:2026-08-21 +> 仓库:https://github.com/Atieei/lede(fork 自 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 ` 即可,传 `-i rmnet_mhi0` 会导致异常 | +| 驱动版本 | pcie_mhi V1.3.8(源码来自 Siriling/5G-Modem-Support 的 quectel_MHI) | +| 拨号工具 | quectel-CM V1.6.8(QConnectManager,MBIM 路径实测通过) | +| 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 > /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 内嵌 JS(145 行) | 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.04,lede 官方 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/e1000e(KVM 常见网卡)+ 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 到现有系统 | diff --git a/RM520N-5G/README.md b/RM520N-5G/README.md new file mode 100644 index 0000000..7e17062 --- /dev/null +++ b/RM520N-5G/README.md @@ -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 是正解。** diff --git a/RM520N-5G/RM520N系列资料/01 SPEC/Quectel_RM520N系列_5G_模块产品规格书_V1.1.pdf b/RM520N-5G/RM520N系列资料/01 SPEC/Quectel_RM520N系列_5G_模块产品规格书_V1.1.pdf new file mode 100644 index 0000000..0b87b16 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/01 SPEC/Quectel_RM520N系列_5G_模块产品规格书_V1.1.pdf differ diff --git a/RM520N-5G/RM520N系列资料/02 PPT/Quectel_RM520N系列_5G_模块产品介绍_V1.1.pdf b/RM520N-5G/RM520N系列资料/02 PPT/Quectel_RM520N系列_5G_模块产品介绍_V1.1.pdf new file mode 100644 index 0000000..a8378fe Binary files /dev/null and b/RM520N-5G/RM520N系列资料/02 PPT/Quectel_RM520N系列_5G_模块产品介绍_V1.1.pdf differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_Anatel_Certificate.zip b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_Anatel_Certificate.zip new file mode 100644 index 0000000..d770ef1 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_Anatel_Certificate.zip differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_CE(RED)_Certificate.pdf b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_CE(RED)_Certificate.pdf new file mode 100644 index 0000000..dc40daf Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_CE(RED)_Certificate.pdf differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_FCC(C2PC)_Certificate.zip b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_FCC(C2PC)_Certificate.zip new file mode 100644 index 0000000..753167d Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_FCC(C2PC)_Certificate.zip differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_FCC_Certificate.zip b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_FCC_Certificate.zip new file mode 100644 index 0000000..bc8db89 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_FCC_Certificate.zip differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_GCF_Certificate.pdf b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_GCF_Certificate.pdf new file mode 100644 index 0000000..aa70b38 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_GCF_Certificate.pdf differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_IC(ISED)_Certificate.pdf b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_IC(ISED)_Certificate.pdf new file mode 100644 index 0000000..4037542 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_IC(ISED)_Certificate.pdf differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_JATE_Certificate.pdf b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_JATE_Certificate.pdf new file mode 100644 index 0000000..4c5ddda --- /dev/null +++ b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_JATE_Certificate.pdf @@ -0,0 +1,2706 @@ +%PDF-1.3 +% +1 0 obj +<< +/Type /Outlines +/Count 0 +>> +endobj +2 0 obj + +[/PDF /Text /ImageC] +endobj +3 0 obj +<< +/Type /Pages +/Count 4 +/Kids [21 0 R 32 0 R 42 0 R 45 0 R ] +>> +endobj +4 0 obj +<< +/Type /Font +/Subtype /TrueType +/Name /F1 +/BaseFont /Arial,Bold +/FirstChar 32 +/LastChar 255 +/Widths [ +277 333 474 556 556 889 722 237 +333 333 389 583 277 333 277 277 +556 556 556 556 556 556 556 556 +556 556 333 333 583 583 583 610 +975 722 722 722 722 666 610 777 +722 277 556 722 610 833 722 777 +666 777 722 666 610 722 666 943 +666 666 610 333 277 333 583 556 +333 556 610 556 610 556 333 610 +610 277 277 556 277 889 610 610 +610 610 389 556 333 610 556 777 +556 556 500 389 279 389 583 0 +556 0 277 556 500 1000 556 556 +333 1000 666 333 1000 0 610 0 +0 277 277 500 500 350 556 1000 +333 1000 556 333 943 0 500 666 +277 333 556 556 556 556 279 556 +333 736 370 556 583 0 736 552 +399 548 333 333 333 576 556 333 +333 333 365 556 833 833 833 610 +722 722 722 722 722 722 1000 722 +666 666 666 666 277 277 277 277 +722 722 777 777 777 777 777 583 +777 722 722 722 722 666 666 610 +556 556 556 556 556 556 889 556 +556 556 556 556 277 277 277 277 +610 610 610 610 610 610 610 548 +610 610 610 610 610 556 610 556 + ] +/Encoding /WinAnsiEncoding +/FontDescriptor 5 0 R +>> +endobj +5 0 obj +<< +/Type /FontDescriptor +/FontName /Arial +/Ascent 905 +/CapHeight 500 +/Descent 211 +/Flags 32 +/FontBBox [ -628 -376 2000 1018 ] +/ItalicAngle 0 +/StemV 0 +>> +endobj +6 0 obj +<< +/Type /Font +/Subtype /TrueType +/Name /F2 +/BaseFont /Arial +/FirstChar 32 +/LastChar 255 +/Widths [ +277 277 354 556 556 889 666 190 +333 333 389 583 277 333 277 277 +556 556 556 556 556 556 556 556 +556 556 277 277 583 583 583 556 +1015 666 666 722 722 666 610 777 +722 277 500 666 556 833 722 777 +666 777 722 666 610 722 666 943 +666 666 610 277 277 277 469 556 +333 556 556 500 556 556 277 556 +556 222 222 500 222 833 556 556 +556 556 333 500 277 556 500 722 +500 500 500 333 259 333 583 0 +556 0 222 556 333 1000 556 556 +333 1000 666 333 1000 0 610 0 +0 222 222 333 333 350 556 1000 +333 1000 500 333 943 0 500 666 +277 333 556 556 556 556 259 556 +333 736 370 556 583 0 736 552 +399 548 333 333 333 576 537 333 +333 333 365 556 833 833 833 610 +666 666 666 666 666 666 1000 722 +666 666 666 666 277 277 277 277 +722 722 777 777 777 777 777 583 +777 722 722 722 722 666 666 610 +556 556 556 556 556 556 889 500 +556 556 556 556 277 277 277 277 +556 556 556 556 556 556 556 548 +610 556 556 556 556 500 556 500 + ] +/Encoding /WinAnsiEncoding +/FontDescriptor 7 0 R +>> +endobj +7 0 obj +<< +/Type /FontDescriptor +/FontName /Arial +/Ascent 905 +/CapHeight 500 +/Descent 211 +/Flags 32 +/FontBBox [ -665 -325 2000 1006 ] +/ItalicAngle 0 +/StemV 0 +>> +endobj +8 0 obj +<< +/Type /Font +/Subtype /TrueType +/Name /F3 +/BaseFont /TimesNewRoman +/FirstChar 32 +/LastChar 255 +/Widths [ +250 333 408 500 500 833 777 180 +333 333 500 563 250 333 250 277 +500 500 500 500 500 500 500 500 +500 500 277 277 563 563 563 443 +920 722 666 666 722 610 556 722 +722 333 389 722 610 889 722 722 +556 722 666 556 610 722 722 943 +722 722 610 333 277 333 469 500 +333 443 500 443 500 443 333 500 +500 277 277 500 277 777 500 500 +500 500 333 389 277 500 500 722 +500 500 443 479 200 479 541 0 +500 0 333 500 443 1000 500 500 +333 1000 556 333 889 0 610 0 +0 333 333 443 443 350 500 1000 +333 979 389 333 722 0 443 722 +250 333 500 500 500 500 200 500 +333 759 275 500 563 0 759 500 +399 548 299 299 333 576 453 333 +333 299 310 500 750 750 750 443 +722 722 722 722 722 722 889 666 +610 610 610 610 333 333 333 333 +722 722 722 722 722 722 722 563 +722 722 722 722 722 722 556 500 +443 443 443 443 443 443 666 443 +443 443 443 443 277 277 277 277 +500 500 500 500 500 500 500 548 +500 500 500 500 500 500 500 500 + ] +/Encoding /WinAnsiEncoding +/FontDescriptor 9 0 R +>> +endobj +9 0 obj +<< +/Type /FontDescriptor +/FontName /TimesNewRoman +/Ascent 891 +/CapHeight 500 +/Descent 216 +/Flags 34 +/FontBBox [ -568 -307 2000 1007 ] +/ItalicAngle 0 +/StemV 0 +>> +endobj +10 0 obj +<< +/Type /Font +/Subtype /TrueType +/Name /F4 +/BaseFont /Arial,BoldItalic +/FirstChar 32 +/LastChar 255 +/Widths [ +277 333 474 556 556 889 722 237 +333 333 389 583 277 333 277 277 +556 556 556 556 556 556 556 556 +556 556 333 333 583 583 583 610 +975 722 722 722 722 666 610 777 +722 277 556 722 610 833 722 777 +666 777 722 666 610 722 666 943 +666 666 610 333 277 333 583 556 +333 556 610 556 610 556 333 610 +610 277 277 556 277 889 610 610 +610 610 389 556 333 610 556 777 +556 556 500 389 279 389 583 0 +556 0 277 556 500 1000 556 556 +333 1000 666 333 1000 0 610 0 +0 277 277 500 500 350 556 1000 +333 1000 556 333 943 0 500 666 +277 333 556 556 556 556 279 556 +333 736 370 556 583 0 736 552 +399 548 333 333 333 576 556 333 +333 333 365 556 833 833 833 610 +722 722 722 722 722 722 1000 722 +666 666 666 666 277 277 277 277 +722 722 777 777 777 777 777 583 +777 722 722 722 722 666 666 610 +556 556 556 556 556 556 889 556 +556 556 556 556 277 277 277 277 +610 610 610 610 610 610 610 548 +610 610 610 610 610 556 610 556 + ] +/Encoding /WinAnsiEncoding +/FontDescriptor 11 0 R +>> +endobj +11 0 obj +<< +/Type /FontDescriptor +/FontName /Arial +/Ascent 905 +/CapHeight 500 +/Descent 211 +/Flags 96 +/FontBBox [ -560 -376 1390 1018 ] +/ItalicAngle -120 +/StemV 0 +>> +endobj +12 0 obj +<< +/Type /Font +/Subtype /TrueType +/Name /F5 +/BaseFont /TimesNewRoman,Bold +/FirstChar 32 +/LastChar 255 +/Widths [ +250 333 555 500 500 1000 833 277 +333 333 500 569 250 333 250 277 +500 500 500 500 500 500 500 500 +500 500 333 333 569 569 569 500 +930 722 666 722 722 666 610 777 +777 389 500 777 666 943 722 777 +610 777 722 556 666 722 722 1000 +722 722 666 333 277 333 581 500 +333 500 556 443 556 443 333 500 +556 277 333 556 277 833 556 500 +556 556 443 389 333 556 500 722 +500 500 443 394 220 394 520 0 +500 0 333 500 500 1000 500 500 +333 1000 556 333 1000 0 666 0 +0 333 333 500 500 350 500 1000 +333 1000 389 333 722 0 443 722 +250 333 500 500 500 500 220 500 +333 747 299 500 569 0 747 500 +399 548 299 299 333 576 540 333 +333 299 330 500 750 750 750 500 +722 722 722 722 722 722 1000 722 +666 666 666 666 389 389 389 389 +722 722 777 777 777 777 777 569 +777 722 722 722 722 722 610 556 +500 500 500 500 500 500 722 443 +443 443 443 443 277 277 277 277 +500 556 500 500 500 500 500 548 +500 556 556 556 556 500 556 500 + ] +/Encoding /WinAnsiEncoding +/FontDescriptor 13 0 R +>> +endobj +13 0 obj +<< +/Type /FontDescriptor +/FontName /TimesNewRoman +/Ascent 891 +/CapHeight 500 +/Descent 216 +/Flags 34 +/FontBBox [ -558 -307 2000 1026 ] +/ItalicAngle 0 +/StemV 0 +>> +endobj +14 0 obj +<< +/Type /Font +/Subtype /TrueType +/Name /F6 +/BaseFont /TimesNewRoman +/FirstChar 32 +/LastChar 255 +/Widths [ +250 333 408 500 500 833 777 180 +333 333 500 563 250 333 250 277 +500 500 500 500 500 500 500 500 +500 500 277 277 563 563 563 443 +920 722 666 666 722 610 556 722 +722 333 389 722 610 889 722 722 +556 722 666 556 610 722 722 943 +722 722 610 333 277 333 469 500 +333 443 500 443 500 443 333 500 +500 277 277 500 277 777 500 500 +500 500 333 389 277 500 500 722 +500 500 443 479 200 479 541 0 +500 0 333 500 443 1000 500 500 +333 1000 556 333 889 0 610 0 +0 333 333 443 443 350 500 1000 +333 979 389 333 722 0 443 722 +250 333 500 500 500 500 200 500 +333 759 275 500 563 0 759 500 +399 548 299 299 333 576 453 333 +333 299 310 500 750 750 750 443 +722 722 722 722 722 722 889 666 +610 610 610 610 333 333 333 333 +722 722 722 722 722 722 722 563 +722 722 722 722 722 722 556 500 +443 443 443 443 443 443 666 443 +443 443 443 443 277 277 277 277 +500 500 500 500 500 500 500 548 +500 500 500 500 500 500 500 500 + ] +/Encoding /WinAnsiEncoding +/FontDescriptor 15 0 R +>> +endobj +15 0 obj +<< +/Type /FontDescriptor +/FontName /TimesNewRoman +/Ascent 891 +/CapHeight 500 +/Descent 216 +/Flags 34 +/FontBBox [ -568 -307 2000 1007 ] +/ItalicAngle 0 +/StemV 0 +>> +endobj +16 0 obj +<< +/Type /Font +/Subtype /TrueType +/Name /F7 +/BaseFont /TimesNewRoman,Bold +/FirstChar 32 +/LastChar 255 +/Widths [ +250 333 555 500 500 1000 833 277 +333 333 500 569 250 333 250 277 +500 500 500 500 500 500 500 500 +500 500 333 333 569 569 569 500 +930 722 666 722 722 666 610 777 +777 389 500 777 666 943 722 777 +610 777 722 556 666 722 722 1000 +722 722 666 333 277 333 581 500 +333 500 556 443 556 443 333 500 +556 277 333 556 277 833 556 500 +556 556 443 389 333 556 500 722 +500 500 443 394 220 394 520 0 +500 0 333 500 500 1000 500 500 +333 1000 556 333 1000 0 666 0 +0 333 333 500 500 350 500 1000 +333 1000 389 333 722 0 443 722 +250 333 500 500 500 500 220 500 +333 747 299 500 569 0 747 500 +399 548 299 299 333 576 540 333 +333 299 330 500 750 750 750 500 +722 722 722 722 722 722 1000 722 +666 666 666 666 389 389 389 389 +722 722 777 777 777 777 777 569 +777 722 722 722 722 722 610 556 +500 500 500 500 500 500 722 443 +443 443 443 443 277 277 277 277 +500 556 500 500 500 500 500 548 +500 556 556 556 556 500 556 500 + ] +/Encoding /WinAnsiEncoding +/FontDescriptor 17 0 R +>> +endobj +17 0 obj +<< +/Type /FontDescriptor +/FontName /TimesNewRoman +/Ascent 891 +/CapHeight 500 +/Descent 216 +/Flags 34 +/FontBBox [ -558 -307 2000 1026 ] +/ItalicAngle 0 +/StemV 0 +>> +endobj +18 0 obj +<< +/Type /Font +/Subtype /TrueType +/Name /F8 +/BaseFont /DDACTR+MSUIGothic +/FirstChar 0 +/LastChar 255 +/Widths [ +0 1000 0 0 0 0 0 0 +0 0 500 0 0 0 0 0 +0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 +304 218 500 500 500 500 593 203 +304 304 500 500 203 500 203 500 +500 500 500 500 500 500 500 500 +500 500 203 203 500 500 500 453 +667 632 636 664 648 566 550 679 +640 246 542 597 539 742 640 707 +617 707 625 601 589 640 632 742 +601 589 566 335 503 335 414 304 +414 476 496 500 496 500 304 460 +500 210 218 460 210 734 500 507 +496 496 347 460 351 500 476 648 +460 476 457 234 234 234 414 0 +664 0 203 628 347 1000 500 500 +457 1000 601 500 667 0 566 0 +0 500 500 500 500 203 503 667 +496 558 460 500 667 0 457 589 +304 218 500 667 511 589 226 1000 +500 667 355 667 640 0 667 667 +1000 1000 238 238 500 503 500 203 +203 238 355 667 511 511 511 453 +632 632 632 632 632 632 667 664 +566 566 566 566 335 289 433 335 +648 640 667 667 667 667 667 1000 +667 640 640 640 640 589 542 542 +476 476 476 476 476 476 667 500 +500 500 500 500 292 292 433 359 +511 500 507 507 507 507 507 1000 +507 500 500 500 500 476 496 476 + ] +/Encoding /WinAnsiEncoding +/FontDescriptor 19 0 R +>> +endobj +19 0 obj +<< +/Type /FontDescriptor +/FontName /DDACTR+MSUIGothic +/Flags 34 +/FontBBox [ -121 -137 1000 859 ] +/ItalicAngle 0 +/CapHeight 500 +/Ascent 859 +/Descent -140 +/StemV 0 +/XHeight 519 +/FontFile2 20 0 R +>> +endobj +20 0 obj +<< /Filter /FlateDecode /Length 20039 /Length1 177988 >> +stream +x fGu&^fʬ-6jQTBB%UJ*m !! @,Bm 0^X1VCSjB4c[ qcxp͸=paf47Q6xbqƍ8*z0s-{?7Կ|{ȯFS_1Ohj<Ǯ;|=%Ͽ~wZGb4uw^̔\O_}hiHsF3ّzvk<(3xfr1\1\ԳbA=k^k<ճ'׍E_=r=0]ggF޿}f+2xyqpb[o?vgvEf C3wIWLv-Y̢V.w~?ugZ}fî>3A~f6OgQ~Kltd['*?6)/jOC~z;姿K~gxx^,?sg~D~ڃWK-n\jrdg\frʳMV0Y9jrU&+LV\mrd5&+ך2YdzLVn4Yd9&+ϕL7ɿW(Kgo'0nA< kl\e \"8{@ˁNML^ Ӯ|! +g;Gtq׮=vy`< 8צ6mX/Ұߴg{5N&kЗ4ڿܮaj| k81>\9e5i7( sq#oj۪8oWE {oyw"30ᦵ 9z%&wTgM#/VC_5ѲWWFy'Q>}{3Gutt׶IZ/V$pi{`ck3uL]ܯ2AW qٌkU8c:M㐺j* F2׮n~;D4QY.S)N`p2k<{0gٱHŒh3$&PyoʛB0֦.,mr]mBL`.iԩ{mbVw#2ӆMp[U0ż*H?+;U:}ZO]ұ 4>h?Am)~խоݜg5(n윦6"7N_qo iP9k[I67MyD>GQӷqo8'T^ӻ c)A8f=ëugxȏ(<)w5\3 v 0gnyv+i婠k_ +ftõEӆ]Qe8bM87,9sz]2{GHe[1*@i>1({Kx;R89gQƬ5FFv}˕6YNc- G:KZPkYvI*ro:v3/Pv(5~:QSnҲl糊JTWF緋U.ZsʱTqϵ\s93qݨc˵:nS:\~Izuzk=^\jA9I:iX?^/2`euIem?uiލB6avۦqS-Gpg5E۽ۦeHsռun]@0q|Mik}6X9ԺZ<Ϊ߬?ڦ]P?[}r\آmv;z85΁)gUonUo_hiym4>Q߮z5Զ1))4OWj=ׅ>U:+oW6l}ob­ 8Y+m;^@kJj@Zrk(սRla7Y(qSk~w5=nhco6)\p=_ma zk{_| Mv-װ@ֈ(zֈjm*=mk4C.ӶOӸ[[5/j^c*xqu*,~*ӿ9-PX |c.SGtq*ZnԯwwkuWq~yIgh~\ ) C͏{7Ӵ=ǕkIFn>TT@꫄5@xB% D]MZ![k+K0WJ>ɮ:5t4䷂i:ͼ9hj۫qM2 ]Mc TǺRL`n s ;8]Wзv65ZCp­%z=20wlzЦrN6Ɂ>8wͨr/-;xe}ʑAL5No1u ]< B#s\*G21h0qQewT~] m|QӬʖ~\ #h##49_CCGuBEOJ`759@Cmc :3Pn|,L +ȫ j_7H'ր_5xO `sx+Z0Nqܕ'<#w2O8\9џ㝡BZq~RbNtr[hΒ=,[2=׉ Q+eA.֕ztJYB]zV@Ho%9_S}4`q3ļ0 QKʓWJ2O}gʖ̛ O4_89˅LNG.B<(gJzڜOvZYi8)gxHSܮΩ]hͮGd0V #0m@v2vc,P7'{*4d])w׫,UNՍk!ߨrFa@t%WCo4>(+@BtP> M *n@Bٽ@ *,(\ifr}}k 4k`nRC<~HD}Dl ]mvуv 9:8G$W`)z(91n-гvc` O!@>8O:hsϵ0'A7|q|k:CБY!s- +wv*p{I {a ѥ9 kh}i g3Kk4_c}WZ}JMՕ )lwn9뜮ЖWMzY-X.=y9Ju SMU@5S7@t[=/k4 $A>!=[7,Z5MkJ[ogyDcN\<}g}st8##/״^_N{ `4rCJs wD!z a>ӟAƫ)G`wi;PoH)9:BgC(SL)=݈tX}O=-4בN={Ѓ>ӧ<4Ѐ#`}]ِbxd>#c\oDS''|ef^ /Y?ѧ@^ЎT/X Y>4H'3 9e!2hEa^7@5t<3Kz΂ݏ-ʜٽ`@;^HZ&ʷ}Z#k LM}x7 ِ-L_S|jԅ%j}KFK +()Çj xs +zԎѾGxCZs1͑qm~5Z[gtu#S :Kc32|ِn&>ҧ +ǒ9U~mx+g3~.x${Q'C=bfINilrzėkf7ޙ.KpI2kB+]q]CNkr[LkNT_='QϚq3 H'1L|SD>-0,Ѯ+I{곦ȏz> F9m{?8&+4=3J+rZb?C<%~&}ME鲌 + mC(cȂtVgl}cM<^uޏu$ƧF{d'צ>ŲvT7{٦fA(;ԊmUAvM=k9Km8wp)KSZ/f:Dxeٳ ϞœtUgm 9$kߚ{+*;G`]bj+Xw6%~gZyܳˆlq3.CZrY=W0eܧm yظN0=Rt=tG6OW%T i>6#Hƀ1A= cXPڸQ_ !z+70G 4mLCJ2Bs\:!d:11[tc!@9ul#*X[qbƱyZY>s<RMNs}ɜc^Z{lp09iѲOz<~$wBFco~\< ,m lJ3x?tKB'^(#}~0b/L|7|ސ]*ڃ6do>dl{d=3=C7Orz7ܑa]kimbg87d+#cgֵy,ګdg*W5X~tya+U9Unk5*Gsޟ0)h ^%5o!'+ <4} g +S=w=W;v= Zʔ=m0?JW9{x[EkӥG:YhGh.6{}ʣ_D;g4V?w'^hcQ +OKME[3{yOnbrrE6|Rg@cUsʳF{o2ϞGcJ-MB-83q 3Xۛ~?ߴfMLRylr|"ءW[cP*{VEὤm{+A@ֳ0麳xyUι J랇Uk ؅^c{{x ^~mKt:xp v7Aۤ7/}q6^~,lvjڃߧΎed <+ =g>d"<GDf!dϊlp+-96hIٷYT[<9xL2c]w8W<{]}gYӍgy/]&7yhA*Kqpߒ/?y>^~~Ơxٌ86 ] Gbʣ?%*#Jȟ#wx^2^*^yFNi쳗<-csTsx< 9γ\Z&$ϝךNY3x=h<58xҡ״zw,xګk{k[GؼǴOn)]7lM񼆌1|fIk ެqwݚ8u}*~|ODbG$5˘~'E/ܳQzc[+衫4ƞ5*\?N="Y-ڒn̷Fzs۾tUo Wi;w~|{beMӒXd3KO±&W~OmO#=IAWEqI/2~[u[ $_qmfrg< <^~n`߹֢Wݎgӱ~، i? ~%% mЎ|/?Wۿ+6!-ԁ}YKO~m}<\WX{l :Ͼz3ɰYh0q=#l$6+Axx.y<((,s]N+]()>OG{T_s]|Cy,dGoҥ/Sګ4/a}}}Q{ٛg{U| O'rmsԚܧ$7~h [£K=aX'']XII{暾8l;N>{ gQ~{[g ○apNhJ}GlaMڳo4(߾彝)bY{?Mi,g*ϾGXzt*U?[ r[3@ŧk>SZdb;b{9CNz -[En}F)ٯu$Ƅt]U`xơl:Cqt:%2 KG„ʗz;ӖжrpRr3מ*w nINihѭ3%-lFzc5zobd#jORZru:mnRg+g[gV{Ij&r|NBϾ5yoM쉬[|}硎V+ϫ}õإǞۿP} +IEebS+ ೾ʖ=(Qϫ퍙웨i n%{C5@mmmձ#{bH^3-{a}gԗlǘt ؎q~@랍GAm}wY}t]hWh>3Ρ ~W-vKyr]{Nr}ի%yXU<^yG9XM˸Xwj %}.o]7 VlMvDag2<YQu<6ЍGeOϯ ]M9}E\ѣ-{yܾ>Q+ϺhZdڣ#HN}W]:Xq[зhiOt/ ~|Y@=̀vƤOb>ʽ7F9O!ޚt5մog?7W[tK=֊xUv<t[txyq0=_?ngǶ.=Szl{x+J]/.|gydկ*mYW!ب-϶{}[iKyضVP%-vY˳4^C/p(;xhٟ״繍g=}SXEfZlW_gVą.4e~V_[scL[ʶIt[(vz +ZsJWٟTjTP|Lp-m5[F6Y'=^۽ou£BKA#f]om\e.y֞uּe?GAcܞ(Cߖ}x0^~^gbkkL,s5&>;@!|M 斝9HSlt=EB6(xO֞x> -5a s lz<붔-9|]J筥f}peol ǭ(In)#Ǖ/e99W O~<6D  +[羯k s҇fd_{JZ7OV)=`\b}|>-6,R;bs޷6^jK-\a{H[rrG^3OȖVoˆllۤx7S4|c׍(h,Ii~.d{7^e.Kcy6qDҡ<򙌙gl؞xl>ٝW~ò[xk <|g7qp46&f hs9Lfr[鐬ς]#ی7?vKN:TWobmDgBg;/~Iه6eILЊVgPǷNkJc#k /oZH׋ #q#pm6ԋW=TO/<< {.ܣspm4Tīx=ܸ=*Ti~kWeA<^*߂إ'DO;.ߋho4@}Bףq|7xR|>Tkn W:QeCDǮdmoi9MJHv0z֪qoHn%'x9sߞ gi~uŵDI<~$:)ye~V\۵=ϖ=!kyxA^EsCtN=-S{Ǟ>[KHPܸY+j}wGmo55ćXΈ3}JZ޵%&уQGP(E $Ed|w8bYy)+jX˿Y(Mp.vB,8PӸ#m]{/ع'E n׺=A+Znn\a͂+zڮhZh$%nB~HAq'q&UҺM_%1e#m˳y&:(9俗^-GMhj!y@{sZ2#~]$L/<\&^%~h'#_hQyBK'SDGM-MycK)/Ͼv)Jwxօm)SJG%M'a.wɵZ+pwۤ"qni7M S_5e59ה=m-[]F?,q|Jqߧ|iSm#OMk _)8.my=>*=G^#mV L9&ύ{;ƽͺQt a4K^?F'k탦L̺g+[]gM1?T6OoɳSϙ6f{5mB{W~l jݘvgx#V}ORgI N ~X޹[܇QG&~].2\'ѝFos$V^=$?#ӾHƭZ`YIM:Y[G6/x\5g*qww)OX41[m$el q#yrx紥͌ܛz1+_Zg%, +\/ $x64]7qFhF[%K}\~`Wg*N*yv93ѵ7gwʶm#Z7^&snaGNVܣc1a k +|/SR#hufyL?O˳A~Rz}>?ώ)lh.e߯rAeue7{Jq{v\V={͏k'ބ?a»6'],o8!aOK;##Y?[ucdl̥$:*8*!9EAo2YYeIW=_ѹyŬCdb\B26MSalۿ^H~)=#zmd,Kmbi/ߐw[4V?%~Ԃ\8hjiʷN}u28m>e۰:_qꔄ$ڮbynej|]۶~RI[igGfZۿ]6m9Ya&'V_'f$KSS'r}TӒJ\`IC.qߦݶoMK?Odݼva>յrӃVbKFk*CKI,VxYڭvl)S:Oi mOw}nGdOYEǟN[>l7?)wl&>pֿ{=ù-G"|ϱF|0ɲ2b-Cb߇[!+Cl2.,,;{;jzml~~2?5rtF£nt oo<{y0LyA UqPe#7X=״~78F*l뽪BLzuGx ]ұK48/8[':_'vwF]痺U]PGF& ?|>p5C@\^u*g.?KmiiqnW{P\|= +3 R/݄1]j;yџ((uȐ #yqv\fb·v˿gfO}><=k ߧcsTݽ Y.=0\vwgw +3DsJ)Ex$mH3Z>J>[aI/`jyfpNYT1Y u^'sqZعON/s6Ot>3xzTu5. iU+sTT֒Q{cW=ҕg7iY6Y7l`";5cL3K=z"#W18Ս:\6ե9ŚX4;nb^}N` Iæ2LC>zv!~S6̼ .mw\ݦ<ޮ~4\~5}Esآ'YԸ{{)m`?[˱m0}nV-V&\u׹>-~]5V5x^/rE'%-`XiVjOnlC:_uNyHޑozH&E z}^»_}ܷ棯o >(O\{$ߓwBe&rRߍ#;[`/K|&ݳ{ߝzrX,HHѯ,]6ȵ\`Ok}C}פf]/ se\綽$(a?-$/IzR7/$?]L||_W:}W_)toX⚓w'03$>|ꖫ|泒LuRh_?n؋iߒ>ߵڇ˻=7tww)WR߱:˻۾}Cw$x<7sRK{ǔ1Ei__t?NlUזFnIy~{<@/Hݕ|MƆWLطmm8Ӯ~ڑCܺi+q,zωTof߬;ީ Y;έgJ&C/gw'=zZ͜Z\/>!mVL"~_ 7k-̳/k-f<إ2HF!g% {Ց}X$QtzcK!]s1;2MGm6}C U8{h wklC -tůkםS#vm>.>,YwYi\FA!O >x@ix/4Ο|}D](Ϩ/j;6YJxc^s!tFϲ&6w.`W]] vu.`W]] vu.`W]] vu.`W]]]VkO5ɇ~Bߕ>#?am_p_U?!0s2/*q?Ae{6 ~[]^&Kna~C s7Ͼ!yrxB#B;!×l<׶\-~?~Cm>$ן:Ӥ9q+'d֮˖nW %:er_G)=lkG}Y/H_yIL> oa k fyD [ZZZ%|.|jKGIK}r=gM=R}Iqtоҳ4O|r[;',sUOrBl[E i}IZKK|+T^'lsvum>,aGi>zک|Vڮ;$ `Wvu:;ϧRG +DhBY@}S?eIîn} vu[vu&/&;ћ2ϓХ̒ܳj?+#H)_)WR%~DȜviy'gx*T{kev8ϒgK<'}StL:"u<{arϒ7G/NikBYiOuϊ0:$WC4*)XU;䫒|n!kYfmn 7wk;y.OIWlϓ}[^MkCYj/Vln)_`wVIitQhCj"::!Kf<=)ik/{>-gͧuTM͙r\,\sISVq!IkIpLR:&%6mq-r_"xwE~/|XJZ-d ~eZN +W0uӖr-Em?{Sv]\C;&56g_ZX +- +;v X۵1v,7̈h(&s׳+v워KmYf)=ۄifrѱ8k{CcJgsӵ׹۱צEu_>v?/OL֍s3;qtg3CwOu;3r;ͣn3K\}#1̳Q]:N6_rEJo u:'M瘟n?{O?YxyhqIk9~< GqF1őE*^t7߮q48'HuO>;T\L'F((zߌ4*Q2If4zU@ ̋7_{C'irt*؟;~c+)n^\ߢ,~}|_[y?ּ5L" +endstream +endobj +21 0 obj +<> +>> +endobj +22 0 obj +<< /Font<> +/XObject<> +/ProcSet 2 0 R +>> +endobj +23 0 obj +<< +/Length 1134 /Filter/FlateDecode +>> +stream +xڥWMo8=@[ eIqt{{Pd֮>InߡDQJb l39o>@$(a +(EQL5Ԅ+J\e n>m7o=XH"Њmټ=_U ̾}D wyy44]~gigiv WKDfv$關/nM~W|!FpЂ %0 U.,S tDD8b}bc< H)ZuN&L)Lm]Z\nG xQ%HyF:: iWwE%bI:)?ꞪZv$bRq AbGx '’sV+ؔE^.}ޥ.I4e^!dk_c]e}Ex< v{bݘHledeJ$:wt0EnڳSLA}W1\싾˥F8a2 :-C]Z7 u,YfxNS#cҕn^zuޫxLY8wcxY]Њ$e)IJ I3i;Ej3OG]oR1/]6c3f9w8gs&kz>wyQW7LHW&E Q\]gw%j؟s{y%3]@SCeTJbسSMGnȿ<_ +endstream +endobj +24 0 obj +<> +stream +JFIF``C + +  !(!0*21/*.-4;K@48G9-.BYBGNPTUT3?]c\RbKSTQC''Q6.6QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ~" + }!1AQa"q2#BR$3br +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz + w!1AQaq"2B #3Rbr +$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?( +( +( +( +( +( +( +( +( +( +( +( +( +( +(,TfU2ic@QU̎{I':I U 4Pp4y4Pp4yj(5}S֠,oSBzEZaКp|z*!7 T>QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE +*65;7S@347*)zmP (((kƗcthyoK?2yM@ ?[thñ(($a?Zm(~U unQ@TUuHbQ@ 9 +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +(8P8^}I9'4+MD)((R}OO@f"c=թ%E "PEPEPEPEPF3EҊSLJzdTP&؃L(è4PZ*Una) 捗~QEY tP` -U*EME !A- +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +(^JZJ)QE(z ԋ #8RE^EQEQEQEQEQEQEQEQEQEQEQEQESYu DGNj2V +)Z#Q( T(<7 jWT-Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@#0QH{ ŎM+oaMb +("zzԩvzS)QEQEQEQEQESYzc:y҉TTPUur?*X0ȤEQEQEQEQEQEQEQER2)h2E2S0܎ 0 S)(P "(d7RUZ$ ցQE((((((((((((()~I&Zh$ESQ@ +# R.TQHaEPEPEPEPEPEPQJ?|}0 +("Lǥs l;U) JU*I#̵ SQH(((((((((e 0EB稩 +Tu*b +(*pAVPP22)iQEQEQEQEQEQEQEQEQEQEQES$}+Uɠhb +URR* D@ޝE(((((((+aqR6jmS +*8WIHaEPF( +( +( +( +( +( +( +( +( +( +( +( +du@H:\ 0 +(G(} NjXsE +( +( +( +( +( +( +( +( +( +F`& Y@IcIE  +@ 8a bQH(((((((NjUQE:!"p0RE!Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@58Q@H*phǽW L(D>J>~S׵$)QEQEQEQEQEQEQEQE2W0:dTtQLAE$IhOC +( +( +( +( +( +( +( +(#CN6pMCVac@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEp@zQSJZQ@/:#mlՐr2) (((((((F!FMV$ɧlL('Ec".-!Q@Q@Q@Q@Q@Q@Q@Q@#M-E1 +(!Te5#/JŠ(((((((((((((((((0w5d`Z)]v))*H_OQ@n\ө ((((()6Uys{Phb +#Thg +(Q@Q@Q@Q@Q@Q@Q@Q@WSڤj`QE&aI)a0((((((((((((((((((JQL?`EEP!5=Um((Q@Q@Q@Q@ )6`QOw7D4(0(((((((((9T4N\qMJ,%IgҁQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEEY"r7zT4k{mjlm=ԆQEQEQEjNfl=(:("\w\{ +QH((((((((Zcր!)*hF> YQER(((((((((((((((((((Uvfr7zP4QE1J* (EPEPNj9$'֠E70%ڞEQEQEQEQEQEQEQEQEQE.}bp (* f|TԆQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE( +`EI0g֣ <Pӣ8q,QE((cңc&u?EV#A@QE(((((((((cǭCO`QE'a3OQŠ(((((((((((((((((((((2ۚV 1J(TABrQER9“KL1@QE*fe=!Q@Q@Q@Q@Q@To(MHN: :sP,y4f?ݨ zR Sq@'bHcǭ ''4QE1* $#&&) (Ϋ׭:{)< DOYTP袊(((((@'QQ4FiG=@(czxPP# @Q@Q@Q@Q@twQMYPES(wH:T >}jza@EI g*!Q@E9 +QE18&D1QEQEQEQEPN:N* q@[QE1TǷցXRv>@0ƇFr*z(+1lgXJb +( +ϭAVTa@bEHHxj + ɢr!sRI sMCBu8 OE(IF )"T M%0$3bYRi(CBŎIO +[;oj+*pF)*(aUH4NEXV 2*9k{P(Q@5 O@bOj0 +t6@QE!Q@Q@(JpZ&))Tn`)*XWP(C +( +( +sT^C4(ˊmKKER2EU"EZr2(0((!⠩&>VҙL('w6OAS*P)i () B:ҊT4*:QKE +("YKSQTu45QH WҫGSi@QE!Q@Q@C1.hQE1YAVhQE((F8RjM1֡EP (6cs@Š(EPS/F EV#(K pVv5fŠ()pw14QLANe6|2J(Q@Q@Vc&9UQE*}hpaH +M-G3p !TKHaEPEPETSS(($ +֤ (0((f\޵5#"+QEb3^ (QLxEeFeXaER(()AQRnbi)(U`*Agҧ06&5^Q@a^R(ڠRQEQEQEQEQEQE x}*:|~Lh~QE + ~f1!EYSږbHaEPEP?oV-OҫS(, KQAޥ0((fl+MV''&Q@rwzTueWj@ŢsޫI9*ܩSEPEPyFMgQSDq ҩ,E#)5Z@iQESTp}) (()4#noa@ )(F&a3Oqffn@X剤 D2ڛSB02J(Q@Q@Q@Q@Q@Q@CMc'ޒTb1ERgSS&LPQE1@x"!8|zQEQETV}hIG*!Q@R;m\S7;EGA99('$rvC9 +GLFrƀ$)QE՚z4(E2hp 2 N\S(E1Q@CO֤=EKHaEPE2hsPRlJ`QE +{q )6+n ESUP* fQH(((((p )f+Ed (]FX +@Š(H*E-Vt:m18 ժSrۊ>(EPQ̹\Tu +Sv;Si*6WV*r(fL;(3/`M $$U7 (T˜JQE "t5Xw!0 +PTP@)QEG$h( a^KT@p*ʍb&8Lz _eQLAEP5bӖBu EEfMWjFbM%0 +(8IT*RGJ@5jU>ALEP"HG͟J|ԝ) (ZcqR -Q@Q@Q@Q@Q@E9 +(2ڧMKHaEPEPsj XS!l6=j:-QHrKHaEPEP25]44Z{DGNE2((➑p*` EP)QET2Ҧ*  xQcixTtPF=*mPGZURT^zѓ֟E U'$S(*>T:Q䚚h$OJQ@Q@*cNXB@D=w NfH{Lgfi( ((!QCH0zShGWVQH((((MO)~* +`Q@ P"c)QHaEPEPP̸l]EW)jj9(۔-QH((QKEFaO_KEF!4-QEQEQEQER-OLO'ҥ")%riP( +( =)MQJs@6QE!5V4(# i<*j(!iꊽ:()C Z(/'Ѩ楢/RzSޓ"GDJ:ԔP b((((( C1cңh !lTu"S@ t(((((((((((((((((((((((((((((((:fXMd ր+Sy+hW*)}|'Y + + 7| +@F!8F:((((((((((((((((( +endstream +endobj +25 0 obj +<> +stream +x+ + +nX8аp=i&ZZr޳-qdlwvJRT*JbrcPw=RIpWs±c%×ޝ(Q*oߗ0J?4 +d/1Tŗ"OTgP>VhӻVqF\pH߁N%.bk ^TBd +P?׏P\N +wc;." / ԘdV\NuϤDa޷{ c#~KK&E g񟧫UC8?Ⱦ0\b?L]|=O6w, 15D 1|Uw DaOf@)PjR22C_seU1]q !}?G#0@o1?F?~-W}`SN˧GT W kmf(Wt+f=c+4$ ky˯ zkP;n^뙅^vBJzx?z)Ix7xzɭ`Z?17"+쳶@.ɝ}5V@6rB3WWfQ5SؿE(;0;ƅ͘ !yX2~7O%<Ir +M<#3..]&̑NOE3,q7YX0s60i1#{'cR-1.18w}04OuEn]EP}d9cNTDŽ$mq{okάe ͘3=(c%߬3[7;J|u |΁D"WK`~[>Qj/x ɜ,DE{43- FQ1{T!R'2PVYPDe( (nS1;] +Q>27==Xޡ]J3<>,2M+ѥ(^^ + jK֌O1!*X:'mJ#Z)GR̖p|D2rOڴn`5W +`~Æ, tpNط炦%zyN28BQNr~|L\*X1KR@p0aN\*X\QU-j3>fCiBR@p(!2H\'eYHa Ry[ %E{GY +xa +b.,jNx[4 .,f22wKC&r +BYRp!G]VδN䶆yzP0c,H/ K +ٯ/ `r>"sY}ñJ`yMMucsdVV {| +hZF'뙙8H]@R༗HZ^KT5^_eNamX?# L~[5\] '\ \˗y#Tr% X@$a䶌#!ek$c +L~Lpx?L|pdp`ۆi2Q 8<{< ׫fzKm '\Of1Ҷ#.u=-72m]:c<&pAaA'd=kQ$09éD&> 0 +xqpaKS%r܄8r& 'x`p=u0b\p= &Qa}$Ӷ7S``X4XEaA<WCJ) k;+8z2bǢgwa` +$Y=FQi!S٠_̪4Sp&?:b +%X=AE` tL{>,섋ed/;Şɺob +ã٢h=52yN?|&b>uJd/a`ay;aa; Tv\a'(YÁpd{xt`hEGě]-섅V9``'|c9/=4Wzb8D+}6zXih6 kM `êzF3,= 1~\G/ddȃfm->PV1E`QL[ VPoP4UDNx+h!XF<01aAaFGl ,>] +S7T;2"j8$1*L“J & 1|XdE`%R5 1J1d/9/ Ɩsde`bY(`dOaE`9euD&3$22pF|VԘN`dRs90p a#鸫|7^bx -XSٱRv 6z2<'|X1r^Dp‹A-NM@d;'lTJ? 7(bk8rhq\^k,솹'o1a^o1{¶anc%GGYa4#Z Kv5 |DAꖂySe-f\2o/h!-3M@0`T_?1Hq +v‹YHv $HQ.؁`@bK'"f$|b~sԼ$(V 3D` Aؾ^ +Ch<Sl nqz 'GV Cf,Nhryq5 ?oBF,2yv [NS}E@"3gEalB-Pd 0pJp ++D*SمyKā  @ Bq'/9}I|&qJ [xiǥ AA"~k?{E5>}S*)}Z-b̯Z[4"k(hSd:W8as2%<1WK@I!3w 08B{ w`DAV;"T.՗c|p~|@ t}aue WapKOr@ lw/ɑoDENÛA ᨅVx.`o)%F%9;r˞ki砵û٥lW"@|8ȡ?MD//}b??TP4l9 jA"PX\o=9e5k-N/f }A}[6zөTsnAd`|-k Ĭs4߁2/u[.w%7}Q*hpH_ʜm0j""4$K@Z5馼rG5Btˣ\Z%tx ZOAs0$Lzڒ58b6tv +C`00mISAL>Ӯ{Y~qCSE`Zhs#B?>F/( &Pr y\(.=?)bbghS#B@Yu~ьۜroڛ>45|yT`SexHv^=.(`,ܿ{@QPgY`S{t| 68kQ&Jf;E`\SM¦HR?6Hw4Myx>  |HKPJ4ܺgߒRqph-e^fG볭ra{Yg]!f`3\$8:i#?yP<&g{H+W~<epBҞzkUR.X:[m7~p20PQޚ[$ԝ@h(aN9؜I%yfzˊ-E;|Yi-;AC_?1uF]R8OYyӳ-yvW(H]Z-BV4Spn>B:0PQih8S./* + +tXBۣa'b]_XXPFDTׂjRw҂HߝW@Zj =^hw vа$}|~.NNZ7?V@ 1M.:ZPZmjZ ٮ/lMD!]eڔ嫀(ʆU:"7 +…[ c0C#OԶfK [q`CLmcandj+p饪5#I:PƬb%bI- mc@v)?|_xˉVwCˣq:1p" "!ÖvȉEUmïE]UbD  >x?Ư_dX0$Vk \|.dmC:c1d_QRځx!'3D@ m!ςԏh͵1[GgK*3VuCzEn NE$&^]ڋC)`4:ͮ"32ͭؽpA{U-p;L^s:r+E|vLiPKyi0-{4cJ{֠O1WZV|C8;49ZN'rªA{ՠG[tߴ>u] m`^A`K;yf(H,J0KI* M_)xcrm](/P;5 /h%3a08NiD9vO딂$!rlI4qN{8Mh%`<myVMQo ])[s bvIEbil&V@|VgQm Aޘd[Ai|iAnYݐ:@0@ݢEPxSJ݃"ƗVR:́,nȊ*$S8b ~T,O}v:Z(/3$l ŠV(XH?!cMdE(hp:9@LjF4kl杳_X/GF&Xp2|tkջھz>(*L`vBܶ7}lİAIJx,Ղma:jY:tn40AdcnDa$ +8Iܭ*S!/_-7SGCd1c '&蒈C M&ύF=@L``n\u.(@LxTjH@v3ѵ̛y]xL%˓ ‚?7&]A" ͵-I2fFNje`ˠ yhL1ܵ51Vu$O|0I# +H@M&&o@Oߴ rc`u*$:˚S5IS#ߟdb)﫫y%.`>0XYx]c]DQpɃ[DM'$#\ՎVdSKdQp{@ߴmC/8m0I¯ !AB(8dAJ1XV +;6vu׀`;nU6q/> ^SK8wxAWKI'4l56y OM62wXx)!s|#",M|hwIM(ϼ#OM`ᵫAhUśwv*؂H^nh߅bQ&Y"_灙}9 ZLc| TУQpvr~s1清٣cYn +FУQm@^l .oY*m7n-PmPNϛub\h2oy Hl8=;<5 + Ӌⲭ^$v+fN/+"AOht27 &V5_/@WǏF7qwzxrv4ASHwOy0['|oA 4~AOh#S; ;e,ZXxO.,W&n/ ),& +ً.f7EQpI&N˄E;aSS2/QFFQpIg&'7+;qG]X|*+Mz<5 +6A lW } +xijΜꃠSh=g^94'ςʔ̬{8aT)k2i=gj1{M/NXX=mP0JYI#݂<5 +6schu65{қ{ZJdB~T;m B@CGrw DNzf-%2h~&eF4Q!x!NZCw|#)rk`qMjzoKJ,>`FөEIM`? _f2(RWGYFl}cUNt. + j"N`4/\:rkㄕ P}Ęy/"hv¤ hq6{' [>f;[WX F|iAL +\5*q82je_Z!C/jH/;ًx;jLаCLG|i@+QNd#^C#ĭcT4iiL>bT h@+G2aupw fM͝ǝ#)X=, $4COtGyH]BXhءYXf h$,k{aG4*vWr>|hŰK, ݯjkdQc:Kar1gfc]ѝΒ6>>"F"@ 'c0Yɛ!=Sv4t +=B& E^ۡfZ"G;GaǧcZC_s<$ {,G ymA3O>}:b f h Do:'C¸(}:݉LBK1@5ef|9\C'sP12Qtz0bzxrϷ#?6k5ci YPtz0bRKCXwkXXQyx2pIv'2a-D(%T+XqXmq6 VCqsz%O #3g t6ib6; L@owaVա;5U% )]s - C%jӁ1lwv/X&JE;(YF<M#vfE:Nlh*  |'fĶTM{'n9U&e2֝߷?`X!6G:v '_B-h:݉LXVBK <>E3#:E^$G 1Y,ٝȄ5 l oo#!7].T>hMB10;M@g=RRޞkbSo,=21;efqp Zm1G_B矓.HG'ED-e!2zpҔAgwp`u[+fw:(2z _1P4:@@Ӭ67\KM l5wDCbG̘~7hrh8mr&nC\zI>AΧH[X Y?p$KC~h.L|Q&'51~ֻ͂.N$_(f}8l9^dG¤Yr%u/o%8ϻ7*";>+v*#A'F; .Ҷ~H[{JddYm>'>'> 0vB"B D J5lDm?0H@ 9F۔P;9Y[b%;Q~ +#Dq7Ue)+;WM탕<$vO* hYOXe +gzȿѢJSB}>BmC* 8 (V/rnH)Vi@Z-}g~;vQ MZ ˀTZDM =Wk}v!zGv!Y`7Pwk֭xw4JPf~1 +. \gN )rQm#1R_vO`! +:k&pݹuTwykQ4e<gB"vDKFuO%|Q_ggeُ4w&-$7 |o,c-FЧnWBuSŔMYEO9b"܁5r6|ާU Ԝ d<~i,P@Èk/Er/z}WTХQdYW^ b(n <Tن';6O?]#Nw>X KjȮzrz@F#Wz>0oD.+ ,-SB2 +p&a ur {yM)av-xWaRN/&G#5sJ4vXWBȤ~Fc[p8=uGw礻 g +qeB7?ҏT Q/tinpBZ* HZp"{NWO+k_e +QxpnYrm~pQDHȚP +sHz'  ?R9avK_K?4 4&mc倦?tfe@ G?];.T|{wwBWTzH_1 +Nm3n6IsKr 1770IbQA H4^&>^|>x(A'zB|I/C|.He6  \ L4ٹ.4 )y+^牠pHX LҢIH4 3X$NC+yu9*`r43 +UyOhDs}DRr?ulNC9aGKL煇ZgKxz/1+#?3JN'ܐ I"=I.I򹫧'_E_=MH„ڢ<-."Π>o"{§]k  +Ka.'0͋Ǟnh~)C*/]':_71=UΙlHP1'h\Co2ا^ >Apwіx&J"wYNJݱ0אڍgҜwӱ m Bb<o|wt׹봖R,{?Bn(~Lc_5EaoްLX!z.B mM?<`ݧ8~핿fh3QPec!@(gfxh:z?¸0~jiEη͜ B30 3 .5 Ta}p= +fڔC1VSr7*YEfM!fЮE/hXLG0fa7OS>( sWIXkboh-)1푆y+A6S&*\|)N/$sQ.nMgmM+]X"0 ?8eb*"f)إsy>"p (%%|a%u + +'Rl+>G` +剣bO).W..ƔgxӶL犫v1=nm\jݏU+''N$YqU.+W/W +u՗CU$*qyM; +a% +K0࿪z M_M| +hz5)oxWFAKXp=/s,TH`*`d ğ|e䪀V\>#Dآ203h?e++Q6oV_6Ҫ̤c:E})7f!z +^#S :ٸ~qH[|OtUX7*.Co<70pCaI~iG2C'ߙč9!1'û(p/=nV|aH 1sxcZG@)kت +b7ˠ*N/g|V3;mn"/}OkHdUQpNk +VUiLo| ;rf.oc)g^yfɿ 3#(i?{f,˦5ϭ/OrGm0Lc/-~>b +*f7Ƚγ.|i_5~+FH +~4y>Vžx參9s~EyK?>Bxj:죥[P Ϻ2`edVa*B.*Le\0)1&q!WWsʸ?R"Tƅ>ٹ 1aXw7*cCi + 0QemZLވ +S[aʨ9olד0Qa(Z"TF~ 1XJ>nS֓X*.*|2"xE[$WSʈ+{h(~ek2)!h|_eގ_e(bRPկ2lnU*CeΉQ߃V"3,s=RC}=d᜞neﭤyqG`"GL|i_uņƮEվwJ\U:%y:ָ#}|>aOTby|J=`> +stream +JFIF``C + +  !(!0*21/*.-4;K@48G9-.BYBGNPTUT3?]c\RbKSTQC''Q6.6QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ= " + }!1AQa"q2#BR$3br +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz + w!1AQaq"2B #3Rbr +$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +(M-uaQp7诜]Q&|\8'$z +h#׿7S)ƀ>v^ޣO4GoQ&={z? AGh+֯x/%~!4%%~!4`kGh bkF⎿جFAeAC6B?o|G %Ķ{OQU,OX6?*@Q@Q@Q@Q@Q@Q@Q@Q@T}\^\G+l +k❅hWqǛ'ȟJ5OxR$5[c7wv+ u@ΰ{RCjk##>1ׄ^Ggsf9&@?= +2DVP?VTՠ?&aԠC^SEz|\PnH7X)n2>}\(<6[ZV5><ɮ-=a'AxU]@iZǢy7y:tZIbgGܟȠ(.~*+dz? +-'Yӵ?NI}~(Q@Q@Q@Q@Q@|BkojA+"xtb~GQ^)RW?z/z/}E|RRW?z/z/}E|RRW?z/HFγv1W ^@BQ_?sǫf~(ui8Fb=;$O]!?+bʐ/twhd+:J+>#zYl8G f9n%Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ZMs&vB#cɮ;{~@/^ߡPmEq?<=??hx{TQ\O-r*2SN0eܻ=Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@[k} +ɡcsQ'oJ+4g\?tW͟Wq[hҿ@I_:麍jf@A=((((((((((((((((((((((((((((((((((((((((+"Fk]s?H_ h((((((((KёU'o8yB w6gCAAt^XϴyNW잫qh? LeŒ:@Q@Q@Q@Q@Q@T z +/MIt;.VI 7_@'#Zie4wc?ǿjSV5q]I<TPEPEPEPEPEPEPS^iIsg#|csOOz|^-fiw?ec~NEPEPEPEP1'DMKԯ v"&ljWEPEPEPEPEPEPEPVlo4=̶⍊V='ÿ&>]F.I~#}u^)~CqzVGCe=8]Z6^%=^ +( +( +( +( +( +(1 F[(Ҁ +( +( +( +({6o?=[T|MsK+:|8O?ÿכR3f%NI4QEQEQEjhuٌz}HHxE@tW +L[vˀ?M \$@@ 7_KxӌMqgxUZˆ:+vdgCԴ;#Qh_z΢(((~x S^ɺs }++2}/RlK_P}⾊o{s@߅Y((("&ljW׻|IR_5+h(((((((((GBo4N;7/ ȽW蚽hَAʞƻ#:>s,F[{eQ@Q@Q@Q@Q@Q@'SG)?ʸ~*'qW@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@o|U7 ,9^_1W;ηIuݦ"??0h(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( GeM|_Gk_4QEQEW7K#?_qQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@@_yzYTQEQEkKvOWuf![O((((((((((((((((((((((((((((((((((((((((wtU@#Sq5(((ѾWͪ坽e_nwtKHLR;JR:*fύ_{؃*&7 J?5p0<?+zo~۽իv&0'cTW[u7j?ǖ~vQImp?Slu fx[H-jc{?Z)H F(( 类C0da׾S_Z,w|F?N!gqfz(##EQEQE_xQvՏPK :OM!ky'I#b{rh:( +( +( KZ֭tg7TrIO6nb:}=O=-aHaAD;X,"G Jv((j:}e% 49V>foxVo j )i,ɆS?Zh%Ɵ(f6?=_<M$2Y#b eQ@Q@z-Lh:kZMvȫWyo$9ǸZ( +( +(9?"j_FxM{ğ5/eR& +( +( +>{4L|+3Ey{g¿c:sA&9NT +Ӣ3? *538,Es>!+~[:ҵGp7) (MoAԴ+'Ph\r5_IZue% 4[ׄxóxoXkG%q qGCנ :( +( +(>^u Z];nG7O`5^]v5Vt[zQEQEQEQEQExO1*㫱#?\uQEQEiiUOekAdWٶm~+<1#V_b +ٶm~(ͰoV +ٶm~(ͰoV +oc~h?ҫ +p|ēԢ9GLJ/6Ylʼk,&X?^^E{# TWw)5M>=S61'?\%QEQE4Naf7a~Ky+$é P4TvӭͬW %@#5%QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEP׬ kw@zƀ +( +( +&3_?7K#ݢ((((((((((((((((0V"] /Z +( +(-i +Nl+i]BQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE@#Sq5߈$j/QEQEQEz^^ccc?jWPEPEPEPEPBJIqbFQzׯWӵ24Djc@9:(((>kgWC+/\~|U,؆y ??t|~5QEQEF6 I1orlk.xu((ˬ_*رMyz]nz4QEQEQEQE4cAEsA?{yōM;ȉn#8u?ye@MG_(s^&Ej9QW +?׿ +e߃&qO#5>$G4Pd{Q@Q@Q@ğ5/eR&Oѩ^@Q@Q@{g¿c::υ&]tEQEQEW#3J]G’Ϋ3)g˟W]U qw\4MPTQEQEQEu M8Y?#ʾ}ca? +?>?}@Q@Q@Q@Q@Q@'SG)?ʸ~*'qW@Q@Q@H1_DH1_DEPEPEPEP=cOUnxg?+x'WlFj6z`c3y?Z(((_g92/>Ҷ+ tF?p nEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEAsyif]Co\3U +YEP?)eh?)ehCoHOoHO U[}KOʷLglr@Q@N$d'W?E}_Ocq?΀=z[` e$*ſ/\BSc2$p0N(((((((((( +5A5}QY_8EPEP_@xD/k&3@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEyYUe__+WPEPEP/B?!_I͚_m?B((((((*kyT7ր/\>$#D.*X?uW7|/9uUSblM;P+{~( +( +( +( +( +( +( +(K,pdT_V8*|V1pGҌǫظ>[?ր4(t2RPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP\5?CZ諝"Fk@ EPEPEPڕ??3l?zuQEQEQEQEQkz-Ν.+aʟhK{ Je=8"⎗5."A(MÆ@5EPEP%0e#?J]KJL_J`H*FA -Q@Q@Q@PHPI & ++č/L-UyγM{X,^0co/ӎOM{N KȾԭa sw4 [t=F^3Ez|qoq$/OcT'm{?ּZb~?ivyl\W)O綺;^TV?p >D Si󞓮4a%7O'_m-SOg:קҀ=lnƲF*r@Q@Q@Q@Q@#2bG$+7]7I/m`?C{+k럷^;ES~@úadgڥl7wN$i\h;4[?5:ۏ'q@=#`j:`!XPĪb#U'0?)Ez5ŋ _J;.PG5_<=I.S7#hG(IcY#utarӫ}Z.>\ꧏǭz!k l'rciEPEPEPEPEk7ī9IE ҟ߇ݳ*)f!TrI8O>K, qq׍k>"&I@A~=k*=Rպ,vi c\|T$8!1пpP\IB?+B⾮EOk[H@Ӯ-Ϭl$+Y- Cc?|E};ExsD*fXL{w>[Mzx~W?h9UoTh+?I?Kz5ݵ\N*&(( GeM|_Gk_4QEQEW7K#?_qʊYU$y&Z;hܣ;ۻ~@݂pa_xv VmVa0O-[]u 3裁Y|Qg Vn _B&-P[Myut8I?@-ݏDf5oE*-/?匿:~G |HӵFKmIE19΀;(##EQEQEQEQEQEQEQEQEQEQEyYUe__+WPEPEP/B?!_I͚_m?B()'3Zj/ N29=kO?+vu,?q4u I|'Q ׊?-'\u I|'Q ׊?-'\ u{gjy @n#5W|鎗+5k=N]'Ae r4Hƃ,p&O-ydF>~ڇ +lOV5ޫwVfV((_Ʊ( +( +UbIrIEt7%Eٺu=G^iZ-gz<9e85ᴠph+| I#ҵFp9O`*((((ֺmΐ,OyO>%^^уZ[3Pk^%%OD/?pZytE%GOכ,$rYI4޿񗈯uYO|%I|4&EQE9p#NO#Q7f(H~+\*j *wa<W}xHWLd+JC":)TWST,]_z"rƀ$(((((((((((((((((((wtU@#Sq5((( gJR: +( +( +( +( +(8"m IVk+߼wmjq;b?WEPEP^rmާfOD Bڻ/wO?렒?w@EPEPmF5 6O|q ʻd0 5#a N$?g(((G#":):_nqQoǟzਠ}|iX*~>k##_1VjV< +?>W<~]w=f {mov8+([nl۹+G9(tho+O5@Q@Q@Q@Q@Q@Q@Q@ğ5/eR&Oѩ^@Q@Q@{g¿c::υ&]tEQEQEW|Z֖IG}tjxS_sO7NV~iz}cTu9ttQa@((>S%- e?C4KA s.>r* +(((((?9I\cUWcSG)?ʸ((ZG~&vZG~& +( +( +( +(Oݱ2zҴ&L]ނV??OW[S5o$4>;S((/ZkneT`sGMpnZ+ Qr#QՏ M,mWl0ܞhQ@Q@Q@#0U,$/i4m2\Z!<-Ou_CӴ9JCt3'I$J((((((8$KKi8բ_oj-.Ur:*Z?.}Y^9:lgX!ԆPAGzZ( +(M,pBJ#K31u4iV2^"1S^1_GDz>jojgad~s+((((((5o9{9yák-.UWr:+~5ѮDo?=ހ=(():EI#E;n.!E(Գ+|geYo*]~|>ރ> +( +( +( +( +( +(=?OhbFRsE}W"+FVSG|^h=FOi-ccx( +( +dEo4Qfv8 +sO(f6bnH'?Ҁ3 pOQOOQEQE<iRsN=7^":6,mTװ}+ƭ%^C1G"sZ0Cqz' J( +( +(>X_RVZ(+4%{R#OAҪ|DY4m*\\S=<,^jOuy;3>^(((((xQؚS[{_~E} Xd,,~hkV5(ﬤ)"Gg^GpklEPEPEPEPEP^ MV]2Gk*擨Mj@Ќƀ>S"ȇSPEPYei{{&^Gvzջ,eG J]ž$&Y-?u}O֠xķ$d(O>h +( +( +( +( +( + ]&-.#~>wVfx-Y!C#RW,1»3mֽb +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +~ ȑE\5?CZJ( +( +(=?ԯN111a+Ө(((((Ì&1E|_MLd.಑_2EPEP]%m6qeԏ\mx5xJ#P~gQ@Q@gEϧ܏A9F}+BXmJ[KهfƩWx֞& !]?s8#_IoӸaq@h((((+WO&Yw1/r+*ߊ[H.7?+$~ pH{L_gxݵ"kKx0M_5Y_] eѹSWm|QԭvǪE$C@Ec^'5S.2`E;FElPEPEPEPEP1'DMKԯ v"&ljWEPEP^hѫʊ|Ũ>o=iBg>{8w&3jqӨ=͚+3 #|UEh{/+=&oG&֊}}u=<PEPEPR[%vye`'Tu +>o5'nl9&Niǂ )#W +( +( +( +( +(+8nmL2*j+~,kãD`?Ы|uGG!=|Q@Q@khCra*~WX}}ogl,kRq_DhUahcrq˷v>8/^ ++jWsIcR1[ Ѯѥ)i\ +J(wxXs?$Ҹ/4{U L{~TPlU'LJ-r0f=kͨ(V*RQ@CxSVOk^}?Q$)U?:j(+*xkSۨOz5V-EeS*cđ_<]yw-×W.{rh( +( +)E$$1#DeGH&Ii\1vcԒrMzSq8U=QEQEWMC.ءFwE]#OUխl"&}s 'Ol!@¡T_z|i1OYYgcOGg?)P&TB.4U|חxwZ5壵ՀG=^M4'TWVV̔V׋o/XL GZ(*摨Kjڄ~ wjʹZs #"Ou1;@Gº(*d::ϊ&]utQEQEQEQEQEQEůz*X_R\ǏޭPjLsl`ҹzvob>D4__ր<((߇^!:.L"93[[8>ڹ*(+|/gv`\[C]^&qj?/k((((xsPDgc:IEyeA^MxŽHзGK>v;_GFpFPdbGPi+OE5h_>F72G^U_ɢچ^{ˡ_~߭pQEQEQE{πux^I_u#F?Z+&jNf."ޣ?O^@Q@Q@Q@Q@Q@Q@Q@Q@Q@@_yzYTQEQEkKvOWuf![O(Ⱦ0~f?@!/ٚ +( +( +( h^#ՒAK)P=3Lծ)죀=IJQ+?^XB#A9cօqq1&\/?T?e~`ֻ(|G-_C-eedW)_Nד|J|zNt؂[1(Fv?^<(((n_~o|L{Yl~י|%5[bx9@!^@UMV-/KAr=q~'"/2h6υLIs窯GyO{u5w irsPPEPEPI4RP2I=)R]_>??ֳ~xyn.$.cB|pG}OzAkgigK[h`AX(*#VOBn}sZtPxxnaqn6+a\?okM> SM]LO_:6RڍŌHco|Z((#*K2ko j˭ ?1_;WK[qx׆KQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW;DOֺ*~ ȑQEQEQE?3l?zuy^@Q@Q@Q@Q@Q@ N+JOUo% vM|@Q@Q@l8$OJֱos)4QEQEQEh~dmuXp}T +(MTыOhsAQW_N5h^]| ?]\KIjP?rFHȌX`€EPEPEPEPEPEPdNU z~%\[2Zk=kΨ-`TUR׃?~ +KMa!PU Wr2=Q@Q@Q@ğ5/eR&Oѩ^@Q@Q@Q@Q@Q@Q@Q@Q@UH֯$ +V=IhZܻ׻Jӭ6 D +zR}泼)_ ig\IM1((((((?9I\cUWcSG)?ʸ((((((()Uw8\)(B>5-x@<'hxk;53m/bg(2[1>z>giX˾';v4z((((Z`#l=N?#]xgčD0Ps/h((ᎀon5H~0O+us?$ß]?(((eWRFAP2ѿIsf1aqÑVz;bu^Ah\M^]@Q@Q@U7O%6Hw~W.>->A^@!A$$iic&<ȝ `E|ARA(( ȑxJi +?M}_2#nUx\TR+#p$>@mQ@Q@~4eռ+}gfIUQ y (L{2i4 }׽Q@ /?'G (L{ ?躵6/o `1e9`Îk( +(usA&9NT +֊+wQ@M;S(ɾB%nqaflVFc7g[ˍQ*QEQEy_K@ze3Ѝyz$΍pWSQEQE_ -IZ9 +ocX"7 ׯY(C\ ׈׸N"1^@Q@Q@Q@Koo5O4pXKi: {晶=ϰtl|7d%ݸڀ<F](SQ?チm~sHڊÏ @{ߏ֪\-eSyv!ȏ]ˉS9<+$1&o cޭq׈WwoP7#[䴄dz3rMPEPEP^E7 ޸yr9cxׇl?pҵ%d0jvm+[\۫ +((R9|LDže׆K^^3s$οS+٨ GeM|_Gk_4QEQEUMXmaɛn{(O5J7g\żg:H4Ȭl* 廻wcjPEPH讌# Z(o[h4̓Oo~U׮|_w4e((of\3g)u|~0a +22 -Q@Q@Q@Q@Q@Q@Q@Q@Q@@_yzYTQEQEkKvOWuf![O(Ⱦ0~f?@!/ٚ +( +( +?:2>VL\^4IӭMkh83̑D_I,h*((/m!wE2aF*z(gO }AUnx~1UFQEQEz1EmF_]F|Y0wZ8ǐJJ+.FEӑRoSG^^)R^/xđ$n?@mQ@Q@(O +J}Y[ܦuv~c@5tF QycZ4Q@Q@xŋm℻Q1?/@?lk-2#,c<Ok'h**_\ ?Gٮ?焟ɠYr`Qg>!?nk55Xzc\4JH4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE@#Sq5߈$j/QEQEQEz^^ccc?jWPEPEPEPEPEP''d=?ﯗϵ?y#ǰ좼Z +( +( +~CxHֹ*ے8g?@EPEPEPEPEPT5MK٨Xq2ÑEyޫmϥnݣo_ς?Z?xN,ElwJZ(Y sFꮤe}+yagyi zKl~uj? 9y 2ٹd~XPOvm?SQ'B+|=;&]2YP?@F:a`mQEQEQE|23inCl!'\z}q\-(%H G r~u Z^9|^ z5@Q@Q@ğ5/eR&Oѩ^@Q@Q@U-3P+ an}p8 +V#5{P]@MG_(s^&Ej9QW +rxcH%zEt0f(}?k5B8P ${D! JYR?3]E$Ն#)[VeMs*SƽoD +Z_s9c޴h(((((((#?\uv??r((+υIa-@>ր<TOΫsj"h(ᧉ-1GotsWV>)@dB)EQEQEWAxkSg)xszq֮~^ HYaC#B M^;şwI +v1'??ƽ +( +( +(4M!FwCNM{ uIA; ?|־|((U,Td+kH dF>bjrT3ӊezߑ@Ex,[(_o~E{eK=mȣ_筿W/"Y~%PsZ!'>]7NGX񶵭iazBs\QEQE\.͆ex( +( +(:'ƾ B7,K)ǘNyM zl[k{|oG=J8\xt"F e9dv-ʃ*+߉ȽA=k~kߊzM{F=Yx(iޗ&>3[U<ʯEPEPY#v8k\ @qd+>]2.5K8NYo5=By}qq+75R=o^VF?FRW;f*(ٟ◇r!c"_-]}5]xxzot{k(.x T m#Zh +( +(=fZ/_kh("Eb:w(((TQc(hkoIʃι%"'c̏W].4.9HLKR +( +(9>og,Q++oRwtιoTbTTbTTb<% j2*"E WEC]^&qj?/k((+>!Um$-x}{o4k^:z( +( +(9 |zu {"Fk^ @Q@Q@}'YۡC_W͕v ;bE_(((((((((0V"] /Z +( +(-i +Nl+i]BQEyׯ?e^3WEPEPe O# ^@Q@Q@Q@ G}O 9@Q@Q@o_{=xv Vp?0{E.~}N|5˅>?@+v!T8ki2v,eQ@Q@v +bc>Lj+оGr^2}atQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE@#Sq5߈$j/QEQEQEz^^ccc?jWPEPEPEPEPE(j6zea_ּ浼Slx6cyVMQEQEz-=c^G_@x&;:tpV? +ݢ(((((((((ڜ{/3"G_ ti7g'h@>vLb7^@Q@Q@ğ5/eR&Oѩ^@Q@Q@zߏ5y]zߏ5zMQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@'SG)?ʸ~*'qW@Q@Q@}NX_84A~(,qG*,XdEqz}RVgudoJ{ +%^Qujj2YBzc@6[-M%4/@Q@Q@{7okٷf~V'c~+j֙\Z֏h[rcҀ>^\ - <~@Q@Q@|]xWW|Xb< <jz(((((((((+/9Q8uҾ}{"Fm@P4}oIO$n>@8kUލKaxeócTkbĶ>ULgQ!ڼS_&eZɢ(((((F*r8" &Oێ1~k=Þ0)?|/>R+\_|4¹Ihèqs +'υOG+ ϔƀ<:*SZ`=|6&¹IkӬaRBQI'hQ@Cwko{nP$и#A:~6sf<enV𞻤n|-cQ}E|E}xkDrot$ss5j? +ɲ}Myj? |Cg8os_l~^Nm'oIP:EPEPEPEP|0S_bHZǗA?Q5f6[[KWѶw1^w S"ȇ،/z_&yҴqgeQEQEQ]oíN׵]F6Ƞ9^C(JW>)?|/>R+\_|4¹Ihèqs +'υOG+ ϔƀ<:W>)?|/>R+\_|4¹Ihèqs +'  z%emL5*aO$I89_e\b`w÷xk3;v,rI9$׋|EsxA:~}Ġ(([{H$f葩cWC\s+6ĒcUFhOHW3Rz/ᆹv =qKiJS73 ?@9xe m3V*fyV\ 0Z(>#xSHt[:mOyz'Ƌ`pN~Ƽ +( +(;5]דi^^;F +ب("Eb:w((+S5i+.<1#V_b>(((  +j !fA:=kEQEQEQEWNZEaXwƻ"@+>!xuwm8#M ((((>Yo#XAr^\m0w\KvM} (("&ljW׻|IR_5+h((T5&+T5&I(((((((((((((*'qW]O1*((/i+;B^"/EQEQEW5HҪkq^h*+Wg?@G!1y_Њʠ((hƍqهp~@IzjۡwoP}W|,صܾ-0~잟ǭzQEs>x+SA0uk.x^?kX#(((J!/?{|ߣ]g6W_H@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@6X3VQ@ޥOj9-폗qW|+4E(E/J(; H&^PW#l0$SF}5K JbXc#؎hQ@Q@{[u@-J+ nQІ_Z|H>wJ((["KW_yO"^ i´Q`"=((((((('Lq$ՎI+>pSDesO@GEPEPVu=RLO֪W]%ƶ&8ee,-: T *G>V +( +(<,CAKJ}6k˫z'46r-FbO\-QEQEw?j'ҽwW_WPEP';Dxu{.ߏCQEQEV?j? V]jxcF#a}EPEPEPEPͺo-Ǔ31R6?b peF< rQEQE)<`"'> x,7^_;bi%Ӯ:?쓃_DPEPEP7|KchgyWآEstu4@yI}p5T_ttQi׎@ WAMGG-]}5]xg}Sԭ`iO]x/iߒ1TC]^&qj?/k((+ݾȉ_5 Dz袊((~ ȑׂW@#Sq5((п׬_k @wzEPEPEPEPEPEPEPEPEPa}^E^@_yQEQEZ+i]B|٥V'+:(/?_>0~f?((g5{x/@j(((_;WGG}O((~qWW+?'l7]J|˘v+{Z\`glOQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE@#Sq5߈$j/QEQEQEw /E:Ԯ򼼫w> |iPFo+(+j +c8:ũu/8L!sTP^VbZX\7{cSҹSvx,=oWEMuus{;Ou<^F,Oj(((*ޙ]jYYDdCA} n$ȉ/5DԿJ((+> Ǯɫ> Ǯɨh((((((((((((?9I\cUWcSG)?ʸ((+ @wzƾп׬_ QEQEQEQEyW 7e厨ĪaW'7wh:*K|\/:Z" +( +( +(YNAA{W: skw ^]Yǟ;=/^*b0¡ߩ4MH7P4(RRLV'3U+]z-Iw?6EPN9Db2M>5HP@Xa+ƾ4kӯcpWn?=AA((mM2) +3^O;]e5ܢ8"RKKAn?缟ѣ7OTPiFy?QQ@{}O#iڕ̎T@Xjj?˵Y#p?(2ͻ񝹛?_ֽ6 }#.ZJ( +( +>^ +O?\W7NGdutb tV|GTpq 7jߠ([$Q!wcjJ򿉞/6J5oWzֵյB@AB0*PEPEPwF +ث>0_Vp??ҽ +(9?$]+ë~'ȑw((<1#V_bS5i(( +( +( +(<d1Cy_μ״NΌ'[_;\C%ĐL%:(:( +( +|3ew2l>|]u&[6<$QEQ\#jCR}v[w-JY1%'((5R\۳gܐM{-pokvT +5A5}QY_8EPEP^DM7ׯ vm"&mkOEPEPEP;DOּx%QEQEv ;bE|_Gh_ӿ/P( +("E#DP-xGItn 7QJS|]>s<&`OU:PK)0D\ҼBDf:'I :SC%(sھ{tujN 3^&+Kh@DG` ((((?ȫ~0V"(( Z_m?B4 +dЅ}'@Q@EC_5yzׯQEQE?L}z# ^@Q@Q@Q@ G}O 9@Q@Q@¿2*?9G\dUVG X̌}yZPd$Ai+#Oub2:!!@Q@Q@\Ui9{_: +{+1Go,WĠ((((((((P2p95 zzH~v0_~ ou,zޥ-ُT* : +Z(((>$ȉ/5DԿJ((+> Ǯɫ> Ǯɨh((((((((((((?9I\cUWcSG)?ʸ((+ @wzƾп׬_ QEQEQEQE2hxdA$R=A5m6r}%hWҵ,> r2#S@Q@Q@Q@Q@_Qn_76cyv}1]|躽n~h%s/p~QC2ShQִu"OLs(I/fBB{[◅_۶QX*Q^S@Q@Q@G=Uc6eqH-|B6Ɍ뒢 +( +( +.n#/,u$Qף*MruuVoPhzji-0n'&QEgxM{hL 9_ +ѣu*H S[~vٓ((+_#K E^}^^/WE}zfO| U<_1`H#Gjчݸ^tQp@E S[4xP`d)l(ׂMrpDfYسzs|iٵk =^ _A\MPEPE=בZZdV +;~m)Lّ4-# {cxS@ú,vIH?_t@Q@^2;=$|vFG^JF5y' ;îY;~T}?yQ@Q@v~}Fwo,=q1\e_= 0QDY| rG**r+JUbIt">hMȑc~ґVa+c?:K1,I'4xڇf . COs\PEPEPMWt~?2kxc%HG6Vm]ljlV?~?{סP\4xB$]Fr}Tt4@ AhBO CV-QEQE^5k/,&1J{w:/25MZ/אQ@AGHYy+REXǁׅQ@":ok`ц̎=a?:h +)ѣ"v!UTd{Tmw 888(Q@Q@‡ i g/{%bȿ?ҽʀ +(9?$]+ë~'ȑw((<1#V_bS5i(( +( +( +( +O6kv$v@8?[cvv#h+SZ%ׇilnFqrc@Q@Q@o3%@ 2(9]<,h$(׵ZeL]sclAO^]귺d +;; +EQEQEcOQwM3QWX]vv2|(s~c?5 66p[a@=M@Q@Q@Q@Q@@_yzYTQEQEkKvOWuf![O(Ⱦ0~f?@!/ٚ +( +(:?;Uw?oި(((~ 9]?@Z(((W#O^^'G(댟ʽ +(1UEks:ck.31WFx5@9E)QER5`&o-bPt_9>KJP`׷ܑ͐>PEPEPEsJn}B+(ɥ8'?uu?d\7?^B@ hR 4\s#z=k^1gFTm^^1gFTm@MQ@Q@Ka{QEQEQEQEQEQEQEQEQEQEQEQEQEQE@#Sq5|qo=׃-i],qf?0x@MG_(s^&Ej9QW +?׿ +eZkG5j? +@tV#5{P]@MG_(s^&El/Bh>0b0FŢO8&Q\ϊ$?=q:MrtWZ)դ _B?[ +Hkۛ;(g"']}{X*hm?O+4 N n:HWsG&hý#I)5}tSQEQEQEQEQEQEQEU=ZY~{P{z|=4v{<=_fHǸ~i>buҼf~zMnij;_MqTTvwV2WvPj +((((( kDVXWQ|<l?pvd`P'Zu]zo.՝J,ko9P>ew&mG_ğv,PƑƃ +=S¾Ӵ"7^_D>X#~QEQEQEQEQEsDԿJ~ 'ĥE'{ +G5j? +@tV#5{P]@MG_(s^&^k=u_M^@MG_+~}/l-KeD2nnQEQEQEQEQEQEQEQEQEQEQEQEQE?rRqQEQEW4A}NX@袊(((+17PQYsWW-& e+2&<.((((/?|_A%{4mn&MFPVguPȸ81ZtTzx*M-:"s(ɀ>O:,aA-k@Z1ZkY Pԩ(((((P2p95xSu밌I ?zg.)o{hzʟPsހ8*)YJV08 QEQEQEQEQEt>KK3ϟ0G#/zoCD& Ð2HW4[쳏t1$˿a[TQEQETW6Imq UѺjZ(¼i^b .+~^?o~6]osK 2)_kF{ 5ſSoDx~Z)ΌQԫ)`M(((()ȱƌ +2I]<b9v>ixKӲ^kbڃǰS(!54U`(*P;REQEr=GОt{{׆:4r4n]I `;WutбjМ,x5ݥō]̇ 0EC@Q@Q@Q@5[L +'XWǏQx4pV#}[Ï5[HaxX#Ӈl3ރ51xJEQE )n?ﯗUE 9DWCa}E-Q@.ߏC?H~? WPEPEPZH1YuCMQ@Q@Q@Q@Q@+ݯto.#2a(Z{_&)Wa؃W,Y#JC|_5eڞocn3,}}YCV("f(((((? /Z⭍g.ryw#5 +S{Q@MG_(.s^&9QW +V'+:N*I6 QEyׯtJ[OEZY;G5j? +@tV#5{P]@MG_(s^&g5{x=bt:U1#i$uQ򞤊(((_;WeX={4NVHݔ׿ +eZkG5j? +@tV#5{P¿2*>e/-, z^@Q@Q@/_P҂C}{^EwkqerP3!#_KV/_-8IWOފ5i4Rp<.o&[y?*?W(((6IO]e\u<p[ø+pRZ xbV>U.l>g>ڵ,lm4TH!N}QEnOOR 5wqw:-@E~}N@j_ ?oWQ@!0n/%?AmY 63?.(#H~B(((((((((((((((((((((((ck*Z(/[G٭ȩho<#Ef""5(Q@Q@Q@Q@Q@!Z(/[G٭ȩho<#Ef""5}xG|?Qk*Z(/[G٭ȩh$c:((cA|F%#G_kJcLZM":`C+|'饾ӥϴk/湯*H 8 WҗZ}vV%o+*xjs"<GyV\ 4O' +YF fl!8gd֝YmtX*:H22<~i_ 5;]Eh?x(Av͸o袀 +( +( +( +(1u +Z|1G:9USug{G8ߘ?V<#'gu;Ś lz:?}3HʮYC؊JM/NesEQEQEQEQEQEck&y?As^*W'q?zoM'z]`up_N;+Mɻg'+PW ϝD#?,Ȋ,ƜV ~xUWL,}LFP3Pn4 drk_N𾻩].v]}6{宝aeV%_*yFR_$ ߙ~t?-e H0+^((((t/\E$~u0W(`K:#w# + +(< kBssVL.]gJ>APGZN5(({|Q>Q?\|54P¹GGhs׸@ +O|GgiSFCsDp={QEQEQEQEQEQEW/ A٤9dO5Q@C|+`%/ OGmG\߃GfHGlw|澁>j'ΫӴP``O@*ݾ`[Sgy}#ExM|MxGKw ï]^5e}[P/ +(i4Ng +zfjPEPEPԶ4mg^{ +2߁W@m +2߁Q +2߁W@m +2߁Q +2߁W@m +2߁Wit -5'3v$j@Q@Q@Q@֋s8x\p-3'UzM-3'U-3'UzM-3'U-3'UzM-3'Uz Cǻ3TPEP`Լ>LFs3~d|FԿ|aw--\QV,,/]L{_zZ\8 !רUM'OJҭ {5n +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +h!xv" (eXam *REQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWTM;K`ǮqV}f.?_G@744,Y݋1=( +􏄚}ne"Pduc8Myחp"p+LFѭtpV +[yEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP^3_R_[)gR?o*j~!cTMNcf!'$@1En?`u描hJ{GliK@om 23/Yr+itpG 潪ٝy +d hJ( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +(? +endstream +endobj +27 0 obj +<> +stream +xusN[3@I8 <2 %[@Ro=h4 H==AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAz}^/H!  V#   ܬ߿ȟ}  e} \d<;)A,DFAЏ +&t3o-RX}P4(?z_o?ⶈ^|xAܲF+- iA?*v ׽-ڶ޾U*lSnTdJsꄮ)ɿ.&,N}_GUژ#A}h3s6ރ =X6}>Rx @=hs~o÷xk=AƷ}-e.1l |Kv_sۏW ?>xCC`t63#e$P.fn}呢 p'$8o ?%vq@Mw`0?q n${kl<ӷgj ';!!|{lߊܬ[w;j>,2[>A0w;!!|{lߐ̬k j#S}# ~NHq,~kX5\;$ 62soR8 J wBC`ӿ5ؖI]ע&;3ܷy ` wBC`ӿN~`WMbo__ +` wBC`ӿ Mexv% j?YIk 9=X6d'IPs9%nAp? wBC`ӿlȮQzQ;jm*ߛXYooa p'$8o ?;x/s8#îoCMn* ]A~NHq,~dP2wdiQ[S1Y>A_|·ˆ}ܑ}CLE㎚O,ǷW=9{_|·ˆvXu;;ji:r_o&/ I>ۃeOrDj54!߽|{ n$trmo+]:jgJzE0A\/ I>ۃeO)ImELJk,>ɫA~NHq,~OB󟏜mPMVq_oAp p'$8o ?'5^QP-F&q_o߷$TȜ~n6S^r>$d;ڢ'mCmLN/蒂 x$;!!|{l?>x܃Pa351=Nq/Ϸ7>%׬$^H0Zݕ0Y ~NHq,~OJMOʠf}Fۻ'/ I>ۃeOiog~j`T8dbݟA =X68 H,2H'e _|·ˆSSٸ Sf~l*~]rg8eL? wB'5ߨ9#ϴ~Q_}Tsσ>'e+}˅_V|·ˆSs5 Ϸ[g# w$?w'6;=` wBC`z*U,cٯH⳴H}&lu`=  wBC`(mߍ}yN[߆;R B*  wBC`zG饷̙5 w$>zxӥX; 8/ I>ۃeOYˆ.!+^\/φb}]Yp'$8o ?|o+>չmeZ)):2zl. u9΍ +o#_Q% X:=X6@a.QT^;ni  SQ\ iؑhx[|X_|·ˆHu~nc27mge< iHH􌗷hI7f]p'$8o ? uZ=oq32?t7ܷުCw&$8o ?j׏J[G܇}u'׍{B  Cु)|\` IBC`RѺ/Q\A$#!,ov+d"?4aD_|·ˆH3Խ7 J87s$$:Z:oZϹA ;!AX ~Yy/>' _s4)HHtg-~if}$jء =X6@JoLB#H9GBHƨ9$jE>$CA;!!|{l?T1ucc29Gě)HH[=(I}׸7M _|·ˆH³֨qc2u6<Żq$#!{x´JA,~NHq,~ Uk:~F#Tz1P';Αhώfb߮zwp'$88! ?2A.Hqd@9-FxkR(#2G.A =X6@f__MOTnW7SH8GB1{퉉}8ȏ\ =X6@v^/H$\sS{րT@9h&mO۟ȴ ` wBC`];"~Хz]4q)޷k]8"?r ^$R6Oh'WDK) ݰA9hٵ/ӄd(J*vw_Z|·ˆHiܥ-h;R[#Ho'7H7GB>Q/|x8 w;!AX ~]wսNK ts$$Q12߮z$RewhTL#!QLa|g Q _|·ˆHe܅/lf7Z#!Qv,|Z@ xt;!!|{l?Ti\_.L܁5aa];0 .=m9rwp'$8BA~ UlcʜR,^;3r]=ats$$j@=(Syviˁ s;!!|{l?Ts[V#lD z]X;$/ I>BMH3O"k$V#lD-(&o\" >A-pwv$$8o ?zz/!Y@"lD-h Vq/ґpw_W|·ˆHUIkv@yĀ4G@9 NHL| s,!!|{l?T5ėEOAjdIHHԂro/`&]ƾҰC^kvgN+XB&RN oj]6H6GB9P9aF ]m>:1=@:;wPlAW#!QO;6|O[wzoM䰿|텵|}%zzGXH"oSe&o @y>/ <ס.~Ns{ Pu6OvTq8?Cs*/e=ѱTvggƄЫe;}l+WGHfAvmƳ)r$$j;]E /&s{%#5~ mWHu+c ~fDlQ3+Vy#?~n.9vMoMқl\G"?D{x?T=гfs@,|ʑ*KDycK_F|״mgNgDjkԲ+!O +~KHr] қլv"|vٖրA5>chG>i+GB95ϸbfucPYK׿t>lY!]z +GEYe|;T{~7GpM _U}{fVo呺Ѣ#OÞpݞw~^;^'f]h\IZmrD_Ө ]UշmѩkwARuZ AO@#!Q rBG<+~DLrm7T^.ޣnֺ+Bty_u"yg_=J vr0 βYx~ ULT`z,AW#!Q IR(_K]k5`v} ƹe{%V[Oo [Io7@m޳g-9 5@A>{NBS ߒ4r$$jAKAoA-QQh{s7?n4zXWϑ>Wʨ}`nrj띑[}s a6ڼE>ԃ2U`ĬC#!Q /h(H҈,JG.Oeif=;}8K;Z˚3U97_{p?\/u+ʑnAZ(]{kZoUvH4_HX/ ~r=k][2oûKK;] +TyRv+ȶ{(]? QD$GϤ6]PD `;A{$"˻ƾmWB>G ${ßogwd&v;]xLqv5'f;4|Kt?Ԫd ?~"0s/ɑqU,|ViR7vc:ȋvM怂vTo8 ~.+ ;5h<- +ro&XCN6O~ U/b>3߶FZ$ɑ(FhJ)-y$J"Nun+l@9ei} e.#v?Lonq ɏ`7u${"TTC{MfKU2!B^$g]D%GB=!aRv!Q-HH=?T݈ O[s9HJGܷk=ę@q{Dkݝ`a>b2'2oib[}{',#?k8mvU‹ +^gh,RuBԾ~X @ϑ(@|hD QKaNKף):̇s+㗥^t4.E ]KU4;F1${p ?ԃhp˻;yD!ioFvTWc_BmGBݺb^ri:,lpj!FIFն[!c+vV3v +E61lEz~0Qs$$Zmk;5>Ji%wHP[g,#ObW#/KI%{JR)BPnc&TXt(v3q͒^ t@~JHvܼ;qDKxnޅDj"Q+RjG0 zg{eI+Uw_>avu:@| EDH9~,aҲX R(! #9-[HԈ +d&p3z X|&[iGM9~,:b{G3֦C(2 ǏEGKװp!U?+ԅ<;{0%9c["`5k6w +qlGP&]@[J:0u X?o-KзOß~*u,* ãRH.GByVί>&3oU8ԅx!MMH߱>9gn-幵2^ Gt][;PV5|Yi$4 CN"+:λHH4˫gYH/7D4(x8994E5x.9S,o$ߛtû r:2avzi3#C:ƏG9MgLp_Ŷ7س >u?)EF.Q{+P^1:̑F;%wTD3 Wm,v_5>oS =RXi>NNHHW?M-#~>P#:ubI҇V/~+vR$'Z~N+KY^Fqp>n6J'seL[zI7*E~ulۍJq~i3#Cr%Ƒ7VY PWr$$W0uRjmH|];3C/- pW-n/Z?9C : 8|RVSBKST[JX(@)ZhW46k cz2%{,6(Oiu\r^\ł_C@B92̠W.݁mHE4w[q1WhkiؾJ' >N{ 8E F5ETI)2|u;EX7b=-W'We|HNN[yp_1zd2./: +r +Áɖ=tTw-j5 ޳sy 5ݍfS+)n;v/OToL7L U96܅H@z6Т:Np #zM?<4&D0  [QMJqCRŷJLCpB }y +){n<^CGS S<M!kݓ ]  >z`w}~$n8EGKԇ)lb okx֛fT"@1]*8+h%G 8rHݷK\ ؠj*ڝ ]F/7.mжXu9[g|c`)"~u8o):۵r33fŀ< +;<18c,ۮ(h*cl^S4_/C˒^~A$PT(+]fv=7Gӛ[YӅu|"kp 70\6"9-ܿ_|bRfۍ/"@a8{$ՖrHyt>0]¹6gA!V=J?[v1J%k-jJ/wZ,|| A z6p̈W KٲgHeJO +rXFU+5ћi!ԔC5@}OϴiE}Zch +x*=B;@YD\BZKraNB ޑ^qMgh m.M=}hWs,-j PF^mq9Yb% foBFrQahkVv\]#kpmHg'[R +ka%qde&>*IoiA{4C5@}OJn 5 Hjp|V?JN~ ym(!< +n 69E]Gʶ+8PUe&߿":oLȜy͌R!ӷ?4!qjkz8ࡣ) n~ 950x)1ܡiAa=ꬢWJ#)͐y$xe ;C)#M3\QO]*gJVxT>|XC5@}#ͅ ޅڈePS.'ژۏ&; 3%$xs)1IPE_ a"]}n?߲fPɬ*fr ֙6aM|OM800gY*G98CK9ZaY)j4]1n +42OCy.!ώG?$$ +pcȒ:R{ Cڮ(;z)^ն.dd[1]@u!hۅJccL3c [Ѓ% ܷok7⌀ڙ<\c+pvv'$-YscTr*ˍP'|-%5HO8–bU15+N[HnD'EHTƂ7>x1WtM|@@OJnBGLLO:Ԋ/r/@ +dm1Kͪ5EY u +1tƄdRU{nyyUS)-;[5uGϑ[//"S~aE7tJ?3*!AXݠCه$ +pq.-D'/`oU# +rXlY!sܰ'cGvV@}q_%kܑt1Zmoٯp%dV-'7x}_X]}fоoJ}"4DHTSƢWFYRwjB%I@M7-JUq$?&V&=nܫqn5П}" kXQ8rC]SSʒ"AFHTSB_誰i}p8㻚dp@I.sRc:y*W]|OnD&] -9H_w93(2lXFXD2 +TZ(M +rBt+;rp)9!r۟fx|ˁRzPٸǻl wka'PܷECTBPmO%JGHT;`A`aq<&{(cGOua$y=$= UGXX/\VO2YZXZݓҞ\wn4HEp>|{8: aL7$)'Hˤq܌" xlqg˲mF߄ca &ZWY+] +x~IτtuHES=1!\N* c%B糾])n5Rn?]^zκtSǤDk.v9Ke&t;ʦu'X?+xvt+SP۷s sozvPezv%9 +YwI'RNB=nnj}+-psm\8Ol?_]vHװoHaiV䋰#Z5"|HӍ i5yX{Z XY |RKHpiߒ +rXBC&/r_|O |{V?bna3'!nufM#f/>ׯ<~iַJ@X='gB"q* c0Bs]qRn\Lh],'>uvd;n'`XhCz۞TG#3~.zҗצs\2vH>$K9ނm^ cbZA5* VDH*» aOC+UDXKn4dM#3}/׾^i~~M!N8o]>$KM3Bzu:kNsW +RncPFpL_Vrd T:x;żAXKnj9 =V^>!Q.էn"|sp'DHT1*B÷B;  $-4ڌG=iHbuʻk|}D`] 5'BXpbݥAO'\ <=MH>0!|sp'DHT㙦Bs >* 9m:qA}:rL3w#7E˝nNzƍz?aa5mXoT)[ +$K1!RAN2z +`BVޚg|J2!H)(S#%sVg#35;✐vl`&2H +x2F|9eyL ! +`B\f!V|;JT4߾Ki>7/{E5$3WvƑ=T{7Ynm*+ ,7xBAN9>8:kIz Rn&W:zWUY$ZWzsThk|Z}]ƞd͘?6mzߪl`jg/vR&? iHza̐d 98"W*3@Or[`*tmʺ4zWUJYf߮뎲ӂu3iCpl?D kUj~t t79=*) mH +F[`* 5lhź*_CY\zHK!zH=6u u>.I\a.)Rϑ IcrK64$m@3%ȵ*$$ PmO$Yo +r|p·.*q[!5b2H2˜KZJuI>KWؙ ;%%Ujʂ/B?d )bEHTC·o7*-ט׉$̹:hV} ޠ ғ%-'*wɕXZLm= ? iO$YonX+xT|-)j!W {C޳&t}FK79'RmPZd 98"W* r{_ lod͂sD7 ,s. %%e<Š>63K79R 6@Xd 98"r*8` ! +<l{f~c fsu ,1^ \ꍚpU˾t5̀sI&G2@j8!$K1aSA ~o)UhoX;=`9^RRf/֣חhӺ/zÓjauVjx#H 5jTD%O0ʩ _:_˷kQP! +Q,xTSs`9^Rɒᴠ;6-ڴK1/΃d:ێM epB RIc&Wݙ& _̹:J qNhӺ/:i[u.%śAT ",7xBQN9>8B}!ۜ%o? d0n@Y\zH% b6: `^-;p%or)RmH F9LI=!WqrE f[Y}G2zft9I&R~;uam* epB RI{BQN9>8B}5!͝tQDYVAw9NhӺ/89({au>X+#RH '!d 98K!to"уB}Jݚ!:&Y,?K$eΥׁ`\>LaqZpX_u%?$Wq)m\!d )bETBk9c!W;75uL2zBrt'e<5ݝY4l7)s] k臤>!H 5l$KM+(:tycX)U2m +vI_5H/˝N |(I-b[7*w_ZnXm4VbDG<H?=!",77EP +r|p8%.uMvE ,w:M#I-t-*~jaR}̲DIL<H?ɵRI"(VJ9>8 [`*@yx@܄deHeΥׁfb;y_SջLš\fvp߂O e, B*"|sSŊ^ g]H &AH}ᓷ[&$^̹:ķ뷤GBy_k?143vRfauRYi+Q]RF ,H"|sz!RAY/:i_ +F)3NHVGD8ƷImZ% C2,`aS#Դ.x)#~$!d 9ol^ g&*@,fE$nehI|SzvV]I"<}! +eU~+Q]RFHCD Isˆ +r|p<X|Cn5Rn=4ƝĥE$nehIyĚs$t?ZoGtk-⫨V +"B?d 9wl^ *W^ +Rnf|lDJ֖%H#$neh1ù> %~3;Ogz`^c؅H@Y\zh1C?v$`/qSn<;VO.x)#h9\p#|sJ9>8:" B}`ǣo;{R62AY#H0˜K-`hgwK_T4;2) usE=@ߑ"AN2}z 7,77AHT3?d*RBa@%&!RA|9TDH0'BZy}R??H0˜Ke ^`M .k#3B?0 6|s=>Q25=z ,7W^BHTB糽'a!xT*@9TVA-wĐb9^R -ΰ{s [\  +kAo!AśAi" 7,7W^6BHTャL_g{(GNkd0 WrH)v#H1˜K }ָ50^?$uAn?5rVH/HB_@aH\z!RA|=Q#d0v.x7 ^$7RBNҥ0oϯܐPGyxSU2Vzk|; \@or)C 8lr_hF +r|p< #t>{>rBq;T9\jd~bv54 +RGBH\N=7kϻ:U`wjgR=RY%dt+6 ,M et F"W*SK{H8Hr{_ivBYJrAY\L!?ÔlqRj);i' (}ifI$-Ղ!'؉Ti2: ++pGKr6(8,791R93C|]R:?y3笙B}~XF^~Gvo"w hq ER "P{FfYaxfQ| Hj"VvM w'Vj +r|pS9̍Y\WU +ķKK}+BH<B}jZ2{'\=`OX r?c2Ϡ́jvn|Cz?)SAsŴ/=!|`x#Hـ`BѠjBzCEX便~PFؚo?6B}EKgw<)#wc ,s.es_3s+dgv%˩,*lg5.,HR&GX.f' zh}᛫W/#W*󣽗" hn! +RǵS;g|KY^hT̹:ڷOZI*}O +$_D?{!,pQlۀ<)+i !RA\|]#s +! +zn2hdǺTpRr^GCz:y |5-̆-ܤ]C8LcIaT',paR  ps;.z>Bzvi)z窟/h RnWWn*"gt|e]nH6˜K}tHt#Pױ$7 š`= k\asa7(TlAr3p>n ҍpߜJ+Ex便f~^N+B ++9-T1/fsu#!,P&-u,*uްG+{3¬ h"z)PnE_d'>74B7W^VGHTweZ!/sir{aUsx?6+WG,lSEmlLI;6z\ Yn +eaցUqXߣ??,p١) ~zzІ2{_h* +r|pf۵^yhu MN-B +^{Qiv풍y_eiQ2cr ,s.&~ ]8OP_A^Ps +iYFr\Tߣ?j>OYvCR6Au+IݪSf ߜ^,߿gﳸ+BML*—9̅|'!u] s¯b;a5c[=Z:QedAYuvʦ~ek<.idmoXo:LYv Ufɾ)ېы Kn켠r+boΞI UϾ +r|pC|-a/BUT" pVь 50J[N gE `0x;"w]P# ,һ,f٣cb+δBϦ͚.[l#{1a{G)u5=@Fþ  + wF ߜuZ?c!RAΜ|]T M^3BM{uA~X ^?DE9Zz |떣oE:e0AXX4j1>maT2ɐ(fK:x gTY6nW+(e[+{N?+y\hRnbm(xE{o;:owa(nv0vKy9[fۛ h{Åh m%v/HYHي%DKm\ʨᛳP㉤Jz(/Z5EbBS#wrӒ٨xߟ/̮AAYwahvn0 Y PN;F^^$Vlhh"֛ > ' :>ͰyGw6dY-V5Bz|!t&g狚R#rRq&~/b`7>Գ-upGZB0܀~9kۋyjWmE@؎V%w7gGB(a8J9>8:lq ne2SMH M=kZ}qn]L@Ywih+r`lo \)G3k`hR8>ǶC,ٗa;@i:j_֯9cU(!RA#[+\wj_} +\hFI ;{b}x=RZ΅YvNLd)Y8L_rBB~4v6ǰfZ/sl~k d[% LB∶=p3K O3 +r|p<$tZaR8+:ʙ|ZL@ M~)7d`bbؗnA-dšǵQﶱQl--W.4 U)nZºVG"ZA@A63cFSAߜ^,٤J9>8:mjj-rC\h=#D7pZ@Md^/fjC0c`;7ѽch7f3=fM9wX=YjPV{.5eFA/7gu3+BzX7ZV#O\h=#$vTϘoN)l6)Qn|gv:jXl/KS݃${{Y/bzLuPHׄLT=GHT p9.U!?BwrSPúޢN[FS]FxԦg׀=-dsY /N㊿,%&^ӧ^1% ̡wUX^QhT/M5ֿKqM9>8:ۓGs)p)7?t?5Uj$.yvݨ޲+kgZ_%߳]4/$ڍKkXw`K0-B8М^Z*o+ +r|p> çѿAGzNa-tS}iH`K0ă<>,wxE9,l]4nW+xB>Ot:)̅{EH~t7&G epػܜBx7ځ&d_i,ֲO Bsh,6.+xBNs:\hѸWJdfmܫgķV-3Kp U@q蜩n(u6L̒} sLtJX^Qh R[l+ +r|pD#W*yT"P3e!a1ԸWi@e1%BQ{X,Rm#Sdڔ(N3 2d_{#qnr7T+xB=NBSAR^ 5EB Q:9D$߂[DXlr +tY/)J2GN0S^QhK6JzX'lmd/Y̅"\e-q*,^\z e0N4FcŵH6bX>^0}XKߒrZ,ٗu5x=nWP+xB<Ǟƽ|TT?n3fCYj}b\z e0N4F+PziwƴGm}i6U~H=*ۧxVaH^QhKl]IT rT;kƑ*yMg ,.`h&\,uE]%WԴJ3K%e]_B]Y=*LCL+xB<N% 8s hBʵf +]v'4,.`h & ʔsI錻dM_h5\qpƓv@tk k~ &l\%և*,(4*vJzXl͖5 MHR6ic +i|M2oi0طO\ɮ6D.d_RqyX@v^ 3BU["\kB3 <?߹dibUgFɧ%,/ ei2"vQ/H#}<,ٗeT%o + +́z O5nW+xBnk_+W5 >B3cs~$YX陑ogJy46(}~ faU;79)QHFxDE +PWa<~@X tћ:.J9>8:ﱵo;wЄ !|okiYٵj?Uvm\XOVv#UsרVJWTud_RV> l 3#(4WR۔J9>8:ﳵڹ MHΨ4$66<:imm/6Xb[Ooyajopaw'%lZ~O͕%Og!Bz w|h\hBbwri`ԓy:lɷ8mBnm +~t)ZA|.G$?Mxeiˆ[:6Vm7-Q07V5?uOJokt 'va14g7 yV?pwM_%4sm?.yE 6dlщ  @^?5}y:>KZ9| :}mk5 f~,5ۃ v2Wv^5ϋ67/?^x#ۃ [2s|OmD^\/yRְn" X,#EA4&3o$##~,5ۃ K|(n  9P~y7?  =Kӧ   EߞПAAAЁ/v{AÁUɶv{AÂ⏥v{AÂ⏥&AAAc诃   @R?_AAAЃ⏥v{AÂҏv{AÂ⏥v{AÂҏv{AÃҏv{AÂҏv{AÃҏAAAЅnAAAЇv{AÏv{AĀv{AĀK   K   ?4  NX   0c[AAAtmԷv{AA̅K   ?AAA0c=ۃ  `6XjlAA|X??CAAԬ_п  +7o   'e5 X͐ +endstream +endobj +28 0 obj +<> +stream +xĽ @g7.^ Gj`[vuv϶.g[OmAEԂULiH!B0d%!8 %\ `#u+%s>y߱܄.?q}+f׼0;s tnw9e9 ss>[\{r?:{6"w6~2ut:`qp v&$LId99sgF\Ԕ5D¹ÝXqɳ{IArA'JGGK-RT24ڐXfjbҥCg?:2yRl鴒8 #6doJNJ +{Ƨ,SSSSw}}Slł{d(;ǧOJJKq,ăKD j5tp ='--Jds<z}\k + (V[/)1 %f$'N^ QQ.aJ&%D +_n-xt-a4rG*S1IJZ@nA.0ƭ,"q1^M j6cF7Ӎb){yD,6LXde9@OD"p$k^G&H+RÂh(> VX_n.\8"O.WTTT(6klBlj?MMdEDuUulR%T V՛׮vF#ttc*ŘcT9 D把tPBEQ YYdp ]}ݷz +%21 K:0h ESbmq{fwgA&;U*SS9XM<8vsvh TEo( ;w~(J |jg{ +&=ܩTaGlW +rWMV*u:]Uubbu"2TN۱c{ %6F,յ*p>?bQM?z~o?63r۴zzV6Sۇ2C!CJNP[0xwwdž JV.2J \LH^4++a&̘r=>$q'_8iqgu]W<qb,El|֞e]?LJ ÙBARC]˯~:L/o,x-ZwǶg(+:01gY |n}/6S]4:#10vt0fK֙ŭL~˭-uZUYp3uꉹ&ᬉ13˚U4OB`K𢢙, +1SM|Lܙ|jȈ;9*WI'j|{tbf"_yBY)dbʁj5eY#`2z_B9\.q2BD,5{.=X:Zb!!\eÌtM.[V~7ɕhgs3=%ƀ5V8;yy|Ł3D!S %?Gk0s/I +sfZlFwc!dӃ˖e;lc_fHjͳrcHzzySe" ?Xd39sY$x`rS +tZyy>f/\ L|^Ƿ +Z) ,`:yl#hz:ǙU\T3Kk;d҃M-fޞ3UZk(h*Ij0&]\98ݠYk׬l?A؂*%\w8yy/&ϻp 2 fx7Y,<=# ˬǛ]( +F̊B,mPXdp?rpj. O:nR'M8B|LLu+P= 4, PCTڷ9GEOjh` s5Tת9I\YPTVHr+JYBq L7Tꔂ +nnt +z;8S9K# B+ z:5\k6с'P#Iv{\X0QYآd^,0_0{}}F^hZmtrIAT Qщ: +e!K̃mF01vf!.W{֏0qP&k]ol%-n&ET587ٴs'/aa5 $zG OY* !GJbdjtz#F1wjNY|'rE^ΎLHIH:yq{U`n)*0mzIf0V;6i=d{1RSĢ9984"چ9;] +yޒFК=t8@ +ܶl)lݞ"SLmssOW + +ܞ@>E9G?Kb>Q E(wx7r1r{/"Lv̀%0b0KӁ_3a<ɍ!RC]'C*΍N[m̚2׀֊<'xqP^Uk^+ףF(#=P WɄ< PzrWjYT ]4V_;z)A\8#AR,`vH A ɦG\StThhz2vihw}Fdž^=4^Vը6t}lD\ىtMR0TUf݊K{tLax8 EM@lMd*. 9\.O'x<12`@B!h*CRio1yyd";vɎ9gg8B6xRb6ͭ۽p`T*tbZVfT~5hmDR1pbbSjooF͞&OFmZ!j 6iVj" iF NfPT 3C XCRCtϱ2nDϿdTq˓LRVY !H:zKJ k[uCӿ~xx|BZVyzfrI^K(Pm +|q6/x'Y~ϻ#sбjx2Bx0 ]:]hVhvvn\ +{^V:a49uhhFz:_xT)b37Z'.*>?3dbhP( w e^vOMaPH@FogDe^9y eq`%`rƥ24n#ok::JIK<ʾz-."`@>8--z7a~!*dj1*<~=70!"^ r^T٩]l8;ׯO%V4 +m}Ew&w^};c%ic٣ccEEwڝLN|gǫ{vwuk?zm;3,yKgv/ [}O<6QjS̞k؞'??zzo:00#W!p/Ks#hv1WP?}/ܵ嗿|=;q卍]u5#6oӒ31Mel(+u +UD)vgu8%OB|ڭ!ɥZwHD,TJF'`us/LdX@]SXʊ*ollY{]:5PK\Y3qnvh`;o7{SbŻ~ܬ0HI&M]"5lJu -殺b6n)t)%xklnֶҐԴ9~{h*(BLR3Omoڰ۶ ܲr#UjWԖċ)朽vŬ[4)]KF4Y~-Zi57X"{ῶ.x| JzORVnԺՋũ깸Ҭf󒪓b.~65ڊ*oauf.e冇%*:X 2|߽^E3੩GaT puzob37D ]*f7>抲Vlk{ݑk&` +2zӳ~4BMC gzfNL*g$}ҵ+UMk^MQ j@Sh.:Mg2Uڍ'_WKhf߹|Yaqu?N8pT*c!㏞ +/xҟ>HDܥkBù)1lj47ktm^-r9r_SS۩ѻh~߾k5ӧch!~sg57H&ˆ$S=RrdJaJI3w6|wHU( ^^Pvڥ3~%HrCidjT!3Q9_w0Υf+Z&`F*&|wXx?~_Mi .s*//WT:ڧ7> go5`mD3֭<4"kS񱗾| nöh`EAyT,^zم\{`ڬx^n?]}5H$h?xrLrFG0Qݺ|۸L(ݥ?<ժq B<++x޾EJ͜sF+`5+ʀu&zϬ$g{9i'/fT>~ʅ Hs%OaMo/IPx.9QhyV"B|50@M 푁2e,5bwfv{PGaVj䂻/<6e/Ur'^<>+ŀGDKkbn襝ZM7 \6PJ oh^3~UB9}2WBqY|x&08Bu]L>1*f>Ys9"\c׵姟~f_ZkUb0ܲQ_SrP,{nUk)36VkW*`u{"+I}/o37>jyq@W q>~rX݁>7ȅ/-t$ɹa$ͦaϷg;畎"-|^ZM./ WC"tzj"-wD=7o݁nFa?urTH 5b4a\}iGA.Îng.5nUz|<1K7Yw8$Hw[^󭙼FYjB!֖}k󰢫al~`O'˱AYk_BbO!v2m +9L8;?LUˬk{00ilnFޗoTjnhNlڷo Y2dg1Ya5&ݞ-6)QQ/045lv¼Lfѕkپ'\ni\xUs}qo 1kx; >׬xPsu&OomJXG"0VjrHdWB8:Yyy/(:WmoMb=ɮG맟;/isP[[Şl_(%r;5ok|PˬنFRA Z$ MbXAzQ-w +tn'qr +L%E\nmI׌ 9]d}X|֫akkXy[j-F/|#YfMcK48;d"{BdV5!ז#Jϖ(+++--e2iiZX]Z"{+AأW͍F ^._]& +kPqHjfTj](.߿M@4׬Q0C3.}*Curɳԑ1o>7C>PMǏG) +}"Y?6Sz+ݼWŤ).2׫W%1Ozʧ/_9R_TWW&JLzNJ̬V{kOU0~ݴ- EEӵO ܮ&~e7JP(K߿MmP(ӶH Q,Rgѐ`clcY?vNM>S4d޸eR4q[ +JNd69YpHp~ x(&Пjt*)TթS}!43*jv`Y6üiɪKaэ3xZW +EFFG8in̯+E9e3H ΢sM=t:ƂAPr2b)cJh_(yۻ\1Y`rL03S cC \3Zd`QKI8HGCC)]^=9ҪBHBa{]֫ӿLM[á3dorJ߬-R(.[IL .d30 9Y@e2Ԉ爘{:E] P@Y B&'KdA}g_,IL0 AരPpB#Yta xpRGɉ_o2|x?U Ž}}ҾreokfNoc6[L-E̪˭ƥu + +{džE}f)KsN{[v{"b[$XBqgcqd Bj =30wd>7'2;$ Yx.; 3#>oDW3ayCW>섏G%_NFo:ZwHQLü/f ";/gUOF ܫYؗe͍7zhWѷ~c;v o߁osrse }2ѵja4v'2!yʧW ͞u{v_l犊bcby +8T{Ʀ}iR !,;(dR5D<{MhPX]\(Pb~˪Ir(=u.I;'g>!cSf`jVy $Xs&}oxd霴3"h`qIII4t-^ïOc$ۥK@ !^z5yL}g-[).B:ent,%5hiۅTW'F`AYwr'[> +m-w{"jxԌhuT'XQfmdp}{Y,{ Ƀg_XIm:+ɹEmj{Lm!kU4'ja%@[dE@B.2ّKfpNX0(a6' +aeB䘑sX~j҉yMcz +fBԤ{(ČkcwO[Wz0 f&._.KfjVRx(;i9UO#5g`qW 5yjTjQq̛{zx1.cbm4T:efUouvjj'qeVwsǁ^}W/RNOev؃kʱ;5(}.@ȧ~Zt0 ,<9[S/.J -4 +]]= >UxLx8J شfX7`% `?}˛~~6.r8 ,.51o˷fR"5,e}SF>ߋΛG볌~$yNYI4* +`T4.GjT,68HƘ|G,ǦG_6{=C.@jGZRcc]5+XIѰ1>4>)LDDټsqNEZ&(!բM:5 ᶙGqS_*eRQʥSw٧[mÃ@!j1U'˗l^NV0`髩^}wT&OO)-裏_ldd;ew\k-H79y?x0*]p1P܏HV]95;7, B={C"||F7ј>ĈJh/3shM(+DݦаA QRoCpփlmjr :!hkn./-:\HI IV ߤ$v<\6vFuRppr|7y?CУL,5+od+ 1ҺΈ>/-RhlSۜ~v#ؾ (kJf\;p&Ic`*=p0oQC[nfu/+`.#`xqaA6xͨWuPh*mzx&lsccpزLud΂pTy'7', v`P'-z $/Ң[SR/ydPX2V܀ruzWihOr ɍTP[Z-MGqbL&PՂ#~Ti!SڴHaѰZ,H_)|T$Ƿd#k Fy&LSSVNJV"֮)u&m.< m@q. N khuc0?qJG X,C;^ZP=~  )ii Cscnʓcprm܄sn&xrcuYWz*\DK7zn\Ȱvրr<*MG@׋8ecC=!  U*5 +*Z{7$5b2?.]R,Suf1T][tA$V[, J^Edx^GY_U[ s&k5 +.1(=C6L|؆_#+<):e)uv!#C4^π'g)J?31QJ"$px +!FDs$B"$/uҐԸ0j܍O}o蘭eT*&}>CXV3izF&T*N|j.F xr +A h@瓓hʕXF3.;41\|:9rlg07mLC; Ng4yJt`nO=GFt𼙬?p!R l1 ᤨ;(+Q{}ww8]_UB1\TˈUm:aĹfDnPS'< 0u4Q,Һ۹jQVT6UxHuj0.}#J݌\=)|~X-+(Ȥ 74>66<>5UVfMST^^Q!y7oP8ଣ'/'%e;98&1:[wzi!Y)fn?L dvQAw'+)NRiLrlNPp +"ӎu1tb +U[M{O)~ukցv8-]szy!lwjJLFyG/dZϲ FuRYIM)$<kfM}™9r֯+Ѭtٰ5M*.'z~jWw3jaF0utDie Z;IM cd(=MMp?K"TL|zPr~ ?aѨCSGmy"::Pk; m1?Gr?> Nh7]Ҭer>wunH«2a͋u?׿`=QtE> RB$9;h|\uYX\R"a&qYX]-dĔX|\W#2Q[ +x(YdEO'Op%9x/ݛ]#ol?LYAX*W]+{ub,( +zGy`<@_A=c0Hgsp9հ+O->6LVVfM5Ņ"hX,6ٓV׽u"6QW y0A}W>K *NN\V%Uܸ&j:V& hjۺ:9`FNjPow2Yi?*5Ht="tR9F%i%"lDܝuRӧΞ-zo)ej:N"͍7j6/٦/j''ǔ`Foijdz.b(.A8p$?޾,mnnM3)T􋉨{f>nr5yT|8-tHߘ[6$O3\ rPsg&)4;ߖڦ.%&$® ?uBOǎ931t9 U|).$y3N Dzj^>%|(̉< 8k.*eԶ%=cy;^",ÜeOqQ{~qY ذ(yL]B\m= >^8J8,ks[7^9}_߅2Σ=Z(Lxw͋F`Ѭ<EQiq9<9un4DX9.H5)T +^ौ5{/{[YF |{! y;H{b糅 ++RJIۙc+{lr*B~6 nPD0ڶM_-*X*9lwnⷅ  +pbd]M&Kqt,]'}qm<̊?9YB2 E䌧2H!1KfݑؾӇFv'_Qê aݙ?P((ۯQu,B!H %?R*?;Z y̥$D^^lkH w#ϕ=ow*v-/o{k^a,1,3["شEęod'0+ :@1Gp9'弛ߐ#ד/_ V/+*x + & +x 68'ݹaj*v.ҍJfj<3t Ҳ8>DDY(B2' =BܞL +A9W=[QWAB.I*|JTTPce% ܟJR92'PCPPyą$f&oB˗O=u%Tȇ3ޑ4N:,i2x +"‚h2uc {,e,Gq)ּ+ʼuzݵ(j޽k$U4<>|->HLlHʵCq>;gC7Nv\T\DܹJ {s_\T,Y<5:j4[3/ zzk.d9 Ӈ/A>crsòX*#; +eAC +ť}4zy=&wRd͢HW*!L.FjZPw_.:pl^VMV +Lltް+v>U-o4q +5 }}!ųm^@y9re]䲏ךV5?J`7a@H3'xb2l|k1"aQ`*Vهqz.u륇!_^i ` VԊ$U?GjƴCQ7·ĘZsj`OS שqAu ta-.ݯJ҃&A٦GI +睯?(/qsW @d`it^kG ,Mf=wxnF4[/rZـkpsoa1U=6P\p#!5 Pf4tUw~M|u5좺´ȸ񌔚؄CʖfHv_ҐRIn7 LݰNM+oEFˆ/6PRH"{".s:hչU QQ>ewfߣqh5_s!D'I ]KK['Amm?+tWrJs2xVUm4=9{wpW b9 +R[_crEo~q=s[a% 3/+&R4ZXpq>F%/p/n'' B-cguWS% FҪ*Qχ4\рj!-5F=?"^rӣ8uܹ*8ޒb߉E$&lBPX8 < - 'ycѻ 1}|5 +*ٶ4k:= LiJ۸{fYcщ=:ũTb<ڂ$rg_q&Nki0L&ParY BC҅lHNS]׮Ҁ +D͔Þ#,t!z,äi5h^5X"-ޏ둓}gw[8Te#5*,,7jl)X,c۷سg!mIM 'QbΈEn_OZocB¿̿4Oj^ښT:N6y)M'j#\(7m$5jqx%5j 7|Mq)W\Vc4Mtb + |ٳ ,w7zrH4,4-B/<9 Dq11Tzk!1`#6Feg%g }/77/x2O(N,ۼjWw%YF~Rߝ;]0|>j|[lnr!fjDz/x۲5J$ZY/ŠvFH[ v.ikʥ5v2:o(~ÇPsMb)0YȾk}sIE\^nCW7Y7;zng 9ewݟ\w'wŽi_q}4("T¥| 17n=yHu/wn@u޻ŕϡ>s4++籱|.&1V<666U6Y&_ٸ?hjtGXK,?y%eM/ρ77w[ EV}-r'o#0" ;? +x73x|#dX,{TwvvUՙ} +С!52,& x@ S|7n3|3%~Yv k"c409gZ)!H`A!B@ +Ytˏ"'z|al\ ;W kP= VvN1.#ɱ՚FASC\kDcB}9)BUJ\BP#{Ȅ;9P|#9F"5h=!+5jV;,tO^H&֭srsWܝ]~_4 xxpΝH@{iYd$nS#mEʤOΗ=Ҙzrz^O)Pt}o d$0DQd#АT`Cgwްj̮.N˯E;x? 'Oիi4ؒB\l 0o8ix`aw>&%UPP(2{d=$Da`^KQPV,UJ7|,9,L$Iz%bd]zԩ>>V&O3{<~ Sh 3w g[UFeYLB7nFuMK%x$64SsP%B (463j[\f扺5a7mX}Uc5 DTj*MM]j8ZL,q>s_ZBu(_oL*Ca2wCm/㨰iރsW|&^IѪb +~-A'z\+jLզa +e7|ayÊc[ +Kex0-"MySIG{]4:M YF=QY eHFPn)p&F~`X*>An˵0, c$^+).kWϷ׏?ھ3]T! +V:BDmSێti3gϞݱgN-GU"a$ QӐ@B`Y 0 p Y+AkH»2q%pϿV< 3cCg"(;::W vf͔Sxу?D,'L\^9D+)([?C$.,?T_y=Ngy-r6C T:SQ^MA,-8;?%HmhH9dpϒV3;ZeR RuCsbPWK0J d=YYA`f!{!jKRG!f1="5\Z''Uy 5KP[11U'd- +|n>"{wHu[;fdsAz\D?[oH2YEnHĮSY=A5탃mÉt^o_Ru3Zχ I:{c` jի6aŋ +FJK|s@>|Jʳ.VK,N&5Rl6Ёa$B',49p +rp}ֵ}ǭ3s"vk܋ /Xq| 7hc!.,l?^>#3Oeǜj`'l08:lDa=G:fvQff&+3[<22[eojismZ6p|w8JqFOZP\:8Ri/)Z'FGF&+#ccãFj4ܕ"TgE/5uV'֩s68F>F)AM%-.$ heRTO&LRb +STNcP(f'=,-f0;h3m.mVb4W+Y}=ɹpv{rUs3HԀ`GI"ta3oo ji5Jucvl0&V S("Tf&$sR+&E3z"3T,枣d֩X\rښa+&'78b/46yLb%s` +(5߳_zF@Sx> &6oݞK^?Q#7V]9xDSS+EWNq| }x4攚=#ٗkgΝ9Oco.`e(5CV=qFYwwrHeiVщ9g ? S΄G6%ث6OF̿şީL)Z;XP1@l^/y/f;ko [>x;.勧{^c{*pl&k JX$ +b( n]xgWr`[赫VK5:oI81y +ZWg wӝCaO[snX4M$0^tn}[G$~Rj~X\R#`"u[s҉B0|4}Aɕ-mk5|bvMei$fʪZ4+5 D88_No4bM ^=2_7C7oJ{rm^yB0OiV<SvVY l[x2x$"3fF9!x^N1:XoR4K|n֍O + ȥz1 c&S?&= +g}}7(b<PZPQiYVͷoN;UE(nuO&9nzgdX1{r)\`{4~OQ9rbCق`(1IWOH/s2;ٖ\-()aj0@JJ@]}r\d*P۩~sHc?ֽUs9>Yk A*DX;_x<-myR;#MMI+rss#Eiq+;{6MYU-;wz@NN&$$9~[>ۅ]s$ݨ !F\Æ +"jwK'H1kiTB/H{;`6齷#="#/g pft4Uݍ:ϨWNY6m{d}$̒J3+e|SGÈam)e-..)ѻweWa<B[TL~5ƌa!Y.n>m^έORS^5rRT&#jW9q@fc6ܹ)|RXvTzn//%h0w@ZP v@:-j@{%eCM?~]Aub 3D!ARR)K NuO{@4hn?IqXMf5*x. T|t!3cH[m7 fV]j]%;eO.2BB! `Щ]Ow"LFָKW.EbԬ&L&u'$H}=| kϽ&u*=-!R`9Vk7s}1[|5zq]@u`*4==2/Cf''{p1-mzCWe2xɜ@ɉNi|flrsi2.lXg&Q$$ͷC;|XuktHhMD)%F#i5A'x +OA`OOMXyjJY\ʊCC>`76eM*V^=Tdt]bWQU+)++rX֍ō9gzo` w-yDKo-Rҏ|- `jh~8>RVҐf5% {v愇oOX-%Y;ssݣPF{3Ex89>Isv}[OwԗrS٤gΣUu"k.<#dqP6'T5**5yjMh0@xH4O%%9)RJŰ{=kczrdp&F4c):P|dKMF먹0c/ ee#MHZTPfu naα8tn/|e Q#S)7 ˇdrِ,aeyy^ _oSWH ea|DWzhHf +rլ{C9 `aDEEt| ey9g .R0 S(n{55asX$HdIJQ QU17~r7=2-KxiA=ђ) 9篎`}@BWgrQ#{Õb/┆JKK3`_S  D]NJr6[ּ +P(hX5KlO#*}mk7S1m6ڪ+uŃ5' JKx4ޝM8HE`3ZVL |+ L kLژ>vG7Hzcr\TAQ οmdfڷ;荦*qqۏe+| *C0f]ѱ$sy\LO(#= Z1scI)b+'[\qXL&&pz%c`^o2MdyA5ǰJѥx}.!kcTۍc:>܇ LAvd8JJ[&ئ(U߉Dϝ.D~ҙa;6$dsEWҩ +G +a2UWN9vf/LMu:Ǧ|D$> \ߴmӓ𨹮.|׹4`EPS&U?$_*R6IÎZ^gg>lQm]汎Jy<o4oz!pJn]ӈ L&pLfON`@07U˅s +rW8 r.]=gFֹ{#r [rzZ:NebN$WBjݩAZHG"jX*U H<>I({dWP)$OϻzF.%$W<>7v{^ċ|d|& +' VTTAK)d~`T]WSdQ[[yY;XރM #M`wRut}'ʉHXɩlh@M\I?˫C0SxQa1=TZE DQŖ-ޛe9kV~;3{YBn-'ˌe&B1<'_|Y#x8!b\8x')߫¼O{zb +MjU"y0O'n7ǯVu#.^qXlqy-G#%$&&>|X䏂7o4iӦ6lА#'+ݧ'{sb=Q^Ą`*A z{ؙ?乾??@|X-MHN5OY o3OQ&j/W "(STוLM$4V s"|s3Zsfl 6S䳘By%$0N2lӜPͳ&9t m|Թ?$TPƸрƴ4ylRXn;F}Fƶѳ48pDU9}ܘ\V:FEyl&$gbx*zfa6!,:.zrNL"{tjⷙB@SR("n7$>o߁asujCj4v+g,蓙Fztl]u'R$ݶ K %~yU87jq!L"c>]:#E Ht"/gSRSj 1M>i6oY,# t}P*ٗ١DvJ%Z)V9Jdžr勧FTڃ.`:/erIaeߙfF㎿E<2!bP B-ll-ɧ/`uǀL@&Za3ڝk>~-|E/(Z)4*[/T5wa}ьX+}]$H%I,YspMu>_Hq֝keIo[twm3UEbfE⠔ǖ^\b"M]TJ/d nXW{HT}$.ĩqVjF@V'3Ciiy-?ў:!`h*"OZEKY>`k~n{-xYW}<䬈jBދ&l{[_gYws(Ttr^O SMť%)B5Vpwi\Fb'ap+9y[ CɥM ޛX㬙 +۔zmۦjyER +_>3tOO:$0| +q<'zm|=gŏZI!sg{**K`0,z~WuP]~S&rquy +Ȁ)ϮGLEDAmw[['x//R'$r!c)׫a `oD z5B89p&̓=}ʚ.JLLJbJxc4IPokF"ZTQ=m?sn/붵ZhL0C$02Lj B q 1\"KZ} y(Of2_8w ?G%.e\q/8Sw'%$LU__g}駸a V>R\@PV3k+w13b|~Mydd7sStr yO.]qh6K,=YXgXwixT?߻UF(PF~zO>YCPBD!" ED +쩎lQL$¾FĔ8$QN-dk"3DCsu1<4֮\E2%P:nM"9 +Wq(Xj P%%H?q=l:-PI$2*,HeTe9Ӿ,HwP"%hM'*:$ͽmYACsU+#TЂ'@_ZBʩ+.n^LQH8k3R7J'2M +CyRJ*>M^H=ml\%J`HD*̦ʨPդ::0@)s:P33A\z:sBHŕD{#`W).fj`M9YVҐ<[_{_SWcΦaqjǔeN*8(7yt=MםdtuPܩecjacqe\_5>B[*M&!hYɷtgAFyƅۥee3vώ-_(%LOOt,YJscbz6~f鲯ƹhsE`-kjr܇ajSGŨ]Rav#&4/?gk(LNR3z[ E$ 6\0MAr3 +KJG'1E1ҊFn=R6zm㓷5>}}pq%)+V0UJJ(Z|I/ڰrPH9 +Lo`'adjEsXX"LOKA;Ȱ\f"zdV=U5Pdb:Sx<>W8MS&drG (|ؑs߲n;l1l|s`bn hn^?7k(;LQB߫q^V@;]u(+۶> {4sl%㇇-=˰T( 2$,a`9/8 5jR 4Ch(GEQF |<Wpz6yf7xy[Ϳy^۵@C{Mv^/y Pu{{<ue?khڍҲɋS3yЃw]wt׮a8mF\'8xОςu|z\&֪fvX,sC&s`ir s +~}piy{q~~q~n k7xǜ[ޑt˧땒hM jʕh)+z28a%XzI6 7/U!c%.9`.lgs0( |-[3ɼm(?'|(!c__&!խf?(!A켲u٥LBQPgb'RA7 +BT*WAC~ڈagBmt_1;Ԗκ5J]+? +,_JR1<ΗK`݈!_HNߟGT +i`W]]ɬ8E1 ̊ʲҩ0If>LE$*LS56A@3YpbbOh ͺew l|לU-ْ9ST)+ 5qh#wX<G4ʊegΜ`Rg0g8 Pdq9@Z3P * ࠃ9+ +vݩ&[mYewh4;h|;:,{}YM^mۈ!zԁU]׮][qFJrx@4_R鲼>_Ps일]$443q8 ,@wRUTX,VohY?fU|u[ZLNG'4{ͥ.LJaߑԓ!F I]@rb)ᴿ4'%3 Rމ`ǭ:]]TY٘X,4YJމf97ʵ$[dqHm3W5r^gX[}V 5d;;8 YTgKpɛ(GJNKΊBQf\> 8 +ﯫUI6tBYTb;_K-ho6ɑ[}xkdjSWeg*oɋkZ9-UU-1R"oL*sj;L뗣oPx{bbޞY}u$[iıZq47 +/+ԸH&I\;Ư\miK'5~ _֬4Id7hNDAT5IJq\#L \듟?r޽ɱZТ6gbAڨ>]& [#26h6][e +gcDJ$jɡ,ݹ8.⡙@?vol!HDhM\{]\S6hFs(*)+6iP)ؠvq^LDK @XmT|u:&KXr^5oo67wNNjl,^?D E;mgE0bYv'q |E 3]aXj]" y{d KdN%XRJ +.51!ݲV 7ITV6ڤ$N\ߑ$i6m#_l#چ4A 2z/Y8m?ޱ`TUh$ѨBэUs~ۢt>hIZx +" +Ӳ́?ÿfۖows l 3ֵ'+gt+谌8oy^91՟~u2%6^FVתG|gqaBjZE WkrS'7[ٕBs$TsҒ{! +rZ^`8NM}JePF3dO +YV_sl2*iW`lu{Ybrv]"F!$`@5 @qŢi|)/LAh"ЗXG&37:ju44MX4{)tw|dlPQJ [W9Cje0lTJ4f؇{ )^vfjw9, oϞCBrهh. MNqMX"bĨL"*K"USC˗b^ϓJQh DIJM:s6-fBFB0u晷wdKΩ5Y,No[6,T M6겆ǦN0;vJleWOH0GkNl^4ST֟MiXM~F=~套#.x5 ];_s{͇% + iI^OwQbHD4-cXKffм%Գ?470>m^7Z 4lAx0-L44 @,0pC"E@I)d?|H^޴GxI`$) \n~?7VPսȴ1X, AAtfPβkʭ$IbֻGɎx8^cԻznMrC9\_q`e %"0lZ,࿞v#%B8(74t-Ǧ AP'VAm`P/* ;5v]X&XN w7H8pK lAc +\Ѵ *e/#>KeD 3fzl.&h6ض;G$VٕRБ)c'ݳgo0]WeSZj{> l `Yhvx-*)-#gh9dq55"4cq{5nfDymfQC)7VRTX؛T;;qBFamt,rblO}(\KQS&[T&z60ɪU/po|K̄jd؝Q+{fq +#IΕd|PZFUX8G"쯇%GfLD.Lyy<6=%(JaȨ-r0%'Gc1ڌlIEkgc/WGKQ=c* pdGD_ZL[v/22~~wD`;:Tǚ*BʗJΚTi<)=𞏧4^.zq'q0kNbY&ϓ\VfJ$|)i8:85.Þ7 f5?دiFh KO2ds4G?rNO"\HۿYPbNh(w8K].b99l`]{,ïuWz20le 4N^ Fx7)!V +?*c:Yz];m]eQ&R5Ƥi&|/m +O0t ЫTrkk0jH) +]˵ZGF/IhaFJ #CGdnV4LJ_yRrj/hZ^ Piuk/8RHkt~$d)0q\sا%͌>UdAD0p^ļP(ˍޡ ?D>Ш*Gzj`mfmD"N XzB}{dqtDFA~rp4I=B8QEeI̯Fk)N狡E +B@YetTHp?\=:I|6&V[16R/rb2z{=*Ezb0tDU7t1B0^QDHXݕjdv,E'J^?oZޢlKcT-5 07V`v.kdjyV'Z@m]*eF:;޼e;3?/KKQW[}񼢻zim}:y\>{#2+,vC6>Žʯ6zW?w:% 0C^:-dk!qnF/%1)!#5&>Qr:M9G|g!UȌ@0%۷Ha\\+0ZUS@d˳8wR4qA?>&mK 6>X|o s#֑FCϤ=iwͽxAahF*Eh(3s?ۇ&:u4P)A|%>>ޮx! }Vz_H˴f2&98a=1ȓU'iݒtĀϙP /S3g{]A9\K;$.1MOn˽_`Ѩ$<7'U(DD\>KˡoҐ0~7I)8YPZx04 6I>+:))).7P?7YãD;gY3UJ\$[Wm"_AT4ͳR,a>mzk + + +`U PAJkoӹM TҖi$}(X'<'{": +54,8EyzŁSX*\&܍$l^n i`(hIuXfj|}²2P7 ! FW6loHSR)O"LA_NNwKǡI4QfPaj,*9Ԓ\pDLН;n7i4Ԥ +iCQnBdRTQCY6msݕF1}c \K +43#zd.4d\ln}\XUk=f*-Nc," +Lf6B.x۰hH(4Ҳ~sXk֏\H0C#ꝟ_bvn\QzV#5,fd+)&g99JKдm0i$%DyX(g ݺ9lq7/֯q \ fO&QFD㭛&"I̤0)Yb;n](/-+Y܇8GH 6;nDzmmurln$CzZ#)Ac5,h$)JfR׬ǻ Y^^V&tζ;;e3KbCD)ƞ4jh<Ef=iL?a,}A&h<=)Zsa[&)Ϳ6bTTJ''Pާ[ 4iuv^F{tPq`7~5v}È81E/5i0U؉@ʐ$g%w^k{wX&-+T@g߶S9oR':Y:)*ȊOָ9Fq8$>^7^ͺ?:+8>*=G}ؐ$8wJg@S˰AUzZV֖X@66D 0b1?d4l%F*&W׵>={*L })4S^t.f* Y+,x1ИC1Z{{tjY(/쬪+y« +Aסaӿ24< 'I07Lp2kƨ"(cI 6GhX͓0L).4zAXx՚ LgF& l#\HjҊC^&ՃŸJa}"h?8),+p2{ nٞ/RTJ'Tc + |'/OJ$05ZIT_ TuL ,f$I B +00,&zH&qhxÒaeإ2B ЂAͮ &YLf!a\rH(PhfFwf}MF#a*H0Hl1%E~ѮȉU+]^-Α**@I.۷Bl69 y- FntNnqs^i`]AC߼? ͞`/f .ju 7/_q[8͢u~W ]o9tdefffa&QxxzzxxZn,ND2Yۣıg=;DFʵkF}kyXP../nذ\ʫ۶Mmo{n]s}? W__3=}g:pz7n_b{޹{g[{wޞObeee`[L\#p;6zΝ`;9ʑ#?Yg,}';fv.> |ݻ lE[p>g G`u +endstream +endobj +29 0 obj +<> +stream +x흍rc;n/%#)}+_%cn˳U[@D_CPlЭŎQ /uanql"ll ,Y-Y7(/ɒ6xOT#r0׵ EߞUTq؆PKӚDp@D}D+X}ꋣcţ 旋k~auN! 6`ʹ߿"KlӃE F>Kr_w-VA("[a0!^袢 t`ei +sB&&` + ;/faaZadHԲI4LkBIkPu٧*FnfD#j 8L u9-ߘH\2-ȨgUEçS#j+8c'#O;*n8c~?-"t{?)pv"Ic.I #z6N쯇_;Ru z)r`S؝AtşH~Y_~n>\'84Ȫ8x5M܃n[I4|f*]_A9܅g;Q-( +6Tyx"Ý,ӋaC-em0+QJeyfetV$+ÕbNsN66R}e/l騾le~g{Ծ:@URIJY=̅[%@l*6x|iODMmӐ$8uOm k-XA2obGcl+L etƷ6/< {djTgo&/Ÿ!4 g/ kkU S>qJ3yNqbUirا?6ׁIV2(-Ò+O~lS *9l~0̓ˆ:#>52k&|b0CLLV{bWLӸk|Z`SV^(!f6t&en^io!/wbtL`PM )PLd-ɬ#xTbXVPJ@KeO#@7hGPB4m[DQx*UA =2S`<%IjJi{!+%!s4dѝ<|iX+514 YzVOc6qv>hY2-nzKOjL+˺~H2'l,D[jV4ϩl"^hW83)v2" rc;¸&v2wFdG$8Y8*:9J=1+f5㑳#bbxH*uquOgJ50;NNYd gD/Sd#%)Hb]68r!RSEI Qxj@#;,C0;xVOxE-0>,HnX&#..Hh@raGB+ѾԔǞfLvS?KRv%ޢ:w.piQ6[b̆1Yx9qj`jy*:!Wnl &27Pj>W 9ߟ^.n'O󡛔0Y!dBԒWwJeA3h31I`&%=gb[MfrWo`itfwsg35`L +#%JH?S*^~v許6e7 +;v^;i&e g;gcJXSשЕReYPjj˜hHlx lDOcǂ'ո_N4̯`X@j@K_a!GN d&-/@2HMܕ~[^BfLz Uv̭nB.!Jb. 5$A?&W:~YWhҏE`cu(]js`tuy*6.5aSi--,$OӼ6w|T_6P6 n0[v0[fD:vb8r5{xp~>8r5[D~K6jLkDWEa50MY2xK5Ge d%P9n'hqr|Z U5&!nP XҪy5l;7SOCF|E@q ,(X0~8>4 xom7qWz2~ӈ3~0n` &`zׯЀ7ʐ~:Cy +]A9 ~:3 檅cfǠ r UJJI4, O@@󛫔8;ދ߯羘bj@+?4c_W{bb>4dsL 4N-bR&DzПcrHiw_MW]av D11PT`?@(cz̍ ӑp̭[?5i j¹QՙSQ+}d1. @aɿjiCǻyO#8 Z}`'3wԩ7A*sKh4!"ȹ'>ͻܛMZysSh>_DW_{d9si8F&*xw4dA'lTN@Pq> +stream +GIHIEGFIgi駨vyT\T^[ddldldlltltt|t||t||.> +stream +JFIF``ExifMM*C + +  !(!0*21/*.-4;K@48G9-.BYBGNPTUT3?]c\RbKSTQC''Q6.6QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" + }!1AQa"q2#BR$3br +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz + w!1AQaq"2B #3Rbr +$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?( +( +( +(WW) +v(B^ZX@fVy>bJ2pw.eIzP4KBV&aF?ru[گ x+6]|^H`1T_W9}FޙTP4hoPV#;?֝|Ihf4lI稠B*j[g^(Euz__ߐ%2?xǶBqзT&5 Jony[AaU Z(t}Q֮<>٥#60=zVlڴNRecSVwyVvIГWW|4nϓf6R+,l`Z[C?J@yeO@ q1#PɭK]%cڎ4{oI7|6}\|<œOxa}Fha*z5zßX塊;o8?kc ȥOkjPW\G}QPWk t,Su%$7QS/m}lH:B=KWXr7!'kB;װxWƺ0=kiV*pE}5Ey~ y̚n p] ?zH9EQEQES4gv + $y>-Dhz|C?Aې xOrz31E(4 |=Q {<#$ӭkݣյxn;?`{zeck% +P`j((((((:eZ5MrExw,D~!;q\e}2)VFAy_|m0rۨɏԯu]..{ M>wX>@3KC3 UԺyƣj1K^IB4BE}+E+:[Mޡo;4?ZѢzngabmS**ɹll!viPKF ]f2>!2&E?ÑF#$|=fEdibI20TRz3maj0is=A~g|+ԥ^An;7hYrur+uMN|蕏X2m7/qt>Yi1F"/{0k=/O_&S]b'?1eh[{M/*{^k@ŗ_}6s{v=5S1??Ɛ㮳gEsnzMz?4{K<;AO(ÿ0Wd'~A#u+f +QZg ޟo1œ~x+t-9f +ʱyX_{o%В(i# tK Pe鑊ꨦR@rIq Mv&QLc}}rxe6T%YX`:(H4FB{#L'ׯC,sD"U |]/axvA>}?4 xOcz+A&@Bh(:?WlE:,Wf?(='|S-76R(++[ +}8BG(ByE4:kocv=N7u=+7^56 +[б\-]\GooI,QFI'[5Am$2I=e?c!dd{{YO邽\rvP}SWSEQEQEQEW+Zx#}Nvz_2Ab+wM񆿦mX5)Z5 N8(߫']Pbb˱ʞFJ/t'9KOcwa_k,'? +_i/Är@K%Bƀ6 gfXg~+&|;OlQkߊme#+#W_ZFױss^3x+6$h}85]1$'=Ѡ}@?uuqypJ,ē4*^7kaK!GE x R$KkKa^:5bu#Ԟ?gDa#Q{?uQ@Q@Q@Q@Q@Q@hi|ܞ|>y޳\Ӊ{hQ}=fheCѼn:5}!}X1콴u@H{W1Y0,֌ ޫ(̑@mRC #7EE$Wi 4 \m.?ue, =My[J-'ˑ죟4mlnGgOEQEQEQEQE +endstream +endobj +32 0 obj +<> +>> +endobj +33 0 obj +<< /Font<> +/XObject<> +/ProcSet[/PDF/Text/ImageC] +>> +endobj +34 0 obj +<< +/Length 2911 /Filter/FlateDecode +>> +stream +xڥZr}6]~R`kEobYNdUv*C &4~"sg8J$0hL{kϛۋom1?}f.f7ۍ> K/c2)jɲnXl#&ݦѤe6_ˊݽ_>Dݷwz/Ǐe_'+^2Q}U>LZjcBҤdX$nuUoPn9VY2E fb-zE#Tdb5+ʆ4{bŽLZwLл݀ǥ`:/GP`_P_tˢa^EO QU)jfWfp :o"Iɰ5E=k88!56y DVl ߜBȉ޲$xҴNf[VQUE^`%VqCQhPY'կjɵ _ VWM^w%[-^k(Egt:gN_O1va 9[z-gYbsٷ Eҡâ6#DgVt:ّ#\xHamm,ӻ, {ѩ)-F^Aȷb;kPdY8 SÔJ*xT8$dRQeCm%72})]A #L(k?qw17Ve>* =KB|45ڨl eECX"6/ +XβB-HAڎSD֛*]k~%{f0۲t& 9mPYp?x&ߖ>K6dW}}EmC\}68:D-hPE]$,?ra8c*JA݉r[xh3~r7 k@1MՃbrDgwlpQUYndR(~ܥpT ko$ae|mZւm.YR'Z*$U@q;A̧_uf +Ɉ3i7 wA)Rl3bsPY$$CZcVvVgTXX&CL'SCA{=)Yc>Fps`WS@=wr.HsS*81oTvBDEۂ ] 4 xycͼ(e, ԈAΥRo9u-'E݌ }O69mש^˙ƣ!.hp/ e) +[5 ~,mDu4*@8ݽxݒYPj hkݏim;\1.frrm,{-ő=Rd} +YOg +%ٱŭ l9*Ω{mvv +)"ʡeqH)}Hp7LĒ|3#xX_w0{1{%4"DR!k)qOK,ZC[z?Pퟪ~װ#v:@ΘZC2 _J4.+ 9~Tcw(C˃a!jqm݆[nMPM#Nj=Opv Z7bC/nA"_)hb["B4⮏\C .՞u8*;O_פfwHˇu Ep 6,o6ĨwU=sv#^#4E@PĻXTTyI`W&gcuEbz1qw`Lò,y1-cγklʁCT(nVhuniKF֟n!JAJg)~ ?%Jι2M p +WDubvsWn{b|w " + ?u*LmU~] Mh107ǁC,AOPx>QS?4q |ȻhSkgM74H/k 51ѺΒ;g +5oN2\^*?=YC7:3)7- X聊>m +ift=Xk\yOg3S(߹cu'Xdԗ(7Ï6%z?Z0oq8uWaxRIT㍬X0 㩡@~ׅ3v݊Go'-O`R,)*lx4WvғDQw.%I8 B4P#7IM[:?Y3! +0^@ܚo;:LДM3\^rfLlKQ%۪AD3Q$2aGCgzu[hX] +ZvS^@M!H.EV<(-l❬,//&JgrxpC +W4'9F Uȋ}h']LJGWOuxe e5=М-uwos|EfK@pI%!ԹM`,>oGI~jG}.wu9sNBŃ͑`YHI Nj>܇mm +endstream +endobj +35 0 obj +<> +stream +JFIFDuckydC + +  !(!0*21/*.-4;K@48G9-.BYBGNPTUT3?]c\RbKSTQC''Q6.6QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" + }!1AQa"q2#BR$3br +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz + w!1AQaq"2B #3Rbr +$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?( +( +( +( +( +( +( +( +( +( +) Nk=UĮ?}?ZMp:W6hr,}f?ưO5Ƨb1sYGO+?94?Z̸>o.?LW EK#9fn:Wgn?T \"_7\>]V:Fǃ#үv׮j9ܯDu YzhTo*' ?b?Ƹ(KGu5ezq"\E?C^oER#)ewh٨DOҊh]J/0"NDvF*Ϫ9CK={_V |5gsEZ,⩕׏fwVkpm oI&#zekBKX1E?*Eg,x/-_3#Uդw~1 ''/u8t-=z)WWg7uKDtۣd*){5xWp?"o'_m t3Xz+|/\gu!d]x +mo&8?C$t5/+FL+FߑmVR3qkszU~ &Wv2oaU4iIYnpХwe?A]V]+Rڱ\?_+(cVHejk+4fԎ6,~i0ӯp~9?FV=|V9-hyEPEPEPEPEPEPEPEPEPEPEPEPEPEPEGSX%?wݏjM4Nuevk]]AgOs*f5k~7]ijbNf1sZyyrA¯U*VGaS$4;f9$iSIX⍤v*V'C('i>&'٣ֻ-7Ct>l|+XғkTiiy]O=Ӽ)ἏoWOa[pדIr"Q[*QGW2SgeUӬWCzժ(v8 +(Q@Q@Q@Q@Q@Q@5$BzE`/r <˥ru+]jw+G+9S;hy',RA!h7^~K6Q˼IGbG#zoitɼɐ8eIbgJ_qTZaz=D?$_2w/5m.Os{ו #ZFʼnүϹWh^2ƻ;kue6Զ>w~ӹ=QVrQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@!!A$$dm<h28/fYKEf}'5uaJ۫5v÷iEH>g<}M^1ʸv +( +( +( +( +;J +ȍycbx?`-c`D~WI##I#cMc:-uW=Mz诮?k[LK{%?A_¼H +5Z]NTZnW oXrnC;Wُwu)+a燗,Š(9Š((((( +ze^@ї{W x6t%Σ訔:ʸw;!Ҋ{zŽ_1G ^uwUϑwS,9V,}O}uwKo4:R=TT'cQR\WG#c14,zQ[UQȲF쎧*pA@ϋRe.:$Ocko>{:~-Wn[EVǐQEQEQEQEQEQEQEQEQE Vqq $,h.$ă%yڡ*sQGn ,L%^!֧3?_s+4ƔT <=ờfA#f@~i彗֯_Y-z} ?zqQq(ª+ZtM[{Hh:'TQ][=)9;Š(AEPEPEPX>,?4\;[sJQ +-kRV|cS*: +ʤQ_oRYNI=M[ҴV-mnK;T' 'KS"l<_f{JSAiIx%Jg,&9dC]ūXc\GA ^áC2\_*tw,N.>+iT*PΌv2EEj-Q[XQEQEQEQEQEQEU{+mB٭YcnDZP4]>"ƒZx7Og|=^@`AoCf*NC_me/Oa?z +ڝ;#0ޕ'V``p(<(((((}>;\; yjG]6cV8y?eWIsHcEG՛~ӿuCbt++}es^2Ӿ|UWE('V +(<ࢊ((((((((((WV=nzc'u̬Uoauk% vYQ5Jw0*N)YJVpAI\׃|J_f|nǯ'~U׈AץxC?vdqȇS-]4_g}MiΖ(((((((5^=M{J,HRnBSwf'BtWް=}kϩ%4^Gbǹ4✹ϯ㇧ʷOwLvͫ k{+H,-#M0>tͫ93 g$>',h`(Sϙ +( +( +( +( +( +vupjxVpo_Q7hua){ZяCHd+S6mDeCo++[Rjr}Pҭmj q~\պ(2r}B(AEPEPEPEPEPEPEPEPEPEPEP! }Q!y5|mR1 {=Zi]ڍG_q-֗1@%V{&]еh9.S dOVy/5ѵ%=~b)XX28 :k/iuQZ!EPEPEPEP]*'>%XPH+ +zMu><>ld &#z~?{ן5i}W叶`F,jYԚmv7ke`oQ3b+ƅ76t>SFӄl +_O(ԬRU$-QE +( +( +( +( +( +/hkW OGgJ~u88}Sy+랳QKV.V5ZӼioav:~vfU90wh>X(((((((((((()"2:V*FA(8fQPyg _rT?ifQE +(z~swc7b&mp)>ŠI$䞤\ JpT࠺cu1~O +z<aiR^ WW]Th5V[-?(((((((7oԀ/'J֯􋫼CH$ɮzd{E-eQ:(i$w`{mmxB~#eb&SJǵV~o=FA%WyMVQE +( +( +( +( +( +( +( +( +( +( +( +( +( +( +|o}Cif[S~\R:#U=)+ѨTS]M9?*p=z?Kɏn_[κ^08I5I'TvP+S +!^yZd4~Uך~[x'YvߗS/-Njm4 UkskFVȕݏ?g7elvPZb@OE|Sm(QEQEQEQH̪ROJʹ.lJɨDOy4KrNs*1Љabo+=WO8VoII=rV +_"QTbq|;kKP]Û_%?kSm<9o2s:-Cr(Š((((((((((((((((}o|Wc}GOר^DחW%UiS砓hIPуE{=YrvTWxzwМџ?CUEc7zqg Q]'xsb9+z֣?tۛˉ~f뽑d肻6-Jħ?Ҹz[34#3.ߩEWYEPEPEʊY*I{s57#s!=ϰORߣ8/=FWOs]s̽w8Q*n.ڞ3b=yΖz# Wx_FPJoϊú5}>vb,oJv8 ^3][tοCJ&-N6o*FYO]9p',3Ii!±V^_cYDn뷷ֶHSՏ_\ؑXZ1r6Q3]JJ n~ +/#ܺ#t2ou%-"klD}kYf$z."wxV_x +x/gx8qn~&cxehB!Ϊtx&{ZjV{ID}V4NJ[WOGWHլzwNc`-b^+C +( ++<_Ţq?(>j^(յC\#?~}OYʬbwV^gV6_w Gќu񆆯lఉ?JI$zEd> ^=c'át-vG]> .%7g<7xQY0zEǹZ:#φ]RIEisѮ*ȑq} +꡷0MQ_hE&.u(&{-e~.+r>UqK$HѺS+Ѽ'c{K K0zG!WFpbBD|Oo<?*G.4$? +rڃ}WG) +5:{PT8QEtQEQEWĆFӬ$pOkϋ#Mys՟G(/6 "/V=#N.r~н+_'2>ν 64HXPSQV>K"|۠QEg1 4Kr2 'Ñ^E^ɪܭurq1׍5mC$KQX|9o/"Sz<9jJ bQڞҼxh/6$o(}:ӏƼƻ?uexTgԁ+z=>җWBD*I1h+`+hV#XQ0y=rH۬PAYFhsg'[>;ң w8Z;%|d C^cXV]J +E#*Kt!PEzSH0va B)]qW.Hw?>W-eԛW]2b]>魧v3z=ԕQEp94&t jڄbHGWM_ +%%i) /N·+#fgx7W+$Bđ3W=^^?z`G61ӭ*Q +%}ZȫBwGY'NIgלAfq$#?PHXF#<\XLԟȏ]UW|[+bsk~AY-U2g[?C\F}=HAǹTQEwqŭy5k!O^\?\Uqyr??̽')n?Яa&-׬ִvgEyQ[8QEU{,f"R~=*pu326lGg.Us Aתs$?U袸OIEY.'HbBHUGrk4=.=#L0X|0&=Mr~|ɤ%_.P-kO+RqQm>(X +e?"*UjΜk ) "(P=>+>RջqknZ3\uWϬ͇Է^.t;{r$dpH xzG5/F̖OˑUі93jUWCK9dCSZ&؎{W=GJ-˷GЎkZ<3ͩ+xzU+$ee=~T~ѬYE9R[8t]ٮ;DT 9h3T9"'*7AknKXǜu|D/o֜Ҍ[9խZ0K_袊>iϪ١ 9˷Tu51(XBW%N]~i?쎿UW](\οȶQEjyT5gi:jdx_'W ZRٚW'+S>OM'>\_\Ù1{{~jߑ}]#ˉ*x~="B=qVklNFC=U9rjM,$ұi$bOriQ\'إ`n{X^Y6[JkՑ84P(_Vy/{(nEWIv 3Fl$ry5|Fp,!+;et(V 3@A?Ҳ+&%>rZE +{SN7ӍQ}tGc\DΏm'g +kqN >o툇QEq^{dM~>e*-=i֬:VmGoG]oG\U>&}f?Sk˻C8תוx=ԊZޏyŠ($(k;*{ +N J]O\8iqz+jT(hʣ +tQҤQG=)]x$aS'~?cwc֭SSs}D,NӠ@$w=sV>.RrmEP bK9$?~"Kn?OI+NS̵K}k!aUXE$嗗aqzÙbJK0Up^ŤY ?Jc@mE]Z_йEWQfWNB.'yz~5&ӿ5UN|?r? +V?S&<9'XQeX'gsڜ-<)kF[ٚ>'~t]ύIқQE3 />߭]܃y_G7W`Ю bOב=g=ܢSyH*RzS+5噆R|A?awcحQR%ؐ.}}MX>-'vQE + \yzp̳` x%יZQ?V?Yv;s#QE}a:pمzyÔ'Q~ ?3֯@_ ٟy5=j[l5+.N$yI?l¼E%U*+; ,pi+%\npac;zʍ779GoVcZQ]YY9r(d:mBF1MvH8b;Go|6zנQQ*q룍F&^.6?\qw>/.!]ëmWwdr?7\ za|3k/)Wi>ZQEu0QE|D5 <t̨2I+ǵjg8Q裁V5h2<)QE}1k޷zrӾˤ5ۮ$ltp?QOk{Z-EVK^/cu먦,nKXօWFx{ZLg?u>yQqTn~%fwoǧνJEXSA4ۡ}]kcq릋ǃҴQuеEVqovkb&VÁ\%l!`r)~sXQL/eB1^+hrr#:ieHe݂M{=ZYlv$?+[Y^ZjQE|QEWko.Aʴ)dp?A^[pؑˏ%zd{E/ +(tC/dAҰ<lm7 34 +۞dyN#K5h15ߡAgs0(ðyHR@OJ OM/K0J/G1:|Vk\l23O*zތt.Q[0QEQEQEW1ujmޟ?QEq^{>4UA +*XS˅@wm(s޽cp}wJ+떲ez-vcs ~]NC 62&){>%hQExEPxx1(su +'B~W5\S?C+٠iϺA ^ ?iGvpfÏEWIEP9}K:)]~G~5uSSZѷAұ}f­iO0Y`3;fsLͩȼEO?~u03*קA +CH*@:S+z[\f;"Sзk{j?uJ51)G\u-}7󮆭5%trU*Sp(fgO ){B&"W-ei\l=W+sxxDO0JךWqSa)Rvy9U.ņsvq =犵\++LO;aN{Zsϙ1f9$J(썯ZĖd##zyøj2c;`~?½twn(Š*}My'H xA.MEn'EHxuԗsq$$b}G\23Xz*5*Kh+x^FRqQW0M"a}Es;z77KkX E|y}F[TlIr#]=yo҅lo:}/m]7S +*ޓfoKkNH};5r}J4.$1[Tvޕ7RnoQEŠ(((|޼"#$:= 7')SYA3E_5ȼ'6Vr-Y%.(ω#n-PxRn0J+k<[iOݨY_'aYhe-9C=zOn|O0Jˏc5|<+Bx=b*ʓC1χ~ZEWaEP؋;8dhkT~o Sf*H=^๼ ڌYq^[]AG4l|0_7:](¹jMZ 㪯s?ۻK c,My.˫R]?*/TtY_9Q\Ԗ,,忾G+,bP޹oEj$m?]uRϙ>ҧ8og%(do `WדxUKxڏy9J~_QEly#d*"rpxz_8yOq +H BRÅv UWhFv\5Öi+棇kƗl R"Q&.,?FW]سRkJK~QJGAQEsBuƫukЫ<)| F6>ǧzsX8(O,+|qaf sџ"M6ݬ_7 ?=yZ?s,;iO +(x nn#/$Tw5.Eh%F]1kFmbbi +SU(Y]7bv_C[fqwHOv<ּf,NI9$e R<lm$ke}w KtFV>5r[>i8>ÏTkC+@((((*n"qi~'?٫h6eO#?ţ WV"=kk?5JN*I=@ ▰A3fX?tCbk;V7(faּ:3il_Yha꿇zT}RnW(܂:ED̎)]ZWIF?y;Qcs^al,-HTSmR,QE|QE>,ӿٕWM0z汫oGLkr_¼ҸGGkj)֌|g\6o?zuxJ_ +J3n"%=5}pfQ]QECuk K,MXWx4\)Fλ*e-Ί5+6"d1RotGo60Fƣ +0IE7}XQE)FȊ 2Q@^dm.Qyd<~:nZ̑5U?1^Ee*Q{ε5ijx++a"Lq2HRkgOOokd.7]Yl]7ߕ:+HQ81긍 +dy4.@3RW5gSm񪓲BR0]O:Nj*( O4c.GTy7)ƽnR9pu˺x*3PI=hk[gboܠ4!TV~yomAMw't-pڷh#N181j/ :Lf {AE[WVg%*587Ha*Xlo.?ZO.o^QY*  )Շ=;iieFp>Qˢ r^E?bާ ;u\K[j[P){3O4 >7ˏíz]v0E"PbQ8ʘ)hQEgQEQEQEP/ƛg x?ZMR9(- e9HtE汨$I9'4W ww>Ҝ8(.ׄl+`FR#ZyV3ߺ3lC*+O̪J-EVQE!oiGHd:WV},kr{G~'UyekgW:F>D$8<+;;*B5"-2AwGm'B3?ĞW]]2ʍG QTbQEQEQEQEQEQEQEQEQE^7fvuo׏½ TM;M~! zx;v%g)y:hVMvi޸ù\SߔSzK3.$>aw]64XXBS*Vs}B(dQEQEQEQEQEQEQEQEQEQEBԷq$ǩ?.]V6s]LqJXW^I{y5/+>ՍiYX=~d!{%$G4}R{1ی/WϖzQ@Q@Q@Q@Q@Q@Q@Q@Sd"䑂O`:Qj7-1G?j7=*qN\a(Jzv4Qc\\QQgr@M{bnckVOWJ7w93JQEu4QEQEQEQEQEQEQEQEQEQEQTuJ-+MLcRnڕSY4[lz&K'I#cMG\R3j +5:4i$XK;RMzCh1 ;z?.Sw {}kz1fi*QoQEnxEPEPEPY`>U)1OVNPwG^Zeu%3 C^&mF>FtJ cxehBk) q&*8_7˦m۟OuHDQfc +sRmWUO+*ekWϡB)ȍ$)gb5}9LVײ.b/ѫ;AIa.ǯ~]*8ޫdQEYQEQEQEQEQEQEQEQEQEQE+<_jT/X8Ovt^7׾ntgSּ0^/ +M_izQܚ^2S.9S֬fw1+Oܰ+H#p=OV(Om݅Q@((((muHMժA5QJIIYҫ*3S:4nU kLKy콇w2]Is;nFơ)˙-~]c='_q?h:X{W[ko8QVwvpXgeQEu8QEQEQEQEQEQEQEQEQEQE-j-pӿG}~zdW-^uc +[SկxU +ʤU磀¿"Iq;3ctVdž95V2 Tv>s(9KD_hkjWIڟa?z2 yUb%QEQQEQEQEQEQEQEkQo+I@}EmQIՙtJԴ۽.wF{0Tfm+$gר>\u݉iw\ο¹IQx\}ښ? Eu.-vnEg8wVNyf{={YXu=>XZN?Q޻M'Q8X82:5x=i;:*Dꍚ]8r汸Lj𠢊+C +( +( +( +( +( +( +( +( +(1 iڶ?*h';GS7-[Ҏ6?³<9Yc#((V[)V*:Pu1 [H+oi`\[O g?+B?hRѰU9%P{]*Дd #T|qD'ژ+ͨ(4@+uvZ) }9Q;)bޑD"cCXVk7~MG/!*ڹu>*t)h]j2YEKEk1.yB9s{S*HɎ95zi|7:G +(<ࢊ((((((((((((gaM7󩨠i2&Ɖ7Ȋ /Sk=&v}<[ypxsFNբX,Mio78@F:*(((((((((()14(f8}b$ѣ)5,@oAXKnx G5ŻYk ն=&Xv,Z֩tn..ǠRw\ћ?_`=iayhvZw1Z#?ʽ3OӭV0O54QG KH*]PXd/]`+C(((((((((((((((((((((((((((][f2gSWxQF[s,Uz .g*a/,u-\48UҺK;k(XA}jEz٭8iM]x\IM/~]gnպ}OSW(.۠QEg(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE%%,e9΋z*`=`yه u<Ie=e9t= 84{Yz@* ڕu**UR + >ϱ, y3Ӧv$F+:т,7?݉c[t^obO{4|uˑ0B;?ckڵDz VuY0eğV 77?4rԑJoI''N9%p;FMzt[l;ſNkml8()/3|o<º0;?Nt>A-# +h(g^{ifh:^ogqn77z~EIly'p)QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE!_#E{ [Ke9[x*UUQPQ@oqhQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE +endstream +endobj +36 0 obj +<> +stream +JFIF``C + +  !(!0*21/*.-4;K@48G9-.BYBGNPTUT3?]c\RbKSTQC''Q6.6QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" + }!1AQa"q2#BR$3br +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz + w!1AQaq"2B #3Rbr +$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?_Ē\]->Һ?UZ?ƏjE6y*-?G*-?^E; +_UZ?ƏUZ?ƽ*,g®ߓ4®ߓ5TQ`<ttJzUX.5]?'h]?'kҨvy*-?G*-?^E WOAh?WOAh(]k + G~O + G~OץQE_UZ?Ƹ;si{=m=pqZ|Li_܏zUy_܏zUOp)((((((((+5_ֿ̟9_2F)_܏zUy_܏zUOp)^ݧ$KEÀeh_o[76Kwp@'TYaUSB.?qkgՇ"VzX(h_oB.?uW7hln'(lI?P)\?/7j묾k6q\ePF~^5}?Ə 5³?G+=o~??/7}?ƺVzX(gՇ";?/mWgb1ƺZq0\o*3c$$V2(Z| kC'i2%OWW%OWP +(((_UmƎNß-i_++9"֕^6hE +( +( +( + kC'k5_̟Gk>u?#m^^k>u?#m^B(b +( +( +6\SWWKOʓ9:ȵפ_{ZWzE +٣ES$(((+5_ֿ̟9_2F)_܏zUy_܏zUOp)((+%'qO^^?/F?*Lh9"֕^ß-i_(CfQL(((ֿ9_2FZ|Lv Sr?U Sr?T!=Š( ((#l?{xĿd)1{ZWzE +Jȵפ_ 4QE2B(((Z| kC'i2%OWW%OWP +(((_UmƎNß-i_++9"֕^6hE +( +( +( + kC'k5_̟Gk>u?#m^^k>u?#m^B(b +( +( +6\SWWKOʓ9:ȵפ_{ZWzE +٣ES$(((+5_ֿ̟9_2F)_܏zUy_܏zUOp)((+gom +*}+<?֟WVc&+ЕP8EP (((ֿ9_2FZ|Lv Sr?U\>s(c+:(X2Ί.>s(c+:(X2Ί.>s+5_̟TP4 +endstream +endobj +37 0 obj +<> +stream +x EӂeX-e4$%Qy;"]i8:ڴ:qo?m>m_ekkkkӟZ77 ;ceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee=:>VXkkkkkkkkkk*]'6m +endstream +endobj +38 0 obj +<> +stream +JFIF``C + +  !(!0*21/*.-4;K@48G9-.BYBGNPTUT3?]c\RbKSTQC''Q6.6QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" + }!1AQa"q2#BR$3br +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz + w!1AQaq"2B #3Rbr +$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?> yZ yrV"9ܟ% Sr?Tl_EsA?6J?+Ҩ+k7'G"9ܟ%zU5_?dnO */ Q7'^Es(_?dJrl/ WQ@\_EsA?6J?+Ҩ.y"9ܟ%rlTP<nO EsA?6J( k7'G"9ܟ%zU5_?dnO */ Q7'^Es(_?dJrl/ WQ@\_EsA?6J?+Ҩ.y"9ܟ%rlTP<nO EsA?6J( k7'G"9ܟ%zU5_?dnO */ Q7'^Es(_?dJrl/ WQ@\_EsA?6J?+Ҩ.y"9ܟ%rlTP<nO EsA?6J( k7'G"9ܟ%zU5_?dnO */ Q7'^Es(_?dJrl/ WQ@\_EsA?6J?+Ҩ.y"9ܟ%rlTP<nO EsA?6J( k7'G"9ܟ%zU5_?dnO */ Q7'^Es(_?dJrl/ WQ@\_EsA?6J?+Ҩ.y"9ܟ%rlTP<nO EsA?6J( k7'G"9ܟ%zU5_?dnO */ Q7'^Es(_?dJrl/ WQ@\_EsA?6J?+Ҩ.y"9ܟ%rlTP<nO EsA?6J( k7'G"9ܟ%zU5_?dnO */ Q7'^Es(_?dJrl/ WQ@\_EsA?6J?+Ҩ.y"9ܟ%rlTP<nO EsA?6J( k7'G"9ܟ%zU5_?dnO */ Q7'^Es(_?dJrl/ WQ@\_EsA?6J?+Ҩ.y"9ܟ%rlTP<nO EsA?6J( k7'G"9ܟ%zU5_?dnO */ Q7'^Es(_?dJrl/ WQ@\_EsA?6J?+Ҩ.y"9ܟ%rlTP<nO EsA?6J( k7'X +^WD _.COZJ__'ZCL>Χͫҫ~ΧͫҨB{QLAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP^kk>t$k^^kk>t$kC>Χͫҫ~Χͫҩ {QLAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP^kk>t$k^^kk>t$kC>Χͫҫ~Χͫҩ {QLAEPEPEPEPEPIA8?>=mwA!?P],I+"c+>"hzqhvv*Ir{t<"u+g؅ߙr6rsd*?J̎)&`#;2kr"KT Mdz-y9>7~w?S]t? <@.{4!R¬oB=OP;?֥CdAR~q֜ Ekm7Kb_c\Ӂ7Zdnuo[!u+TFmZ`X?o^^AAހ>Ȣ;þ:մfX-GX[u ))h((ϵkV>$l N~POFq,Uf?5Ty&/ /G&/ /^E0q,Uq,UzU5eeҨ.y&/ /G&/ /^Es5Y?5Y( k Q WQ@\_M|_@8_*M|_@8_**kTkUTP<_?_?Jq,U[5KU &F,:]yY =*( ((Q TdW ~$h9gŠе-zK"c}$ ZtQEQY:|>蔉 =1=kF˿?ƀQ\,Op2-3@u4V}j$-8 kBQ@On+λՑR>|UkJƻʡI@QEQMD'F 3Er>nϸsOț_mL@(?F˿?ƺ[b*V h(0֐*@V?^{{rQPZo<7,LQ8#,uUM/RմmR3>n +( +*l%pDٽrD%"+w!UDC$uW/xIo&ᇖ?>z,{aR;ƀ4((#Q0TPYS@A݀gD9jΥ}3KזJQ_it#>c~_k6YzzJQEQ\4g/w$`cuW5}m3O[v\D%1T5s@6l@`ȡNqlEQEQ\tK$7l-c*_PeEyRɤ(_V-~([wh?4VniE.UFZ3ï^@((((|Iֽ*|Iֆ5|%OWW%OWR@ +((((($40TQpGXѝ*$^?^VӴ +ءq ~3%ђEG zʼn$OsI]| s& )?sKo{/=Ѵ/Fmb|Qn]h6 kzr{v⌟j-Q@ F@ڷ]YOڬ"A1^0 >?'>+i(曛yfh.!xSG\S.,gT9WC+|G;6.0$GxLj9 Qjx-_eV+ވ_>W̨2;W>q.UBtQIK@Q@kc^^kc^Hl(b +( +( +( +( +( +( +( +( +X?DX?DCGQEQEQEs<+rU%xE$#X#F@"[i-yׁ>E2{H3j?f%v]GבnWtVA[?{24m92EL}QLGZm3?^xyh3y@8gk=KTNWB+=o~?ZaRR('Qb,+?_d/m&cк5sZ"zygЇ9t^sm )4Mr yO~` cBrb'ҁ^j6qJ%AaV+Ǽ5eЮ+g?f^LMX񆂺j灿xsj] +*Q^^]/eŲ~s֓zF{{nۢC/*y=mܾ"~c޽B0gk:6=ʽݏA^%w"B̻$&>{SSиLZ é=k*iX%lrǹ>`_?1ivIu#?Jcඇp'ew:֗Oa? [c٢uvݬGbHkS ++3úZ x l?Q{Zt![ J]3[>um3<+ zqLJ2ExO4=j{$ѱ$= Es?$2n}5S$+N]*C2`~'+˨l庸}D +R^*5Ę1EQRcF WZs&mls?]} GHЁ3sf uhï[X,@7J?+TAds +#cdc +AT ͤ<̣>LnaÌTSеs#uF8ntS u>ƼV>"1.8ῡk]{'9kY>_enGsO뫠hrk +}Zۯ&o?nO鶘ZZڰ-sYdJ 6K}&9'1aҧԙ{pZD_9wTVnfQ&`70a +t-bøm;e{=x!-K;?8o3,ݷE2Sjj~jCZO[ڙ'WM=@$YGuEYl= =ZJù|/ VVoUf omit륻2be߇$ҽJv >Z]]wqn-ס#HXpGk%Gk> +z]/‰>F7QE +( +( +( +__'Z__'Z>u?#m^^k>u?#m^I(b +( +( +( +JZ>Oz1%?砠7⇊ Ht;9ve=e{זynd#f=ɭrOj[XivS@a֥`^R?{Q$1,Q DAP0p@QpK@Q@RPE%-Q@Q@ U5M2V{;؄?cP{Ex<5sD!-Ɋ_C+ &Hr } -*ѻ^iN5&DZ4 "H pG_>W[ARLD׿4w$иxPø4%Q@kc^^kc^Hl(b9OY.&ecaџ1GHB?c(?c+hvGџ1џ1PGџ1џ1PGџ1џ1PGџ1џ1PG;/_o[v۽k9G1ocףS%Q@,*,TQE1Q@#2b'wǚwnJ$㹠$/_Wn,:(+4[{Kݻ5^ӿ|Uls{U$S(HT7kpJ055g&Y~x$d'{oxrt>)?yx>/`1"?]ٌ2\تM"eU (5Wt3)VV(IzukZHeI½zk,w$p$;0=Uښ_7v?45\96}#{z0+sw?uo[m- ^:;?AOa:bvGq uT;|<dzz/jQE2B?~Mm26'f?һGE6GPR2 ywD#-ڽ/z-`~HPWxB}Zxm'}GO֪Wroi}~Thu׈շ$dSg,5VvZEkn!B +<] O:b|E+MQLG;BEk&dl<W7\Ŵϋ[#d'~>A2E*zx"+zD=䢝1$=H߉گH .?OO~VޝT{5m'r>VG'k4m:VK!՘GoB6cϧ7 zEkoV HP-2XWBfGCaA=Aft=v)dl[Ml:Az^#- zF[KO']] ~7"O5H$: t/^>:~{sW&{ p({qt6 qWn{ܟry('9oB)7]H}O#Ú䢝$=G^^;B)[㢷Re#9IpȡBC\WwzrP>&V?|O Z=͕EQppڕ׈5'(^{T~Fí OXtk2'ï=kk3úDz&`0'=Oi +N5>xXqֽ 6E⺌O̧0^[@ּf=EuXI5gᧈn hY ?S> :!uw\uT#?j<m%LI'xW^fdp'zS^ hZpɏG8 ES$\jRQ [KM@1CD2OJ5 +y~S9kRE2QLm7OC?-d|*)][w?GI7xb#takl|]N?LJEt=ڔ!}{||eğޕh`kl"Zj%mU <驜g*2B0t+'3}\o$ሎ~uh>\׮oVkk~>B??ҽQE1#3U$+5ڥմvvE+FBG<=xZisyPG4; 񖮚׈#ꣿI?zot9s.c4z8sX5GC{ ;XNwtaESQEQEQEWZ3?ץWZ3?Ʒj_jHESQEQEQEWRM46cz]+ֵ+Ӯ/$#l19ėws\s$]'4hH,@w5A`oV= tԼS.>^ +s@ EPtMޡm;<ʹGVhJfa<4Q^M|T%$XC_Dȧb;@wXY)kas_eb!CRIdH{ɦ_Vij(ӱ?^!'5??JRXq~w{ŝBI9$k3ß.iVQI@ E2IcKI"aj4Jɨ# J߯> =5W2ۀ'jiL{4~C5N -HY3}rFNo4y4[2Zz' +/VL7_4P(5K1}AJ5K1}AJ6QE1 xsXOȬi!<?Qx?ȩ(I1"(HQE_7ndZv7$~J\7/Vw!+k56rO{-QLs\5{ּydwRG +?O\%kw *DqH9zVx^H I;&󁜳Ԋ!xZk5>n ȿ+B~C85. sJ+Op~ג|;fp`=NW +(rt/=nln::~@#d'? /(S..+oϯ^Up{ _RE2QL(:ę̐UGxH_FWBSU(:e#ayVN< ^?^Y>[LdULL(5{2?>kQ@:ȒC#Qԕe=AEw 4/:Mft"&8~>xBQ +Ez_iOc)QERtu}*{ 쫀{ɭgRD{еJ@1!8&R'ohQ3ofA\i;~]*Ȭk&QE>w?νE{Gf3}/ƽ |1XJHiE8+/|j-ܓA5VݮƹQa,ݏR>|:"c0Wq"Lh`sa^"g$cKa>@L?_) +>+Hc@J@|9X['k+ȾF_Ev?ֽvQE1ǀg9Fբfoe@8IOG]Z_jդ6Đ*Q{Uz}ۻt(è=Ś%݅ "4n}v]P'*#HoTzMQL(((5:g5J5:g5n Sr?U Sr?T=Š( (((|m<'$*p2,rxz9]6=w0 ĺo4Z{GLAi +v桾@w\/8tmOQ`,g=?>|4@ij qrNhWYǃ$ǺĻGs]F MӢ,?OZ=+Q`7#$~uX|8 Gjz?iH5ڊ}>mm|1=0Ndp|;d7~+ÞY$wfS(+i<A5_@B݋e=\~kTg`d֟PM))ev:^|+Ԥyv?ᦁjYuG(YK@3Zu}2'M[-L K!uAίP|-gz\KaH*77/9`]QEyY zUyY zU!)((+~-ǶkЫ~-Ƕhc[k^EfK^G^EfKI QL(((5K1}AJ5K1}A4zUQLAEP;G;·%[\~?]ט7HZ{u $2zr[5;Tӑ[t7O֟­;˰ԝ~iD''V? T y?Ms_ 3#W)º~lofGqzufO{e-$ggÕ@w=pyҽRxc6H"pL~OK?:k=KY u 漻Z% +e '=@EgAbSjNj|=n }ZyimV,p)߇&]C+ z^! jHIw:~^FE:p\)=^>?*쩒!!T@Mx<@ua&?c)ob?>!-+H?v?OY[\&lHfG~O^KGn@3drWO<1A$ xR>dM] YctNC>G so-ݏHSQHetariٽJ:>w>87`ͧYʫ?MhL,J}s&O|T9i{׮x7H:7-]G߀)7kwhm<]|1U?k+4KIOr?]xFs&GEt$Z-䌿ht'۱+QHetar uyśfӬUa6]^LD<s^!ZMsZs8kBe/H8bS뜓^X^ {yl~QǷ0? +ݦ (KPδIpֺ3]ҵT)8h 4QY:爴{2.h|YS6jܪ08<,ڝl즹kUֵIo8iGE^W E}@S:>kF>Q:E=QE +( +( +( +__'Z__'Z>u?#m^^k>u?#m^I(b +( +( +(< Xao͏Wˁk~0Մ>CS@IiѬzm(XUOp}%?X*i<).rFI~G<1 |Q dž?-@q |Ru 'Ak]O,S +$õyWo❶ o !A~(Q aOºKkFqg'TǾ_@(€64 K( qUys1ToǾ_@%-s_\QnX^6QZH%Qpnf(((5K1}AJ5K1}AJ6QE1Q@Q@yů?ߓ-zyů?ߓ- ksk>Ȭk>Ȭi!)QEQEQEf?5Uf?4J)(mkH6y .b`V3Mmei-Fr5-\#:.ѯv3"|G9tWUf,2ІHPoQ@+i~%58/6Hw+d-gw0}zպ(g^%յI.n4˯:LhQ@+ִ&H.c֍aEP )/b֞K{{X>[wN^EL~g^as+I~5QE +(<2 kq!M> +ɧ#( 8= Q@(c^>tu++,dW@ӱe Ѵr0ACZTQ@((#񆃫{}6XWH"{? [B̞fq2GC[P;Q@(.}gO# 脌F[Շy f"hQ@AͼNHRЃ^QOj|6]ٞB2'n7ڞLPFORz]_M׾hU +TaJùx;Z\G +b[;ObǦ}(!QAͼNHRЃRQ@IOj|6]ٞB2'o=4#$)8â=FiXw< WS.oxKKaWC+|iXϵwT*)h\(b<žKJ6;A+5+"whJ)Xw>zj:mT:N~zƽ񡉛sD,;/NyT>?ֻ(HQEQEQEQEWZ3?ץWZ3?Ʒj_jHESQEQEQEINFOW׸|K7u{vYGׇ~g9)>18?5Pן|_$>^\ǎ|;sM>XhL1@Ewoǿ—V?PEz*S/*O6y?5?M¨#k7PQ^ +SG*O6PQ^~j]+cjKj?-@}^:oq?51;o<3Kx~NE^,O Z( +( +(<,*,*QEQEQEL~OUL~O15\_"%#\_"%Š(HQEQEQEW% ץW% =*( ((((((|e"\O~LtǻQE((Oۦnayu4-f+ɋL{e"Vݨ *oj߼ }s@W2]r=RȷfIоJŽdX~&;s%OG (|e Bh wh9ɼ9Kpقc1R!p )?P3~Ǩ;xY=?H,zQLAE=^=E]F :+KwZX̄~YkoterS|RF{ixF[O}iV +(SU!ҴˋWv3ǰ^+kb<˫xԞ!@ҹW K4srs <+ wR E"RXdЊZb +(;ky'Ef=@Q^ k׌$U{:"ZV~stmtS&8!ypf=MxcyO$4x8TS o@%snC9IKTq|H!Ѯ$`M𮹪D&sr$rHF]sKq]p>'!:+)tGO' rc+a9IpV= 15aQQOHRǠVeψKW)6j8*$~)};bSս~U4 뺜"km=!)\v=_%lz/?*Oޕ!D!}pN?y㶼 r{ E67IcY#`29LAQ<6'"ANGk_mcy*W$;g/;K=q.Hi't ŹIo-êڳ? ^ 7&8퀇8>xurQ({ h5- +ufi-۹ʑ}{.[jl7֭U=ՋQE +( +( +( +__'Z__'Z>u?#m^^k>u?#m^I(b +( +( +( K9dIPF+=JM?Qa }'^GcF6~m:~cPw=Ti(XdlEvIvr~54NȲ!*C;^q5 Fɗч /ڥqrsZ|]z4h|L?/,~W@ 3ÿ޸T?x<=s~<߿x?u~׌Q@ ;?A?'(gu5m6mL0x8}{t[Cj)3TdSef1Shdv] 5wC񦗮"'2m.K& ]SM*MRTėGjgO(5K1}AJ5K1}AJ6QE1Q@Q@yů?ߓ-zyů?ߓ- ksk>Ȭk>Ȭi!)QEQEQEf?5Uf?4J)((((((Zq?WSF3S׮xE-K+)#N_)2ǻQE +( +WMb\tFث;_af2|2[D0^ܞz|DI"?q5Hu& 8è hH$ +_k][l!տvAf[.n#G#lhLڐϞݗ]\&/.8#NJ袙&74״Y-|1÷WZ&`Ŵ278*Q^^e3LZOBٻ7ƏC//1L>V>kd:EI=?z$1<0D@Yu4>4ƅs8G߇yWYAGj i?[ܗ _ۏSֽG~*Oz/H{#zb8!@FQG@AT*-gH_áBd?#]ae=SWҭe\vq(h[%Z>WsUg}Qm&w7G1"QEruB.*LkoC߉ɓW9UO޸*M}?5t}:=){{qּPѵimd%'^:r~ vD O@MO}bGM}e5@&Q.:}ׯ{ekbgŭxW?j`¼.}͵+>\$عp~ʼ:< |胯z}M&4w /ؾ:bk, ~xcH&@ȥYOBZXHHP*)<:L&=䪝ѱ$= z5 |G7$e6~ߥ6wŠryh~]?*4R]Y0vؑ?{][GqE;I$~ǣ@9RM罸lE +>Ow3kJTsw.Gl †>h_ԧL=Nߗ_ʽfhZ\Z6oa>Z=OW8Z6IS(u=ۂ +mџFo +&= +9,!)x =YYm%.:+ 5/GOA;╰ðcqϱ20xC.!tǸSH|SZ :Ln|U׎H7-3a1C)V0A^3 +=\G]+FQ^\?[a&iu) ?L7=UtIfFyG+%]ta$?L׭S@/xu|DqI9d.G֯iZ.XZ\|ό}OSWAHJH<{G-lRE`~`=τ׎WPceQNAKU,]&ah7vmcA_ c`B1XWC(dQ@Q@Q@yů?ܓzUyů?ܓ kpK:6J5K:6JQE1Q@Q@Q@fI[,%oc֕!@6jsiY.١b+s>$oZṂq] O8ɹ~G ʼh%xRXZ%˓~3S.Cϔ½v7Y:0eaA¦~ +o '?^Eyo*i,4d"?^E|jIfn.:'*ω.bB@+95¦ sy+S?L_ׯP<4_׮úYt;m8%07kJ.%XnfcG2]HXJ?kr̷ ~X: +Nj ߑnJXD~EW/ RM*UQԓ@e׵l +uZ+[h]ơTz\4Gz/]5QEyY zUyY zU!)((+~-ǶkЫ~-Ƕhc[k^EfK^G^EfKI QL(((5K1}AJ5K1}A4zUQLAEPEPEPEPEPEP/RԿbŸ4νs_)j_1^GOtOI=ڊ(HQEQE|?ºݲ|ٿx\ ce2!FJzWsBJu*=ExVRmFX\i,}]|7RatQ}U:c;]ǩ=^+nyJ{){C^B +(kK QˍYBm8ܿ7}S1n ``vu+w❵i6R0K}`rz:˗쁞qH᷇|u{ͽb GޓxTVP趉mh_: 1uv¿ָ*fK迉hQE2@Ad׈C:$([KOmnwބ$վd?P(e/Z)76`/'O?Oֹ(6d?Oֹ+T/ ͜\2}sº3V͓>d裯z~4ci}MmVtC<ޑcQ*( +ZdQ@EB~ +b㢿ǯ[ 56 4Y2A{yOu##Y_nК+[VId[Pi;Oיh;RI| +E?z}mYEyi TCB,QEQEQEQELrOULrO1/|Gڽ*/|Gڽ*QEQEQEQEQE#(e A%Y5M*-֬wK^z*BP{̽+OHE-YIբqZ4|IQ#̖~r#J2#F.-&Iqj|뺎p&do^|Mbc6.2(^Z&xFҧJ#ufzzX M+5gch?5/׫W3?bk!vB1]5VSb2_o:<sGw(յ ,IK@;׍x^!1FZ+9Hֱ5=VV7/4ڡK{h^i\QIKU8jq71F@T $:>H,U1OwqKEQEf?5Uf?5TŠ( ((OmaL<ؤ>|?zH,ᔃk +((((,*,TQE1Q@Q@Q@Q@Q@Q@2KR'? +Ӧ:ȥy?i??&Rj(!EPEWS [lQ.{OEnl\1z~uxOxHs0!(T./|G +ﹺ* ;Aa4la2=M"ۨs5]6 frqp? ݲpK=T~Mz$>\PS袊d6I(Y" ,NWL-LzrOo2n0)\zj/ ]hzUjuNy>Vo {f`_+5+5=>{+˴z4=e.V._r^ܞK,=bKg%.-ʺӐ5״hL\C}hBfQLAL("e==E>UK-^2JA;Ĥ!X|m 4 +m"77$אxF]W{/?V??$S5dQ@=AE:w/-"I>Qq^^%uA-6FvwGu5|>+O1 (QEx, Usڒ.'v -M{OK9Voe5KOʻ+7|ZE=Š(HQEW|O^! gzyŏ tc5,څL̛#V$!Wמ%mOWSQE<71~-ԗſ>z_4@uڼEӾ0'$榻O7_ yY1ZE=(Ix3P2 ?‘Kct$k^^kk>t$kC>Χͫҫ~Χͫҩ {QLAEPEPEPEPEP:k#`+?uPϺ߅F]hd?oƱq_M:+VPF<\S-"ۛY8ҀT>[P\|V>Fnū#~ C0_׭`ç@y dֽExGʀA%2'sNĚN5g«`ڕQt?v7t=kh$Yf3(ʼ?}cZus KOROM4bR$hUG@AOI ݴ7 xR5Z>Me)97{޽/Z7vWN֓e]єٻL;Fk +&v2a?$?_u+ܡ9IpHB ݩEy6Ub+ؕs#QGnh!0u%} ~T\V%ީ K@L|Mi)J[^jn39oq:{ DAnW9f '۽{vcelbv'ܞhY:=Q Ӻߥz-Tt5]6{\:؏py$y]-4hœ~#WW:=.UB=;Fk +wo эc#Ґ=^fv0h G2?U^t%=*y @C 1 P X>>Ruq3W{PXCae TfL#mnf'ڬ' +ֵ>&/тAƊm;IXWu%Dvb"QWs?ϨV5ej65XZ-aKu۸{p_S"<xg=qMf~DO%8'~u{×Ў +@PS^ӥV !r{hBQL(((5:g5J5:g5n Sr?U Sr?T=Š( ((((((((&;F~PEP( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +("#E #t"m#[TP/"^MhK mQ@x{6O +Ȯ\ +A(?PNq5shgivbAai\tuv((o }ҭNO ?aJ``X1m#[@t((cFZprCV ?7)sk ɝgԢ(ȟO6#v rMip@8PIEQEQEwnteYr{V [ZıBڋd((;4F^XE48fOm(ڽ3Vh((j:6H}g +\t~iVZȶۉ,:U((wsU"9+ZZ1soKEí ˁ]m66D\ +(QE항9?.~9hn.G< uP%k@p-;K/Z۬1/DE-QE"u 0Ao,zD$8+~ᦆ͑=A"Uv^ky.HHHqK$Q¢(}E +( +( +( +( +__'Z__'Z>u?#m^^k>u?#m^I(b +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +__'Z__'Z>u?#m^^k>u?#m^I(b +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +( +__'Z__'Z>u?#m^^x3b>ֶdkܴgi (#rFOƋǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮQ^G #^?G,{ Ey,{5Z߳4\,z5Z߳4׿i~pW׿i~ #^?EǮWZ3?o,{b" a/uǹi +endstream +endobj +39 0 obj +<> +stream +xy:F}xmi LZpHٱ*I*!%xB[)$Z;ټwcQd$ =E+cyl ++V[l ++V[l ++V[l ++V[l ++V[l ++V[l ++V[l ++V[l ++V[l ++V[N:o`W`W`W9oO_E `W‡X#U$+| ,QyY?1NO_E rTZ[e"l%NV<8X*X[V>7RsԝWߊ'Kvo!,vÉZ;5 Vy݂6WG_Z#54D[ _pze:եtRގvwIVc{ m;n8y .JCS7# qշv_4_7KJ zգ[ AkTTlܒVf:}(nYi2QkxU$oq[[[X/CaOHPj<^#6663dzN3Sw| ,2-:Yfidј4-"mDMUyG/N$>\D[ơhw8EXU$orStP|;h6> +Pٷ_R/="̮Z:>%{n7$nRCu$%yN<ՌWw| ,<ZMjtHK4G2uy,n+缄m̕J|q Wp\×_]N_&^H`?8AuBh2.r0.3ryU"uY Z*h-?(J?!žy*vWkUNJ%_E %94ۇ sz:u>+$u?E%e_] ,[<gThON拕 yw6I"rg%53UaK.ƔT2]u-}HG7x  Sn3㝆W{B7XaiI՜[*t6Zb {r|4EZU.LnpƠ?e"L]qO=!T}kbV(_˦$G<5WKɗXeQNVzzW2~sِ\v(^,ve| ,&6_’>9IV}#ߩ Tk%lr>9RWPŁڭ +/1ӨWg14N*%/Y/jW۩YrNck_+dŅv<#ab۾80fi,mvC.^H`w{鎳&V_ҢruVV@{%5k#M3bK4fMC\IXӐdّM #Po;]Wg9VQJ;9>+BtPS9:[R{i/*n ǽ,|oZ&_Sj'eP/l~t^YhyQʹAŁ@m}6"{)Pxz_],|ol:hz|/V>gCii1V 8B҇i*郎WK! dC&A,-Nt,|o|:h|EZ&I_ʛ(F!{j&ԅQ5iVҗZ Uև#Qծ_E _ƛin=)1&._Q2اC{"=>hǀ#rnH0_r aF2>Le +Sk>U$uhkzt0!tIWmM<٬rn(BMK#R,fm\ZfeHtq*8#) _ƛin=) M W_. (IzI^T2u(es/K/}YK_ {ۮs{dWySm7zBS@3fH8Ӕ#OD0}OG<CI, 1 Ay8ϧp.8| ,|oq:% gnJFԜ|gDMtF&u!FiV0-} ?tANK_Zt=Nu^&W=f[OqΑpq_KQq`} [ԇ@iH:1dv;;CT| ,|oy:N}j1MlMg#,WEH I+B1rNW3Xu{@[4QBLݪLq]%<"T$hl[ͥ/52vҡN# JC_ vl:0u,?_MXǕ[[hn=96&wUqwmm!]!}h{B6Oů\Q=zgV]0u,?_=X3Ÿ:v:L~=!$zU&|ґz|Ly L{N׾=!Z [r%z ,X^ }}ҍd_f!L47y1 4iP]}?\8M&(:,|Wi:(fR*S՜DzCbzyr? G&Yۯ^kc?fLySUVtP;jrS+I}/z;P>"ԅlH$Vͤ/od,ԭ:,|WrW{1_f,׉|\R˙!]j- I)n6VH`=Pg]jMS竎E]#/GI$җo>V])BXάYut=!\W'I]R= +}85빚TN%*XnY,+}$g8!5kUν*ڂ 87;Q;{Зt_E Us:؞|հ|ڦ$œ$qFqͻ/ikBaj rҞ|\}ZF𕷝al,Wz|E7udҹTHu|c-]<[>g|x Ўo_ +z6U*0n5_E ky5iUL^jC&Sx-"})Iz˝}ƽ ;H`;=bM?9Ug>5sqPM"#S' ^V>(#})qYpg_mgJ >ͩWwzŚ| h0ƸSv0]׾My_Lo; ]ezpΛ"h"_٧3ƵMP*}I ۏg_&My˝}eK rҾ U$}h"_٧„3Ƶ_{)% #Tr7O=<2!m;XzW+mg {+hu@>fr1䏅j<ܵ4K͊k_&(޾2쐸v;X>4YEpϴyrGʏ)j'Iy6iĎ}qo_d{NJ _E 6UA1N]J>%+GsgylH}_i;ro_/@jH`!Gr]lrEǹy}q+ۮMNw+y:Xx<2$w=E&m;X+:tbh>5(S6?r`*+y:Xc|w(Y-Sf}HtQrWFڄwWT0L;,pi]/UH`=_٧ 2Ɵ?7lU*kgNYD:u<Q/j{㳮vw d]n/_E u1,0ʋ}ץILV?cS]ſO:0]a@8*U$Y e"񗗷q]SLvՖPk{/ONwiG;_E u3KEhQR55I YYrW:?<>#\\oiWP{+IuU$Yh$ ҄Z-kmKvʉސ=hh.o*o-r 彸d[]9L{*XVXRzbLХ W*,ƕ:sɽX; +6'[T/C)-ry'U$0 +_zld/f`kI15+^l&C{[ o59'_JZxQrO:fOE%Pv;Ԯ"U$_9Pq~$ex;ruqY^%U]$Jmodzf+tP6|5Z %$QUSbC=_==I=KffcViD{jB4x8X1Ж<.-[§-Ň$.Gڂ!Kyi4&|z*X6X:p|hBOS~f7_E AW_~t^zZ_E AW,| ,=_U$|bW__E AW,| ,=_U$|bW__E AW,| ,=_U$|bW__E AW,| ,=_yy{1*L={}q@vy"+׼~柤:H_ٽa}Ǵ}W_\K#ʉ & ?^ggК\ocnށwn8hw(du`Ȗdo]ݰ.H`/ 4r̀~)sd:Cb,e-~4~߀h1Rr^g9V4_P*X zbzGl07| {vh? ]Z +-vC)$i/XoCɕo[NFhD3zW~EjkYibg$z IKiPC\wÂv~mYLʮT^u2ǔ):"s$k +ԌV)P饯i-5t7,h=rWw| ,=_%(eioPg4TA%&T IJݐN{&4+m6Wڗ| ,=_%rҬRwrvجS}=oZ9Qm.U nA)}* $U{ +߇J#/QKWߟô[."+ +D"tvE7:NHͬs*Bi-b46)i])0*u7Ӌs!<1'jw:W H`/*CǢ>Ǣh|4&vK r/e+͖ mېN>jX;ҧlkRb6~<"57 *X z$:Ǫ󰪞ԌҎf5%5!Gyv7DrvpC@V*>X+7Z*X z*YkSĠZ>q6,s^`)K$ӵEJd*[#I甛yheQ| ,=_ @T@ONYN55- JY*K7fS!}!I߈=Ƭ.|,;H`/DIhJWr!ZR9.J-i',BnwA2 k}kRU/U$|5JRFW@'mH oW- U mA7}dfIK|~FiYE?B_c&yU$|5!'I‰7uZe0zh :y%T!+wvE*+g; ɜqLr fx裸}| ,=_M!7̛ѕɜ"ʹ\|"ϲjyU㳿!yGlKj-J!wުNy|u AW y~.Y,Ԭ]9O;"xn#@/! ΆYP[PJ $:+dXwR=ucc c'Rh\:'U;56m>U!$9ֿCDkAz w |5CYQLZ Ȭf\+GOCwct8 +>A[n) AWsu'KjVVoT !3dJʔ`C5=-Kw~]}|7E`/j$iCZjj^T-6*lUt4)y%yJUVAZ~nΛ"|%0g- vj&WE r]07kd< SFD_ѱl]2|Ch_E AW4"rgP?Ffb|ְXWhl{e%O +2i<+.$؈qcR3FitmQ-B pi2ehL0]sH=) AW"*6`pZ߱fʹB6lҗIRđr0KdAv|7E`/Jd$5YI,K fdnp{` )j̐JPMHkN*z4u/ AW2fPXR3^9.]5T/8lii7Е7+_MX z@ŜeFCyژjĬETR5\/t>{X z{NW;E}piV0CZp4ƅ_5_:Kہ;5߁5X zAQs>@DgdH niX0)}yiOj;bX?wI#ySj;VsS{E`/A>8֤#S\^t]f=r9c4 Ѿڽ\x=|~lW_Uz4HWFCOR~D8;7Ѿ*ؽU_E AWUpvq <[|pen"_5ŧU$|Ue:3Uܧ$WŽU&Xz[iH6\o֕Z"H,5sPRq8PCpmG| ,=_U=IRew3lU4 c0bO&_9m7[" + ++JM~w]㢻ӛ~A”IحR셜[⾃E.Eu+}%OgHrF X<|Z{!l/qySdqpLSNm2cS?h|7E`/JdR۶$H͊x/+V_~c=-Sd}6LF$;) AJ1$^j_&-}d_e!5vjG,uD)5s|-H`?m(ɰP/$I}isNV_I.HN4al U$6DhUTrvS o_,;$nc3,1?U$#Kߡ,3}`2Eq<ɒYyG`?q( Sq=f&ڰfŨ^_|`dMaX|u? p8Ci~!)+/%l:lۚZ_}7dv{P8xhơ4G:PkvAHʧ %ӯW)=ma\dR!L&3HKi{]n_E p8CiRrMj%IbjV&Ž~3❸i벦[$R +{"P8xhơġ6ޒ$ m \YKZW+}unC=qvTטM]+EҨ#iioׇwceo]IYW&. +| ,zC)ԏnHպx-5{*JG=6UU'v|Τ=>齷C=qZ* ^QjjV-ٳlz}EedH Yw-^C=qZ=IItNTU*}hyA1SD-C3wSkC=qRu7qXIPءL*M+0~(yֱqZ’캀"P8xh_UOhUS;$-kUg*BE;co#s;ue~ +| ,zC)Tr磎~Q䮎}]2kUMg(4;/2˺l] | ,zC;_ə|ŵB=%wƴӯWr#ֶ kOs^ʗw;| ,Q9KW_@}Vcac:M YљbNj_ + +1 gy%ŻgOޙg"o-_E ?w +Nwkv~Rexż~ݾ9bj{8׼y|mz(_"T g)H`Wz$R2UhrZx{qn!.>|eg/\_2JySD~9-+UQh5GԎ | }W{#YdM͞{kv_5ӜIׅ5y F׮"U$h|EpɈVإާPfƭmYFbzz"v2oeT;oꡀr{?U$h|5o}x-}n;=+,Y$~t/XCK;AhIz3wæ?`΋vEk=7wiKߥ&|*>42VSCmAN:Vh{gM0ӝԍ>kf7p'_4Qs|wHp<]5>Lm:99w| +`+W_| +`+W_| +`+W_| +`+W_| +`+W_| +`+W_| +`+W_| +`+W_| +`+W_| +`+W_| +`+W_| +`+W_| +`+W_| +`+W@zU~`K +endstream +endobj +40 0 obj +<> +stream +JFIF``C + +  !(!0*21/*.-4;K@48G9-.BYBGNPTUT3?]c\RbKSTQC''Q6.6QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQa" + }!1AQa"q2#BR$3br +%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz + w!1AQaq"2B #3Rbr +$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?( +( +( +( +( +( +( +( +( +( +( +HwV;nQO>>|Nl,O($ zfāIa_i nps،Gƪ@?){2Yk7 r#oOz/Zcoտ䙚!ݘ~Ҁ=(((((((((((((((((((((kz.q\^ݔ}M_/ Pi6ϛk3ti{]>3PQM3c#I"ƊY؀I'6VLlqO_~zW>&pItWv;/ +VCV= %nJ}v˼|/CE|,5eSڹ{Ϋ+XRM'bH\?B9Y]a{ 峔+Ѿ&^uk6ȀI}̫XusJ<њ[` \GZ5_ }F;u,NAcn>Hǹ?Sq /& oZs_죴?E8\( Mw2xR &&Md1&?ć{r4/m™Y_޽lzܲe#:<| Wx#I+f;ٿk~+cYw8d:ug9R++e!A(((((((((((((((((("n6)ϧ]S^\aB}|jơ?VW?qGEgV4y5f $BwJPu?Eeװ|>au:blTj1_Vz/ȸGDopB#BE|3w՝!ER(|ubl-.s$X觺ȊUKk'_=tOƼ:DxhJ#}WK⎏g4fzhigH|YE"D'ѵk}BJat5-> g OPQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@V_u Pm?`h-&l*K;4n&w/4]'LDi$TE,@ h6LĒz/@kš"Z$V=yws:~_bYΘGQ^qaEPEP^[/B%&!;eZ*ijd7r3Ob>N< +r-==~YM_gpeZ x\ta5i+f48/B6L\uk^Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@xįkb|YAB/cT~". +>h_lV3 Y)=\}څ6vY*wtt2 qDr~|_㼿#Jqr*[hd'**Ǡ0%tf]Tr}6>"jr2iʶP'ƹcVoB^=5<UsK9Qrs!=wJ!o93Ĭ?WkXWx[u[el{0zGI֮Rdx__q^SZ]XGsk+E4g*y5eFmBX~ ^!<,,{ܯNT5fӾ7BYO p;fy}<1A$ xR &&d(}Aq|mƤm_غ趝gxB>Oj߈!@#'_o}ǽ{GYEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPMD'F 3WU,WD|Mr7NAp/ן:י" +{ aZ$5,! +AOi[ Z ʁ@eߏOLP$QEQ,V"XqVV +k#~ !׬Wjֱ{p&I&WԘEPEPEPEPEPEPEPEPEPEPEPEPEPX/ד יv +{mׅ@ 1Î@̲<Ҽ1wrYI=M"#HꈥIqBfڴ-N#IԊ^8zN +ׄEдHhfE5*J:EVc +Ge|cω$65-u]'A_&.).3Ϩ#((ڼSŌ=\et#emxBsٍz|y>j=IhQExfTm:[L|3؏QU8(PNv a~BѺ1VSA+vۦn׻)Bэ)@\c'{׭bnY|2ԍ Eg\>n':~^bm Z!C~_+s*;oỷṕчpjj((((((((((+>,xdi?ؒê׵h4=PǠM|}y=ח/is@Vt5f V;a(:܊̯_{ek1uvTUbEn_בP3:xcP$QUQ(n(@QEɢYWRF)SN|X:V|>ef´_:aTQEyŅQ@Q@Q@QJ 7q4?PHX.?⽮2bO ]N|,ugLjqO" 9 lt0UFc"\ܟ7^;4(((((wk2с(OJnn-F핑0)"zsgªQE{[_Q%#W-x{i +Ԍ@c5g9hkIj_+Š((((?y&n0t>>B۱b8h_غ,t_UEsujƴHV}'_i_[6f@"5w/%$Gzj (((((('^|sS |q<7\N呋k/p[lDGo>P^B:b9#^x#B\[I'|uXx~(`(((((ⵦ.b~3yX9_o j%H'v`*{,J~u>'IiJ]%|&aSbg/;}PV +(J +( +( +( +( +(1|Y.KlB~=v:e8 +}P]VGcd0kr\_, l2 A2M6 :94(oAaL\u5uHP +S jr+;"f8 i|5оۨ:f SѤuXbkIԗAwc𖈺[TQ_R[+hQEf0(((((a[YmJЌTSN扠H\ab="+;(1mi?_W4L}ۼUFTK7~o_*{nse_ k*z(mݝaER(((((zc8%\gCW(Zw@|g6}5*«ץNмSZO<$w^t++0x-\Ƿ|8Ά-g|Y=믯|/r Ɍ4=GWVso):0A$(((jphUơrq+ܜ ++x?g 쿉y=Z5)[t9f*X;u,G^\0%?=kcyq>p3;p{oǧ^_%Oc7+Q^QEQEQEQEQEQEQEJ%~s~߅V^nwzGD}Xκ:LگO٢igA" Aw“6D\\HddqQ? 5[_ٜo2΢(5 +( +( +( +( +( +( +(#+y "e=^-"]XLS6?ć={r/Mp&na?l+rKΤn UQ/6Ю_2[*:}u϶h\:q_fsJQT4=V kH-*un~EPEP^CS_mWF|js)OO~W__h2)ip)uwi$i;Y&Z^%,cV9r+6{t UO_Ҹ3 Wh-ވGMZo +5;Ҥf:(EPEPEPEPEPEPX mtȂ/.ߎ+3>)öl>2g_}}+IievG9fcO5W39#)r-4 !$iiܩs\ kr~,˞G~35)#[S`n>ǡuWΠWx]%n{׍}]Z:4K((((((((E8п5/A꿁rź"$÷ҼAՑJz_mF3q;"p?e⽆cVd`J9kPq/sWAEPNO&+FϷ|]ނuH=??J^s 1}[?JTf8u&:h_z S6oC迉iXB"`>''?߇O]mfXm|+E3 +(Š((((((*b$ԵOX,5',-#EsI 4:5nUƟ?åf,ŘI'%z/%:CŞe^\ÉawY1ďЊ+sGoꂴB(((((((((/xOդ{GQs;q]m(KIx@E7+(L#{X$@IcOMe#٣оZZ:Ϫ."%}{]U +kj%Qܴ(QEQEQEQEQEQEQEQEW/IK j*# ]exH:xEu`JxJ=Ф ߶:6*9eSǥ8"7+mAt+ +'l|Gak*S/ތLk%V,{c@>h&{ ee<q] ٬UqH#RW5\% +ϚScRkcϭ.Y߷닢"<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?t\]ga<,o}l?uxTYES1sP2p95< +R`ؕ[Z&d jMηr@yk힧Wgv>=/#,>i6h'>$x:(袊((((((((((((((((((((|?x:?FtQ@EP +endstream +endobj +41 0 obj +<> +stream +xݱQM`TE YX<W66|  " 2 B8 "zg}t;xwۼ<,aHW͖#@-˗HgxP%wAO~sw;}W cy<^pcy^Ԝs7*d݋7_ ޭ^EVwWxj`+cw݄~rWkj.cU]q=PPNUt]]Ok7SPV]{eA悻4uu'g+SCU w^aK]fpŽ:dZκe{?Ȼ^7+@KSVIK[s_{6խ^1쒖v}X暻ˠQ|֝ RQ\/桺UP\%^c+ R7Wm!qIKCu[攻p-v֝ *QTҮ 2~Up9y~qV1ZpBu+t]YT2 +%- ;"U kʡͧT0ӛM9J̛+wI ʤ\ce3mjg2[{5ϺTU=NU R[(kN2JZ㖨\0@b,^v}7 CuM!bO8'vg b=W)yLɡUa'g4)J| 89m 891 %-x\ 89sg݉k*e_a&LUC4KqW)Cq&TÕ43qÕ4+g`pr6b!KZpprۀprbTaKZZUs\m;vNku;tIK9q<g]Bkk<.\3o 8>1JY]OnqݣCjqCLɵnQ2!b=3'״EpjY݆|Nvgޡ&X(ii57\v 861ŚTӥEu6C8WY 9'W;BIxLuϼ "1JBk +\!p4c, +0@YWZJs0DEpDg<8L)RnQ**CI̋ %8L+:bN]Iં8ȫ[U +x&*U(i!Frs*X*\U-&*ˌPV7 겪[uˉ\51 W(iX:ઑqkdYE-̒3/&Zc!j\S\u Ƙ UcϼxNWs{[Cpx&ຘnQv17ઓ3/ cfMLuCUh7BIx&ڎpl_:K0]R"x+ǡ5#gL?=lxm@h rv#GVܔin +osSnhK jS]S c"C\b ƮLCaOԃ`%sK~pf>_U3}at6y; 1/yLfkގ^vWإ恜@b/ 4x{z}q{Rz};Q +endstream +endobj +42 0 obj +<> +endobj +43 0 obj +<< /Font<> +/XObject<> +/ProcSet[/PDF/Text/ImageC] +>> +endobj +44 0 obj +<< +/Length 2010 /Filter/FlateDecode +>> +stream +xڵYێ7}B@{챽0FG>բ$ff5c*EҨ5l,0Z:UN1 X߂4b^q X y U_^ܽ]}bB H`Y}ُen>pXn~x/_KX~w*wG1"8;W&I"t[*gϚ B$7Liþبv'UY +jS~] Rjr” sCObIp|٩QTe[7 4^@VM;7陖Ͼe^x5lJa` B^Hc:{vӪf_d4(k!\Zf`vyӇu~ +uKCO "OP&O~^rݞC[wj'o-Xf^>Xe)YKPkj6*.3LL9}]MK'V.PG<45 /}w  z"1gqf@b:~/I݄^|2@|!lR¤o{Ք"XecF!u"6ik/ +>cUoulU?"9 +Vr=8 Y.JArgZ"E[= R!gYܶU#f}Paya^3 *ͩ"܁Y6_tO6ykVӾ[K)qά0 ?' RcȃjwOZ0Ubt 7΅qɼIUʴHG~\l6@r_6`1;Rl}S躄lQjCf"Q +1X_xMʐ>oݤ&% \*nH" q +3lꢨO7ȧk ϠN\MEDQn7RN~ E¶Fj30ȕ5 @z:I_$!08=~_٩";O%lPCh3w+(3 Ny0kgDG^cOTAq@p[lnA@x.ʋrldXvXjl Č}Cו#.NI +.wy~^M|>$d.jz>*3ڧnuA\;x#D. q9s~4}ߵP̚/)-iZC85lj8vPnEс6/g*s8G1*A:A|tˡǿ{ۓN?:q A[3#dhbzKPB|/5ܪQ5N` w M;x/"9oR20LU7s;_\N' WJE٣~InUg؟.]z[x$y~@ H BGO]> +endobj +46 0 obj +<< /Font<> +/XObject<> +/ProcSet[/PDF/Text/ImageC] +>> +endobj +47 0 obj +<< +/Length 1911 /Filter/FlateDecode +>> +stream +xڵYɒ6=[L"Y%I4I[QQm;"D\^>> IC+$ F"H +\ BA$D*&Lb"pg<d@az?wj*!BØ =a}aRL&cdNvkKO +$ +Aѐ  /P(ܠ\IFԻ_GRII?Ҕϰɒ]iwUTe>7n^SU@9`EVsT]?/Lb$_bB#)8(b3K+>j@nM0I/eu'3ű2jL=>| 6!P=ae4,\t뻇1'bF8M׭I 1*yLbFW?G%&WFL:z 3ƒ2m3 ~h6ϻ, +.95 6]/YJvl("#E[ 1d)] |f)' WEV6IAV%Uj6-ޭb-5||~V_Ƹm yultb>BHE 8;Wy(YmwFfjR{{w&ٝ;cg!i/ׄ"!EX[[_WW}&76\Z +lkՍd^{|9n=[EJlv*;JcS^lם#L 2&cJb66]V I ~6d-CVv[pBľbj7aD: |鍨ɂ]{+h Xr6}+T :it6)\.3a +Z'HM١t\ ȼD0$V@qm]β|&H%18z/cd3PaֈNl|=v7M>jحKjv]XkQxhKm+e-13rԢ_03ǧۢquұ8)A*CMY9}Ff6c.gGT? +u]SbNúv8[>>SK(AUKE4ë28Oqmus, _n_b IUy`6i9,5 lP(KuTu@0yWjYYL%iż<[k'B 9Whk̰5#L'6MRg8///zVNkGs˷ 򜏽pFDĖFc^2Ї^lee8"?c24posOL1n UPAlQηtb1m/Xx{{0y̋fSk/'<+_L}fy!Pt6k-W`  YV w&r"h$Kt PP^Z* +(39>k^̭Egrk 76=ܮsn{ +Pz_%L{)v?ۺJlL7)ppy101"Td_}cnH`zc?\o&wՉ\<4[^(dLKGH|ohMDճ7]6gH +endstream +endobj +48 0 obj +<< +/Creator (GrapeCity ActiveReports \(tm\) for .NET) +/Producer (ActiveReports for .NET 7.0J) +/CreationDate (D:20220930145436+09'00') +>> +endobj +49 0 obj +<< +/Type /Catalog +/Pages 3 0 R +/Outlines 1 0 R +/PageMode /UseNone +/ViewerPreferences << +/HideToolbar false +/HideMenubar false +/HideWindowUI false +/FitWindow false +/CenterWindow false +/DisplayDocTitle false +>> +>> +endobj +xref +0 50 +0000000000 65535 f +0000000017 00000 n +0000000069 00000 n +0000000110 00000 n +0000000197 00000 n +0000001329 00000 n +0000001510 00000 n +0000002638 00000 n +0000002819 00000 n +0000003951 00000 n +0000004140 00000 n +0000005280 00000 n +0000005465 00000 n +0000006609 00000 n +0000006799 00000 n +0000007933 00000 n +0000008123 00000 n +0000009267 00000 n +0000009457 00000 n +0000010643 00000 n +0000010870 00000 n +0000031006 00000 n +0000031163 00000 n +0000031318 00000 n +0000032532 00000 n +0000043941 00000 n +0000060102 00000 n +0000109690 00000 n +0000136853 00000 n +0000175902 00000 n +0000180651 00000 n +0000181475 00000 n +0000189144 00000 n +0000189301 00000 n +0000189528 00000 n +0000192519 00000 n +0000215950 00000 n +0000219011 00000 n +0000219412 00000 n +0000252889 00000 n +0000265018 00000 n +0000276660 00000 n +0000279411 00000 n +0000279523 00000 n +0000279682 00000 n +0000281772 00000 n +0000281884 00000 n +0000282033 00000 n +0000284024 00000 n +0000284252 00000 n +trailer +<< +/Size 50 +/Root 49 0 R +/ID [<19fa33fe23b946a1ac35625ded69aeb8><6e30232f47dc49ae9f08de4cba3ed40e>] +/Info 48 0 R +>> +startxref +284496 +%%EOF diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_KC_Certificate.pdf b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_KC_Certificate.pdf new file mode 100644 index 0000000..37edd83 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_KC_Certificate.pdf differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_NCC_Certificate.pdf b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_NCC_Certificate.pdf new file mode 100644 index 0000000..41c3685 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_NCC_Certificate.pdf differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_PTCRB_Certificate.pdf b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_PTCRB_Certificate.pdf new file mode 100644 index 0000000..1d84304 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_PTCRB_Certificate.pdf differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_RCM_SDoC.pdf b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_RCM_SDoC.pdf new file mode 100644 index 0000000..7a62722 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_RCM_SDoC.pdf differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_T-Mobile_Certificate.pdf b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_T-Mobile_Certificate.pdf new file mode 100644 index 0000000..0ca6e43 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_T-Mobile_Certificate.pdf differ diff --git a/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_TELEC_Certificate.zip b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_TELEC_Certificate.zip new file mode 100644 index 0000000..8968cbb Binary files /dev/null and b/RM520N-5G/RM520N系列资料/03 Certificate/Quectel_RM520N-GL_TELEC_Certificate.zip differ diff --git a/RM520N-5G/RM520N系列资料/Quectel_RG520N&RG52xF&RG530F&RM520N&RM530N_Series_AT_Commands_Manual_V1.0.0_Preli.pdf b/RM520N-5G/RM520N系列资料/Quectel_RG520N&RG52xF&RG530F&RM520N&RM530N_Series_AT_Commands_Manual_V1.0.0_Preli.pdf new file mode 100644 index 0000000..1824e67 Binary files /dev/null and b/RM520N-5G/RM520N系列资料/Quectel_RG520N&RG52xF&RG530F&RM520N&RM530N_Series_AT_Commands_Manual_V1.0.0_Preli.pdf differ diff --git a/RM520N-5G/文件/binaries/quectel-CM b/RM520N-5G/文件/binaries/quectel-CM new file mode 100644 index 0000000..3e3e344 Binary files /dev/null and b/RM520N-5G/文件/binaries/quectel-CM differ diff --git a/RM520N-5G/文件/binaries/quectel-atc-proxy b/RM520N-5G/文件/binaries/quectel-atc-proxy new file mode 100644 index 0000000..9333e63 Binary files /dev/null and b/RM520N-5G/文件/binaries/quectel-atc-proxy differ diff --git a/RM520N-5G/文件/binaries/quectel-mbim-proxy b/RM520N-5G/文件/binaries/quectel-mbim-proxy new file mode 100644 index 0000000..8d54ff7 Binary files /dev/null and b/RM520N-5G/文件/binaries/quectel-mbim-proxy differ diff --git a/RM520N-5G/文件/binaries/quectel-qmi-proxy b/RM520N-5G/文件/binaries/quectel-qmi-proxy new file mode 100644 index 0000000..b7b769b Binary files /dev/null and b/RM520N-5G/文件/binaries/quectel-qmi-proxy differ diff --git a/RM520N-5G/文件/configs/90-pcie_mhi.txt b/RM520N-5G/文件/configs/90-pcie_mhi.txt new file mode 100644 index 0000000..dfc0992 --- /dev/null +++ b/RM520N-5G/文件/configs/90-pcie_mhi.txt @@ -0,0 +1 @@ +pcie_mhi mhi_mbim_enabled=1 diff --git a/RM520N-5G/文件/configs/init.d_modem5g b/RM520N-5G/文件/configs/init.d_modem5g new file mode 100644 index 0000000..1a62b89 --- /dev/null +++ b/RM520N-5G/文件/configs/init.d_modem5g @@ -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 +} diff --git a/RM520N-5G/文件/configs/modem_config.txt b/RM520N-5G/文件/configs/modem_config.txt new file mode 100644 index 0000000..2c3c00d --- /dev/null +++ b/RM520N-5G/文件/configs/modem_config.txt @@ -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' + diff --git a/RM520N-5G/文件/configs/rc.local b/RM520N-5G/文件/configs/rc.local new file mode 100644 index 0000000..6327e23 --- /dev/null +++ b/RM520N-5G/文件/configs/rc.local @@ -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 diff --git a/RM520N-5G/文件/logs/quectel_cm_success_log.txt b/RM520N-5G/文件/logs/quectel_cm_success_log.txt new file mode 100644 index 0000000..b3440cd --- /dev/null +++ b/RM520N-5G/文件/logs/quectel_cm_success_log.txt @@ -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 \ No newline at end of file diff --git a/RM520N-5G/文件/logs/官方参考_pcie_mhi_mbim.txt b/RM520N-5G/文件/logs/官方参考_pcie_mhi_mbim.txt new file mode 100644 index 0000000..7a988a9 --- /dev/null +++ b/RM520N-5G/文件/logs/官方参考_pcie_mhi_mbim.txt @@ -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 diff --git a/RM520N-5G/文件/source/CMakeLists.txt b/RM520N-5G/文件/source/CMakeLists.txt new file mode 100644 index 0000000..4df0bdb --- /dev/null +++ b/RM520N-5G/文件/source/CMakeLists.txt @@ -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) diff --git a/RM520N-5G/文件/source/GobiNetCM.c b/RM520N-5G/文件/source/GobiNetCM.c new file mode 100644 index 0000000..80add0e --- /dev/null +++ b/RM520N-5G/文件/source/GobiNetCM.c @@ -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 +#include +#include +#include +#include +#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 + diff --git a/RM520N-5G/文件/source/NOTICE b/RM520N-5G/文件/source/NOTICE new file mode 100644 index 0000000..898aac4 --- /dev/null +++ b/RM520N-5G/文件/source/NOTICE @@ -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. diff --git a/RM520N-5G/文件/source/QCQCTL.h b/RM520N-5G/文件/source/QCQCTL.h new file mode 100644 index 0000000..8a8eb80 --- /dev/null +++ b/RM520N-5G/文件/source/QCQCTL.h @@ -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 \ No newline at end of file diff --git a/RM520N-5G/文件/source/QCQMI.h b/RM520N-5G/文件/source/QCQMI.h new file mode 100644 index 0000000..ec01905 --- /dev/null +++ b/RM520N-5G/文件/source/QCQMI.h @@ -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 diff --git a/RM520N-5G/文件/source/QCQMUX.c b/RM520N-5G/文件/source/QCQMUX.c new file mode 100644 index 0000000..2cefc7f --- /dev/null +++ b/RM520N-5G/文件/source/QCQMUX.c @@ -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); +} diff --git a/RM520N-5G/文件/source/QCQMUX.h b/RM520N-5G/文件/source/QCQMUX.h new file mode 100644 index 0000000..6f5a77d --- /dev/null +++ b/RM520N-5G/文件/source/QCQMUX.h @@ -0,0 +1,4332 @@ +/* + 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 QCQMUX_H +#define QCQMUX_H + +#include "QCQMI.h" + +#pragma pack(push, 1) + +#define QMIWDS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWDS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWDS_EVENT_REPORT_IND 0x0001 +#define QMIWDS_START_NETWORK_INTERFACE_REQ 0x0020 +#define QMIWDS_START_NETWORK_INTERFACE_RESP 0x0020 +#define QMIWDS_STOP_NETWORK_INTERFACE_REQ 0x0021 +#define QMIWDS_STOP_NETWORK_INTERFACE_RESP 0x0021 +#define QMIWDS_GET_PKT_SRVC_STATUS_REQ 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_RESP 0x0022 +#define QMIWDS_GET_PKT_SRVC_STATUS_IND 0x0022 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ 0x0023 +#define QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP 0x0023 +#define QMIWDS_GET_PKT_STATISTICS_REQ 0x0024 +#define QMIWDS_GET_PKT_STATISTICS_RESP 0x0024 +#define QMIWDS_CREATE_PROFILE_REQ 0x0027 +#define QMIWDS_CREATE_PROFILE_RESP 0x0027 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_REQ 0x0028 +#define QMIWDS_MODIFY_PROFILE_SETTINGS_RESP 0x0028 +#define QMIWDS_GET_PROFILE_LIST_REQ 0x002A +#define QMIWDS_GET_PROFILE_LIST_RESP 0x002A +#define QMIWDS_GET_PROFILE_SETTINGS_REQ 0x002B +#define QMIWDS_GET_PROFILE_SETTINGS_RESP 0x002B +#define QMIWDS_GET_DEFAULT_SETTINGS_REQ 0x002C +#define QMIWDS_GET_DEFAULT_SETTINGS_RESP 0x002C +#define QMIWDS_GET_RUNTIME_SETTINGS_REQ 0x002D +#define QMIWDS_GET_RUNTIME_SETTINGS_RESP 0x002D +#define QMIWDS_GET_MIP_MODE_REQ 0x002F +#define QMIWDS_GET_MIP_MODE_RESP 0x002F +#define QMIWDS_GET_DATA_BEARER_REQ 0x0037 +#define QMIWDS_GET_DATA_BEARER_RESP 0x0037 +#define QMIWDS_DUN_CALL_INFO_REQ 0x0038 +#define QMIWDS_DUN_CALL_INFO_RESP 0x0038 +#define QMIWDS_DUN_CALL_INFO_IND 0x0038 +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ 0x004D +#define QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP 0x004D +#define QMIWDS_SET_AUTO_CONNECT_REQ 0x0051 +#define QMIWDS_SET_AUTO_CONNECT_RESP 0x0051 +#define QMIWDS_BIND_MUX_DATA_PORT_REQ 0x00A2 +#define QMIWDS_BIND_MUX_DATA_PORT_RESP 0x00A2 + + +// Stats masks +#define QWDS_STAT_MASK_TX_PKT_OK 0x00000001 +#define QWDS_STAT_MASK_RX_PKT_OK 0x00000002 +#define QWDS_STAT_MASK_TX_PKT_ER 0x00000004 +#define QWDS_STAT_MASK_RX_PKT_ER 0x00000008 +#define QWDS_STAT_MASK_TX_PKT_OF 0x00000010 +#define QWDS_STAT_MASK_RX_PKT_OF 0x00000020 + +// TLV Types for xfer statistics +#define TLV_WDS_TX_GOOD_PKTS 0x10 +#define TLV_WDS_RX_GOOD_PKTS 0x11 +#define TLV_WDS_TX_ERROR 0x12 +#define TLV_WDS_RX_ERROR 0x13 +#define TLV_WDS_TX_OVERFLOW 0x14 +#define TLV_WDS_RX_OVERFLOW 0x15 +#define TLV_WDS_CHANNEL_RATE 0x16 +#define TLV_WDS_DATA_BEARER 0x17 +#define TLV_WDS_DORMANCY_STATUS 0x18 + +#define QWDS_PKT_DATA_UNKNOW 0x00 +#define QWDS_PKT_DATA_DISCONNECTED 0x01 +#define QWDS_PKT_DATA_CONNECTED 0x02 +#define QWDS_PKT_DATA_SUSPENDED 0x03 +#define QWDS_PKT_DATA_AUTHENTICATING 0x04 + +#define QMIWDS_ADMIN_SET_DATA_FORMAT_REQ 0x0020 +#define QMIWDS_ADMIN_SET_DATA_FORMAT_RESP 0x0020 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_REQ 0x0021 +#define QMIWDS_ADMIN_GET_DATA_FORMAT_RESP 0x0021 +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_REQ 0x002B +#define QMIWDS_ADMIN_SET_QMAP_SETTINGS_RESP 0x002B +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_REQ 0x002C +#define QMIWDS_ADMIN_GET_QMAP_SETTINGS_RESP 0x002C +#define QMI_WDA_SET_LOOPBACK_CONFIG_REQ 0x002F +#define QMI_WDA_SET_LOOPBACK_CONFIG_RESP 0x002F +#define QMI_WDA_SET_LOOPBACK_CONFIG_IND 0x002F + +#define NETWORK_DESC_ENCODING_OCTET 0x00 +#define NETWORK_DESC_ENCODING_EXTPROTOCOL 0x01 +#define NETWORK_DESC_ENCODING_7BITASCII 0x02 +#define NETWORK_DESC_ENCODING_IA5 0x03 +#define NETWORK_DESC_ENCODING_UNICODE 0x04 +#define NETWORK_DESC_ENCODING_SHIFTJIS 0x05 +#define NETWORK_DESC_ENCODING_KOREAN 0x06 +#define NETWORK_DESC_ENCODING_LATINH 0x07 +#define NETWORK_DESC_ENCODING_LATIN 0x08 +#define NETWORK_DESC_ENCODING_GSM7BIT 0x09 +#define NETWORK_DESC_ENCODING_GSMDATA 0x0A +#define NETWORK_DESC_ENCODING_UNKNOWN 0xFF + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT +{ + USHORT Type; // QMUX type 0x0000 + USHORT Length; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT, *PQMIWDS_ADMIN_SET_DATA_FORMAT; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR QOSSetting; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG Value; +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_TLV, *PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV; + +typedef struct _QMIWDS_ENDPOINT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; +} __attribute__ ((packed)) QMIWDS_ENDPOINT_TLV, *PQMIWDS_ENDPOINT_TLV; + +typedef struct _QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG +{ + USHORT Type; + USHORT Length; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS QosDataFormatTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UnderlyingLinkLayerProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationProtocolTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DownlinkDataAggregationMaxSizeTlv; + QMIWDS_ENDPOINT_TLV epTlv; +#ifdef QUECTEL_UL_DATA_AGG + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV DlMinimumPassingTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxDatagramsTlv; + QMIWDS_ADMIN_SET_DATA_FORMAT_TLV UplinkDataAggregationMaxSizeTlv; +#endif +} __attribute__ ((packed)) QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG, *PQMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG; + +typedef struct _QMI_U8_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TLVVaule; +} __attribute__ ((packed)) QMI_U8_TLV, *PQMI_U8_TLV; + +typedef struct _QMI_U32_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG TLVVaule; +} __attribute__ ((packed)) QMI_U32_TLV, *PQMI_U32_TLV; + +typedef struct _QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG { + USHORT Type; + USHORT Length; + QMI_U8_TLV loopback_state; //0x01 + QMI_U32_TLV replication_factor; //0x10 +} __attribute__ ((packed)) QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG, *PQMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG; + +typedef struct _QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG +{ + USHORT Type; + USHORT Length; + QMI_U8_TLV loopback_state; //0x01 + QMI_U32_TLV replication_factor; //0x10 +} __attribute__ ((packed)) QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG, *PQMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG; + +#if 0 +typedef enum _QMI_RETURN_CODES { + QMI_SUCCESS = 0, + QMI_SUCCESS_NOT_COMPLETE, + QMI_FAILURE +}QMI_RETURN_CODES; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG +{ + USHORT Type; // 0x0022 + USHORT Length; // 0x0000 +} QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLVType2; + USHORT TLVLength2; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + // 0x04: QWDS_PKT_DATA_AUTHENTICATING +} QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG; + +typedef struct _QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; // 0x01: QWDS_PKT_DATAC_DISCONNECTED + // 0x02: QWDS_PKT_DATA_CONNECTED + // 0x03: QWDS_PKT_DATA_SUSPENDED + UCHAR ReconfigRequired; // 0x00: No need to reconfigure + // 0x01: Reconfiguration required +} QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG, *PQMIWDS_GET_PKT_SRVC_STATUS_IND_MSG; + +typedef struct _WDS_PKT_SRVC_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} WDS_PKT_SRVC_IP_FAMILY_TLV, *PWDS_PKT_SRVC_IP_FAMILY_TLV; + +typedef struct _QMIWDS_DUN_CALL_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Mask; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR ReportConnectionStatus; +} QMIWDS_DUN_CALL_INFO_REQ_MSG, *PQMIWDS_DUN_CALL_INFO_REQ_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWDS_DUN_CALL_INFO_RESP_MSG, *PQMIWDS_DUN_CALL_INFO_RESP_MSG; + +typedef struct _QMIWDS_DUN_CALL_INFO_IND_MSG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; +} QMIWDS_DUN_CALL_INFO_IND_MSG, *PQMIWDS_DUN_CALL_INFO_IND_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG; + +typedef struct _QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + 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 + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 16 + //ULONG CallHandle; // Context corresponding to reported channel + ULONG CurrentTxRate; // bps + ULONG CurrentRxRate; // bps + ULONG ServingSystemTxRate; // bps + ULONG ServingSystemRxRate; // bps + +} QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG, *PQMIWDS_GET_CURRENT_CHANNEL_RATE_RESP; + +#define QWDS_EVENT_REPORT_MASK_RATES 0x01 +#define QWDS_EVENT_REPORT_MASK_STATS 0x02 + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0042 + USHORT Length; + + UCHAR TLVType; // 0x10 -- current channel rate indicator + USHORT TLVLength; // 1 + UCHAR Mode; // 0-do not report; 1-report when rate changes + + UCHAR TLV2Type; // 0x11 + USHORT TLV2Length; // 5 + UCHAR StatsPeriod; // seconds between reports; 0-do not report + ULONG StatsMask; // + + UCHAR TLV3Type; // 0x12 -- current data bearer indicator + USHORT TLV3Length; // 1 + UCHAR Mode3; // 0-do not report; 1-report when changes + + UCHAR TLV4Type; // 0x13 -- dormancy status indicator + USHORT TLV4Length; // 1 + UCHAR DormancyStatus; // 0-do not report; 1-report when changes +} QMIWDS_SET_EVENT_REPORT_REQ_MSG, *PQMIWDS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWDS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0042 + 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_NO_BATTERY + // QMI_ERR_FAULT +} QMIWDS_SET_EVENT_REPORT_RESP_MSG, *PQMIWDS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWDS_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; +} QMIWDS_EVENT_REPORT_IND_MSG, *PQMIWDS_EVENT_REPORT_IND_MSG; + +// PQCTLV_PKT_STATISTICS + +typedef struct _QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV +{ + UCHAR Type; + USHORT Length; // 8 + ULONG TxRate; + ULONG RxRate; +} QMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV, *PQMIWDS_EVENT_REPORT_IND_CHAN_RATE_TLV; + +#ifdef QCUSB_MUX_PROTOCOL +#error code not present +#endif // QCUSB_MUX_PROTOCOL + +typedef struct _QMIWDS_GET_PKT_STATISTICS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 4 + ULONG StateMask; // 0x00000001 tx success packets + // 0x00000002 rx success packets + // 0x00000004 rx packet errors (checksum) + // 0x00000008 rx packets dropped (memory) + +} QMIWDS_GET_PKT_STATISTICS_REQ_MSG, *PQMIWDS_GET_PKT_STATISTICS_REQ_MSG; + +typedef struct _QMIWDS_GET_PKT_STATISTICS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0041 + USHORT Length; + UCHAR TLVType; // 0x02 + 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 +} QMIWDS_GET_PKT_STATISTICS_RESP_MSG, *PQMIWDS_GET_PKT_STATISTICS_RESP_MSG; + +// optional TLV for stats +typedef struct _QCTLV_PKT_STATISTICS +{ + UCHAR TLVType; // see above definitions for TLV types + USHORT TLVLength; // 4 + ULONG Count; +} QCTLV_PKT_STATISTICS, *PQCTLV_PKT_STATISTICS; +#endif + +//#ifdef QC_IP_MODE + +/* + \E2\80?Bit 0 \E2\80?Profile identifier + \E2\80?Bit 1 \E2\80?Profile name + \E2\80?Bit 2 \E2\80?PDP type + \E2\80?Bit 3 \E2\80?APN name + \E2\80?Bit 4 \E2\80?DNS address + \E2\80?Bit 5 \E2\80?UMTS/GPRS granted QoS + \E2\80?Bit 6 \E2\80?Username + \E2\80?Bit 7 \E2\80?Authentication Protocol + \E2\80?Bit 8 \E2\80?IP address + \E2\80?Bit 9 \E2\80?Gateway information (address and subnet mask) + \E2\80?Bit 10 \E2\80?PCSCF address using a PCO flag + \E2\80?Bit 11 \E2\80?PCSCF server address list + \E2\80?Bit 12 \E2\80?PCSCF domain name list + \E2\80?Bit 13 \E2\80?MTU + \E2\80?Bit 14 \E2\80?Domain name list + \E2\80?Bit 15 \E2\80?IP family + \E2\80?Bit 16 \E2\80?IM_CM flag + \E2\80?Bit 17 \E2\80?Technology name + \E2\80?Bit 18 \E2\80?Operator reserved PCO +*/ +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR (1 << 4) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR (1 << 8) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR (1 << 9) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU (1 << 13) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR (1 << 11) +#define QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME (1 << 14) + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_REQ + USHORT Length; + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 0x0004 + ULONG Mask; // mask, bit 8: IP addr -- 0x0100 +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MuxId; + UCHAR TLV3Type; + USHORT TLV3Length; + ULONG client_type; +} __attribute__ ((packed)) QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG, *PQMIWDS_BIND_MUX_DATA_PORT_REQ_MSG; + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS 0x15 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS 0x16 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 0x1E +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY 0x20 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET 0x21 + +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 0x25 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY 0x26 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS 0x27 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS 0x28 +#define QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU 0x29 + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU + USHORT TLVLength; // 4 + ULONG Mtu; // MTU +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4 + USHORT TLVLength; // 4 + ULONG IPV4Address; // address +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR +{ + UCHAR TLVType; // QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6 + USHORT TLVLength; // 16 + UCHAR IPV6Address[16]; // address + UCHAR PrefixLength; // prefix length +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR, *PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PCSCFNumber; +} __attribute__ ((packed)) QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR, *PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR; + +typedef struct _QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PCSCFNumber; +} __attribute__ ((packed)) QMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR, *PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR; + +typedef struct _QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG +{ + USHORT Type; // QMIWDS_GET_RUNTIME_SETTINGS_RESP + USHORT Length; + UCHAR TLVType; // QCTLV_TYPE_RESULT_CODE + USHORT TLVLength; // 0x0004 + USHORT QMUXResult; // result code + USHORT QMUXError; // error code +} __attribute__ ((packed)) QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG, *PQMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG; + +//#endif // QC_IP_MODE + +typedef struct _QMIWDS_IP_FAMILY_TLV +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 1 + UCHAR IpFamily; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_IP_FAMILY_TLV, *PQMIWDS_IP_FAMILY_TLV; + +typedef struct _QMIWDS_PKT_SRVC_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ConnectionStatus; + UCHAR ReconfigReqd; +} __attribute__ ((packed)) QMIWDS_PKT_SRVC_TLV, *PQMIWDS_PKT_SRVC_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReason; +} __attribute__ ((packed)) QMIWDS_CALL_END_REASON_TLV, *PQMIWDS_CALL_END_REASON_TLV; + +typedef struct _QMIWDS_CALL_END_REASON_V_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CallEndReasonType; + USHORT CallEndReason; +} __attribute__ ((packed)) QMIWDS_CALL_END_REASON_V_TLV, *PQMIWDS_CALL_END_REASON_V_TLV; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x004D + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR IpPreference; // IPV4-0x04, IPV6-0x06 +} __attribute__ ((packed)) QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG; + +typedef struct _QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS, QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL, QMI_ERR_MALFORMED_MSG, QMI_ERR_INVALID_ARG +} __attribute__ ((packed)) QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG, *PQMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG; + +typedef struct _QMIWDS_SET_AUTO_CONNECT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0051 + USHORT Length; + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR autoconnect_setting; // 0x00 ?C Disabled, 0x01 ?C Enabled, 0x02 ?C Paused (resume on power cycle) +} __attribute__ ((packed)) QMIWDS_SET_AUTO_CONNECT_REQ_MSG, *PQMIWDS_SET_AUTO_CONNECT_REQ_MSG; + +#if 0 +typedef struct _QMIWDS_GET_MIP_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; +} QMIWDS_GET_MIP_MODE_REQ_MSG, *PQMIWDS_GET_MIP_MODE_REQ_MSG; + +typedef struct _QMIWDS_GET_MIP_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + 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 + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + UCHAR MipMode; // +} QMIWDS_GET_MIP_MODE_RESP_MSG, *PQMIWDS_GET_MIP_MODE_RESP_MSG; +#endif + +typedef struct _QMIWDS_TECHNOLOGY_PREFERECE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TechPreference; +} __attribute__ ((packed)) QMIWDS_TECHNOLOGY_PREFERECE, *PQMIWDS_TECHNOLOGY_PREFERECE; + +typedef struct _QMIWDS_PROFILE_IDENTIFIER +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_PROFILE_IDENTIFIER, *PQMIWDS_PROFILE_IDENTIFIER; + +typedef struct _QMIWDS_BRINGUP_APNTYPE +{ + UCHAR TLVType; + USHORT TLVLength; + BOOL bring_up_by_apn_type; +} __attribute__ ((packed)) QMIWDS_BRINGUP_APNTYPE, *PQMIWDS_BRINGUP_APNTYPE; + + +typedef struct _QMIWDS_BRINGUP_APNNAME +{ + UCHAR TLVType; + USHORT TLVLength; + BOOL bring_up_by_apn_name; +} __attribute__ ((packed)) QMIWDS_BRINGUP_APNNAME, *PQMIWDS_BRINGUP_APNNAME; + +typedef struct _PQMIWDS_APN_TYPE +{ + UCHAR TLVType; + USHORT TLVLength; + int apn_type; +} __attribute__ ((packed)) QMIWDS_APN_TYPE, *PQMIWDS_APN_TYPE; + +#if 0 +typedef struct _QMIWDS_IPADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG IPv4Address; +}QMIWDS_IPADDRESS, *PQMIWDS_IPADDRESS; + +/* +typedef struct _QMIWDS_UMTS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TrafficClass; + ULONG MaxUplinkBitRate; + ULONG MaxDownlinkBitRate; + ULONG GuarUplinkBitRate; + ULONG GuarDownlinkBitRate; + UCHAR QOSDevOrder; + ULONG MAXSDUSize; + UCHAR SDUErrorRatio; + UCHAR ResidualBerRatio; + UCHAR DeliveryErrorSDUs; + ULONG TransferDelay; + ULONG TrafficHndPri; +}QMIWDS_UMTS_QOS, *PQMIWDS_UMTS_QOS; + +typedef struct _QMIWDS_GPRS_QOS +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG PrecedenceClass; + ULONG DelayClass; + ULONG ReliabilityClass; + ULONG PeekThroClass; + ULONG MeanThroClass; +}QMIWDS_GPRS_QOS, *PQMIWDS_GPRS_QOS; +*/ +#endif + +typedef struct _QMIWDS_PDPCONTEXT +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR pdp_context; +} __attribute__ ((packed)) QMIWDS_PDPCONTEXT, *PQMIWDS_PDPCONTEXT; + +typedef struct _QMIWDS_PROFILELIST +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileList[1024]; +} __attribute__ ((packed)) QMIWDS_PROFILELIST, *PQMIWDS_PROFILELIST; + +typedef struct _QMIWDS_PROFILENAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileName; +} __attribute__ ((packed)) QMIWDS_PROFILENAME, *PQMIWDS_PROFILENAME; + +typedef struct _QMIWDS_PDPTYPE +{ + UCHAR TLVType; + USHORT TLVLength; +// 0 ?C PDP-IP (IPv4) +// 1 ?C PDP-PPP +// 2 ?C PDP-IPv6 +// 3 ?C PDP-IPv4v6 + UCHAR PdpType; +} __attribute__ ((packed)) QMIWDS_PDPTYPE, *PQMIWDS_PDPTYPE; + +typedef struct _QMIWDS_USERNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UserName; +} __attribute__ ((packed)) QMIWDS_USERNAME, *PQMIWDS_USERNAME; + +typedef struct _QMIWDS_PD +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR Pd; +} __attribute__ ((packed)) QMIWDS_PD, *PQMIWDS_PD; + +typedef struct _QMIWDS_AUTH_PREFERENCE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AuthPreference; +} __attribute__ ((packed)) QMIWDS_AUTH_PREFERENCE, *PQMIWDS_AUTH_PREFERENCE; + +typedef struct _QMIWDS_IPTYPE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR IPType; +} __attribute__ ((packed)) QMIWDS_IPTYPE, *PQMIWDS_IPTYPE; + +typedef struct _QMIWDS_APNNAME +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ApnName; +} __attribute__ ((packed)) QMIWDS_APNNAME, *PQMIWDS_APNNAME; + +typedef struct _QMIWDS_AUTOCONNECT +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR AutoConnect; +} __attribute__ ((packed)) QMIWDS_AUTOCONNECT, *PQMIWDS_AUTOCONNECT; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_START_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_CALLENDREASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Reason; +}__attribute__ ((packed)) QMIWDS_CALLENDREASON, *PQMIWDS_CALLENDREASON; + +typedef struct _QMIWDS_START_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + 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 + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 20 + ULONG Handle; // +} __attribute__ ((packed)) QMIWDS_START_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_START_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + ULONG Handle; +} __attribute__ ((packed)) QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG; + +typedef struct _QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0040 + USHORT Length; + UCHAR TLVType; // 0x02 + 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)) QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG, *PQMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG, *PQMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG, *PQMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG; + +typedef struct _QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR ProfileIndex; +} __attribute__ ((packed)) QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_GET_PROFILE_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_CREATE_PROFILE_SETTINGS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileType; + UCHAR TLV2Type; //0x25 + USHORT TLV2Length; + UCHAR pdp_context; +} __attribute__ ((packed)) QMIWDS_CREATE_PROFILE_SETTINGS_REQ_MSG, *PQMIWDS_CREATE_PROFILE_SETTINGS_REQ_MSG; + +typedef struct _QMIWDS_GET_PROFILE_LIST_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIWDS_GET_PROFILE_LIST_REQ_MSG, *PQMIWDS_GET_PROFILE_LIST_REQ_MSG; + +typedef struct _QMIWDS_GET_PROFILE_LIST_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ProfileList[1024]; +} __attribute__ ((packed)) QMIWDS_GET_PROFILE_LIST_RESP_MSG, *PQMIWDS_GET_PROFILE_LIST_RESP_MSG; + +#if 0 +typedef struct _QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DataBearer; +} QMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV, *PQMIWDS_EVENT_REPORT_IND_DATA_BEARER_TLV; + +typedef struct _QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV +{ + UCHAR Type; + USHORT Length; + UCHAR DormancyStatus; +} QMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV, *PQMIWDS_EVENT_REPORT_IND_DORMANCY_STATUS_TLV; + + +typedef struct _QMIWDS_GET_DATA_BEARER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; +} QMIWDS_GET_DATA_BEARER_REQ_MSG, *PQMIWDS_GET_DATA_BEARER_REQ_MSG; + +typedef struct _QMIWDS_GET_DATA_BEARER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0037 + USHORT Length; + UCHAR TLVType; // 0x02 + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + // QMI_ERR_OUT_OF_CALL + // QMI_ERR_INFO_UNAVAILABLE + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // + UCHAR Technology; // +} QMIWDS_GET_DATA_BEARER_RESP_MSG, *PQMIWDS_GET_DATA_BEARER_RESP_MSG; +#endif + +// ======================= DMS ============================== +#define QMIDMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIDMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIDMS_EVENT_REPORT_IND 0x0001 +#define QMIDMS_GET_DEVICE_CAP_REQ 0x0020 +#define QMIDMS_GET_DEVICE_CAP_RESP 0x0020 +#define QMIDMS_GET_DEVICE_MFR_REQ 0x0021 +#define QMIDMS_GET_DEVICE_MFR_RESP 0x0021 +#define QMIDMS_GET_DEVICE_MODEL_ID_REQ 0x0022 +#define QMIDMS_GET_DEVICE_MODEL_ID_RESP 0x0022 +#define QMIDMS_GET_DEVICE_REV_ID_REQ 0x0023 +#define QMIDMS_GET_DEVICE_REV_ID_RESP 0x0023 +#define QMIDMS_GET_MSISDN_REQ 0x0024 +#define QMIDMS_GET_MSISDN_RESP 0x0024 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ 0x0025 +#define QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP 0x0025 +#define QMIDMS_UIM_SET_PIN_PROTECTION_REQ 0x0027 +#define QMIDMS_UIM_SET_PIN_PROTECTION_RESP 0x0027 +#define QMIDMS_UIM_VERIFY_PIN_REQ 0x0028 +#define QMIDMS_UIM_VERIFY_PIN_RESP 0x0028 +#define QMIDMS_UIM_UNBLOCK_PIN_REQ 0x0029 +#define QMIDMS_UIM_UNBLOCK_PIN_RESP 0x0029 +#define QMIDMS_UIM_CHANGE_PIN_REQ 0x002A +#define QMIDMS_UIM_CHANGE_PIN_RESP 0x002A +#define QMIDMS_UIM_GET_PIN_STATUS_REQ 0x002B +#define QMIDMS_UIM_GET_PIN_STATUS_RESP 0x002B +#define QMIDMS_GET_DEVICE_HARDWARE_REV_REQ 0x002C +#define QMIDMS_GET_DEVICE_HARDWARE_REV_RESP 0x002C +#define QMIDMS_GET_OPERATING_MODE_REQ 0x002D +#define QMIDMS_GET_OPERATING_MODE_RESP 0x002D +#define QMIDMS_SET_OPERATING_MODE_REQ 0x002E +#define QMIDMS_SET_OPERATING_MODE_RESP 0x002E +#define QMIDMS_GET_ACTIVATED_STATUS_REQ 0x0031 +#define QMIDMS_GET_ACTIVATED_STATUS_RESP 0x0031 +#define QMIDMS_ACTIVATE_AUTOMATIC_REQ 0x0032 +#define QMIDMS_ACTIVATE_AUTOMATIC_RESP 0x0032 +#define QMIDMS_ACTIVATE_MANUAL_REQ 0x0033 +#define QMIDMS_ACTIVATE_MANUAL_RESP 0x0033 +#define QMIDMS_UIM_GET_ICCID_REQ 0x003C +#define QMIDMS_UIM_GET_ICCID_RESP 0x003C +#define QMIDMS_UIM_GET_CK_STATUS_REQ 0x0040 +#define QMIDMS_UIM_GET_CK_STATUS_RESP 0x0040 +#define QMIDMS_UIM_SET_CK_PROTECTION_REQ 0x0041 +#define QMIDMS_UIM_SET_CK_PROTECTION_RESP 0x0041 +#define QMIDMS_UIM_UNBLOCK_CK_REQ 0x0042 +#define QMIDMS_UIM_UNBLOCK_CK_RESP 0x0042 +#define QMIDMS_UIM_GET_IMSI_REQ 0x0043 +#define QMIDMS_UIM_GET_IMSI_RESP 0x0043 +#define QMIDMS_UIM_GET_STATE_REQ 0x0044 +#define QMIDMS_UIM_GET_STATE_RESP 0x0044 +#define QMIDMS_GET_BAND_CAP_REQ 0x0045 +#define QMIDMS_GET_BAND_CAP_RESP 0x0045 + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_MFR_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIDMS_GET_DEVICE_MFR_REQ_MSG, *PQMIDMS_GET_DEVICE_MFR_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MFR_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + UCHAR DeviceManufacturer; // first byte of string +} QMIDMS_GET_DEVICE_MFR_RESP_MSG, *PQMIDMS_GET_DEVICE_MFR_RESP_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0004 + USHORT Length; +} QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0004 + 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 + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the modem id string + UCHAR DeviceModelID; // device model id +} QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG; +#endif + +typedef struct _QMIDMS_GET_DEVICE_REV_ID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0005 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_GET_DEVICE_REV_ID_REQ_MSG, *PQMIDMS_GET_DEVICE_REV_ID_REQ_MSG; + +typedef struct _DEVICE_REV_ID +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RevisionID; +} __attribute__ ((packed)) DEVICE_REV_ID, *PDEVICE_REV_ID; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_REV_ID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0023 + 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 +} QMIDMS_GET_DEVICE_REV_ID_RESP_MSG, *PQMIDMS_GET_DEVICE_REV_ID_RESP_MSG; + +typedef struct _QMIDMS_GET_MSISDN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_GET_MSISDN_REQ_MSG, *PQMIDMS_GET_MSISDN_REQ_MSG; + +typedef struct _QCTLV_DEVICE_VOICE_NUMBERS +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR VoideNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_VOICE_NUMBERS, *PQCTLV_DEVICE_VOICE_NUMBERS; + + +typedef struct _QMIDMS_GET_MSISDN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_GET_MSISDN_RESP_MSG, *PQMIDMS_GET_MSISDN_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_IMSI_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_REQ_MSG, *PQMIDMS_UIM_GET_IMSI_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_IMSI_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR IMSI; +} __attribute__ ((packed)) QMIDMS_UIM_GET_IMSI_RESP_MSG, *PQMIDMS_UIM_GET_IMSI_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0007 + USHORT Length; +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG; + +#define QCTLV_TYPE_SER_NUM_ESN 0x10 +#define QCTLV_TYPE_SER_NUM_IMEI 0x11 +#define QCTLV_TYPE_SER_NUM_MEID 0x12 + +typedef struct _QCTLV_DEVICE_SERIAL_NUMBER +{ + UCHAR TLVType; // as defined above + USHORT TLVLength; // 4/7/7 + UCHAR SerialNumberString; // ESN, IMEI, or MEID + +} QCTLV_DEVICE_SERIAL_NUMBER, *PQCTLV_DEVICE_SERIAL_NUMBER; + +typedef struct _QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0007 + 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 + // followed by optional TLV +} QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG, *PQMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP; + +typedef struct _QMIDMS_GET_DMS_BAND_CAP +{ + USHORT Type; + USHORT Length; +} QMIDMS_GET_BAND_CAP_REQ_MSG, *PQMIDMS_GET_BAND_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_BAND_CAP_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_NONE + // QMI_ERR_INTERNAL + // QMI_ERR_MALFORMED_MSG + // QMI_ERR_NO_MEMORY + + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + ULONG64 BandCap; +} QMIDMS_GET_BAND_CAP_RESP_MSG, *PQMIDMS_GET_BAND_CAP_RESP; + +typedef struct _QMIDMS_GET_DEVICE_CAP_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_DEVICE_CAP_REQ_MSG, *PQMIDMS_GET_DEVICE_CAP_REQ_MSG; + +typedef struct _QMIDMS_GET_DEVICE_CAP_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + ULONG MaxTxChannelRate; + ULONG MaxRxChannelRate; + UCHAR VoiceCap; + UCHAR SimCap; + + UCHAR RadioIfListCnt; // #elements in radio interface list + UCHAR RadioIfList; // N 1-byte elements +} QMIDMS_GET_DEVICE_CAP_RESP_MSG, *PQMIDMS_GET_DEVICE_CAP_RESP_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG, *PQMIDMS_GET_ACTIVATES_STATUD_REQ_MSG; + +typedef struct _QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + USHORT ActivatedStatus; +} QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG, *PQMIDMS_GET_ACTIVATED_STATUS_RESP_MSG; + +typedef struct _QMIDMS_GET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; +} QMIDMS_GET_OPERATING_MODE_REQ_MSG, *PQMIDMS_GET_OPERATING_MODE_REQ_MSG; + +typedef struct _OFFLINE_REASON +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT OfflineReason; +} OFFLINE_REASON, *POFFLINE_REASON; + +typedef struct _HARDWARE_RESTRICTED_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR HardwareControlledMode; +} HARDWARE_RESTRICTED_MODE, *PHARDWARE_RESTRICTED_MODE; + +typedef struct _QMIDMS_GET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT + UCHAR TLV2Type; // 0x01 + USHORT TLV2Length; // 2 + + UCHAR OperatingMode; +} QMIDMS_GET_OPERATING_MODE_RESP_MSG, *PQMIDMS_GET_OPERATING_MODE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} QMIDMS_UIM_GET_ICCID_REQ_MSG, *PQMIDMS_UIM_GET_ICCID_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_ICCID_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + 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 + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // var + UCHAR ICCID; // String of voice number +} QMIDMS_UIM_GET_ICCID_RESP_MSG, *PQMIDMS_UIM_GET_ICCID_RESP_MSG; +#endif + +typedef struct _QMIDMS_SET_OPERATING_MODE_REQ_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR OperatingMode; +} __attribute__ ((packed)) QMIDMS_SET_OPERATING_MODE_REQ_MSG, *PQMIDMS_SET_OPERATING_MODE_REQ_MSG; + +typedef struct _QMIDMS_SET_OPERATING_MODE_RESP_MSG +{ + USHORT Type; // QMUX type 0x0002 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} __attribute__ ((packed)) QMIDMS_SET_OPERATING_MODE_RESP_MSG, *PQMIDMS_SET_OPERATING_MODE_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR ActivateCodelen; + UCHAR ActivateCode; +} QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + 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 +} QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG, *PQMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG; + + +typedef struct _SPC_MSG +{ + UCHAR SPC[6]; + USHORT SID; +} SPC_MSG, *PSPC_MSG; + +typedef struct _MDN_MSG +{ + UCHAR MDNLEN; + UCHAR MDN; +} MDN_MSG, *PMDN_MSG; + +typedef struct _MIN_MSG +{ + UCHAR MINLEN; + UCHAR MIN; +} MIN_MSG, *PMIN_MSG; + +typedef struct _PRL_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + USHORT PRLLEN; + UCHAR PRL; +} PRL_MSG, *PPRL_MSG; + +typedef struct _MN_HA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_HA_KEY_LEN; + UCHAR MN_HA_KEY; +} MN_HA_KEY_MSG, *PMN_HA_KEY_MSG; + +typedef struct _MN_AAA_KEY_MSG +{ + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR MN_AAA_KEY_LEN; + UCHAR MN_AAA_KEY; +} MN_AAA_KEY_MSG, *PMN_AAA_KEY_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // + UCHAR Value; +} QMIDMS_ACTIVATE_MANUAL_REQ_MSG, *PQMIDMS_ACTIVATE_MANUAL_REQ_MSG; + +typedef struct _QMIDMS_ACTIVATE_MANUAL_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + 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 +} QMIDMS_ACTIVATE_MANUAL_RESP_MSG, *PQMIDMS_ACTIVATE_MANUAL_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_GET_STATE_REQ_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_REQ_MSG, *PQMIDMS_UIM_GET_STATE_REQ_MSG; + +typedef struct _QMIDMS_UIM_GET_STATE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR UIMState; +} __attribute__ ((packed)) QMIDMS_UIM_GET_STATE_RESP_MSG, *PQMIDMS_UIM_GET_STATE_RESP_MSG; + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_REQ_MSG; + +typedef struct _QMIDMS_UIM_PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PINStatus; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_PIN_STATUS, *PQMIDMS_UIM_PIN_STATUS; + +#define QMI_PIN_STATUS_NOT_INIT 0 +#define QMI_PIN_STATUS_NOT_VERIF 1 +#define QMI_PIN_STATUS_VERIFIED 2 +#define QMI_PIN_STATUS_DISABLED 3 +#define QMI_PIN_STATUS_BLOCKED 4 +#define QMI_PIN_STATUS_PERM_BLOCKED 5 +#define QMI_PIN_STATUS_UNBLOCKED 6 +#define QMI_PIN_STATUS_CHANGED 7 + + +typedef struct _QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + 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 + UCHAR PinStatus; +} __attribute__ ((packed)) QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_PIN_STATUS_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_GET_CK_STATUS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; +} QMIDMS_UIM_GET_CK_STATUS_REQ_MSG, *PQMIDMS_UIM_GET_CK_STATUS_REQ_MSG; + + +typedef struct _QMIDMS_UIM_CK_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR FacilityStatus; + UCHAR FacilityVerifyRetriesLeft; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_CK_STATUS, *PQMIDMS_UIM_CK_STATUS; + +typedef struct _QMIDMS_UIM_CK_OPERATION_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperationBlocking; +} QMIDMS_UIM_CK_OPERATION_STATUS, *PQMIDMS_UIM_CK_OPERATION_STATUS; + +typedef struct _QMIDMS_UIM_GET_CK_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR CkStatus; +} QMIDMS_UIM_GET_CK_STATUS_RESP_MSG, *PQMIDMS_UIM_GET_CK_STATUS_RESP_MSG; +#endif + +typedef struct _QMIDMS_UIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_REQ_MSG, *PQMIDMS_UIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIDMS_UIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + 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 + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIDMS_UIM_VERIFY_PIN_RESP_MSG, *PQMIDMS_UIM_VERIFY_PIN_RESP_MSG; + +#if 0 +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR ProtectionSetting; + UCHAR PINLen; + UCHAR PINValue; +} QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + 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 + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacilityState; + UCHAR FacliltyLen; + UCHAR FacliltyValue; +} QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG; + +typedef struct _QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityRetriesLeft; +} QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG, *PQMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG; + + +typedef struct _UIM_PIN +{ + UCHAR PinLength; + UCHAR PinValue; +} UIM_PIN, *PUIM_PIN; + +typedef struct _QMIDMS_UIM_CHANGE_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_CHANGE_PIN_REQ_MSG, *PQMIDMS_UIM_CHANGE_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_CHANGE_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + 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 + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_CHANGE_PIN_RESP_MSG, *PQMIDMS_UIM_CHANGE_PIN_RESP_MSG; + +typedef struct _UIM_PUK +{ + UCHAR PukLength; + UCHAR PukValue; +} UIM_PUK, *PUIM_PUK; + +typedef struct _QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0024 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR PINID; + UCHAR PinDetails; +} QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG, *PQMIDMS_UIM_BLOCK_PIN_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0024 + 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 + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_PIN_RESP_MSG; + +typedef struct _QMIDMS_UIM_UNBLOCK_CK_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Facility; + UCHAR FacliltyUnblockLen; + UCHAR FacliltyUnblockValue; +} QMIDMS_UIM_UNBLOCK_CK_REQ_MSG, *PQMIDMS_UIM_BLOCK_CK_REQ_MSG; + +typedef struct QMIDMS_UIM_UNBLOCK_CK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR FacilityUnblockRetriesLeft; +} QMIDMS_UIM_UNBLOCK_CK_RESP_MSG, *PQMIDMS_UIM_UNBLOCK_CK_RESP_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_SET_EVENT_REPORT_REQ_MSG, *PQMIDMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIDMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMI_RESULT_SUCCESS + // QMI_RESULT_FAILURE + USHORT QMUXError; // QMI_ERR_INVALID_ARG +} QMIDMS_SET_EVENT_REPORT_RESP_MSG, *PQMIDMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _PIN_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportPinState; +} PIN_STATUS, *PPIN_STATUS; + +typedef struct _POWER_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR PowerStatus; + UCHAR BatteryLvl; +} POWER_STATUS, *PPOWER_STATUS; + +typedef struct _ACTIVATION_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT ActivationState; +} ACTIVATION_STATE, *PACTIVATION_STATE; + +typedef struct _ACTIVATION_STATE_REQ +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ActivationState; +} ACTIVATION_STATE_REQ, *PACTIVATION_STATE_REQ; + +typedef struct _OPERATING_MODE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR OperatingMode; +} OPERATING_MODE, *POPERATING_MODE; + +typedef struct _UIM_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR UIMState; +} UIM_STATE, *PUIM_STATE; + +typedef struct _WIRELESS_DISABLE_STATE +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR WirelessDisableState; +} WIRELESS_DISABLE_STATE, *PWIRELESS_DISABLE_STATE; + +typedef struct _QMIDMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMIDMS_EVENT_REPORT_IND_MSG, *PQMIDMS_EVENT_REPORT_IND_MSG; +#endif + +// ============================ END OF DMS =============================== + +// ======================= QOS ============================== +typedef struct _MPIOC_DEV_INFO MPIOC_DEV_INFO, *PMPIOC_DEV_INFO; + +#define QMI_QOS_SET_EVENT_REPORT_REQ 0x0001 +#define QMI_QOS_SET_EVENT_REPORT_RESP 0x0001 +#define QMI_QOS_SET_EVENT_REPORT_IND 0x0001 +#define QMI_QOS_BIND_DATA_PORT_REQ 0x002B +#define QMI_QOS_BIND_DATA_PORT_RESP 0x002B +#define QMI_QOS_INDICATION_REGISTER_REQ 0x002F +#define QMI_QOS_INDICATION_REGISTER_RESP 0x002F +#define QMI_QOS_GLOBAL_QOS_FLOW_IND 0x0031 +#define QMI_QOS_GET_QOS_INFO_REQ 0x0033 +#define QMI_QOS_GET_QOS_INFO_RESP 0x0033 + + +#if 1 +typedef struct _QMI_QOS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + // UCHAR TLVType; // 0x01 - physical link state + // USHORT TLVLength; // 1 + // UCHAR PhyLinkStatusRpt; // 0-enable; 1-disable + UCHAR TLVType2; // 0x02 = global flow reporting + USHORT TLVLength2; // 1 + UCHAR GlobalFlowRpt; // 1-enable; 0-disable +} QMI_QOS_SET_EVENT_REPORT_REQ_MSG, *PQMI_QOS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMI_QOS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0010 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT QMUXResult; // QMUX_RESULT_SUCCESS + // QMUX_RESULT_FAILURE + USHORT QMUXError; // QMUX_ERR_INVALID_ARG + // QMUX_ERR_NO_MEMORY + // QMUX_ERR_INTERNAL + // QMUX_ERR_FAULT +} QMI_QOS_SET_EVENT_REPORT_RESP_MSG, *PQMI_QOS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMI_QOS_SET_EVENT_REPORT_IND_MSG +{ + USHORT Type; // QMUX type 0x0001 + USHORT Length; + UCHAR TLVs; +} QMI_QOS_SET_EVENT_REPORT_IND_MSG, *PQMI_QOS_SET_EVENT_REPORT_IND_MSG; + + +typedef struct _QMI_QOS_BIND_DATA_PORT_TLV_EP_ID +{ + UCHAR TLVType; //0x10 + USHORT TLVLength; + ULONG ep_type; + ULONG iface_id; +} __attribute__ ((packed)) QMI_QOS_BIND_DATA_PORT_TLV_EP_ID, *PQMI_QOS_BIND_DATA_PORT_TLV_EP_ID; + +typedef struct _QMI_QOS_BIND_DATA_PORT_TLV_MUX_ID +{ + UCHAR TLVType; //0x11 + USHORT TLVLength; + UCHAR mux_id; +} __attribute__ ((packed)) QMI_QOS_BIND_DATA_PORT_TLV_MUX_ID, *PQMI_QOS_BIND_DATA_PORT_TLV_MUX_ID; + +typedef struct _QMI_QOS_BIND_DATA_PORT_TLV_DATA_PORT +{ + UCHAR TLVType; //0x12 + USHORT TLVLength; + USHORT data_port; +} __attribute__ ((packed)) QMI_QOS_BIND_DATA_PORT_TLV_DATA_PORT, *PQMI_QOS_BIND_DATA_PORT_TLV_DATA_PORT; + +typedef struct _QMI_QOS_BIND_DATA_PORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + QMI_QOS_BIND_DATA_PORT_TLV_EP_ID EpIdTlv; + QMI_QOS_BIND_DATA_PORT_TLV_MUX_ID MuxIdTlv; + //QMI_QOS_BIND_DATA_PORT_TLV_DATA_PORT DataPortTlv; +} __attribute__ ((packed)) QMI_QOS_BIND_DATA_PORT_REQ_MSG, *PQMI_QOS_BIND_DATA_PORT_REQ_MSG; + +typedef struct _QMI_QOS_BIND_DATA_PORT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; //0x02 + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMI_QOS_BIND_DATA_PORT_RESP_MSG, *PQMI_QOS_BIND_DATA_PORT_RESP_MSG; + +typedef struct _QMI_QOS_INDICATION_REGISTER_TLV_REPORT_GLOBAL_QOS_FLOW +{ + UCHAR TLVType; //0x10 + USHORT TLVLength; + UCHAR report_global_qos_flows; +} __attribute__ ((packed)) QMI_QOS_INDICATION_REGISTER_TLV_REPORT_GLOBAL_QOS_FLOW, *PQMI_QOS_INDICATION_REGISTER_TLV_REPORT_GLOBAL_QOS_FLOW; + +typedef struct _QMI_QOS_INDICATION_REGISTER_TLV_SUPPRESS_REPORT_FLOW_CTL +{ + UCHAR TLVType; //0x11 + USHORT TLVLength; + UCHAR suppress_report_flow_control; +} __attribute__ ((packed)) QMI_QOS_INDICATION_REGISTER_TLV_SUPPRESS_REPORT_FLOW_CTL, *PQMI_QOS_INDICATION_REGISTER_TLV_SUPPRESS_REPORT_FLOW_CTL; + +typedef struct _QMI_QOS_INDICATION_REGISTER_TLV_SUPPRESS_NW_STATUS_IND +{ + UCHAR TLVType; //0x12 + USHORT TLVLength; + UCHAR suppress_network_status_ind; +} __attribute__ ((packed)) QMI_QOS_INDICATION_REGISTER_TLV_SUPPRESS_NW_STATUS_IND, *PQMI_QOS_INDICATION_REGISTER_TLV_SUPPRESS_NW_STATUS_IND; + +typedef struct _QMI_QOS_INDICATION_REGISTER_REQ_MSG +{ + USHORT Type; + USHORT Length; + QMI_QOS_INDICATION_REGISTER_TLV_REPORT_GLOBAL_QOS_FLOW ReportGlobalQosFlowTlv; + //QMI_QOS_INDICATION_REGISTER_TLV_SUPPRESS_REPORT_FLOW_CTL SuppressReportFlowCtlTlv; + //QMI_QOS_INDICATION_REGISTER_TLV_SUPPRESS_NW_STATUS_IND SuppressNWStatusIndTlv; +} __attribute__ ((packed)) QMI_QOS_INDICATION_REGISTER_REQ_MSG, *PQMI_QOS_INDICATION_REGISTER_REQ_MSG; + +typedef struct _QMI_QOS_INDICATION_REGISTER_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; //0x02 + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMI_QOS_INDICATION_REGISTER_RESP_MSG, *PQMI_QOS_INDICATION_REGISTER_RESP_MSG; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_STATE +{ + UCHAR TLVType; //0x01 + USHORT TLVLength; + ULONG qos_id; + UCHAR new_flow; + ULONG state_change; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_STATE, *PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_STATE; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED +{ + UCHAR TLVType; //0x10 0x11 + USHORT TLVLength; + ULONG64 flow_valid_params; + ULONG ip_flow_trf_cls; + ULONG64 data_rate_max; + ULONG64 guaranteed_rate; + ULONG peak_rate; + ULONG token_rate; + ULONG bucket_size; + ULONG ip_flow_latency; + ULONG ip_flow_jitter; + USHORT ip_flow_pkt_error_rate_multiplier; + USHORT ip_flow_pkt_error_rate_exponent; + ULONG ip_flow_min_policed_packet_size; + ULONG ip_flow_max_allowed_packet_size; + ULONG ip_flow_3gpp_residual_bit_error_rate; + ULONG ip_flow_3gpp_traffic_handling_priority; + USHORT ip_flow_3gpp2_profile_id; + UCHAR ip_flow_3gpp2_flow_priority; + UCHAR ip_flow_3gpp_im_cn_flag; + UCHAR ip_flow_3gpp_sig_ind; + ULONG ip_flow_lte_qci; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED, *PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_TLV_FILTER +{ + UCHAR TLVType; //0x12 0x13 + USHORT TLVLength; + UCHAR tx_rx_qos_filter_len; + UCHAR ip_version; + ULONG64 valid_params0; + ULONG ipv4_addr0; + ULONG subnet_mask0; + ULONG ipv4_addr1; + ULONG subnet_mask1; + UCHAR val4; + UCHAR mask4; + ULONG64 valid_params01; + UCHAR ipv6_address00; + UCHAR ipv6_address01; + UCHAR ipv6_address02; + UCHAR ipv6_address03; + UCHAR ipv6_address04; + UCHAR ipv6_address05; + UCHAR ipv6_address06; + UCHAR ipv6_address07; + UCHAR ipv6_address08; + UCHAR ipv6_address09; + UCHAR ipv6_address010; + UCHAR ipv6_address011; + UCHAR ipv6_address012; + UCHAR ipv6_address013; + UCHAR ipv6_address014; + ULONG ipv6_address015; + UCHAR prefix_len0; + UCHAR ipv6_address10; + UCHAR ipv6_address11; + UCHAR ipv6_address12; + UCHAR ipv6_address13; + UCHAR ipv6_address14; + UCHAR ipv6_address15; + UCHAR ipv6_address16; + UCHAR ipv6_address17; + UCHAR ipv6_address18; + UCHAR ipv6_address19; + UCHAR ipv6_address110; + UCHAR ipv6_address111; + UCHAR ipv6_address112; + UCHAR ipv6_address113; + UCHAR ipv6_address114; + ULONG ipv6_address115; + UCHAR prefix_len1; + UCHAR val6; + UCHAR mask6; + ULONG flow_label; + ULONG xport_protocol; + ULONG64 valid_params2; + USHORT port0; + USHORT range0; + USHORT port1; + USHORT range1; + ULONG64 valid_params3; + USHORT port2; + USHORT range2; + USHORT port3; + USHORT range3; + ULONG64 valid_params4; + UCHAR type; + UCHAR code; + ULONG64 valid_params5; + ULONG spi0; + ULONG64 valid_params6; + ULONG spi1; + USHORT filter_id; + USHORT filter_precedence; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_TLV_FILTER, *PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FILTER; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_TYPE +{ + UCHAR TLVType; //0x14 + USHORT TLVLength; + ULONG flow_type; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_TYPE, *PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_TYPE; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_TLV_BEARER_ID +{ + UCHAR TLVType; //0x15 + USHORT TLVLength; + UCHAR bearer_id; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_TLV_BEARER_ID, *PQMI_QOS_GLOBAL_QOS_FLOW_TLV_BEARER_ID; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_CTL_SEQ_NUM +{ + UCHAR TLVType; //0x16 + USHORT TLVLength; + USHORT fc_seq_num; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_CTL_SEQ_NUM, *PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_CTL_SEQ_NUM; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_TLV_5G_QCI +{ + UCHAR TLVType; //0x17 0x18 + USHORT TLVLength; + ULONG tx_rx_5g_qci; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_TLV_5G_QCI, *PQMI_QOS_GLOBAL_QOS_FLOW_TLV_5G_QCI; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_TLV_AVG_WINDOW +{ + UCHAR TLVType; //0x19 0x1A + USHORT TLVLength; + USHORT tx_rx_avg_window; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_TLV_AVG_WINDOW, *PQMI_QOS_GLOBAL_QOS_FLOW_TLV_AVG_WINDOW; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_TLV_TX_FILTER_MATCH_ALL +{ + UCHAR TLVType; //0x1B + USHORT TLVLength; + UCHAR tx_filter_match_all_len; + USHORT filter_id; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_TLV_TX_FILTER_MATCH_ALL, *PQMI_QOS_GLOBAL_QOS_FLOW_TLV_TX_FILTER_MATCH_ALL; + +typedef struct _QMI_QOS_GLOBAL_QOS_FLOW_IND_MSG +{ + USHORT Type; + USHORT Length; + QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_STATE FlowStateTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED TxFlowGrantedTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED RxFlowGrantedTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_FILTER TxFilterTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_FILTER RxFilterTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_TYPE FlowTypeTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_BEARER_ID BearerIdTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_CTL_SEQ_NUM FlowCtlSeqNumTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_5G_QCI Tx5GQciTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_5G_QCI Rx5GQciTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_AVG_WINDOW AvgWindowTlv; + //QMI_QOS_GLOBAL_QOS_FLOW_TLV_TX_FILTER_MATCH_ALL TxFilterMatchAllTlv; +} __attribute__ ((packed)) QMI_QOS_GLOBAL_QOS_FLOW_IND_MSG, *PQMI_QOS_GLOBAL_QOS_FLOW_IND_MSG; + +typedef struct _QMI_QOS_GET_QOS_INFO_TLV_QOS_ID +{ + UCHAR TLVType; //0x01 + USHORT TLVLength; + ULONG qos_id; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_TLV_QOS_ID, *PQMI_QOS_GET_QOS_INFO_TLV_QOS_ID; + +typedef struct _QMI_QOS_GET_QOS_INFO_TLV_FLOW_STATUS +{ + UCHAR TLVType; //0x10 + USHORT TLVLength; + UCHAR flow_status; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_TLV_FLOW_STATUS, *PQMI_QOS_GET_QOS_INFO_TLV_FLOW_STATUS; + +typedef struct _QMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW +{ + UCHAR TLVType; //0x11 0x12 + USHORT TLVLength; + ULONG64 flow_valid_params; + ULONG ip_flow_trf_cls; + ULONG64 data_rate_max; + ULONG64 guaranteed_rate; + ULONG peak_rate; + ULONG token_rate; + ULONG bucket_size; + ULONG ip_flow_latency; + ULONG ip_flow_jitter; + USHORT ip_flow_pkt_error_rate_multiplier; + USHORT ip_flow_pkt_error_rate_exponent; + ULONG ip_flow_min_policed_packet_size; + ULONG ip_flow_max_allowed_packet_size; + ULONG ip_flow_3gpp_residual_bit_error_rate; + ULONG ip_flow_3gpp_traffic_handling_priority; + USHORT ip_flow_3gpp2_profile_id; + UCHAR ip_flow_3gpp2_flow_priority; + UCHAR ip_flow_3gpp_im_cn_flag; + UCHAR ip_flow_3gpp_sig_ind; + ULONG ip_flow_lte_qci; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW, *PQMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW; + +typedef struct _QMI_QOS_GET_QOS_INFO_TLV_FILTER_SPECS +{ + UCHAR TLVType; //0x13 0x14 + USHORT TLVLength; + UCHAR tx_rx_qos_filter_len; + UCHAR ip_version; + ULONG64 valid_params0; + ULONG ipv4_addr0; + ULONG subnet_mask0; + ULONG ipv4_addr1; + ULONG subnet_mask1; + UCHAR val4; + UCHAR mask4; + ULONG64 valid_params01; + UCHAR ipv6_address00; + UCHAR ipv6_address01; + UCHAR ipv6_address02; + UCHAR ipv6_address03; + UCHAR ipv6_address04; + UCHAR ipv6_address05; + UCHAR ipv6_address06; + UCHAR ipv6_address07; + UCHAR ipv6_address08; + UCHAR ipv6_address09; + UCHAR ipv6_address010; + UCHAR ipv6_address011; + UCHAR ipv6_address012; + UCHAR ipv6_address013; + UCHAR ipv6_address014; + ULONG ipv6_address015; + UCHAR prefix_len0; + UCHAR ipv6_address10; + UCHAR ipv6_address11; + UCHAR ipv6_address12; + UCHAR ipv6_address13; + UCHAR ipv6_address14; + UCHAR ipv6_address15; + UCHAR ipv6_address16; + UCHAR ipv6_address17; + UCHAR ipv6_address18; + UCHAR ipv6_address19; + UCHAR ipv6_address110; + UCHAR ipv6_address111; + UCHAR ipv6_address112; + UCHAR ipv6_address113; + UCHAR ipv6_address114; + ULONG ipv6_address115; + UCHAR prefix_len1; + UCHAR val6; + UCHAR mask6; + ULONG flow_label; + ULONG xport_protocol; + ULONG64 valid_params2; + USHORT port0; + USHORT range0; + USHORT port1; + USHORT range1; + ULONG64 valid_params3; + USHORT port2; + USHORT range2; + USHORT port3; + USHORT range3; + ULONG64 valid_params4; + UCHAR type; + UCHAR code; + ULONG64 valid_params5; + ULONG spi0; + ULONG64 valid_params6; + ULONG spi1; + USHORT filter_id; + USHORT filter_precedence; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_TLV_FILTER_SPECS, *PQMI_QOS_GET_QOS_INFO_TLV_FILTER_SPECS; + +typedef struct _QMI_QOS_GET_QOS_INFO_TLV_EXT_ERROR_INFO +{ + UCHAR TLVType; //0x15 + USHORT TLVLength; + USHORT ext_error_info; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_TLV_EXT_ERROR_INFO, *PQMI_QOS_GET_QOS_INFO_TLV_EXT_ERROR_INFO; + +typedef struct _QMI_QOS_GET_QOS_INFO_TLV_5G_QCI +{ + UCHAR TLVType; //0x16 0x17 + USHORT TLVLength; + ULONG tx_rx_5g_qci; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_TLV_5G_QCI, *PQMI_QOS_GET_QOS_INFO_TLV_5G_QCI; + +typedef struct _QMI_QOS_GET_QOS_INFO_TLV_AVG_WINDOW +{ + UCHAR TLVType; //0x18 0x19 + USHORT TLVLength; + USHORT tx_rx_averaging_window; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_TLV_AVG_WINDOW, *PQMI_QOS_GET_QOS_INFO_TLV_AVG_WINDOW; + +typedef struct _QMI_QOS_GET_QOS_INFO_TLV_TX_FILTER_MATCH_ALL +{ + UCHAR TLVType; //0x1A + USHORT TLVLength; + UCHAR tx_filter_match_all_len; + USHORT filter_id; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_TLV_TX_FILTER_MATCH_ALL, *PQMI_QOS_GET_QOS_INFO_TLV_TX_FILTER_MATCH_ALL; + +typedef struct _QMI_QOS_GET_QOS_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; + QMI_QOS_GET_QOS_INFO_TLV_QOS_ID QosIdTlv; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_REQ_MSG, *PQMI_QOS_GET_QOS_INFO_REQ_MSG; + +typedef struct _QMI_QOS_GET_QOS_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; //0x02 + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + //QMI_QOS_GET_QOS_INFO_TLV_FLOW_STATUS FlowStatusTlv; + //QMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW TxGrantedFlowTlv; + //QMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW RxGrantedFlowTlv; + //QMI_QOS_GET_QOS_INFO_TLV_FILTER_SPECS TxFilterSpecsTlv; + //QMI_QOS_GET_QOS_INFO_TLV_FILTER_SPECS RxFilterSpecsTlv; + //QMI_QOS_GET_QOS_INFO_TLV_EXT_ERROR_INFO ExtErrorInfoTlv; + //QMI_QOS_GET_QOS_INFO_TLV_5G_QCI Tx5GQciTlv; + //QMI_QOS_GET_QOS_INFO_TLV_5G_QCI Rx5GQciTlv; + //QMI_QOS_GET_QOS_INFO_TLV_AVG_WINDOW TxAvgWindowTlv; + //QMI_QOS_GET_QOS_INFO_TLV_AVG_WINDOW RxAvgWindowTlv; + //QMI_QOS_GET_QOS_INFO_TLV_TX_FILTER_MATCH_ALL TxFilterMatchAllTlv; +} __attribute__ ((packed)) QMI_QOS_GET_QOS_INFO_RESP_MSG, *PQMI_QOS_GET_QOS_INFO_RESP_MSG; + +#define QOS_IND_FLOW_STATE_ACTIVATED 0x00 +#define QOS_IND_FLOW_STATE_MODIFIED 0x01 +#define QOS_IND_FLOW_STATE_DELETED 0x02 +#define QOS_IND_FLOW_STATE_SUSPENDED 0x03 +#define QOS_IND_FLOW_STATE_ENABLED 0x04 +#define QOS_IND_FLOW_STATE_DISABLED 0x05 +#define QOS_IND_FLOW_STATE_INVALID 0x06 + +#define QOS_EVENT_RPT_IND_FLOW_ACTIVATED 0x01 +#define QOS_EVENT_RPT_IND_FLOW_MODIFIED 0x02 +#define QOS_EVENT_RPT_IND_FLOW_DELETED 0x03 +#define QOS_EVENT_RPT_IND_FLOW_SUSPENDED 0x04 +#define QOS_EVENT_RPT_IND_FLOW_ENABLED 0x05 +#define QOS_EVENT_RPT_IND_FLOW_DISABLED 0x06 + +#define QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE_TYPE 0x01 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_STATE 0x10 +#define QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT_TYPE 0x10 +#define QOS_EVENT_RPT_IND_TLV_TX_FLOW_TYPE 0x11 +#define QOS_EVENT_RPT_IND_TLV_RX_FLOW_TYPE 0x12 +#define QOS_EVENT_RPT_IND_TLV_TX_FILTER_TYPE 0x13 +#define QOS_EVENT_RPT_IND_TLV_RX_FILTER_TYPE 0x14 +#define QOS_EVENT_RPT_IND_TLV_FLOW_SPEC 0x10 +#define QOS_EVENT_RPT_IND_TLV_FILTER_SPEC 0x10 + +typedef struct _QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE +{ + UCHAR TLVType; // 0x01 + USHORT TLVLength; // 1 + UCHAR PhyLinkState; // 0-dormant, 1-active +} QOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE, *PQOS_EVENT_RPT_IND_TLV_PHY_LINK_STATE; + +typedef struct _QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 6 + ULONG QosId; + UCHAR NewFlow; // 1: newly added flow; 0: existing flow + UCHAR StateChange; // 1: activated; 2: modified; 3: deleted; + // 4: suspended(delete); 5: enabled; 6: disabled +} QOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT, *PQOS_EVENT_RPT_IND_TLV_GLOBAL_FL_RPT; + +// QOS Flow + +typedef struct _QOS_EVENT_RPT_IND_TLV_FLOW +{ + UCHAR TLVType; // 0x10-TX flow; 0x11-RX flow + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_TX_FLOW, *PQOS_EVENT_RPT_IND_TLV_TX_FLOW; + +#define QOS_FLOW_TLV_IP_FLOW_IDX_TYPE 0x10 +#define QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS_TYPE 0x11 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX_TYPE 0x12 +#define QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET_TYPE 0x13 +#define QOS_FLOW_TLV_IP_FLOW_LATENCY_TYPE 0x14 +#define QOS_FLOW_TLV_IP_FLOW_JITTER_TYPE 0x15 +#define QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE_TYPE 0x16 +#define QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE_TYPE 0x17 +#define QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE_TYPE 0x18 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE_TYPE 0x19 +#define QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY_TYPE 0x1A +#define QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID_TYPE 0x1B + +typedef struct _QOS_FLOW_TLV_IP_FLOW_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFlowIndex; +} QOS_FLOW_TLV_IP_FLOW_IDX, *PQOS_FLOW_TLV_IP_FLOW_IDX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR TrafficClass; +} QOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS, *PQOS_FLOW_TLV_IP_FLOW_TRAFFIC_CLASS; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG DataRateMax; + ULONG GuaranteedRate; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_MIN_MAX; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 12 + ULONG PeakRate; + ULONG TokenRate; + ULONG BucketSize; +} QOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET, *PQOS_FLOW_TLV_IP_FLOW_DATA_RATE_TOKEN_BUCKET; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_LATENCY +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 4 + ULONG IpFlowLatency; +} QOS_FLOW_TLV_IP_FLOW_LATENCY, *PQOS_FLOW_TLV_IP_FLOW_LATENCY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_JITTER +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 4 + ULONG IpFlowJitter; +} QOS_FLOW_TLV_IP_FLOW_JITTER, *PQOS_FLOW_TLV_IP_FLOW_JITTER; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 4 + USHORT ErrRateMultiplier; + USHORT ErrRateExponent; +} QOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_PKT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 4 + ULONG MinPolicedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MIN_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE +{ + UCHAR TLVType; // 0x18 + USHORT TLVLength; // 4 + ULONG MaxAllowedPktSize; +} QOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE, *PQOS_FLOW_TLV_IP_FLOW_MAX_PKT_SIZE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 1 + UCHAR ResidualBitErrorRate; +} QOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE, *PQOS_FLOW_TLV_IP_FLOW_3GPP_BIT_ERR_RATE; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 1 + UCHAR TrafficHandlingPriority; +} QOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY, *PQOS_FLOW_TLV_IP_FLOW_3GPP_TRAF_PRIORITY; + +typedef struct _QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID +{ + UCHAR TLVType; // 0x1B + USHORT TLVLength; // 2 + USHORT ProfileId; +} QOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID, *PQOS_FLOW_TLV_IP_FLOW_3GPP2_PROFILE_ID; + +// QOS Filter + +#define QOS_FILTER_TLV_IP_FILTER_IDX_TYPE 0x10 +#define QOS_FILTER_TLV_IP_VERSION_TYPE 0x11 +#define QOS_FILTER_TLV_IPV4_SRC_ADDR_TYPE 0x12 +#define QOS_FILTER_TLV_IPV4_DEST_ADDR_TYPE 0x13 +#define QOS_FILTER_TLV_NEXT_HDR_PROTOCOL_TYPE 0x14 +#define QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE_TYPE 0x15 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TCP_TYPE 0x1B +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TCP_TYPE 0x1C +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_UDP_TYPE 0x1D +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_UDP_TYPE 0x1E +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE_TYPE 0x1F +#define QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE_TYPE 0x20 +#define QOS_FILTER_TLV_TCP_UDP_PORT_SRC_TYPE 0x24 +#define QOS_FILTER_TLV_TCP_UDP_PORT_DEST_TYPE 0x25 + +typedef struct _QOS_EVENT_RPT_IND_TLV_FILTER +{ + UCHAR TLVType; // 0x12-TX filter; 0x13-RX filter + USHORT TLVLength; // var + // embedded TLV's +} QOS_EVENT_RPT_IND_TLV_RX_FILTER, *PQOS_EVENT_RPT_IND_TLV_RX_FILTER; + +typedef struct _QOS_FILTER_TLV_IP_FILTER_IDX +{ + UCHAR TLVType; // 0x10 + USHORT TLVLength; // 1 + UCHAR IpFilterIndex; +} QOS_FILTER_TLV_IP_FILTER_IDX, *PQOS_FILTER_TLV_IP_FILTER_IDX; + +typedef struct _QOS_FILTER_TLV_IP_VERSION +{ + UCHAR TLVType; // 0x11 + USHORT TLVLength; // 1 + UCHAR IpVersion; +} QOS_FILTER_TLV_IP_VERSION, *PQOS_FILTER_TLV_IP_VERSION; + +typedef struct _QOS_FILTER_TLV_IPV4_SRC_ADDR +{ + UCHAR TLVType; // 0x12 + USHORT TLVLength; // 8 + ULONG IpSrcAddr; + ULONG IpSrcSubnetMask; +} QOS_FILTER_TLV_IPV4_SRC_ADDR, *PQOS_FILTER_TLV_IPV4_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV4_DEST_ADDR +{ + UCHAR TLVType; // 0x13 + USHORT TLVLength; // 8 + ULONG IpDestAddr; + ULONG IpDestSubnetMask; +} QOS_FILTER_TLV_IPV4_DEST_ADDR, *PQOS_FILTER_TLV_IPV4_DEST_ADDR; + +typedef struct _QOS_FILTER_TLV_NEXT_HDR_PROTOCOL +{ + UCHAR TLVType; // 0x14 + USHORT TLVLength; // 1 + UCHAR NextHdrProtocol; +} QOS_FILTER_TLV_NEXT_HDR_PROTOCOL, *PQOS_FILTER_TLV_NEXT_HDR_PROTOCOL; + +typedef struct _QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE +{ + UCHAR TLVType; // 0x15 + USHORT TLVLength; // 2 + UCHAR Ipv4TypeOfService; + UCHAR Ipv4TypeOfServiceMask; +} QOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE, *PQOS_FILTER_TLV_IPV4_TYPE_OF_SERVICE; + +typedef struct _QOS_FILTER_TLV_TCP_UDP_PORT +{ + UCHAR TLVType; // source port: 0x1B-TCP; 0x1D-UDP + // dest port: 0x1C-TCP; 0x1E-UDP + USHORT TLVLength; // 4 + USHORT FilterPort; + USHORT FilterPortRange; +} QOS_FILTER_TLV_TCP_UDP_PORT, *PQOS_FILTER_TLV_TCP_UDP_PORT; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE +{ + UCHAR TLVType; // 0x1F + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgType; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_TYPE; + +typedef struct _QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE +{ + UCHAR TLVType; // 0x20 + USHORT TLVLength; // 1 + UCHAR IcmpFilterMsgCode; +} QOS_FILTER_TLV_ICMP_FILTER_MSG_CODE, *PQOS_FILTER_TLV_ICMP_FILTER_MSG_CODE; + +#define QOS_FILTER_PRECEDENCE_INVALID 256 +#define QOS_FILTER_TLV_PRECEDENCE_TYPE 0x22 +#define QOS_FILTER_TLV_ID_TYPE 0x23 + +typedef struct _QOS_FILTER_TLV_PRECEDENCE +{ + UCHAR TLVType; // 0x22 + USHORT TLVLength; // 2 + USHORT Precedence; // precedence of the filter +} QOS_FILTER_TLV_PRECEDENCE, *PQOS_FILTER_TLV_PRECEDENCE; + +typedef struct _QOS_FILTER_TLV_ID +{ + UCHAR TLVType; // 0x23 + USHORT TLVLength; // 2 + USHORT FilterId; // filter ID +} QOS_FILTER_TLV_ID, *PQOS_FILTER_TLV_ID; + +#ifdef QCQOS_IPV6 + +#define QOS_FILTER_TLV_IPV6_SRC_ADDR_TYPE 0x16 +#define QOS_FILTER_TLV_IPV6_DEST_ADDR_TYPE 0x17 +#define QOS_FILTER_TLV_IPV6_NEXT_HDR_PROTOCOL_TYPE 0x14 // same as IPV4 +#define QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS_TYPE 0x19 +#define QOS_FILTER_TLV_IPV6_FLOW_LABEL_TYPE 0x1A + +typedef struct _QOS_FILTER_TLV_IPV6_SRC_ADDR +{ + UCHAR TLVType; // 0x16 + USHORT TLVLength; // 17 + UCHAR IpSrcAddr[16]; + UCHAR IpSrcAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_SRC_ADDR, *PQOS_FILTER_TLV_IPV6_SRC_ADDR; + +typedef struct _QOS_FILTER_TLV_IPV6_DEST_ADDR +{ + UCHAR TLVType; // 0x17 + USHORT TLVLength; // 17 + UCHAR IpDestAddr[16]; + UCHAR IpDestAddrPrefixLen; // [0..128] +} QOS_FILTER_TLV_IPV6_DEST_ADDR, *PQOS_FILTER_TLV_IPV6_DEST_ADDR; + +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_TCP 0x06 +#define QOS_FILTER_IPV6_NEXT_HDR_PROTOCOL_UDP 0x11 + +typedef struct _QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS +{ + UCHAR TLVType; // 0x19 + USHORT TLVLength; // 2 + UCHAR TrafficClass; + UCHAR TrafficClassMask; // compare the first 6 bits only +} QOS_FILTER_TLV_IPV6_TRAFFIC_CLASS, *PQOS_FILTER_TLV_IPV6_TRAFFIC_CLASS; + +typedef struct _QOS_FILTER_TLV_IPV6_FLOW_LABEL +{ + UCHAR TLVType; // 0x1A + USHORT TLVLength; // 4 + ULONG FlowLabel; +} QOS_FILTER_TLV_IPV6_FLOW_LABEL, *PQOS_FILTER_TLV_IPV6_FLOW_LABEL; + +#endif // QCQOS_IPV6 +#endif + +// ======================= WMS ============================== +#define QMIWMS_SET_EVENT_REPORT_REQ 0x0001 +#define QMIWMS_SET_EVENT_REPORT_RESP 0x0001 +#define QMIWMS_EVENT_REPORT_IND 0x0001 +#define QMIWMS_RAW_SEND_REQ 0x0020 +#define QMIWMS_RAW_SEND_RESP 0x0020 +#define QMIWMS_RAW_WRITE_REQ 0x0021 +#define QMIWMS_RAW_WRITE_RESP 0x0021 +#define QMIWMS_RAW_READ_REQ 0x0022 +#define QMIWMS_RAW_READ_RESP 0x0022 +#define QMIWMS_MODIFY_TAG_REQ 0x0023 +#define QMIWMS_MODIFY_TAG_RESP 0x0023 +#define QMIWMS_DELETE_REQ 0x0024 +#define QMIWMS_DELETE_RESP 0x0024 +#define QMIWMS_GET_MESSAGE_PROTOCOL_REQ 0x0030 +#define QMIWMS_GET_MESSAGE_PROTOCOL_RESP 0x0030 +#define QMIWMS_LIST_MESSAGES_REQ 0x0031 +#define QMIWMS_LIST_MESSAGES_RESP 0x0031 +#define QMIWMS_GET_SMSC_ADDRESS_REQ 0x0034 +#define QMIWMS_GET_SMSC_ADDRESS_RESP 0x0034 +#define QMIWMS_SET_SMSC_ADDRESS_REQ 0x0035 +#define QMIWMS_SET_SMSC_ADDRESS_RESP 0x0035 +#define QMIWMS_GET_STORE_MAX_SIZE_REQ 0x0036 +#define QMIWMS_GET_STORE_MAX_SIZE_RESP 0x0036 + + +#define WMS_MESSAGE_PROTOCOL_CDMA 0x00 +#define WMS_MESSAGE_PROTOCOL_WCDMA 0x01 + +#if 0 +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG; + +typedef struct _QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR MessageProtocol; +} QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG, *PQMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_REQ_MSG; + +typedef struct _QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG MemStoreMaxSize; +} QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG, *PQMIWMS_GET_STORE_MAX_SIZE_RESP_MSG; + +typedef struct _REQUEST_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR TagType; +} REQUEST_TAG, *PREQUEST_TAG; + +typedef struct _QMIWMS_LIST_MESSAGES_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_LIST_MESSAGES_REQ_MSG, *PQMIWMS_LIST_MESSAGES_REQ_MSG; + +typedef struct _QMIWMS_MESSAGE +{ + ULONG MessageIndex; + UCHAR TagType; +} QMIWMS_MESSAGE, *PQMIWMS_MESSAGE; + +typedef struct _QMIWMS_LIST_MESSAGES_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + ULONG NumMessages; +} QMIWMS_LIST_MESSAGES_RESP_MSG, *PQMIWMS_LIST_MESSAGES_RESP_MSG; + +typedef struct _QMIWMS_RAW_READ_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; +} QMIWMS_RAW_READ_REQ_MSG, *PQMIWMS_RAW_READ_REQ_MSG; + +typedef struct _QMIWMS_RAW_READ_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR TagType; + UCHAR Format; + USHORT MessageLength; + UCHAR Message; +} QMIWMS_RAW_READ_RESP_MSG, *PQMIWMS_RAW_READ_RESP_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG MemoryIndex; + UCHAR TagType; +} QMIWMS_MODIFY_TAG_REQ_MSG, *PQMIWMS_MODIFY_TAG_REQ_MSG; + +typedef struct _QMIWMS_MODIFY_TAG_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_MODIFY_TAG_RESP_MSG, *PQMIWMS_MODIFY_TAG_RESP_MSG; + +typedef struct _QMIWMS_RAW_SEND_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SmsFormat; + USHORT SmsLength; + UCHAR SmsMessage; +} QMIWMS_RAW_SEND_REQ_MSG, *PQMIWMS_RAW_SEND_REQ_MSG; + +typedef struct _RAW_SEND_CAUSE_CODE +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT CauseCode; +} RAW_SEND_CAUSE_CODE, *PRAW_SEND_CAUSE_CODE; + + +typedef struct _QMIWMS_RAW_SEND_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_RAW_SEND_RESP_MSG, *PQMIWMS_RAW_SEND_RESP_MSG; + + +typedef struct _WMS_DELETE_MESSAGE_INDEX +{ + UCHAR TLVType; + USHORT TLVLength; + ULONG MemoryIndex; +} WMS_DELETE_MESSAGE_INDEX, *PWMS_DELETE_MESSAGE_INDEX; + +typedef struct _WMS_DELETE_MESSAGE_TAG +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR MessageTag; +} WMS_DELETE_MESSAGE_TAG, *PWMS_DELETE_MESSAGE_TAG; + +typedef struct _QMIWMS_DELETE_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; +} QMIWMS_DELETE_REQ_MSG, *PQMIWMS_DELETE_REQ_MSG; + +typedef struct _QMIWMS_DELETE_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_DELETE_RESP_MSG, *PQMIWMS_DELETE_RESP_MSG; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMIWMS_GET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_GET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SMSC_ADDRESS +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddressType[3]; + UCHAR SMSCAddressLength; + UCHAR SMSCAddressDigits; +} QMIWMS_SMSC_ADDRESS, *PQMIWMS_SMSC_ADDRESS; + + +typedef struct _QMIWMS_GET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR SMSCAddress; +} QMIWMS_GET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_GET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR SMSCAddress; +} QMIWMS_SET_SMSC_ADDRESS_REQ_MSG, *PQMIWMS_SET_SMSC_ADDRESS_REQ_MSG; + +typedef struct _QMIWMS_SET_SMSC_ADDRESS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_SMSC_ADDRESS_RESP_MSG, *PQMIWMS_SET_SMSC_ADDRESS_RESP_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportNewMessage; +} QMIWMS_SET_EVENT_REPORT_REQ_MSG, *PQMIWMS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMIWMS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMIWMS_SET_EVENT_REPORT_RESP_MSG, *PQMIWMS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMIWMS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR StorageType; + ULONG StorageIndex; +} QMIWMS_EVENT_REPORT_IND_MSG, *PQMIWMS_EVENT_REPORT_IND_MSG; +#endif + +// ======================= End of WMS ============================== + + +// ======================= NAS ============================== +#define QMINAS_SET_EVENT_REPORT_REQ 0x0002 +#define QMINAS_SET_EVENT_REPORT_RESP 0x0002 +#define QMINAS_EVENT_REPORT_IND 0x0002 +#define QMINAS_GET_SIGNAL_STRENGTH_REQ 0x0020 +#define QMINAS_GET_SIGNAL_STRENGTH_RESP 0x0020 +#define QMINAS_PERFORM_NETWORK_SCAN_REQ 0x0021 +#define QMINAS_PERFORM_NETWORK_SCAN_RESP 0x0021 +#define QMINAS_INITIATE_NW_REGISTER_REQ 0x0022 +#define QMINAS_INITIATE_NW_REGISTER_RESP 0x0022 +#define QMINAS_INITIATE_ATTACH_REQ 0x0023 +#define QMINAS_INITIATE_ATTACH_RESP 0x0023 +#define QMINAS_GET_SERVING_SYSTEM_REQ 0x0024 +#define QMINAS_GET_SERVING_SYSTEM_RESP 0x0024 +#define QMINAS_SERVING_SYSTEM_IND 0x0024 +#define QMINAS_GET_HOME_NETWORK_REQ 0x0025 +#define QMINAS_GET_HOME_NETWORK_RESP 0x0025 +#define QMINAS_GET_PREFERRED_NETWORK_REQ 0x0026 +#define QMINAS_GET_PREFERRED_NETWORK_RESP 0x0026 +#define QMINAS_SET_PREFERRED_NETWORK_REQ 0x0027 +#define QMINAS_SET_PREFERRED_NETWORK_RESP 0x0027 +#define QMINAS_GET_FORBIDDEN_NETWORK_REQ 0x0028 +#define QMINAS_GET_FORBIDDEN_NETWORK_RESP 0x0028 +#define QMINAS_SET_FORBIDDEN_NETWORK_REQ 0x0029 +#define QMINAS_SET_FORBIDDEN_NETWORK_RESP 0x0029 +#define QMINAS_SET_TECHNOLOGY_PREF_REQ 0x002A +#define QMINAS_SET_TECHNOLOGY_PREF_RESP 0x002A +#define QMINAS_GET_RF_BAND_INFO_REQ 0x0031 +#define QMINAS_GET_RF_BAND_INFO_RESP 0x0031 +#define QMINAS_GET_CELL_LOCATION_INFO_REQ 0x0043 +#define QMINAS_GET_CELL_LOCATION_INFO_RESP 0x0043 +#define QMINAS_GET_PLMN_NAME_REQ 0x0044 +#define QMINAS_GET_PLMN_NAME_RESP 0x0044 +#define QUECTEL_PACKET_TRANSFER_START_IND 0X100 +#define QUECTEL_PACKET_TRANSFER_END_IND 0X101 +#define QMINAS_GET_SYS_INFO_REQ 0x004D +#define QMINAS_GET_SYS_INFO_RESP 0x004D +#define QMINAS_SYS_INFO_IND 0x004E +#define QMINAS_GET_SIG_INFO_REQ 0x004F +#define QMINAS_GET_SIG_INFO_RESP 0x004F + +typedef struct _QMINAS_GET_HOME_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} __attribute__ ((packed)) QMINAS_GET_HOME_NETWORK_REQ_MSG, *PQMINAS_GET_HOME_NETWORK_REQ_MSG; + +typedef struct _HOME_NETWORK_SYSTEMID +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT SystemID; + USHORT NetworkID; +} __attribute__ ((packed)) HOME_NETWORK_SYSTEMID, *PHOME_NETWORK_SYSTEMID; + +typedef struct _HOME_NETWORK +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) HOME_NETWORK, *PHOME_NETWORK; + +#if 0 +typedef struct _HOME_NETWORK_EXT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDescDisp; + UCHAR NetworkDescEncoding; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} HOME_NETWORK_EXT, *PHOME_NETWORK_EXT; + +typedef struct _QMINAS_GET_HOME_NETWORK_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} QMINAS_GET_HOME_NETWORK_RESP_MSG, *PQMINAS_GET_HOME_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_GET_PREFERRED_NETWORK_REQ_MSG; + + +typedef struct _PREFERRED_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} PREFERRED_NETWORK, *PPREFERRED_NETWORK; + +typedef struct _QMINAS_GET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumPreferredNetwork; +} QMINAS_GET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_GET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _FORBIDDEN_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} FORBIDDEN_NETWORK, *PFORBIDDEN_NETWORK; + +typedef struct _QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 + UCHAR TLV2Type; // 0x01 - required parameter + USHORT TLV2Length; // length of the mfr string + USHORT NumForbiddenNetwork; +} QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SERVING_SYSTEM_REQ_MSG, *PQMINAS_GET_SERVING_SYSTEM_REQ_MSG; + +typedef struct _QMINAS_ROAMING_INDICATOR_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR RoamingIndicator; +} QMINAS_ROAMING_INDICATOR_MSG, *PQMINAS_ROAMING_INDICATOR_MSG; +#endif + +typedef struct _QMINAS_DATA_CAP +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + UCHAR DataCapListLen; + UCHAR DataCap; +} __attribute__ ((packed)) QMINAS_DATA_CAP, *PQMINAS_DATA_CAP; + +typedef struct _QMINAS_CURRENT_PLMN_MSG +{ + UCHAR TLVType; // 0x01 - required parameter + USHORT TLVLength; // length of the mfr string + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkDesclen; + UCHAR NetworkDesc; +} __attribute__ ((packed)) QMINAS_CURRENT_PLMN_MSG, *PQMINAS_CURRENT_PLMN_MSG; + +typedef struct _QMINAS_GET_SERVING_SYSTEM_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SERVING_SYSTEM_RESP_MSG, *PQMINAS_GET_SERVING_SYSTEM_RESP_MSG; + +typedef struct _SERVING_SYSTEM +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR RegistrationState; + UCHAR CSAttachedState; + UCHAR PSAttachedState; + UCHAR RegistredNetwork; + UCHAR InUseRadioIF; + UCHAR RadioIF; +} __attribute__ ((packed)) SERVING_SYSTEM, *PSERVING_SYSTEM; + +typedef struct _QMINAS_GET_SYS_INFO_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMINAS_GET_SYS_INFO_RESP_MSG, *PQMINAS_GET_SYS_INFO_RESP_MSG; + +typedef struct _QMINAS_SYS_INFO_IND_MSG +{ + USHORT Type; + USHORT Length; +} __attribute__ ((packed)) QMINAS_SYS_INFO_IND_MSG, *PQMINAS_SYS_INFO_IND_MSG; + +typedef struct _SERVICE_STATUS_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvStatus; + UCHAR true_srv_status; + UCHAR IsPrefDataPath; +} __attribute__ ((packed)) SERVICE_STATUS_INFO, *PSERVICE_STATUS_INFO; + +typedef struct _CDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR PRevInUseValid; + UCHAR PRevInUse; + UCHAR BSPRevValid; + UCHAR BSPRev; + UCHAR CCSSupportedValid; + UCHAR CCSSupported; + UCHAR CDMASysIdValid; + USHORT SID; + USHORT NID; + UCHAR BSInfoValid; + USHORT BaseID; + ULONG BaseLAT; + ULONG BaseLONG; + UCHAR PacketZoneValid; + USHORT PacketZone; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; +} __attribute__ ((packed)) CDMA_SYSTEM_INFO, *PCDMA_SYSTEM_INFO; + +typedef struct _HDR_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR IsSysPrlMatchValid; + UCHAR IsSysPrlMatch; + UCHAR HdrPersonalityValid; + UCHAR HdrPersonality; + UCHAR HdrActiveProtValid; + UCHAR HdrActiveProt; + UCHAR is856SysIdValid; + UCHAR is856SysId[16]; +} __attribute__ ((packed)) HDR_SYSTEM_INFO, *PHDR_SYSTEM_INFO; + +typedef struct _GSM_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR EgprsSuppValid; + UCHAR EgprsSupp; + UCHAR DtmSuppValid; + UCHAR DtmSupp; +} __attribute__ ((packed)) GSM_SYSTEM_INFO, *PGSM_SYSTEM_INFO; + +typedef struct _WCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR PscValid; + UCHAR Psc; +} __attribute__ ((packed)) WCDMA_SYSTEM_INFO, *PWCDMA_SYSTEM_INFO; + +typedef struct _LTE_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR TacValid; + USHORT Tac; +} __attribute__ ((packed)) LTE_SYSTEM_INFO, *PLTE_SYSTEM_INFO; + +typedef struct _TDSCDMA_SYSTEM_INFO +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SrvDomainValid; + UCHAR SrvDomain; + UCHAR SrvCapabilityValid; + UCHAR SrvCapability; + UCHAR RoamStatusValid; + UCHAR RoamStatus; + UCHAR IsSysForbiddenValid; + UCHAR IsSysForbidden; + UCHAR LacValid; + USHORT Lac; + UCHAR CellIdValid; + ULONG CellId; + UCHAR RegRejectInfoValid; + UCHAR RejectSrvDomain; + UCHAR RejCause; + UCHAR NetworkIdValid; + UCHAR MCC[3]; + UCHAR MNC[3]; + UCHAR HsCallStatusValid; + UCHAR HsCallStatus; + UCHAR HsIndValid; + UCHAR HsInd; + UCHAR CellParameterIdValid; + USHORT CellParameterId; + UCHAR CellBroadcastCapValid; + ULONG CellBroadcastCap; + UCHAR CsBarStatusValid; + ULONG CsBarStatus; + UCHAR PsBarStatusValid; + ULONG PsBarStatus; + UCHAR CipherDomainValid; + UCHAR CipherDomain; +} __attribute__ ((packed)) TDSCDMA_SYSTEM_INFO, *PTDSCDMA_SYSTEM_INFO; + +typedef enum { + NAS_SYS_SRV_STATUS_NO_SRV_V01 = 0, + NAS_SYS_SRV_STATUS_LIMITED_V01 = 1, + NAS_SYS_SRV_STATUS_SRV_V01 = 2, + NAS_SYS_SRV_STATUS_LIMITED_REGIONAL_V01 = 3, + NAS_SYS_SRV_STATUS_PWR_SAVE_V01 = 4, +}nas_service_status_enum_type_v01; + +typedef enum { + SYS_SRV_DOMAIN_NO_SRV_V01 = 0, + SYS_SRV_DOMAIN_CS_ONLY_V01 = 1, + SYS_SRV_DOMAIN_PS_ONLY_V01 = 2, + SYS_SRV_DOMAIN_CS_PS_V01 = 3, + SYS_SRV_DOMAIN_CAMPED_V01 = 4, +}nas_service_domain_enum_type_v01; + +typedef enum { + QMI_NAS_RADIO_INTERFACE_UNKNOWN = -1, + QMI_NAS_RADIO_INTERFACE_NONE = 0x00, + QMI_NAS_RADIO_INTERFACE_CDMA_1X = 0x01, + QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO = 0x02, + QMI_NAS_RADIO_INTERFACE_AMPS = 0x03, + QMI_NAS_RADIO_INTERFACE_GSM = 0x04, + QMI_NAS_RADIO_INTERFACE_UMTS = 0x05, + QMI_NAS_RADIO_INTERFACE_LTE = 0x08, + QMI_NAS_RADIO_INTERFACE_TD_SCDMA = 0x09, + QMI_NAS_RADIO_INTERFACE_5GNR = 0x0C, +} QMI_NAS_RADIO_INTERFACE_E; + +typedef enum { + QMI_NAS_ACTIVE_BAND_BC_0 = 0, + QMI_NAS_ACTIVE_BAND_BC_1 = 1, + QMI_NAS_ACTIVE_BAND_BC_2 = 2, + QMI_NAS_ACTIVE_BAND_BC_3 = 3, + QMI_NAS_ACTIVE_BAND_BC_4 = 4, + QMI_NAS_ACTIVE_BAND_BC_5 = 5, + QMI_NAS_ACTIVE_BAND_BC_6 = 6, + QMI_NAS_ACTIVE_BAND_BC_7 = 7, + QMI_NAS_ACTIVE_BAND_BC_8 = 8, + QMI_NAS_ACTIVE_BAND_BC_9 = 9, + QMI_NAS_ACTIVE_BAND_BC_10 = 10, + QMI_NAS_ACTIVE_BAND_BC_11 = 11, + QMI_NAS_ACTIVE_BAND_BC_12 = 12, + QMI_NAS_ACTIVE_BAND_BC_13 = 13, + QMI_NAS_ACTIVE_BAND_BC_14 = 14, + QMI_NAS_ACTIVE_BAND_BC_15 = 15, + QMI_NAS_ACTIVE_BAND_BC_16 = 16, + QMI_NAS_ACTIVE_BAND_BC_17 = 17, + QMI_NAS_ACTIVE_BAND_BC_18 = 18, + QMI_NAS_ACTIVE_BAND_BC_19 = 19, + QMI_NAS_ACTIVE_BAND_GSM_450 = 40, + QMI_NAS_ACTIVE_BAND_GSM_480 = 41, + QMI_NAS_ACTIVE_BAND_GSM_750 = 42, + QMI_NAS_ACTIVE_BAND_GSM_850 = 43, + QMI_NAS_ACTIVE_BAND_GSM_900_EXTENDED = 44, + QMI_NAS_ACTIVE_BAND_GSM_900_PRIMARY = 45, + QMI_NAS_ACTIVE_BAND_GSM_900_RAILWAYS = 46, + QMI_NAS_ACTIVE_BAND_GSM_DCS_1800 = 47, + QMI_NAS_ACTIVE_BAND_GSM_PCS_1900 = 48, + QMI_NAS_ACTIVE_BAND_WCDMA_2100 = 80, + QMI_NAS_ACTIVE_BAND_WCDMA_PCS_1900 = 81, + QMI_NAS_ACTIVE_BAND_WCDMA_DCS_1800 = 82, + QMI_NAS_ACTIVE_BAND_WCDMA_1700_US = 83, + QMI_NAS_ACTIVE_BAND_WCDMA_850 = 84, + QMI_NAS_ACTIVE_BAND_WCDMA_800 = 85, + QMI_NAS_ACTIVE_BAND_WCDMA_2600 = 86, + QMI_NAS_ACTIVE_BAND_WCDMA_900 = 87, + QMI_NAS_ACTIVE_BAND_WCDMA_1700_JAPAN = 88, + QMI_NAS_ACTIVE_BAND_WCDMA_1500_JAPAN = 90, + QMI_NAS_ACTIVE_BAND_WCDMA_850_JAPAN = 91, + QMI_NAS_ACTIVE_BAND_EUTRAN_1 = 120, + QMI_NAS_ACTIVE_BAND_EUTRAN_2 = 121, + QMI_NAS_ACTIVE_BAND_EUTRAN_3 = 122, + QMI_NAS_ACTIVE_BAND_EUTRAN_4 = 123, + QMI_NAS_ACTIVE_BAND_EUTRAN_5 = 124, + QMI_NAS_ACTIVE_BAND_EUTRAN_6 = 125, + QMI_NAS_ACTIVE_BAND_EUTRAN_7 = 126, + QMI_NAS_ACTIVE_BAND_EUTRAN_8 = 127, + QMI_NAS_ACTIVE_BAND_EUTRAN_9 = 128, + QMI_NAS_ACTIVE_BAND_EUTRAN_10 = 129, + QMI_NAS_ACTIVE_BAND_EUTRAN_11 = 130, + QMI_NAS_ACTIVE_BAND_EUTRAN_12 = 131, + QMI_NAS_ACTIVE_BAND_EUTRAN_13 = 132, + QMI_NAS_ACTIVE_BAND_EUTRAN_14 = 133, + QMI_NAS_ACTIVE_BAND_EUTRAN_17 = 134, + QMI_NAS_ACTIVE_BAND_EUTRAN_18 = 143, + QMI_NAS_ACTIVE_BAND_EUTRAN_19 = 144, + QMI_NAS_ACTIVE_BAND_EUTRAN_20 = 145, + QMI_NAS_ACTIVE_BAND_EUTRAN_21 = 146, + QMI_NAS_ACTIVE_BAND_EUTRAN_23 = 152, + QMI_NAS_ACTIVE_BAND_EUTRAN_24 = 147, + QMI_NAS_ACTIVE_BAND_EUTRAN_25 = 148, + QMI_NAS_ACTIVE_BAND_EUTRAN_26 = 153, + QMI_NAS_ACTIVE_BAND_EUTRAN_27 = 164, + QMI_NAS_ACTIVE_BAND_EUTRAN_28 = 158, + QMI_NAS_ACTIVE_BAND_EUTRAN_29 = 159, + QMI_NAS_ACTIVE_BAND_EUTRAN_30 = 160, + QMI_NAS_ACTIVE_BAND_EUTRAN_31 = 165, + QMI_NAS_ACTIVE_BAND_EUTRAN_32 = 154, + QMI_NAS_ACTIVE_BAND_EUTRAN_33 = 135, + QMI_NAS_ACTIVE_BAND_EUTRAN_34 = 136, + QMI_NAS_ACTIVE_BAND_EUTRAN_35 = 137, + QMI_NAS_ACTIVE_BAND_EUTRAN_36 = 138, + QMI_NAS_ACTIVE_BAND_EUTRAN_37 = 139, + QMI_NAS_ACTIVE_BAND_EUTRAN_38 = 140, + QMI_NAS_ACTIVE_BAND_EUTRAN_39 = 141, + QMI_NAS_ACTIVE_BAND_EUTRAN_40 = 142, + QMI_NAS_ACTIVE_BAND_EUTRAN_41 = 149, + QMI_NAS_ACTIVE_BAND_EUTRAN_42 = 150, + QMI_NAS_ACTIVE_BAND_EUTRAN_43 = 151, + QMI_NAS_ACTIVE_BAND_EUTRAN_46 = 163, + QMI_NAS_ACTIVE_BAND_EUTRAN_47 = 166, + QMI_NAS_ACTIVE_BAND_EUTRAN_48 = 167, + QMI_NAS_ACTIVE_BAND_EUTRAN_66 = 161, + QMI_NAS_ACTIVE_BAND_EUTRAN_71 = 168, + QMI_NAS_ACTIVE_BAND_EUTRAN_125 = 155, + QMI_NAS_ACTIVE_BAND_EUTRAN_126 = 156, + QMI_NAS_ACTIVE_BAND_EUTRAN_127 = 157, + QMI_NAS_ACTIVE_BAND_EUTRAN_250 = 162, + QMI_NAS_ACTIVE_BAND_TDSCDMA_A = 200, + QMI_NAS_ACTIVE_BAND_TDSCDMA_B = 201, + QMI_NAS_ACTIVE_BAND_TDSCDMA_C = 202, + QMI_NAS_ACTIVE_BAND_TDSCDMA_D = 203, + QMI_NAS_ACTIVE_BAND_TDSCDMA_E = 204, + QMI_NAS_ACTIVE_BAND_TDSCDMA_F = 205, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_1 = 250, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_2 = 251, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_3 = 252, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_5 = 253, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_7 = 254, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_8 = 255, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_20 = 256, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_28 = 257, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_38 = 258, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_41 = 259, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_50 = 260, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_51 = 261, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_66 = 262, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_70 = 263, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_71 = 264, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_74 = 265, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_75 = 266, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_76 = 267, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_77 = 268, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_78 = 269, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_79 = 270, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_80 = 271, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_81 = 272, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_82 = 273, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_83 = 274, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_84 = 275, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_85 = 276, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_257= 277, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_258= 278, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_259= 279, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_260= 280, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_261= 281, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_12 = 282, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_25 = 283, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_34 = 284, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_39 = 285, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_40 = 286, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_65 = 287, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_86 = 288, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_48 = 289, + QMI_NAS_ACTIVE_BAND_NR5G_BAND_14 = 290 +} QMI_NAS_ACTIVE_BAND_E; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8_t srv_domain_valid; + uint8_t srv_domain; + uint8_t srv_capability_valid; + uint8_t srv_capability; + uint8_t roam_status_valid; + uint8_t roam_status; + uint8_t is_sys_forbidden_valid; + uint8_t is_sys_forbidden; + + uint8_t lac_valid; + uint16_t lac; + uint8_t cell_id_valid; + uint32_t cell_id; + uint8_t reg_reject_info_valid; + uint8_t reject_srv_domain; + uint8_t rej_cause; + uint8_t network_id_valid; + UCHAR MCC[3]; + UCHAR MNC[3]; + + uint8_t tac_valid; + uint16_t tac; +} __attribute__ ((packed)) NR5G_SYSTEM_INFO, *PNR5G_SYSTEM_INFO; + +#if 0 +typedef struct _QMINAS_SERVING_SYSTEM_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_SERVING_SYSTEM_IND_MSG, *PQMINAS_SERVING_SYSTEM_IND_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumPreferredNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + USHORT RadioAccess; +} QMINAS_SET_PREFERRED_NETWORK_REQ_MSG, *PQMINAS_SET_PREFERRED_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_PREFERRED_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 +} QMINAS_SET_PREFERRED_NETWORK_RESP_MSG, *PQMINAS_SET_PREFERRED_NETWORK_RESP_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT NumForbiddenNetwork; + USHORT MobileCountryCode; + USHORT MobileNetworkCode; +} QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG; + +typedef struct _QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 +} QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG, *PQMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_REQ_MSG; + +typedef struct _VISIBLE_NETWORK +{ + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR NetworkStatus; + UCHAR NetworkDesclen; +} VISIBLE_NETWORK, *PVISIBLE_NETWORK; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 +} QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG, *PQMINAS_PERFORM_NETWORK_SCAN_RESP_MSG; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO +{ + UCHAR TLVType; // 0x010 - required parameter + USHORT TLVLength; // length + USHORT NumNetworkInstances; +} QMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_NETWORK_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO +{ + UCHAR TLVType; // 0x011 - required parameter + USHORT TLVLength; // length + USHORT NumInst; +} QMINAS_PERFORM_NETWORK_SCAN_RAT_INFO, *PQMINAS_PERFORM_NETWORK_SCAN_RAT_INFO; + +typedef struct _QMINAS_PERFORM_NETWORK_SCAN_RAT +{ + USHORT MCC; + USHORT MNC; + UCHAR RAT; +} QMINAS_PERFORM_NETWORK_SCAN_RAT, *PQMINAS_PERFORM_NETWORK_SCAN_RAT; + + +typedef struct _QMINAS_MANUAL_NW_REGISTER +{ + UCHAR TLV2Type; // 0x02 - result code + USHORT TLV2Length; // 4 + USHORT MobileCountryCode; + USHORT MobileNetworkCode; + UCHAR RadioAccess; +} QMINAS_MANUAL_NW_REGISTER, *PQMINAS_MANUAL_NW_REGISTER; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + UCHAR RegisterAction; +} QMINAS_INITIATE_NW_REGISTER_REQ_MSG, *PQMINAS_INITIATE_NW_REGISTER_REQ_MSG; + +typedef struct _QMINAS_INITIATE_NW_REGISTER_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 +} QMINAS_INITIATE_NW_REGISTER_RESP_MSG, *PQMINAS_INITIATE_NW_REGISTER_RESP_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; + UCHAR TLVType; // 0x02 - result code + USHORT TLVLength; // 4 + USHORT TechPref; + UCHAR Duration; +} QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_REQ_MSG; + +typedef struct _QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 +} QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG, *PQMINAS_SET_TECHNOLOGY_PREF_RESP_MSG; + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG +{ + USHORT Type; // QMUX type 0x0003 + USHORT Length; +} QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_REQ_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH +{ + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH, *PQMINAS_SIGNAL_STRENGTH; + +typedef struct _QMINAS_SIGNAL_STRENGTH_LIST +{ + UCHAR TLV3Type; + USHORT TLV3Length; + USHORT NumInstance; +} QMINAS_SIGNAL_STRENGTH_LIST, *PQMINAS_SIGNAL_STRENGTH_LIST; + + +typedef struct _QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 + UCHAR TLV2Type; + USHORT TLV2Length; + CHAR SignalStrength; + UCHAR RadioIf; +} QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG, *PQMINAS_GET_SIGNAL_STRENGTH_RESP_MSG; + + +typedef struct _QMINAS_SET_EVENT_REPORT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR ReportSigStrength; + UCHAR NumTresholds; + CHAR TresholdList[2]; +} QMINAS_SET_EVENT_REPORT_REQ_MSG, *PQMINAS_SET_EVENT_REPORT_REQ_MSG; + +typedef struct _QMINAS_SET_EVENT_REPORT_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 +} QMINAS_SET_EVENT_REPORT_RESP_MSG, *PQMINAS_SET_EVENT_REPORT_RESP_MSG; + +typedef struct _QMINAS_SIGNAL_STRENGTH_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + CHAR SigStrength; + UCHAR RadioIf; +} QMINAS_SIGNAL_STRENGTH_TLV, *PQMINAS_SIGNAL_STRENGTH_TLV; + +typedef struct _QMINAS_REJECT_CAUSE_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR ServiceDomain; + USHORT RejectCause; +} QMINAS_REJECT_CAUSE_TLV, *PQMINAS_REJECT_CAUSE_TLV; + +typedef struct _QMINAS_EVENT_REPORT_IND_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_EVENT_REPORT_IND_MSG, *PQMINAS_EVENT_REPORT_IND_MSG; + +typedef struct _QMINAS_GET_RF_BAND_INFO_REQ_MSG +{ + USHORT Type; + USHORT Length; +} QMINAS_GET_RF_BAND_INFO_REQ_MSG, *PQMINAS_GET_RF_BAND_INFO_REQ_MSG; + +typedef struct _QMINASRF_BAND_INFO +{ + UCHAR RadioIf; + USHORT ActiveBand; + USHORT ActiveChannel; +} QMINASRF_BAND_INFO, *PQMINASRF_BAND_INFO; + +typedef struct _QMINAS_GET_RF_BAND_INFO_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR NumInstances; +} QMINAS_GET_RF_BAND_INFO_RESP_MSG, *PQMINAS_GET_RF_BAND_INFO_RESP_MSG; + + +typedef struct _QMINAS_GET_PLMN_NAME_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT MCC; + USHORT MNC; +} QMINAS_GET_PLMN_NAME_REQ_MSG, *PQMINAS_GET_PLMN_NAME_REQ_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_RESP_MSG +{ + 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 +} QMINAS_GET_PLMN_NAME_RESP_MSG, *PQMINAS_GET_PLMN_NAME_RESP_MSG; + +typedef struct _QMINAS_GET_PLMN_NAME_SPN +{ + UCHAR TLVType; + USHORT TLVLength; + UCHAR SPN_Enc; + UCHAR SPN_Len; +} QMINAS_GET_PLMN_NAME_SPN, *PQMINAS_GET_PLMN_NAME_SPN; + +typedef struct _QMINAS_GET_PLMN_NAME_PLMN +{ + UCHAR PLMN_Enc; + UCHAR PLMN_Ci; + UCHAR PLMN_SpareBits; + UCHAR PLMN_Len; +} QMINAS_GET_PLMN_NAME_PLMN, *PQMINAS_GET_PLMN_NAME_PLMN; + +typedef struct _QMINAS_INITIATE_ATTACH_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR PsAttachAction; +} QMINAS_INITIATE_ATTACH_REQ_MSG, *PQMINAS_INITIATE_ATTACH_REQ_MSG; + +typedef struct _QMINAS_INITIATE_ATTACH_RESP_MSG +{ + USHORT Type; // QMUX type 0x0003 + 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 +} QMINAS_INITIATE_ATTACH_RESP_MSG, *PQMINAS_INITIATE_ATTACH_RESP_MSG; +#endif +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + SHORT ecio; +} __attribute__ ((packed)) QMINAS_SIG_INFO_CDMA_TLV_MSG, *PQMINAS_SIG_INFO_CDMA_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + SHORT ecio; + CHAR sinr; + INT io; +} __attribute__ ((packed)) QMINAS_SIG_INFO_HDR_TLV_MSG, *PQMINAS_SIG_INFO_HDR_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; +} __attribute__ ((packed)) QMINAS_SIG_INFO_GSM_TLV_MSG, *PQMINAS_SIG_INFO_GSM_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + SHORT ecio; +} __attribute__ ((packed)) QMINAS_SIG_INFO_WCDMA_TLV_MSG, *PQMINAS_SIG_INFO_WCDMA_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rssi; + CHAR rsrq; + SHORT rsrp; + SHORT snr; +} __attribute__ ((packed)) QMINAS_SIG_INFO_LTE_TLV_MSG, *PQMINAS_SIG_INFO_LTE_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + CHAR rscp; +} __attribute__ ((packed)) QMINAS_SIG_INFO_TDSCDMA_TLV_MSG, *PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + SHORT rsrp; + SHORT snr; +} __attribute__ ((packed)) QMINAS_SIG_INFO_5G_NSA_TLV_MSG, *PQMINAS_SIG_INFO_5G_NSA_TLV_MSG; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + SHORT nr5g_rsrq; +} __attribute__ ((packed)) QMINAS_SIG_INFO_5G_SA_TLV_MSG, *PQMINAS_SIG_INFO_5G_SA_TLV_MSG; + +typedef struct { + uint8 radio_if; + uint16 active_band; + uint16 active_channel; +} __attribute__ ((packed)) NasGetRfBandInfo; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8 num_instances; + NasGetRfBandInfo bands_array[0]; +} __attribute__ ((packed)) NasGetRfBandInfoList; + +typedef struct { + uint8 radio_if; + uint16 dedicated_band; +} __attribute__ ((packed)) NasGetRfBandInfoDedicated; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8 num_instances; + NasGetRfBandInfoDedicated bands_array[0]; +} __attribute__ ((packed)) NasGetRfBandInfoDedicatedList; + +typedef struct { + uint8 radio_if; + uint16 active_band; + uint32 active_channel; +} __attribute__ ((packed)) NasGetRfBandInfoExtended; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8 num_instances; + NasGetRfBandInfoExtended bands_array[0]; +} __attribute__ ((packed)) NasGetRfBandInfoExtendedList; + +typedef struct { + uint8 radio_if; + uint32 bandwidth; +} __attribute__ ((packed)) NasGetRfBandInfoBandWidth; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8 num_instances; + NasGetRfBandInfoBandWidth bands_array[0]; +} __attribute__ ((packed)) NasGetRfBandInfoBandWidthList; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8 plmn[3]; + uint8 tac[3]; + uint64 global_cell_id; + uint16 physical_cell_id; + int16 rsrq; + int16 rsrp; + int16 snr; +} __attribute__ ((packed)) NasGetCellLocationNr5gServingCell; + +typedef struct { + uint16 physical_cell_id; + int16 rsrq; + int16 rsrp; + int16 rssi; + int16 cell_selection_rx_level; +} __attribute__ ((packed)) NasGetCellLocationLteInfoCell; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8 ue_in_idle; + uint8 plmn[3]; + uint16 tracking_area_code; + uint32 global_cell_id; + uint16 absolute_rf_channel_number; + uint16 serving_cell_id; + uint8 cell_reselection_priority; + uint8 s_non_intra_search_threshold; + uint8 serving_cell_low_threshold; + uint8 s_intra_search_threshold; + uint8 cells_len; + NasGetCellLocationLteInfoCell cells_array[0]; +} __attribute__ ((packed)) NasGetCellLocationLteInfoIntrafrequency; + +typedef struct _QmiMessageNasGetCellLocationInfoOutputInterfrequencyLteInfoFrequencyElement { + uint16 eutra_absolute_rf_channel_number; + uint8 cell_selection_rx_level_low_threshold; + uint8 cell_selection_rx_level_high_threshold; + uint8 cell_reselection_priority; + uint8 cells_len; + NasGetCellLocationLteInfoCell cells_array[0]; +} __attribute__ ((packed)) NasGetCellLocationLteInfoInterfrequencyFrequencyElement; + +typedef struct { + UCHAR TLVType; + USHORT TLVLength; + + uint8 ue_in_idle; + uint8 freqs_len; + NasGetCellLocationLteInfoInterfrequencyFrequencyElement freqs[0]; +} __attribute__ ((packed)) NasGetCellLocationLteInfoInterfrequency; + +// ======================= End of NAS ============================== + +// ======================= UIM ============================== +#define QMIUIM_READ_TRANSPARENT_REQ 0x0020 +#define QMIUIM_READ_TRANSPARENT_RESP 0x0020 +#define QMIUIM_READ_TRANSPARENT_IND 0x0020 +#define QMIUIM_READ_RECORD_REQ 0x0021 +#define QMIUIM_READ_RECORD_RESP 0x0021 +#define QMIUIM_READ_RECORD_IND 0x0021 +#define QMIUIM_WRITE_TRANSPARENT_REQ 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_RESP 0x0022 +#define QMIUIM_WRITE_TRANSPARENT_IND 0x0022 +#define QMIUIM_WRITE_RECORD_REQ 0x0023 +#define QMIUIM_WRITE_RECORD_RESP 0x0023 +#define QMIUIM_WRITE_RECORD_IND 0x0023 +#define QMIUIM_SET_PIN_PROTECTION_REQ 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_RESP 0x0025 +#define QMIUIM_SET_PIN_PROTECTION_IND 0x0025 +#define QMIUIM_VERIFY_PIN_REQ 0x0026 +#define QMIUIM_VERIFY_PIN_RESP 0x0026 +#define QMIUIM_VERIFY_PIN_IND 0x0026 +#define QMIUIM_UNBLOCK_PIN_REQ 0x0027 +#define QMIUIM_UNBLOCK_PIN_RESP 0x0027 +#define QMIUIM_UNBLOCK_PIN_IND 0x0027 +#define QMIUIM_CHANGE_PIN_REQ 0x0028 +#define QMIUIM_CHANGE_PIN_RESP 0x0028 +#define QMIUIM_CHANGE_PIN_IND 0x0028 +#define QMIUIM_DEPERSONALIZATION_REQ 0x0029 +#define QMIUIM_DEPERSONALIZATION_RESP 0x0029 +#define QMIUIM_EVENT_REG_REQ 0x002E +#define QMIUIM_EVENT_REG_RESP 0x002E +#define QMIUIM_GET_CARD_STATUS_REQ 0x002F +#define QMIUIM_GET_CARD_STATUS_RESP 0x002F +#define QMIUIM_STATUS_CHANGE_IND 0x0032 +#define QMIUIM_POWER_DOWN 0x0030 +#define QMIUIM_POWER_UP 0x0031 + + +typedef struct _QMIUIM_GET_CARD_STATUS_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_GET_CARD_STATUS_RESP_MSG, *PQMIUIM_GET_CARD_STATUS_RESP_MSG; + +#define UIM_CARD_STATE_ABSENT 0x00 +#define UIM_CARD_STATE_PRESENT 0x01 +#define UIM_CARD_STATE_ERROR 0x02 + +typedef struct _QMIUIM_APP_INFO +{ + UCHAR AppType; + UCHAR AppState; + UCHAR PersoState; + UCHAR PersoFeature; + UCHAR PersoRetries; + UCHAR PersoUnblockRetries; + UCHAR AIDLength; +} __attribute__ ((packed)) QMIUI_APP_INFO; + +typedef struct _QMIUIM_CARD_STATUS +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT IndexGWPri; + USHORT Index1XPri; + USHORT IndexGWSec; + USHORT Index1XSec; + UCHAR NumSlot; + UCHAR CardState; + UCHAR UPINState; + UCHAR UPINRetries; + UCHAR UPUKRetries; + UCHAR ErrorCode; + UCHAR NumApp; + QMIUI_APP_INFO APP_INFO; +} __attribute__ ((packed)) QMIUIM_CARD_STATUS, *PQMIUIM_CARD_STATUS; + +typedef struct _QMIUIM_PIN_STATE +{ + UCHAR UnivPIN; + UCHAR PIN1State; + UCHAR PIN1Retries; + UCHAR PUK1Retries; + UCHAR PIN2State; + UCHAR PIN2Retries; + UCHAR PUK2Retries; +} __attribute__ ((packed)) QMIUIM_PIN_STATE, *PQMIUIM_PIN_STATE; + +typedef struct _QMIUIM_VERIFY_PIN_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINID; + UCHAR PINLen; + UCHAR PINValue; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_REQ_MSG, *PQMIUIM_VERIFY_PIN_REQ_MSG; + +typedef struct _QMIUIM_VERIFY_PIN_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; + UCHAR TLV2Type; + USHORT TLV2Length; + UCHAR PINVerifyRetriesLeft; + UCHAR PINUnblockRetriesLeft; +} __attribute__ ((packed)) QMIUIM_VERIFY_PIN_RESP_MSG, *PQMIUIM_VERIFY_PIN_RESP_MSG; + +typedef struct _QMIUIM_READ_TRANSPARENT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR Session_Type; + UCHAR Aid_Len; + UCHAR TLV2Type; + USHORT TLV2Length; + USHORT file_id; + UCHAR path_len; + UCHAR path[]; +} __attribute__ ((packed)) QMIUIM_READ_TRANSPARENT_REQ_MSG, *PQMIUIM_READ_TRANSPARENT_REQ_MSG; + +typedef struct _READ_TRANSPARENT_TLV +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT Offset; + USHORT Length; +} __attribute__ ((packed)) READ_TRANSPARENT_TLV, *PREAD_TRANSPARENT_TLV; + +typedef struct _QMIUIM_CONTENT +{ + UCHAR TLVType; + USHORT TLVLength; + USHORT content_len; + UCHAR content[]; +} __attribute__ ((packed)) QMIUIM_CONTENT, *PQMIUIM_CONTENT; + +typedef struct _QMIUIM_READ_TRANSPARENT_RESP_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + USHORT QMUXResult; + USHORT QMUXError; +} __attribute__ ((packed)) QMIUIM_READ_TRANSPARENT_RESP_MSG, *PQMIUIM_READ_TRANSPARENT_RESP_MSG; + +typedef struct _QMIUIM_SET_CARD_SLOT_REQ_MSG +{ + USHORT Type; + USHORT Length; + UCHAR TLVType; + USHORT TLVLength; + UCHAR slot; +} __attribute__ ((packed)) QMIUIM_SET_CARD_SLOT_REQ_MSG, *PQMIUIM_SET_CARD_SLOT_REQ_MSG; + +// ======================= COEX ============================== +#define QMI_COEX_GET_WWAN_STATE_REQ 0x22 +#define QMI_COEX_GET_WWAN_STATE_RESP 0x22 + +typedef struct { + + uint32_t freq; + /**< Band center frequency in MHz. */ + + uint32_t bandwidth; + /**< Bandwidth in MHz. */ +}coex_band_type_v01; /* Type */ + +typedef struct _QMI_COEX_GET_WWAN_STATE_RESP_MSG_LTE_BAND +{ + UCHAR TLVType; + USHORT TLVLength; + coex_band_type_v01 ul_band; + coex_band_type_v01 dl_band; +} __attribute__ ((packed)) QMI_COEX_GET_WWAN_STATE_RESP_MSG_LTE_BAND, *PQMI_COEX_GET_WWAN_STATE_RESP_MSG_LTE_BAND; + + +typedef struct _QMUX_MSG +{ + QCQMUX_HDR QMUXHdr; + union + { + // Message Header + QCQMUX_MSG_HDR QMUXMsgHdr; + QCQMUX_MSG_HDR_RESP QMUXMsgHdrResp; + + // QMIWDS Message +#if 0 + QMIWDS_GET_PKT_SRVC_STATUS_REQ_MSG PacketServiceStatusReq; + QMIWDS_GET_PKT_SRVC_STATUS_RESP_MSG PacketServiceStatusRsp; + QMIWDS_GET_PKT_SRVC_STATUS_IND_MSG PacketServiceStatusInd; + QMIWDS_EVENT_REPORT_IND_MSG EventReportInd; + QMIWDS_GET_CURRENT_CHANNEL_RATE_REQ_MSG GetCurrChannelRateReq; + QMIWDS_GET_CURRENT_CHANNEL_RATE_RESP_MSG GetCurrChannelRateRsp; + QMIWDS_GET_PKT_STATISTICS_REQ_MSG GetPktStatsReq; + QMIWDS_GET_PKT_STATISTICS_RESP_MSG GetPktStatsRsp; + QMIWDS_SET_EVENT_REPORT_REQ_MSG EventReportReq; + QMIWDS_SET_EVENT_REPORT_RESP_MSG EventReportRsp; +#endif + //#ifdef QC_IP_MODE + QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG GetRuntimeSettingsReq; + QMIWDS_GET_RUNTIME_SETTINGS_RESP_MSG GetRuntimeSettingsRsp; + //#endif // QC_IP_MODE + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG SetClientIpFamilyPrefReq; + QMIWDS_SET_CLIENT_IP_FAMILY_PREF_RESP_MSG SetClientIpFamilyPrefResp; + QMIWDS_SET_AUTO_CONNECT_REQ_MSG SetAutoConnectReq; +#if 0 + QMIWDS_GET_MIP_MODE_REQ_MSG GetMipModeReq; + QMIWDS_GET_MIP_MODE_RESP_MSG GetMipModeResp; +#endif + QMIWDS_START_NETWORK_INTERFACE_REQ_MSG StartNwInterfaceReq; + QMIWDS_START_NETWORK_INTERFACE_RESP_MSG StartNwInterfaceResp; + QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG StopNwInterfaceReq; + QMIWDS_STOP_NETWORK_INTERFACE_RESP_MSG StopNwInterfaceResp; + QMIWDS_GET_DEFAULT_SETTINGS_REQ_MSG GetDefaultSettingsReq; + QMIWDS_GET_DEFAULT_SETTINGS_RESP_MSG GetDefaultSettingsResp; + QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG ModifyProfileSettingsReq; + QMIWDS_MODIFY_PROFILE_SETTINGS_RESP_MSG ModifyProfileSettingsResp; + QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG GetProfileSettingsReq; + QMIWDS_CREATE_PROFILE_SETTINGS_REQ_MSG CreatetProfileSettingsReq; + QMIWDS_GET_PROFILE_LIST_REQ_MSG GetProfileListReq; + QMIWDS_GET_PROFILE_LIST_RESP_MSG GetProfileListResp; +#if 0 + QMIWDS_GET_DATA_BEARER_REQ_MSG GetDataBearerReq; + QMIWDS_GET_DATA_BEARER_RESP_MSG GetDataBearerResp; + QMIWDS_DUN_CALL_INFO_REQ_MSG DunCallInfoReq; + QMIWDS_DUN_CALL_INFO_RESP_MSG DunCallInfoResp; +#endif + QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG BindMuxDataPortReq; + + // QMIDMS Messages +#if 0 + QMIDMS_GET_DEVICE_MFR_REQ_MSG GetDeviceMfrReq; + QMIDMS_GET_DEVICE_MFR_RESP_MSG GetDeviceMfrRsp; + QMIDMS_GET_DEVICE_MODEL_ID_REQ_MSG GetDeviceModeIdReq; + QMIDMS_GET_DEVICE_MODEL_ID_RESP_MSG GetDeviceModeIdRsp; + QMIDMS_GET_DEVICE_REV_ID_REQ_MSG GetDeviceRevIdReq; + QMIDMS_GET_DEVICE_REV_ID_RESP_MSG GetDeviceRevIdRsp; + QMIDMS_GET_MSISDN_REQ_MSG GetMsisdnReq; + QMIDMS_GET_MSISDN_RESP_MSG GetMsisdnRsp; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_REQ_MSG GetDeviceSerialNumReq; + QMIDMS_GET_DEVICE_SERIAL_NUMBERS_RESP_MSG GetDeviceSerialNumRsp; + QMIDMS_GET_DEVICE_CAP_REQ_MSG GetDeviceCapReq; + QMIDMS_GET_DEVICE_CAP_RESP_MSG GetDeviceCapResp; + QMIDMS_GET_BAND_CAP_REQ_MSG GetBandCapReq; + QMIDMS_GET_BAND_CAP_RESP_MSG GetBandCapRsp; + QMIDMS_GET_ACTIVATED_STATUS_REQ_MSG GetActivatedStatusReq; + QMIDMS_GET_ACTIVATED_STATUS_RESP_MSG GetActivatedStatusResp; + QMIDMS_GET_OPERATING_MODE_REQ_MSG GetOperatingModeReq; + QMIDMS_GET_OPERATING_MODE_RESP_MSG GetOperatingModeResp; +#endif + QMIDMS_SET_OPERATING_MODE_REQ_MSG SetOperatingModeReq; + QMIDMS_SET_OPERATING_MODE_RESP_MSG SetOperatingModeResp; +#if 0 + QMIDMS_UIM_GET_ICCID_REQ_MSG GetICCIDReq; + QMIDMS_UIM_GET_ICCID_RESP_MSG GetICCIDResp; + QMIDMS_ACTIVATE_AUTOMATIC_REQ_MSG ActivateAutomaticReq; + QMIDMS_ACTIVATE_AUTOMATIC_RESP_MSG ActivateAutomaticResp; + QMIDMS_ACTIVATE_MANUAL_REQ_MSG ActivateManualReq; + QMIDMS_ACTIVATE_MANUAL_RESP_MSG ActivateManualResp; +#endif + QMIDMS_UIM_GET_PIN_STATUS_REQ_MSG UIMGetPinStatusReq; + QMIDMS_UIM_GET_PIN_STATUS_RESP_MSG UIMGetPinStatusResp; + QMIDMS_UIM_VERIFY_PIN_REQ_MSG UIMVerifyPinReq; + QMIDMS_UIM_VERIFY_PIN_RESP_MSG UIMVerifyPinResp; +#if 0 + QMIDMS_UIM_SET_PIN_PROTECTION_REQ_MSG UIMSetPinProtectionReq; + QMIDMS_UIM_SET_PIN_PROTECTION_RESP_MSG UIMSetPinProtectionResp; + QMIDMS_UIM_CHANGE_PIN_REQ_MSG UIMChangePinReq; + QMIDMS_UIM_CHANGE_PIN_RESP_MSG UIMChangePinResp; + QMIDMS_UIM_UNBLOCK_PIN_REQ_MSG UIMUnblockPinReq; + QMIDMS_UIM_UNBLOCK_PIN_RESP_MSG UIMUnblockPinResp; + QMIDMS_SET_EVENT_REPORT_REQ_MSG DmsSetEventReportReq; + QMIDMS_SET_EVENT_REPORT_RESP_MSG DmsSetEventReportResp; + QMIDMS_EVENT_REPORT_IND_MSG DmsEventReportInd; +#endif + QMIDMS_UIM_GET_STATE_REQ_MSG UIMGetStateReq; + QMIDMS_UIM_GET_STATE_RESP_MSG UIMGetStateResp; + QMIDMS_UIM_GET_IMSI_REQ_MSG UIMGetIMSIReq; + QMIDMS_UIM_GET_IMSI_RESP_MSG UIMGetIMSIResp; +#if 0 + QMIDMS_UIM_GET_CK_STATUS_REQ_MSG UIMGetCkStatusReq; + QMIDMS_UIM_GET_CK_STATUS_RESP_MSG UIMGetCkStatusResp; + QMIDMS_UIM_SET_CK_PROTECTION_REQ_MSG UIMSetCkProtectionReq; + QMIDMS_UIM_SET_CK_PROTECTION_RESP_MSG UIMSetCkProtectionResp; + QMIDMS_UIM_UNBLOCK_CK_REQ_MSG UIMUnblockCkReq; + QMIDMS_UIM_UNBLOCK_CK_RESP_MSG UIMUnblockCkResp; +#endif + + // QMIQOS Messages +#if 1 + QMI_QOS_SET_EVENT_REPORT_REQ_MSG QosSetEventReportReq; + QMI_QOS_SET_EVENT_REPORT_RESP_MSG QosSetEventReportRsp; + QMI_QOS_SET_EVENT_REPORT_IND_MSG QosSetEventReportInd; + QMI_QOS_BIND_DATA_PORT_REQ_MSG QosBindDataPortReq; + QMI_QOS_BIND_DATA_PORT_RESP_MSG QosBindDataPortRsp; + QMI_QOS_INDICATION_REGISTER_REQ_MSG QosIndRegReq; + QMI_QOS_INDICATION_REGISTER_RESP_MSG QosIndRegRsp; + QMI_QOS_GLOBAL_QOS_FLOW_IND_MSG QosGlobalQosFlowInd; + QMI_QOS_GET_QOS_INFO_REQ_MSG QosGetQosInfoReq; + QMI_QOS_GET_QOS_INFO_RESP_MSG QosGetQosInfoRsp; +#endif + + // QMIWMS Messages +#if 0 + QMIWMS_GET_MESSAGE_PROTOCOL_REQ_MSG GetMessageProtocolReq; + QMIWMS_GET_MESSAGE_PROTOCOL_RESP_MSG GetMessageProtocolResp; + QMIWMS_GET_SMSC_ADDRESS_REQ_MSG GetSMSCAddressReq; + QMIWMS_GET_SMSC_ADDRESS_RESP_MSG GetSMSCAddressResp; + QMIWMS_SET_SMSC_ADDRESS_REQ_MSG SetSMSCAddressReq; + QMIWMS_SET_SMSC_ADDRESS_RESP_MSG SetSMSCAddressResp; + QMIWMS_GET_STORE_MAX_SIZE_REQ_MSG GetStoreMaxSizeReq; + QMIWMS_GET_STORE_MAX_SIZE_RESP_MSG GetStoreMaxSizeResp; + QMIWMS_LIST_MESSAGES_REQ_MSG ListMessagesReq; + QMIWMS_LIST_MESSAGES_RESP_MSG ListMessagesResp; + QMIWMS_RAW_READ_REQ_MSG RawReadMessagesReq; + QMIWMS_RAW_READ_RESP_MSG RawReadMessagesResp; + QMIWMS_SET_EVENT_REPORT_REQ_MSG WmsSetEventReportReq; + QMIWMS_SET_EVENT_REPORT_RESP_MSG WmsSetEventReportResp; + QMIWMS_EVENT_REPORT_IND_MSG WmsEventReportInd; + QMIWMS_DELETE_REQ_MSG WmsDeleteReq; + QMIWMS_DELETE_RESP_MSG WmsDeleteResp; + QMIWMS_RAW_SEND_REQ_MSG RawSendMessagesReq; + QMIWMS_RAW_SEND_RESP_MSG RawSendMessagesResp; + QMIWMS_MODIFY_TAG_REQ_MSG WmsModifyTagReq; + QMIWMS_MODIFY_TAG_RESP_MSG WmsModifyTagResp; +#endif + + // QMINAS Messages +#if 0 + QMINAS_GET_HOME_NETWORK_REQ_MSG GetHomeNetworkReq; + QMINAS_GET_HOME_NETWORK_RESP_MSG GetHomeNetworkResp; + QMINAS_GET_PREFERRED_NETWORK_REQ_MSG GetPreferredNetworkReq; + QMINAS_GET_PREFERRED_NETWORK_RESP_MSG GetPreferredNetworkResp; + QMINAS_GET_FORBIDDEN_NETWORK_REQ_MSG GetForbiddenNetworkReq; + QMINAS_GET_FORBIDDEN_NETWORK_RESP_MSG GetForbiddenNetworkResp; + QMINAS_GET_SERVING_SYSTEM_REQ_MSG GetServingSystemReq; +#endif + QMINAS_GET_SERVING_SYSTEM_RESP_MSG GetServingSystemResp; + QMINAS_GET_SYS_INFO_RESP_MSG GetSysInfoResp; + QMINAS_SYS_INFO_IND_MSG NasSysInfoInd; +#if 0 + QMINAS_SERVING_SYSTEM_IND_MSG NasServingSystemInd; + QMINAS_SET_PREFERRED_NETWORK_REQ_MSG SetPreferredNetworkReq; + QMINAS_SET_PREFERRED_NETWORK_RESP_MSG SetPreferredNetworkResp; + QMINAS_SET_FORBIDDEN_NETWORK_REQ_MSG SetForbiddenNetworkReq; + QMINAS_SET_FORBIDDEN_NETWORK_RESP_MSG SetForbiddenNetworkResp; + QMINAS_PERFORM_NETWORK_SCAN_REQ_MSG PerformNetworkScanReq; + QMINAS_PERFORM_NETWORK_SCAN_RESP_MSG PerformNetworkScanResp; + QMINAS_INITIATE_NW_REGISTER_REQ_MSG InitiateNwRegisterReq; + QMINAS_INITIATE_NW_REGISTER_RESP_MSG InitiateNwRegisterResp; + QMINAS_SET_TECHNOLOGY_PREF_REQ_MSG SetTechnologyPrefReq; + QMINAS_SET_TECHNOLOGY_PREF_RESP_MSG SetTechnologyPrefResp; + QMINAS_GET_SIGNAL_STRENGTH_REQ_MSG GetSignalStrengthReq; + QMINAS_GET_SIGNAL_STRENGTH_RESP_MSG GetSignalStrengthResp; + QMINAS_SET_EVENT_REPORT_REQ_MSG SetEventReportReq; + QMINAS_SET_EVENT_REPORT_RESP_MSG SetEventReportResp; + QMINAS_EVENT_REPORT_IND_MSG NasEventReportInd; + QMINAS_GET_RF_BAND_INFO_REQ_MSG GetRFBandInfoReq; + QMINAS_GET_RF_BAND_INFO_RESP_MSG GetRFBandInfoResp; + QMINAS_INITIATE_ATTACH_REQ_MSG InitiateAttachReq; + QMINAS_INITIATE_ATTACH_RESP_MSG InitiateAttachResp; + QMINAS_GET_PLMN_NAME_REQ_MSG GetPLMNNameReq; + QMINAS_GET_PLMN_NAME_RESP_MSG GetPLMNNameResp; +#endif + + // QMIUIM Messages + QMIUIM_GET_CARD_STATUS_RESP_MSG UIMGetCardStatus; + QMIUIM_VERIFY_PIN_REQ_MSG UIMUIMVerifyPinReq; + QMIUIM_VERIFY_PIN_RESP_MSG UIMUIMVerifyPinResp; +#if 0 + QMIUIM_SET_PIN_PROTECTION_REQ_MSG UIMUIMSetPinProtectionReq; + QMIUIM_SET_PIN_PROTECTION_RESP_MSG UIMUIMSetPinProtectionResp; + QMIUIM_CHANGE_PIN_REQ_MSG UIMUIMChangePinReq; + QMIUIM_CHANGE_PIN_RESP_MSG UIMUIMChangePinResp; + QMIUIM_UNBLOCK_PIN_REQ_MSG UIMUIMUnblockPinReq; + QMIUIM_UNBLOCK_PIN_RESP_MSG UIMUIMUnblockPinResp; +#endif + QMIUIM_READ_TRANSPARENT_REQ_MSG UIMUIMReadTransparentReq; + QMIUIM_READ_TRANSPARENT_RESP_MSG UIMUIMReadTransparentResp; + QMIUIM_SET_CARD_SLOT_REQ_MSG UIMSetCardSlotReq; + + QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG SetDataFormatReq; + QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG SetLoopBackReq; + QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG SetLoopBackInd; + }; +} __attribute__ ((packed)) QMUX_MSG, *PQMUX_MSG; + +typedef struct _QCQMIMSG { + QCQMI_HDR QMIHdr; + union { + QMICTL_MSG CTLMsg; + QMUX_MSG MUXMsg; + }; +} __attribute__ ((packed)) QCQMIMSG, *PQCQMIMSG; + +#pragma pack(pop) + +#endif // QCQMUX_H + diff --git a/RM520N-5G/文件/source/QMIThread.c b/RM520N-5G/文件/source/QMIThread.c new file mode 100644 index 0000000..d4ad232 --- /dev/null +++ b/RM520N-5G/文件/source/QMIThread.c @@ -0,0 +1,3099 @@ +/* + 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" +#include + +#ifndef MIN +#define MIN(a, b) ((a) < (b)? (a): (b)) +#endif + +#define qmi_rsp_check_and_return() do { \ + if (err < 0 || pResponse == NULL) { \ + dbg_time("%s err = %d", __func__, err); \ + return err; \ + } \ + pMUXMsg = &pResponse->MUXMsg; \ + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { \ + USHORT QMUXError = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); \ + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, \ + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), QMUXError); \ + free(pResponse); \ + return QMUXError; \ + } \ +} while(0) + +#define qmi_rsp_check() do { \ + if (err < 0 || pResponse == NULL) { \ + dbg_time("%s err = %d", __func__, err); \ + return err; \ + } \ + pMUXMsg = &pResponse->MUXMsg; \ + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { \ + USHORT QMUXError = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); \ + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, \ + le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), QMUXError); \ + } \ +} while(0) + +#define UIM_APP_TYPE_CSIM 0x04 +#define UIM_APP_TYPE_USIM 0x02 + +static uint32_t WdsConnectionIPv4Handle = 0; +static uint32_t WdsConnectionIPv6Handle = 0; +static int s_is_cdma = 0; +static int s_5g_type = WWAN_DATA_CLASS_NONE; +static int s_hdr_personality = 0; // 0x01-HRPD, 0x02-eHRPD +static char *qstrcpy(char *to, const char *from) { //no __strcpy_chk + char *save = to; + for (; (*to = *from) != '\0'; ++from, ++to); + return(save); +} + +static void uchar2char(char *dst_ptr, size_t dst_len, const UCHAR *src_ptr, size_t src_len) { + size_t copy = MIN(dst_len-1, src_len); + + if (copy) + memcpy(dst_ptr, src_ptr, copy); + dst_ptr[copy] = 0; +} + +static int s_9x07 = 1; + +typedef USHORT (*CUSTOMQMUX)(PQMUX_MSG pMUXMsg, void *arg); + +// To retrieve the ith (Index) TLV +PQMI_TLV_HDR GetTLV (PQCQMUX_MSG_HDR pQMUXMsgHdr, int TLVType) { + int TLVFind = 0; + USHORT Length = le16_to_cpu(pQMUXMsgHdr->Length); + PQMI_TLV_HDR pTLVHdr = (PQMI_TLV_HDR)(pQMUXMsgHdr + 1); + + while (Length >= sizeof(QMI_TLV_HDR)) { + TLVFind++; + if (TLVType > 0x1000) { + if ((TLVFind + 0x1000) == TLVType) + return pTLVHdr; + } else if (pTLVHdr->TLVType == TLVType) { + return pTLVHdr; + } + Length -= (le16_to_cpu((pTLVHdr->TLVLength)) + sizeof(QMI_TLV_HDR)); + pTLVHdr = (PQMI_TLV_HDR)(((UCHAR *)pTLVHdr) + le16_to_cpu(pTLVHdr->TLVLength) + sizeof(QMI_TLV_HDR)); + } + + return NULL; +} + +static USHORT GetQMUXTransactionId(void) { + static int TransactionId = 0; + if (++TransactionId > 0xFFFF) + TransactionId = 1; + return TransactionId; +} + +static PQCQMIMSG ComposeQMUXMsg(UCHAR QMIType, USHORT Type, CUSTOMQMUX customQmuxMsgFunction, void *arg) { + UCHAR QMIBuf[WDM_DEFAULT_BUFSIZE]; + PQCQMIMSG pRequest = (PQCQMIMSG)QMIBuf; + int Length; + + memset(QMIBuf, 0x00, sizeof(QMIBuf)); + pRequest->QMIHdr.IFType = USB_CTL_MSG_TYPE_QMI; + pRequest->QMIHdr.CtlFlags = 0x00; + pRequest->QMIHdr.QMIType = QMIType; + + pRequest->MUXMsg.QMUXHdr.CtlFlags = QMUX_CTL_FLAG_SINGLE_MSG | QMUX_CTL_FLAG_TYPE_CMD; + pRequest->MUXMsg.QMUXHdr.TransactionId = cpu_to_le16(GetQMUXTransactionId()); + pRequest->MUXMsg.QMUXMsgHdr.Type = cpu_to_le16(Type); + if (customQmuxMsgFunction) + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(customQmuxMsgFunction(&pRequest->MUXMsg, arg) - sizeof(QCQMUX_MSG_HDR)); + else + pRequest->MUXMsg.QMUXMsgHdr.Length = cpu_to_le16(0x0000); + + pRequest->QMIHdr.Length = cpu_to_le16(le16_to_cpu(pRequest->MUXMsg.QMUXMsgHdr.Length) + sizeof(QCQMUX_MSG_HDR) + sizeof(QCQMUX_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; +} + +#if 0 +static USHORT NasSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetEventReportReq.TLVType = 0x10; + pMUXMsg->SetEventReportReq.TLVLength = 0x04; + pMUXMsg->SetEventReportReq.ReportSigStrength = 0x00; + pMUXMsg->SetEventReportReq.NumTresholds = 2; + pMUXMsg->SetEventReportReq.TresholdList[0] = -113; + pMUXMsg->SetEventReportReq.TresholdList[1] = -50; + return sizeof(QMINAS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT WdsSetEventReportReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->EventReportReq.TLVType = 0x10; // 0x10 -- current channel rate indicator + pMUXMsg->EventReportReq.TLVLength = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode = 0x00; // 0-do not report; 1-report when rate changes + + pMUXMsg->EventReportReq.TLV2Type = 0x11; // 0x11 + pMUXMsg->EventReportReq.TLV2Length = 0x0005; // 5 + pMUXMsg->EventReportReq.StatsPeriod = 0x00; // seconds between reports; 0-do not report + pMUXMsg->EventReportReq.StatsMask = 0x000000ff; // + + pMUXMsg->EventReportReq.TLV3Type = 0x12; // 0x12 -- current data bearer indicator + pMUXMsg->EventReportReq.TLV3Length = 0x0001; // 1 + pMUXMsg->EventReportReq.Mode3 = 0x01; // 0-do not report; 1-report when changes + + pMUXMsg->EventReportReq.TLV4Type = 0x13; // 0x13 -- dormancy status indicator + pMUXMsg->EventReportReq.TLV4Length = 0x0001; // 1 + pMUXMsg->EventReportReq.DormancyStatus = 0x00; // 0-do not report; 1-report when changes + return sizeof(QMIWDS_SET_EVENT_REPORT_REQ_MSG); +} + +static USHORT DmsSetEventReportReq(PQMUX_MSG pMUXMsg) { + PPIN_STATUS pPinState = (PPIN_STATUS)(&pMUXMsg->DmsSetEventReportReq + 1); + PUIM_STATE pUimState = (PUIM_STATE)(pPinState + 1); + // Pin State + pPinState->TLVType = 0x12; + pPinState->TLVLength = 0x01; + pPinState->ReportPinState = 0x01; + // UIM State + pUimState->TLVType = 0x15; + pUimState->TLVLength = 0x01; + pUimState->UIMState = 0x01; + return sizeof(QMIDMS_SET_EVENT_REPORT_REQ_MSG) + sizeof(PIN_STATUS) + sizeof(UIM_STATE); +} +#endif + +static USHORT WdsStartNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + PQMIWDS_TECHNOLOGY_PREFERECE pTechPref; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PD pPd; + PQMIWDS_APNNAME pApnName; + PQMIWDS_IP_FAMILY_TLV pIpFamily; + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + const char *profile_user = profile->user; + const char *profile_pd = profile->pd; + int profile_auth = profile->auth; + + if (s_is_cdma && (profile_user == NULL || profile_user[0] == '\0') && (profile_pd == NULL || profile_pd[0] == '\0')) { + profile_user = "ctnet@mycdma.cn"; + profile_pd = "vnet.mobi"; + profile_auth = 2; //chap + } + + pTLV = (UCHAR *)(&pMUXMsg->StartNwInterfaceReq + 1); + pMUXMsg->StartNwInterfaceReq.Length = 0; + + // Set technology Preferece + pTechPref = (PQMIWDS_TECHNOLOGY_PREFERECE)(pTLV + TLVLength); + pTechPref->TLVType = 0x30; + pTechPref->TLVLength = cpu_to_le16(0x01); + if (s_is_cdma == 0) + pTechPref->TechPreference = 0x01; + else + pTechPref->TechPreference = 0x02; + TLVLength +=(le16_to_cpu(pTechPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn && !s_is_cdma) { //cdma no apn + pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile_user) { + pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x17; + pUserName->TLVLength = cpu_to_le16(strlen(profile_user)); + qstrcpy((char *)&pUserName->UserName, profile_user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile_pd) { + pPd = (PQMIWDS_PD)(pTLV + TLVLength); + pPd->TLVType = 0x18; + pPd->TLVLength = cpu_to_le16(strlen(profile_pd)); + qstrcpy((char *)&pPd->Pd, profile_pd); + TLVLength += (le16_to_cpu(pPd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile_user && profile_pd) { + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x16; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile_auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Add IP Family Preference + pIpFamily = (PQMIWDS_IP_FAMILY_TLV)(pTLV + TLVLength); + pIpFamily->TLVType = 0x19; + pIpFamily->TLVLength = cpu_to_le16(0x01); + pIpFamily->IpFamily = profile->curIpFamily; + TLVLength += (le16_to_cpu(pIpFamily->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + if(profile->usb_dev.idProduct != 0x0316){ + + //Set Profile Index + if (profile->profile_index && !s_is_cdma) { //cdma only support one pdp, so no need to set profile index + PQMIWDS_PROFILE_IDENTIFIER pProfileIndex = (PQMIWDS_PROFILE_IDENTIFIER)(pTLV + TLVLength); + pProfileIndex->TLVLength = cpu_to_le16(0x01); + pProfileIndex->TLVType = 0x31; + pProfileIndex->ProfileIndex = profile->profile_index; + if (s_is_cdma && s_hdr_personality == 0x02) { + pProfileIndex->TLVType = 0x32; //profile_index_3gpp2 + pProfileIndex->ProfileIndex = 101; + } + TLVLength += (le16_to_cpu(pProfileIndex->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + } + else if(profile->usb_dev.idProduct == 0x0316){ + + if(profile->bring_up_by_apn_name) + { + PQMIWDS_BRINGUP_APNNAME pBring_up_by_apn_name = (PQMIWDS_BRINGUP_APNNAME)(pTLV + TLVLength); + pBring_up_by_apn_name->TLVLength = cpu_to_le16(0x01); + pBring_up_by_apn_name->TLVType = 0x3C; + pBring_up_by_apn_name->bring_up_by_apn_name = profile->bring_up_by_apn_name; + TLVLength += (le16_to_cpu(pBring_up_by_apn_name->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + if(profile->bring_up_by_apn_type) + { + PQMIWDS_BRINGUP_APNTYPE pBring_up_by_apn_type = (PQMIWDS_BRINGUP_APNTYPE)(pTLV + TLVLength); + pBring_up_by_apn_type->TLVLength = cpu_to_le16(0x01); + pBring_up_by_apn_type->TLVType = 0x3D; + pBring_up_by_apn_type->bring_up_by_apn_type = profile->bring_up_by_apn_type; + TLVLength += (le16_to_cpu(pBring_up_by_apn_type->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + if(profile->apn_type) + { + PQMIWDS_APN_TYPE pApn_type = (PQMIWDS_APN_TYPE)(pTLV + TLVLength); + pApn_type->TLVLength = cpu_to_le32(0x04); + pApn_type->TLVType = 0x38; + pApn_type->apn_type = cpu_to_le32(profile->apn_type); + TLVLength += (le16_to_cpu(pApn_type->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + } + return sizeof(QMIWDS_START_NETWORK_INTERFACE_REQ_MSG) + TLVLength; +} + +static USHORT WdsStopNwInterfaceReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->StopNwInterfaceReq.TLVType = 0x01; + pMUXMsg->StopNwInterfaceReq.TLVLength = cpu_to_le16(0x04); + if (*((int *)arg) == IpFamilyV4) + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv4Handle); + else + pMUXMsg->StopNwInterfaceReq.Handle = cpu_to_le32(WdsConnectionIPv6Handle); + return sizeof(QMIWDS_STOP_NETWORK_INTERFACE_REQ_MSG); +} + +static USHORT WdsSetClientIPFamilyPref(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetClientIpFamilyPrefReq.TLVType = 0x01; + pMUXMsg->SetClientIpFamilyPrefReq.TLVLength = cpu_to_le16(0x01); + pMUXMsg->SetClientIpFamilyPrefReq.IpPreference = *((UCHAR *)arg); + return sizeof(QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ_MSG); +} + +static USHORT WdsSetAutoConnect(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetAutoConnectReq.TLVType = 0x01; + pMUXMsg->SetAutoConnectReq.TLVLength = cpu_to_le16(0x01); + pMUXMsg->SetAutoConnectReq.autoconnect_setting = *((UCHAR *)arg); + return sizeof(QMIWDS_SET_AUTO_CONNECT_REQ_MSG); +} + +enum peripheral_ep_type { + DATA_EP_TYPE_RESERVED = 0x0, + DATA_EP_TYPE_HSIC = 0x1, + DATA_EP_TYPE_HSUSB = 0x2, + DATA_EP_TYPE_PCIE = 0x3, + DATA_EP_TYPE_EMBEDDED = 0x4, + DATA_EP_TYPE_BAM_DMUX = 0x5, +}; + +static USHORT WdsSetQMUXBindMuxDataPort(PQMUX_MSG pMUXMsg, void *arg) { + QMAP_SETTING *qmap_settings = (QMAP_SETTING *)arg; + + pMUXMsg->BindMuxDataPortReq.TLVType = 0x10; + pMUXMsg->BindMuxDataPortReq.TLVLength = cpu_to_le16(0x08); + pMUXMsg->BindMuxDataPortReq.ep_type = cpu_to_le32(qmap_settings->ep_type); + pMUXMsg->BindMuxDataPortReq.iface_id = cpu_to_le32(qmap_settings->iface_id); + pMUXMsg->BindMuxDataPortReq.TLV2Type = 0x11; + pMUXMsg->BindMuxDataPortReq.TLV2Length = cpu_to_le16(0x01); + pMUXMsg->BindMuxDataPortReq.MuxId = qmap_settings->MuxId; + pMUXMsg->BindMuxDataPortReq.TLV3Type = 0x13; + pMUXMsg->BindMuxDataPortReq.TLV3Length = cpu_to_le16(0x04); + pMUXMsg->BindMuxDataPortReq.client_type = cpu_to_le32(1); //WDS_CLIENT_TYPE_TETHERED + + return sizeof(QMIWDS_BIND_MUX_DATA_PORT_REQ_MSG); +} + +static int qmap_version = 0x05; +static USHORT WdaSetDataFormat(PQMUX_MSG pMUXMsg, void *arg) { + QMAP_SETTING *qmap_settings = (QMAP_SETTING *)arg; + + if (qmap_settings->rx_urb_size == 0) { + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS pWdsAdminQosTlv; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV dlTlp; + + pWdsAdminQosTlv = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV_QOS)(&pMUXMsg->QMUXMsgHdr + 1); + pWdsAdminQosTlv->TLVType = 0x10; + pWdsAdminQosTlv->TLVLength = cpu_to_le16(0x0001); + pWdsAdminQosTlv->QOSSetting = 0; /* no-QOS header */ + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(pWdsAdminQosTlv + 1); + linkProto->TLVType = 0x11; + linkProto->TLVLength = cpu_to_le16(4); + linkProto->Value = cpu_to_le32(0x01); /* Set Ethernet mode */ + + dlTlp = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)(linkProto + 1);; + dlTlp->TLVType = 0x13; + dlTlp->TLVLength = cpu_to_le16(4); + dlTlp->Value = cpu_to_le32(0x00); + + if (sizeof(*linkProto) != 7 ) + dbg_time("%s sizeof(*linkProto) = %zu, is not 7!", __func__, sizeof(*linkProto) ); + + return sizeof(QCQMUX_MSG_HDR) + sizeof(*pWdsAdminQosTlv) + sizeof(*linkProto) + sizeof(*dlTlp); + } + else { + //Indicates whether the Quality of Service(QOS) data format is used by the client. + pMUXMsg->SetDataFormatReq.QosDataFormatTlv.TLVType = 0x10; + pMUXMsg->SetDataFormatReq.QosDataFormatTlv.TLVLength = cpu_to_le16(0x0001); + pMUXMsg->SetDataFormatReq.QosDataFormatTlv.QOSSetting = 0; /* no-QOS header */ + + //Underlying Link Layer Protocol + pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVType = 0x11; + pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UnderlyingLinkLayerProtocolTlv.Value = cpu_to_le32(0x02); /* Set IP mode */ + + //Uplink (UL) data aggregation protocol to be used for uplink data transfer. + pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVType = 0x12; + pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UplinkDataAggregationProtocolTlv.Value = cpu_to_le32(qmap_version); //UL QMAP is enabled + + //Downlink (DL) data aggregation protocol to be used for downlink data transfer + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVType = 0x13; + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationProtocolTlv.Value = cpu_to_le32(qmap_version); //DL QMAP is enabled + + //Maximum number of datagrams in a single aggregated packet on downlink + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVType = 0x15; + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(qmap_settings->rx_urb_size/512); + + //Maximum size in bytes of a single aggregated packet allowed on downlink + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVType = 0x16; + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DownlinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(qmap_settings->rx_urb_size); + + //Peripheral End Point ID + pMUXMsg->SetDataFormatReq.epTlv.TLVType = 0x17; + pMUXMsg->SetDataFormatReq.epTlv.TLVLength = cpu_to_le16(8); + pMUXMsg->SetDataFormatReq.epTlv.ep_type = cpu_to_le32(qmap_settings->ep_type); + pMUXMsg->SetDataFormatReq.epTlv.iface_id = cpu_to_le32(qmap_settings->iface_id); + +#ifdef QUECTEL_UL_DATA_AGG + if (!qmap_settings->ul_data_aggregation_max_datagrams) { + return ((size_t)&((QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG *)0)->DlMinimumPassingTlv); + } + + //Maximum number of datagrams in a single aggregated packet on uplink + pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.TLVType = 0x19; + pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.DlMinimumPassingTlv.Value = cpu_to_le32(qmap_settings->dl_minimum_padding); + + //Maximum number of datagrams in a single aggregated packet on uplink + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVType = 0x1B; + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxDatagramsTlv.Value = cpu_to_le32(qmap_settings->ul_data_aggregation_max_datagrams); + + //Maximum size in bytes of a single aggregated packet allowed on downlink + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVType = 0x1C; + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->SetDataFormatReq.UplinkDataAggregationMaxSizeTlv.Value = cpu_to_le32(qmap_settings->ul_data_aggregation_max_size); +#endif + + return sizeof(QMIWDS_ADMIN_SET_DATA_FORMAT_REQ_MSG); + } +} + +#ifdef CONFIG_SIM +static USHORT DmsUIMVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->UIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMVerifyPinReq.PINLen = strlen((const char *)arg); + qstrcpy((char *)&pMUXMsg->UIMVerifyPinReq.PINValue, ((const char *)arg)); + pMUXMsg->UIMVerifyPinReq.TLVLength = cpu_to_le16(2 + strlen((const char *)arg)); + return sizeof(QMIDMS_UIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +static USHORT UimVerifyPinReqSend(PQMUX_MSG pMUXMsg, void *arg) +{ + pMUXMsg->UIMUIMVerifyPinReq.TLVType = 0x01; + pMUXMsg->UIMUIMVerifyPinReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->UIMUIMVerifyPinReq.Session_Type = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.Aid_Len = 0x00; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMVerifyPinReq.TLV2Length = cpu_to_le16(2 + strlen((const char *)arg)); + pMUXMsg->UIMUIMVerifyPinReq.PINID = 0x01; //Pin1, not Puk + pMUXMsg->UIMUIMVerifyPinReq.PINLen= strlen((const char *)arg); + qstrcpy((char *)&pMUXMsg->UIMUIMVerifyPinReq.PINValue, ((const char *)arg)); + return sizeof(QMIUIM_VERIFY_PIN_REQ_MSG) + (strlen((const char *)arg) - 1); +} + +#ifdef CONFIG_IMSI_ICCID +static USHORT UimReadTransparentIMSIReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PREAD_TRANSPARENT_TLV pReadTransparent; + + pMUXMsg->UIMUIMReadTransparentReq.TLVType = 0x01; + pMUXMsg->UIMUIMReadTransparentReq.TLVLength = cpu_to_le16(0x02); + if (!strcmp((char *)arg, "EF_ICCID")) { + pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x06; + pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00; + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x2FE2); + pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F; + } + else if(!strcmp((char *)arg, "EF_IMSI")) { + pMUXMsg->UIMUIMReadTransparentReq.Session_Type = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.Aid_Len = 0x00; + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Type = 0x02; + pMUXMsg->UIMUIMReadTransparentReq.file_id = cpu_to_le16(0x6F07); + pMUXMsg->UIMUIMReadTransparentReq.path_len = 0x04; + pMUXMsg->UIMUIMReadTransparentReq.path[0] = 0x00; + pMUXMsg->UIMUIMReadTransparentReq.path[1] = 0x3F; + pMUXMsg->UIMUIMReadTransparentReq.path[2] = 0xFF; + pMUXMsg->UIMUIMReadTransparentReq.path[3] = 0x7F; + } + + pMUXMsg->UIMUIMReadTransparentReq.TLV2Length = cpu_to_le16(3 + pMUXMsg->UIMUIMReadTransparentReq.path_len); + + pReadTransparent = (PREAD_TRANSPARENT_TLV)(&pMUXMsg->UIMUIMReadTransparentReq.path[pMUXMsg->UIMUIMReadTransparentReq.path_len]); + pReadTransparent->TLVType = 0x03; + pReadTransparent->TLVLength = cpu_to_le16(0x04); + pReadTransparent->Offset = cpu_to_le16(0x00); + pReadTransparent->Length = cpu_to_le16(0x00); + + return (sizeof(QMIUIM_READ_TRANSPARENT_REQ_MSG) + pMUXMsg->UIMUIMReadTransparentReq.path_len + sizeof(READ_TRANSPARENT_TLV)); +} +#endif +#endif + +#ifdef CONFIG_APN + +static USHORT WdsGetProfileListReqSend(PQMUX_MSG pMUXMsg, void *arg) { + (void)(arg); + pMUXMsg->GetProfileListReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_LIST_REQ_MSG) - 4); + return sizeof(QMIWDS_GET_PROFILE_LIST_REQ_MSG); +} + +static USHORT WdsCreateProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->CreatetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_CREATE_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->CreatetProfileSettingsReq.TLVType = 0x01; + pMUXMsg->CreatetProfileSettingsReq.TLVLength = cpu_to_le16(0x01); + pMUXMsg->CreatetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->CreatetProfileSettingsReq.TLV2Type = 0x25; + pMUXMsg->CreatetProfileSettingsReq.TLV2Length = cpu_to_le16(0x01); + pMUXMsg->CreatetProfileSettingsReq.pdp_context = profile->pdp; // 0 ~ 3GPP, 1 ~ 3GPP2 + return sizeof(QMIWDS_CREATE_PROFILE_SETTINGS_REQ_MSG); +} + +static USHORT WdsGetProfileSettingsReqSend(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->GetProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->GetProfileSettingsReq.TLVType = 0x01; + pMUXMsg->GetProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->GetProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->GetProfileSettingsReq.ProfileIndex = profile->profile_index; + return sizeof(QMIWDS_GET_PROFILE_SETTINGS_REQ_MSG); +} + +static USHORT WdsModifyProfileSettingsReq(PQMUX_MSG pMUXMsg, void *arg) { + USHORT TLVLength = 0; + UCHAR *pTLV; + PROFILE_T *profile = (PROFILE_T *)arg; + PQMIWDS_PDPTYPE pPdpType; + + pMUXMsg->ModifyProfileSettingsReq.Length = cpu_to_le16(sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) - 4); + pMUXMsg->ModifyProfileSettingsReq.TLVType = 0x01; + pMUXMsg->ModifyProfileSettingsReq.TLVLength = cpu_to_le16(0x02); + pMUXMsg->ModifyProfileSettingsReq.ProfileType = 0x00; // 0 ~ 3GPP, 1 ~ 3GPP2 + pMUXMsg->ModifyProfileSettingsReq.ProfileIndex = profile->profile_index; + + pTLV = (UCHAR *)(&pMUXMsg->ModifyProfileSettingsReq + 1); + + pPdpType = (PQMIWDS_PDPTYPE)(pTLV + TLVLength); + pPdpType->TLVType = 0x11; + pPdpType->TLVLength = cpu_to_le16(0x01); + pPdpType->PdpType = profile->iptype; + TLVLength +=(le16_to_cpu(pPdpType->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + + // Set APN Name + if (profile->apn) { + PQMIWDS_APNNAME pApnName = (PQMIWDS_APNNAME)(pTLV + TLVLength); + pApnName->TLVType = 0x14; + pApnName->TLVLength = cpu_to_le16(strlen(profile->apn)); + qstrcpy((char *)&pApnName->ApnName, profile->apn); + TLVLength +=(le16_to_cpu(pApnName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set User Name + if (profile->user) { + PQMIWDS_USERNAME pUserName = (PQMIWDS_USERNAME)(pTLV + TLVLength); + pUserName->TLVType = 0x1B; + pUserName->TLVLength = cpu_to_le16(strlen(profile->user)); + qstrcpy((char *)&pUserName->UserName, profile->user); + TLVLength += (le16_to_cpu(pUserName->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Password + if (profile->pd) { + PQMIWDS_PD pPd = (PQMIWDS_PD)(pTLV + TLVLength); + pPd->TLVType = 0x1C; + pPd->TLVLength = cpu_to_le16(strlen(profile->pd)); + qstrcpy((char *)&pPd->Pd, profile->pd); + TLVLength +=(le16_to_cpu(pPd->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + // Set Auth Protocol + if (profile->user && profile->pd) { + PQMIWDS_AUTH_PREFERENCE pAuthPref = (PQMIWDS_AUTH_PREFERENCE)(pTLV + TLVLength); + pAuthPref->TLVType = 0x1D; + pAuthPref->TLVLength = cpu_to_le16(0x01); + pAuthPref->AuthPreference = profile->auth; // 0 ~ None, 1 ~ Pap, 2 ~ Chap, 3 ~ MsChapV2 + TLVLength += (le16_to_cpu(pAuthPref->TLVLength) + sizeof(QCQMICTL_TLV_HDR)); + } + + return sizeof(QMIWDS_MODIFY_PROFILE_SETTINGS_REQ_MSG) + TLVLength; +} +#endif + +static USHORT WdsGetRuntimeSettingReq(PQMUX_MSG pMUXMsg, void *arg) +{ + (void)arg; + pMUXMsg->GetRuntimeSettingsReq.TLVType = 0x10; + pMUXMsg->GetRuntimeSettingsReq.TLVLength = cpu_to_le16(0x04); + // the following mask also applies to IPV6 + pMUXMsg->GetRuntimeSettingsReq.Mask = cpu_to_le32(QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4DNS_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_MTU | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_IPV4GATEWAY_ADDR) | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_SV_ADDR | + QMIWDS_GET_RUNTIME_SETTINGS_MASK_PCSCF_DOM_NAME; + + return sizeof(QMIWDS_GET_RUNTIME_SETTINGS_REQ_MSG); +} + +static PQCQMIMSG s_pRequest; +static PQCQMIMSG s_pResponse; + +static int is_response(const PQCQMIMSG pRequest, const PQCQMIMSG pResponse) { + if ((pRequest->QMIHdr.QMIType == pResponse->QMIHdr.QMIType) + && (pRequest->QMIHdr.ClientId == pResponse->QMIHdr.ClientId)) { + USHORT requestTID, responseTID; + if (pRequest->QMIHdr.QMIType == QMUX_TYPE_CTL) { + requestTID = pRequest->CTLMsg.QMICTLMsgHdr.TransactionId; + responseTID = pResponse->CTLMsg.QMICTLMsgHdr.TransactionId; + } else { + requestTID = le16_to_cpu(pRequest->MUXMsg.QMUXHdr.TransactionId); + responseTID = le16_to_cpu(pResponse->MUXMsg.QMUXHdr.TransactionId); + } + return (requestTID == responseTID); + } + return 0; +} + +int (*qmidev_send)(PQCQMIMSG pRequest); + +int QmiThreadSendQMITimeout(PQCQMIMSG pRequest, PQCQMIMSG *ppResponse, unsigned msecs, const char *funcname) { + int ret; + + if (!pRequest) + return -EINVAL; + + pthread_mutex_lock(&cm_command_mutex); + + if (ppResponse) + *ppResponse = NULL; + + dump_qmi(pRequest, le16_to_cpu(pRequest->QMIHdr.Length) + 1); + + s_pRequest = pRequest; + s_pResponse = NULL; + + ret = qmidev_send(pRequest); + if (ret == 0) { + ret = pthread_cond_timeout_np(&cm_command_cond, &cm_command_mutex, msecs); + if (!ret) { + if (s_pResponse && ppResponse) { + *ppResponse = s_pResponse; + } + else if (s_pResponse) { + free(s_pResponse); + s_pResponse = NULL; + } + else { + dbg_time("%s message null", funcname); + ret = -EINVAL; + } + } else { + dbg_time("%s message timeout", funcname); + } + } + + pthread_mutex_unlock(&cm_command_mutex); + + return ret; +} + +void QmiThreadRecvQMI(PQCQMIMSG pResponse) { + pthread_mutex_lock(&cm_command_mutex); + if (pResponse == NULL) { + if (s_pRequest) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = NULL; + pthread_cond_signal(&cm_command_cond); + } + pthread_mutex_unlock(&cm_command_mutex); + return; + } + dump_qmi(pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pRequest && is_response(s_pRequest, pResponse)) { + free(s_pRequest); + s_pRequest = NULL; + s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1); + if (s_pResponse != NULL) { + memcpy(s_pResponse, pResponse, le16_to_cpu(pResponse->QMIHdr.Length) + 1); + } + pthread_cond_signal(&cm_command_cond); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_CTL) + && (le16_to_cpu(pResponse->CTLMsg.QMICTLMsgHdrRsp.QMICTLType == QMICTL_REVOKE_CLIENT_ID_IND))) { + qmidevice_send_event_to_main(MODEM_REPORT_RESET_EVENT); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SERVING_SYSTEM_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMIWDS_GET_PKT_SRVC_STATUS_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_NAS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMINAS_SYS_INFO_IND)) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_WDS_ADMIN) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMI_WDA_SET_LOOPBACK_CONFIG_IND)) { + qmidevice_send_event_to_main_ext(RIL_UNSOL_LOOPBACK_CONFIG_IND, + &pResponse->MUXMsg.SetLoopBackInd, sizeof(pResponse->MUXMsg.SetLoopBackInd)); + } +#ifdef CONFIG_REG_QOS_IND + else if ((pResponse->QMIHdr.QMIType == QMUX_TYPE_QOS) + && (le16_to_cpu(pResponse->MUXMsg.QMUXMsgHdrResp.Type) == QMI_QOS_GLOBAL_QOS_FLOW_IND)) { + UINT qos_id = 0; + UCHAR new_flow = ql_get_global_qos_flow_ind_qos_id(pResponse, &qos_id); + if (qos_id != 0 && new_flow == 1) + qmidevice_send_event_to_main_ext(RIL_UNSOL_GLOBAL_QOS_FLOW_IND_QOS_ID, &qos_id, sizeof(qos_id)); +#ifdef CONFIG_GET_QOS_DATA_RATE + if (new_flow) { + ULONG64 max_data_rate[2] = {0}; + if (ql_get_global_qos_flow_ind_data_rate(pResponse, (void *)max_data_rate) == 0){} + } +#endif + } +#endif + else { + if (debug_qmi) + dbg_time("nobody care this qmi msg!!"); + } + pthread_mutex_unlock(&cm_command_mutex); +} + +#ifdef CONFIG_COEX_WWAN_STATE +static int requestGetCoexWWANState(void) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMI_COEX_GET_WWAN_STATE_RESP_MSG_LTE_BAND pLteBand; + static QMI_COEX_GET_WWAN_STATE_RESP_MSG_LTE_BAND oldLteBand = {-1, -1}; + int err; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_COEX, QMI_COEX_GET_WWAN_STATE_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + + if (err < 0 || pResponse == NULL) { + dbg_time("%s err = %d", __func__, err); + return err; + } + + pMUXMsg = &pResponse->MUXMsg; + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + dbg_time("%s QMUXResult = 0x%x, QMUXError = 0x%x", __func__, le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult), le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)); + err = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + free(pResponse); + return err; + } + pLteBand = (PQMI_COEX_GET_WWAN_STATE_RESP_MSG_LTE_BAND)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + + if (pLteBand && memcmp(pLteBand, &oldLteBand, sizeof(oldLteBand))) { + oldLteBand = *pLteBand; + dbg_time("%s ul_freq %d ul_bandwidth %d", __func__, le32_to_cpu(pLteBand->ul_band.freq), le32_to_cpu(pLteBand->ul_band.bandwidth)); + dbg_time("%s dl_freq %d dl_bandwidth %d", __func__, le32_to_cpu(pLteBand->dl_band.freq), le32_to_cpu(pLteBand->dl_band.bandwidth)); + } + free(pResponse); + return 0; +} +#endif + +static int requestSetEthMode(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse = NULL; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV linkProto; + UCHAR IpPreference; + UCHAR autoconnect_setting = 0; + QMAP_SETTING qmap_settings = {0}; + + qmap_settings.size = sizeof(qmap_settings); + + if (profile->qmap_mode) { + profile->rawIP = 1; + s_9x07 = profile->rawIP; + + qmap_settings.MuxId = profile->muxid; + + if (profile->hardware_interface == HARDWARE_PCIE) { //SDX20_PCIE + qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB + qmap_settings.ep_type = DATA_EP_TYPE_PCIE; + qmap_settings.iface_id = 0x04; + } + else if (profile->hardware_interface == HARDWARE_IPA) { // for inside sdx + qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB + qmap_settings.ep_type = DATA_EP_TYPE_EMBEDDED; + qmap_settings.iface_id = 1; //DSI_DEFAULT_EPID_IPA; + //skip for netmgrd send QMI_WDA_SET_DATA_FORMAT_REQ_V01 & QMI_DPM_OPEN_PORT_REQ_V01 + goto skip_WdaSetDataFormat; + } + else { // for MDM9x07&MDM9x40&SDX20 USB + qmap_settings.rx_urb_size = profile->qmap_size; //SDX24&SDX55 support 32KB + qmap_settings.ep_type = DATA_EP_TYPE_HSUSB; + qmap_settings.iface_id = 0x04; + if(profile->usb_dev.idProduct == 0x0316) //X35 Rmnet interface is 3 + qmap_settings.iface_id = 0x03; + } + + qmap_settings.ul_data_aggregation_max_datagrams = 11; //by test result, 11 can get best TPUT + qmap_settings.ul_data_aggregation_max_size = 8*1024; + qmap_settings.dl_minimum_padding = 0; //no effect when register to real netowrk + if(profile->qmap_version != 0x09) + profile->qmap_version = 0x05; + + qmap_version = profile->qmap_version; + if (profile->rmnet_info.size) { + qmap_settings.rx_urb_size = profile->rmnet_info.rx_urb_size; + qmap_settings.ep_type = profile->rmnet_info.ep_type; + qmap_settings.iface_id = profile->rmnet_info.iface_id; + qmap_settings.dl_minimum_padding = profile->rmnet_info.dl_minimum_padding; + qmap_version = profile->rmnet_info.qmap_version; + } + + if (!profile->wda_client) { + if (qmidev_is_gobinet(profile->qmichannel)) { + //when QMAP enabled, set data format in GobiNet driver + } + else if (profile->proxy[0]) { + /* the first running 'quectel-cm' had alloc wda client and set data format, + so we can ingore to set data format here. */ + } + goto skip_WdaSetDataFormat; + } + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMIWDS_ADMIN_SET_DATA_FORMAT_REQ, WdaSetDataFormat, (void *)&qmap_settings); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (linkProto != NULL) { + profile->rawIP = (le32_to_cpu(linkProto->Value) == 2); + s_9x07 = profile->rawIP; //MDM90x7 only support RAW IP, do not support Eth + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x16); + if (linkProto != NULL && profile->qmap_mode) { + qmap_settings.rx_urb_size = le32_to_cpu(linkProto->Value); + dbg_time("qmap_settings.rx_urb_size = %u", qmap_settings.rx_urb_size); //must same as rx_urb_size defined in GobiNet&qmi_wwan driver + } + +#ifdef QUECTEL_UL_DATA_AGG + if (qmap_settings.ul_data_aggregation_max_datagrams) + { + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x17); + if (linkProto != NULL) { + qmap_settings.ul_data_aggregation_max_datagrams = MIN(qmap_settings.ul_data_aggregation_max_datagrams, le32_to_cpu(linkProto->Value)); + dbg_time("qmap_settings.ul_data_aggregation_max_datagrams = %u", qmap_settings.ul_data_aggregation_max_datagrams); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x18); + if (linkProto != NULL) { + qmap_settings.ul_data_aggregation_max_size = MIN(qmap_settings.ul_data_aggregation_max_size, le32_to_cpu(linkProto->Value)); + dbg_time("qmap_settings.ul_data_aggregation_max_size = %u", qmap_settings.ul_data_aggregation_max_size); + } + + linkProto = (PQMIWDS_ADMIN_SET_DATA_FORMAT_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1A); + if (linkProto != NULL) { + qmap_settings.dl_minimum_padding = le32_to_cpu(linkProto->Value); + dbg_time("qmap_settings.dl_minimum_padding = %u", qmap_settings.dl_minimum_padding); + } + + if (qmap_settings.ul_data_aggregation_max_datagrams > 1) { + ql_set_driver_qmap_setting(profile, &qmap_settings); + } + } +#endif + + free(pResponse); + +skip_WdaSetDataFormat: + if (profile->enable_ipv4) { + if (profile->qmapnet_adapter[0]) { + // bind wds mux data port + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_BIND_MUX_DATA_PORT_REQ , WdsSetQMUXBindMuxDataPort, (void *)&qmap_settings); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + } + + // set ipv4 + IpPreference = IpFamilyV4; + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference); + err = QmiThreadSendQMI(pRequest, &pResponse); + if (pResponse) free(pResponse); + } + + if (profile->enable_ipv6) { + if (profile->qmapnet_adapter[0]) { + // bind wds ipv6 mux data port + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_IPV6, QMIWDS_BIND_MUX_DATA_PORT_REQ , WdsSetQMUXBindMuxDataPort, (void *)&qmap_settings); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + } + + // set ipv6 + IpPreference = IpFamilyV6; + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_IPV6, QMIWDS_SET_CLIENT_IP_FAMILY_PREF_REQ, WdsSetClientIPFamilyPref, (void *)&IpPreference); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_SET_AUTO_CONNECT_REQ , WdsSetAutoConnect, (void *)&autoconnect_setting); + QmiThreadSendQMI(pRequest, &pResponse); + if (pResponse) free(pResponse); + + return 0; +} + +#ifdef CONFIG_SIM +static int requestGetPINStatus(SIM_Status *pSIMStatus) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIDMS_UIM_PIN_STATUS pPin1Status = NULL; + //PQMIDMS_UIM_PIN_STATUS pPin2Status = NULL; + + if (s_9x07) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_PIN_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pPin1Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + //pPin2Status = (PQMIDMS_UIM_PIN_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + + if (pPin1Status != NULL) { + if (pPin1Status->PINStatus == QMI_PIN_STATUS_NOT_VERIF) { + *pSIMStatus = SIM_PIN; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_BLOCKED) { + *pSIMStatus = SIM_PUK; + } else if (pPin1Status->PINStatus == QMI_PIN_STATUS_PERM_BLOCKED) { + *pSIMStatus = SIM_BAD; + } + } + + free(pResponse); + return 0; +} + +static int requestGetSIMStatus(SIM_Status *pSIMStatus) { //RIL_REQUEST_GET_SIM_STATUS + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + const char * SIM_Status_String[] = { + "SIM_ABSENT", + "SIM_NOT_READY", + "SIM_READY", /* SIM_READY means the radio state is RADIO_STATE_SIM_READY */ + "SIM_PIN", + "SIM_PUK", + "SIM_NETWORK_PERSONALIZATION" + }; + + if (s_9x07) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_GET_CARD_STATUS_REQ, NULL, NULL); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_STATE_REQ, NULL, NULL); + + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + *pSIMStatus = SIM_ABSENT; + if (s_9x07) + { + PQMIUIM_CARD_STATUS pCardStatus = NULL; + PQMIUIM_PIN_STATE pPINState = NULL; + QMIUI_APP_INFO *appinfo = NULL; + UCHAR CardState = 0x01; + UCHAR PIN1State = QMI_PIN_STATUS_NOT_VERIF; + //UCHAR PIN1Retries; + //UCHAR PUK1Retries; + //UCHAR PIN2State; + //UCHAR PIN2Retries; + //UCHAR PUK2Retries; + + pCardStatus = (PQMIUIM_CARD_STATUS)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + + if (pCardStatus != NULL) + { + pPINState = (PQMIUIM_PIN_STATE)((PUCHAR)pCardStatus + sizeof(QMIUIM_CARD_STATUS) + pCardStatus->APP_INFO.AIDLength); + for(int app_index = 0; app_index < pCardStatus->NumApp - 1; app_index++) + { + if (pCardStatus->APP_INFO.AppType != UIM_APP_TYPE_USIM) + { + appinfo = (QMIUI_APP_INFO *)((PUCHAR)pPINState + sizeof(QMIUIM_PIN_STATE)); + pPINState = (PQMIUIM_PIN_STATE)((PUCHAR)appinfo + sizeof(QMIUI_APP_INFO) + appinfo->AIDLength); + } + } + + CardState = pCardStatus->CardState; + if (CardState == UIM_CARD_STATE_PRESENT) { + if (pPINState->UnivPIN == 1) + { + PIN1State = pCardStatus->UPINState; + //PIN1Retries = pCardStatus->UPINRetries; + //PUK1Retries = pCardStatus->UPUKRetries; + } + else + { + PIN1State = pPINState->PIN1State; + //PIN1Retries = pPINState->PIN1Retries; + //PUK1Retries = pPINState->PUK1Retries; + } + //PIN2State = pPINState->PIN2State; + //PIN2Retries = pPINState->PIN2Retries; + //PUK2Retries = pPINState->PUK2Retries; + } + } + + *pSIMStatus = SIM_ABSENT; + if ((CardState == 0x01) && ((PIN1State == QMI_PIN_STATUS_VERIFIED)|| (PIN1State == QMI_PIN_STATUS_DISABLED))) + { + *pSIMStatus = SIM_READY; + } + else if (CardState == 0x01) + { + if (PIN1State == QMI_PIN_STATUS_NOT_VERIF) + { + *pSIMStatus = SIM_PIN; + } + if ( PIN1State == QMI_PIN_STATUS_BLOCKED) + { + *pSIMStatus = SIM_PUK; + } + else if (PIN1State == QMI_PIN_STATUS_PERM_BLOCKED) + { + *pSIMStatus = SIM_BAD; + } + else if (PIN1State == QMI_PIN_STATUS_NOT_INIT || PIN1State == QMI_PIN_STATUS_VERIFIED || PIN1State == QMI_PIN_STATUS_DISABLED) + { + *pSIMStatus = SIM_READY; + } + } + else if (CardState == 0x00 || CardState == 0x02) + { + } + else + { + } + } + else + { + //UIM state. Values: + // 0x00 UIM initialization completed + // 0x01 UIM is locked or the UIM failed + // 0x02 UIM is not present + // 0x03 Reserved + // 0xFF UIM state is currently + //unavailable + if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x00) { + *pSIMStatus = SIM_READY; + } else if (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x01) { + *pSIMStatus = SIM_ABSENT; + err = requestGetPINStatus(pSIMStatus); + } else if ((pResponse->MUXMsg.UIMGetStateResp.UIMState == 0x02) || (pResponse->MUXMsg.UIMGetStateResp.UIMState == 0xFF)) { + *pSIMStatus = SIM_ABSENT; + } else { + *pSIMStatus = SIM_ABSENT; + } + } + dbg_time("%s SIMStatus: %s", __func__, SIM_Status_String[*pSIMStatus]); + + free(pResponse); + + return 0; +} + +static int requestEnterSimPin(const char *pPinCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (s_9x07) + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_VERIFY_PIN_REQ, UimVerifyPinReqSend, (void *)pPinCode); + else + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_VERIFY_PIN_REQ, DmsUIMVerifyPinReqSend, (void *)pPinCode); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_IMSI_ICCID +static int requestGetICCID(void) { //RIL_REQUEST_GET_IMSI + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMIUIM_CONTENT pUimContent; + int err; + + //dbg_time("enter requestGetICCID_2"); + + if (s_9x07) { + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_ICCID"); + err = QmiThreadSendQMI(pRequest, &pResponse); + } else { + return 0; + } + qmi_rsp_check_and_return(); + + pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pUimContent != NULL) { + static char DeviceICCID[32] = {'\0'}; + int i = 0, j = 0; + + for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content_len); ++i) { + char charmaps[] = "0123456789ABCDEF"; + + DeviceICCID[j++] = charmaps[(pUimContent->content[i] & 0x0F)]; + DeviceICCID[j++] = charmaps[((pUimContent->content[i] & 0xF0) >> 0x04)]; + } + DeviceICCID[j] = '\0'; + + dbg_time("%s DeviceICCID: %s", __func__, DeviceICCID); + } + + free(pResponse); + return 0; +} + +static int requestGetIMSI(void) { //RIL_REQUEST_GET_IMSI + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMIUIM_CONTENT pUimContent; + int err; + + if (s_9x07) { + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, QMIUIM_READ_TRANSPARENT_REQ, UimReadTransparentIMSIReqSend, (void *)"EF_IMSI"); + err = QmiThreadSendQMI(pRequest, &pResponse); + } else { + return 0; + } + qmi_rsp_check_and_return(); + + pUimContent = (PQMIUIM_CONTENT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pUimContent != NULL) { + static char DeviceIMSI[32] = {'\0'}; + int i = 0, j = 0; + + for (i = 0, j = 0; i < le16_to_cpu(pUimContent->content[0]); ++i) { + if (i != 0) + DeviceIMSI[j++] = (pUimContent->content[i+1] & 0x0F) + '0'; + DeviceIMSI[j++] = ((pUimContent->content[i+1] & 0xF0) >> 0x04) + '0'; + } + DeviceIMSI[j] = '\0'; + + dbg_time("%s DeviceIMSI: %s", __func__, DeviceIMSI); + } + + free(pResponse); + return 0; +} +#endif + +#if 1 +static void quectel_convert_cdma_mcc_2_ascii_mcc( USHORT *p_mcc, USHORT mcc ) +{ + unsigned int d1, d2, d3, buf = mcc + 111; + + if ( mcc == 0x3FF ) // wildcard + { + *p_mcc = 3; + } + else + { + d3 = buf % 10; + buf = ( d3 == 0 ) ? (buf-10)/10 : buf/10; + + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + +//dbg_time("d1:%d, d2:%d,d3:%d",d1,d2,d3); + if ( d1<10 && d2<10 && d3<10 ) + { + *p_mcc = d1*100+d2*10+d3; +#if 0 + *(p_mcc+0) = '0' + d1; + *(p_mcc+1) = '0' + d2; + *(p_mcc+2) = '0' + d3; +#endif + } + else + { + //dbg_time( "invalid digits %d %d %d", d1, d2, d3 ); + *p_mcc = 0; + } + } +} + +static void quectel_convert_cdma_mnc_2_ascii_mnc( USHORT *p_mnc, USHORT imsi_11_12) +{ + unsigned int d1, d2, buf = imsi_11_12 + 11; + + if ( imsi_11_12 == 0x7F ) // wildcard + { + *p_mnc = 7; + } + else + { + d2 = buf % 10; + buf = ( d2 == 0 ) ? (buf-10)/10 : buf/10; + + d1 = ( buf == 10 ) ? 0 : buf; + + if ( d1<10 && d2<10 ) + { + *p_mnc = d1*10 + d2; + } + else + { + //dbg_time( "invalid digits %d %d", d1, d2, 0 ); + *p_mnc = 0; + } + } +} + +static int requestGetHomeNetwork(USHORT *p_mcc, USHORT *p_mnc, USHORT *p_sid, USHORT *p_nid) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PHOME_NETWORK pHomeNetwork; + PHOME_NETWORK_SYSTEMID pHomeNetworkSystemID; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_HOME_NETWORK_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pHomeNetwork = (PHOME_NETWORK)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pHomeNetwork && p_mcc && p_mnc ) { + *p_mcc = le16_to_cpu(pHomeNetwork->MobileCountryCode); + *p_mnc = le16_to_cpu(pHomeNetwork->MobileNetworkCode); + //dbg_time("%s MobileCountryCode: %d, MobileNetworkCode: %d", __func__, *pMobileCountryCode, *pMobileNetworkCode); + } + + pHomeNetworkSystemID = (PHOME_NETWORK_SYSTEMID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pHomeNetworkSystemID && p_sid && p_nid) { + *p_sid = le16_to_cpu(pHomeNetworkSystemID->SystemID); //china-hefei: sid 14451 + *p_nid = le16_to_cpu(pHomeNetworkSystemID->NetworkID); + //dbg_time("%s SystemID: %d, NetworkID: %d", __func__, *pSystemID, *pNetworkID); + } + + free(pResponse); + + return 0; +} +#endif + +#if 0 +// Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length. +static const char * MCCMNC_CODES_HAVING_3DIGITS_MNC[] = { + "302370", "302720", "310260", + "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032", + "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040", + "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750", + "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800", + "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808", + "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816", + "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824", + "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832", + "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840", + "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848", + "405849", "405850", "405851", "405852", "405853", "405875", "405876", "405877", + "405878", "405879", "405880", "405881", "405882", "405883", "405884", "405885", + "405886", "405908", "405909", "405910", "405911", "405912", "405913", "405914", + "405915", "405916", "405917", "405918", "405919", "405920", "405921", "405922", + "405923", "405924", "405925", "405926", "405927", "405928", "405929", "405930", + "405931", "405932", "502142", "502143", "502145", "502146", "502147", "502148" +}; + +static const char * MCC_CODES_HAVING_3DIGITS_MNC[] = { + "302", //Canada + "310", //United States of America + "311", //United States of America + "312", //United States of America + "313", //United States of America + "314", //United States of America + "315", //United States of America + "316", //United States of America + "334", //Mexico + "338", //Jamaica + "342", //Barbados + "344", //Antigua and Barbuda + "346", //Cayman Islands + "348", //British Virgin Islands + "365", //Anguilla + "708", //Honduras (Republic of) + "722", //Argentine Republic + "732" //Colombia (Republic of) +}; + +int requestGetIMSI(const char **pp_imsi, USHORT *pMobileCountryCode, USHORT *pMobileNetworkCode) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + if (pp_imsi) *pp_imsi = NULL; + if (pMobileCountryCode) *pMobileCountryCode = 0; + if (pMobileNetworkCode) *pMobileNetworkCode = 0; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_UIM_GET_IMSI_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + if (pMUXMsg->UIMGetIMSIResp.TLV2Type == 0x01 && le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length) >= 5) { + int mnc_len = 2; + unsigned i; + char tmp[4]; + + if (pp_imsi) *pp_imsi = strndup((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), le16_to_cpu(pMUXMsg->UIMGetIMSIResp.TLV2Length)); + + for (i = 0; i < sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCCMNC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCCMNC_CODES_HAVING_3DIGITS_MNC[i], 6)) { + mnc_len = 3; + break; + } + } + if (mnc_len == 2) { + for (i = 0; i < sizeof(MCC_CODES_HAVING_3DIGITS_MNC)/sizeof(MCC_CODES_HAVING_3DIGITS_MNC[0]); i++) { + if (!strncmp((const char *)(&pMUXMsg->UIMGetIMSIResp.IMSI), MCC_CODES_HAVING_3DIGITS_MNC[i], 3)) { + mnc_len = 3; + break; + } + } + } + + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[0]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[1]; + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[2]; + tmp[3] = 0; + if (pMobileCountryCode) *pMobileCountryCode = atoi(tmp); + tmp[0] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[3]; + tmp[1] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[4]; + tmp[2] = 0; + if (mnc_len == 3) { + tmp[2] = (&pMUXMsg->UIMGetIMSIResp.IMSI)[6]; + } + if (pMobileNetworkCode) *pMobileNetworkCode = atoi(tmp); + } + + free(pResponse); + + return 0; +} +#endif + +static struct wwan_data_class_str class2str[] = { + {WWAN_DATA_CLASS_NONE, "UNKNOWN"}, + {WWAN_DATA_CLASS_GPRS, "GPRS"}, + {WWAN_DATA_CLASS_EDGE, "EDGE"}, + {WWAN_DATA_CLASS_UMTS, "UMTS"}, + {WWAN_DATA_CLASS_HSDPA, "HSDPA"}, + {WWAN_DATA_CLASS_HSUPA, "HSUPA"}, + {WWAN_DATA_CLASS_LTE, "LTE"}, + {WWAN_DATA_CLASS_5G_NSA, "5G_NSA"}, + {WWAN_DATA_CLASS_5G_SA, "5G_SA"}, + {WWAN_DATA_CLASS_1XRTT, "1XRTT"}, + {WWAN_DATA_CLASS_1XEVDO, "1XEVDO"}, + {WWAN_DATA_CLASS_1XEVDO_REVA, "1XEVDO_REVA"}, + {WWAN_DATA_CLASS_1XEVDV, "1XEVDV"}, + {WWAN_DATA_CLASS_3XRTT, "3XRTT"}, + {WWAN_DATA_CLASS_1XEVDO_REVB, "1XEVDO_REVB"}, + {WWAN_DATA_CLASS_UMB, "UMB"}, + {WWAN_DATA_CLASS_CUSTOM, "CUSTOM"}, +}; + +static const char *wwan_data_class2str(ULONG class) +{ + unsigned int i = 0; + for (i = 0; i < sizeof(class2str)/sizeof(class2str[0]); i++) { + if (class2str[i].class == class) { + return class2str[i].str; + } + } + return "UNKNOWN"; +} + +static USHORT char2ushort(UCHAR str[3]) { + int i; + char temp[4]; + USHORT ret= 0; + + memcpy(temp, str, 3); + temp[3] = '\0'; + + for (i = 0; i < 4; i++) { + if ((UCHAR)temp[i] == 0xFF) { + temp[i] = '\0'; + } + } + ret = (USHORT)atoi(temp); + + return ret; +} + +static int requestRegistrationState2(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + LONG remainingLen; + PSERVICE_STATUS_INFO pServiceStatusInfo; + int is_lte = 0; + PCDMA_SYSTEM_INFO pCdmaSystemInfo; + PHDR_SYSTEM_INFO pHdrSystemInfo; + PGSM_SYSTEM_INFO pGsmSystemInfo; + PWCDMA_SYSTEM_INFO pWcdmaSystemInfo; + PLTE_SYSTEM_INFO pLteSystemInfo; + PTDSCDMA_SYSTEM_INFO pTdscdmaSystemInfo; + PNR5G_SYSTEM_INFO pNr5gSystemInfo; + UCHAR DeviceClass = 0; + ULONG DataCapList = 0; + + /* Additional LTE System Info - Availability of Dual connectivity of E-UTRA with NR5G */ + uint8_t endc_available_valid = 0; /**< Must be set to true if endc_available is being passed */ + uint8_t endc_available = 0x00; + /**< + Upper layer indication in LTE SIB2. Values: \n + - 0x00 -- 5G Not available \n + - 0x01 -- 5G Available + + */ + /* Additional LTE System Info - DCNR restriction Info */ + uint8_t restrict_dcnr_valid = 0; /**< Must be set to true if restrict_dcnr is being passed */ + uint8_t restrict_dcnr = 0x01; + /**< + DCNR restriction in NAS attach/TAU accept. Values: \n + - 0x00 -- Not restricted \n + - 0x01 -- Restricted + */ + + *pPSAttachedState = 0; + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SYS_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pServiceStatusInfo = (PSERVICE_STATUS_INFO)(((PCHAR)&pMUXMsg->GetSysInfoResp) + QCQMUX_MSG_HDR_SIZE); + remainingLen = le16_to_cpu(pMUXMsg->GetSysInfoResp.Length); + + s_is_cdma = 0; + s_5g_type = WWAN_DATA_CLASS_NONE; + s_hdr_personality = 0; + //dbg_time("pServiceStatusInfo->TLVType:%d\n", pServiceStatusInfo->TLVType); + //dbg_time("remainingLen:%ld\n", remainingLen); + while (remainingLen > 0) { + switch (pServiceStatusInfo->TLVType) { + case 0x10: // CDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_1XRTT| + WWAN_DATA_CLASS_1XEVDO| + WWAN_DATA_CLASS_1XEVDO_REVA| + WWAN_DATA_CLASS_1XEVDV| + WWAN_DATA_CLASS_1XEVDO_REVB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x11: // HDR + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_3XRTT| + WWAN_DATA_CLASS_UMB; + DeviceClass = DEVICE_CLASS_CDMA; + s_is_cdma = (0 == is_lte); + } + break; + case 0x12: // GSM + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_GPRS| + WWAN_DATA_CLASS_EDGE; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x13: // WCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_UMTS; + DeviceClass = DEVICE_CLASS_GSM; + } + break; + case 0x14: // LTE + if (pServiceStatusInfo->SrvStatus == 0x02) { + DataCapList = WWAN_DATA_CLASS_LTE; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x4A: // NR5G Service Status Info + if (pServiceStatusInfo->SrvStatus == NAS_SYS_SRV_STATUS_SRV_V01) { + DataCapList |= WWAN_DATA_CLASS_5G_SA; + DeviceClass = DEVICE_CLASS_GSM; + is_lte = 1; + s_is_cdma = 0; + } + break; + case 0x4B: // NR5G System Info + pNr5gSystemInfo = (PNR5G_SYSTEM_INFO)pServiceStatusInfo; + if (pNr5gSystemInfo->srv_domain_valid == 0x01) { + if (pNr5gSystemInfo->srv_domain & SYS_SRV_DOMAIN_PS_ONLY_V01) { + *pPSAttachedState = 1; + } + } + + if (pNr5gSystemInfo->network_id_valid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pNr5gSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pNr5gSystemInfo->MNC); + //dbg_time("%d", __LINE__); + } + break; + case 0x4E: //Additional LTE System Info - Availability of Dual Connectivity of E-UTRA with NR5G + endc_available_valid = 1; + endc_available = pServiceStatusInfo->SrvStatus; + break; + + case 0x4F: //Additional LTE System Info - DCNR restriction Info + restrict_dcnr_valid = 1; + restrict_dcnr = pServiceStatusInfo->SrvStatus; + break; + + case 0x24: // TDSCDMA + if (pServiceStatusInfo->SrvStatus == 0x02) { + pDataCapStr = "TD-SCDMA"; + } + break; + case 0x15: // CDMA + // CDMA_SYSTEM_INFO + pCdmaSystemInfo = (PCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pCdmaSystemInfo->SrvDomainValid == 0x01) { + if (pCdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#if 0 + if (pCdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pCdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#endif + if (pCdmaSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pCdmaSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pCdmaSystemInfo->MNC); + //dbg_time("%d", __LINE__); + } + break; + case 0x16: // HDR + // HDR_SYSTEM_INFO + pHdrSystemInfo = (PHDR_SYSTEM_INFO)pServiceStatusInfo; + if (pHdrSystemInfo->SrvDomainValid == 0x01) { + if (pHdrSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#if 0 + if (pHdrSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pHdrSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + s_is_cdma = (0 == is_lte); + } + } +#endif + if (*pPSAttachedState && pHdrSystemInfo->HdrPersonalityValid == 0x01) { + if (pHdrSystemInfo->HdrPersonality == 0x03) + s_hdr_personality = 0x02; + //else if (pHdrSystemInfo->HdrPersonality == 0x02) + // s_hdr_personality = 0x01; + } + USHORT cmda_mcc = 0, cdma_mnc = 0; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + //dbg_time("%d", __LINE__); + } + break; + case 0x17: // GSM + // GSM_SYSTEM_INFO + pGsmSystemInfo = (PGSM_SYSTEM_INFO)pServiceStatusInfo; + if (pGsmSystemInfo->SrvDomainValid == 0x01) { + if (pGsmSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pGsmSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pGsmSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pGsmSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pGsmSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pGsmSystemInfo->MNC); + //dbg_time("%d", __LINE__); + } + break; + case 0x18: // WCDMA + // WCDMA_SYSTEM_INFO + pWcdmaSystemInfo = (PWCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pWcdmaSystemInfo->SrvDomainValid == 0x01) { + if (pWcdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pWcdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pWcdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pWcdmaSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pWcdmaSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pWcdmaSystemInfo->MNC); + //dbg_time("%d", __LINE__); + } + break; + case 0x19: // LTE_SYSTEM_INFO + // LTE_SYSTEM_INFO + pLteSystemInfo = (PLTE_SYSTEM_INFO)pServiceStatusInfo; + if (pLteSystemInfo->SrvDomainValid == 0x01) { + if (pLteSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } +#if 0 + if (pLteSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pLteSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + is_lte = 1; + s_is_cdma = 0; + } + } +#endif + if (pLteSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pLteSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pLteSystemInfo->MNC); + //dbg_time("%d", __LINE__); + } + break; + case 0x25: // TDSCDMA + // TDSCDMA_SYSTEM_INFO + pTdscdmaSystemInfo = (PTDSCDMA_SYSTEM_INFO)pServiceStatusInfo; + if (pTdscdmaSystemInfo->SrvDomainValid == 0x01) { + if (pTdscdmaSystemInfo->SrvDomain & 0x02) { + *pPSAttachedState = 1; + } + } +#if 0 + if (pTdscdmaSystemInfo->SrvCapabilityValid == 0x01) { + *pPSAttachedState = 0; + if (pTdscdmaSystemInfo->SrvCapability & 0x02) { + *pPSAttachedState = 1; + } + } +#endif + if (pTdscdmaSystemInfo->NetworkIdValid == 0x01) { + MobileCountryCode = (USHORT)char2ushort(pTdscdmaSystemInfo->MCC); + MobileNetworkCode = (USHORT)char2ushort(pTdscdmaSystemInfo->MNC); + //dbg_time("%d", __LINE__); + } + break; + default: + break; + } /* switch (pServiceStatusInfo->TLYType) */ + + remainingLen -= (le16_to_cpu(pServiceStatusInfo->TLVLength) + 3); + pServiceStatusInfo = (PSERVICE_STATUS_INFO)((PCHAR)&pServiceStatusInfo->TLVLength + le16_to_cpu(pServiceStatusInfo->TLVLength) + sizeof(USHORT)); + } /* while (remainingLen > 0) */ + + if (DataCapList & WWAN_DATA_CLASS_LTE) { + if (endc_available_valid && restrict_dcnr_valid) { + if (endc_available && !restrict_dcnr) { + DataCapList |= WWAN_DATA_CLASS_5G_NSA; + } + } + } + + if (DeviceClass == DEVICE_CLASS_CDMA) { + if (s_hdr_personality == 2) { + pDataCapStr = s_hdr_personality == 2 ? "eHRPD" : "HRPD"; + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVB); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO_REVA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO_REVA); + } else if (DataCapList & WWAN_DATA_CLASS_1XEVDO) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XEVDO); + } else if (DataCapList & WWAN_DATA_CLASS_1XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_1XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_3XRTT) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_3XRTT); + } else if (DataCapList & WWAN_DATA_CLASS_UMB) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMB); + } + } else { + if (DataCapList & WWAN_DATA_CLASS_5G_SA) { + s_5g_type = WWAN_DATA_CLASS_5G_SA; + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_5G_SA); + } else if (DataCapList & WWAN_DATA_CLASS_5G_NSA) { + s_5g_type = WWAN_DATA_CLASS_5G_NSA; + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_5G_NSA); + } else if (DataCapList & WWAN_DATA_CLASS_LTE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_LTE); + } else if ((DataCapList & WWAN_DATA_CLASS_HSDPA) && (DataCapList & WWAN_DATA_CLASS_HSUPA)) { + pDataCapStr = "HSDPA_HSUPA"; + } else if (DataCapList & WWAN_DATA_CLASS_HSDPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSDPA); + } else if (DataCapList & WWAN_DATA_CLASS_HSUPA) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_HSUPA); + } else if (DataCapList & WWAN_DATA_CLASS_UMTS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_UMTS); + } else if (DataCapList & WWAN_DATA_CLASS_EDGE) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_EDGE); + } else if (DataCapList & WWAN_DATA_CLASS_GPRS) { + pDataCapStr = wwan_data_class2str(WWAN_DATA_CLASS_GPRS); + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +static int requestRegistrationState(UCHAR *pPSAttachedState) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMINAS_CURRENT_PLMN_MSG pCurrentPlmn; + PSERVING_SYSTEM pServingSystem; + PQMINAS_DATA_CAP pDataCap; + USHORT MobileCountryCode = 0; + USHORT MobileNetworkCode = 0; + const char *pDataCapStr = "UNKNOW"; + + if (s_9x07) { + return requestRegistrationState2(pPSAttachedState); + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SERVING_SYSTEM_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pCurrentPlmn = (PQMINAS_CURRENT_PLMN_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (pCurrentPlmn) { + MobileCountryCode = le16_to_cpu(pCurrentPlmn->MobileCountryCode); + MobileNetworkCode = le16_to_cpu(pCurrentPlmn->MobileNetworkCode); + } + + *pPSAttachedState = 0; + pServingSystem = (PSERVING_SYSTEM)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pServingSystem) { + //Packet-switched domain attach state of the mobile. + //0x00 PS_UNKNOWN ?Unknown or not applicable + //0x01 PS_ATTACHED ?Attached + //0x02 PS_DETACHED ?Detached + *pPSAttachedState = pServingSystem->RegistrationState; + if (pServingSystem->RegistrationState == 0x01) //0x01 ?C REGISTERED ?C Registered with a network + *pPSAttachedState = pServingSystem->PSAttachedState; + else { + //MobileCountryCode = MobileNetworkCode = 0; + *pPSAttachedState = 0x02; + } + } + + pDataCap = (PQMINAS_DATA_CAP)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pDataCap && pDataCap->DataCapListLen) { + UCHAR *DataCap = &pDataCap->DataCap; + if (pDataCap->DataCapListLen == 2) { + if ((DataCap[0] == 0x06) && ((DataCap[1] == 0x08) || (DataCap[1] == 0x0A))) + DataCap[0] = DataCap[1]; + } + switch (DataCap[0]) { + case 0x01: pDataCapStr = "GPRS"; break; + case 0x02: pDataCapStr = "EDGE"; break; + case 0x03: pDataCapStr = "HSDPA"; break; + case 0x04: pDataCapStr = "HSUPA"; break; + case 0x05: pDataCapStr = "UMTS"; break; + case 0x06: pDataCapStr = "1XRTT"; break; + case 0x07: pDataCapStr = "1XEVDO"; break; + case 0x08: pDataCapStr = "1XEVDO_REVA"; break; + case 0x09: pDataCapStr = "GPRS"; break; + case 0x0A: pDataCapStr = "1XEVDO_REVB"; break; + case 0x0B: pDataCapStr = "LTE"; break; + case 0x0C: pDataCapStr = "HSDPA"; break; + case 0x0D: pDataCapStr = "HSDPA"; break; + default: pDataCapStr = "UNKNOW"; break; + } + } + + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && pServingSystem->RadioIF == 0x09) { + pDataCapStr = "TD-SCDMA"; + } + + s_is_cdma = 0; + if (pServingSystem && pServingSystem->RegistrationState == 0x01 && pServingSystem->InUseRadioIF && (pServingSystem->RadioIF == 0x01 || pServingSystem->RadioIF == 0x02)) { + USHORT cmda_mcc = 0, cdma_mnc = 0; + s_is_cdma = 1; + if(!requestGetHomeNetwork(&cmda_mcc, &cdma_mnc,NULL, NULL) && cmda_mcc) { + quectel_convert_cdma_mcc_2_ascii_mcc(&MobileCountryCode, cmda_mcc); + quectel_convert_cdma_mnc_2_ascii_mnc(&MobileNetworkCode, cdma_mnc); + } + if (1) { + PQCQMUX_TLV pTLV = (PQCQMUX_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); + if (pTLV) + s_hdr_personality = pTLV->Value; + else + s_hdr_personality = 0; + if (s_hdr_personality == 2) + pDataCapStr = "eHRPD"; + } + } + + dbg_time("%s MCC: %d, MNC: %d, PS: %s, DataCap: %s", __func__, + MobileCountryCode, MobileNetworkCode, (*pPSAttachedState == 1) ? "Attached" : "Detached" , pDataCapStr); + + free(pResponse); + + return 0; +} + +static int requestQueryDataCall(UCHAR *pConnectionStatus, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_PKT_SRVC_TLV pPktSrvc; + UCHAR oldConnectionStatus = *pConnectionStatus; + UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_PKT_SRVC_STATUS_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + pPktSrvc = (PQMIWDS_PKT_SRVC_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (pPktSrvc) { + *pConnectionStatus = pPktSrvc->ConnectionStatus; + if ((le16_to_cpu(pPktSrvc->TLVLength) == 2) && (pPktSrvc->ReconfigReqd == 0x01)) + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (*pConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) { + if (curIpFamily == IpFamilyV4) + WdsConnectionIPv4Handle = 0; + else + WdsConnectionIPv6Handle = 0; + } + + if (oldConnectionStatus != *pConnectionStatus || debug_qmi) { + dbg_time("%s %sConnectionStatus: %s", __func__, (curIpFamily == IpFamilyV4) ? "IPv4" : "IPv6", + (*pConnectionStatus == QWDS_PKT_DATA_CONNECTED) ? "CONNECTED" : "DISCONNECTED"); + } + + free(pResponse); + return 0; +} + +static int requestSetupDataCall(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err = 0; + UCHAR QMIType = (curIpFamily == IpFamilyV4) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + +//DualIPSupported means can get ipv4 & ipv6 address at the same time, one wds for ipv4, the other wds for ipv6 + profile->curIpFamily = curIpFamily; + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_START_NETWORK_INTERFACE_REQ, WdsStartNwInterfaceReq, profile); + err = QmiThreadSendQMITimeout(pRequest, &pResponse, 120 * 1000, __func__); + qmi_rsp_check(); + + if (le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXResult) || le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError)) { + PQMI_TLV_HDR pTLVHdr; + + pTLVHdr = GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (pTLVHdr) { + uint16_t *data16 = (uint16_t *)(pTLVHdr+1); + uint16_t call_end_reason = le16_to_cpu(data16[0]); + dbg_time("call_end_reason is %d", call_end_reason); + } + + pTLVHdr = GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (pTLVHdr) { + uint16_t *data16 = (uint16_t *)(pTLVHdr+1); + uint16_t call_end_reason_type = le16_to_cpu(data16[0]); + uint16_t verbose_call_end_reason = le16_to_cpu(data16[1]); + + dbg_time("call_end_reason_type is %d", call_end_reason_type); + dbg_time("call_end_reason_verbose is %d", verbose_call_end_reason); + } + + err = le16_to_cpu(pMUXMsg->QMUXMsgHdrResp.QMUXError); + free(pResponse); + return err; + } + + if (curIpFamily == IpFamilyV4) { + WdsConnectionIPv4Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s WdsConnectionIPv4Handle: 0x%08x", __func__, WdsConnectionIPv4Handle); + } else { + WdsConnectionIPv6Handle = le32_to_cpu(pResponse->MUXMsg.StartNwInterfaceResp.Handle); + dbg_time("%s WdsConnectionIPv6Handle: 0x%08x", __func__, WdsConnectionIPv6Handle); + } + + free(pResponse); + + return 0; +} + +static int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + + (void)profile; + if (curIpFamily == IpFamilyV4 && WdsConnectionIPv4Handle == 0) + return 0; + if (curIpFamily == IpFamilyV6 && WdsConnectionIPv6Handle == 0) + return 0; + + dbg_time("%s WdsConnectionIPv%dHandle", __func__, curIpFamily == IpFamilyV4 ? 4 : 6); + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_STOP_NETWORK_INTERFACE_REQ , WdsStopNwInterfaceReq, &curIpFamily); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + if (curIpFamily == IpFamilyV4) + WdsConnectionIPv4Handle = 0; + else + WdsConnectionIPv6Handle = 0; + free(pResponse); + return 0; +} + +static int requestGetIPAddress(PROFILE_T *profile, int curIpFamily) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR pIpv4Addr; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR pIpv6Addr = NULL; + PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU pMtu; + IPV4_T *pIpv4 = &profile->ipv4; + IPV6_T *pIpv6 = &profile->ipv6; + UCHAR QMIType = (curIpFamily == 0x04) ? QMUX_TYPE_WDS : QMUX_TYPE_WDS_IPV6; + PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR pPCSCFIpv6Addr; + PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR pPCSCFIpv4Addr; + + if (curIpFamily == IpFamilyV4) { + memset(pIpv4, 0x00, sizeof(IPV4_T)); + if (WdsConnectionIPv4Handle == 0) + return 0; + } else if (curIpFamily == IpFamilyV6) { + memset(pIpv6, 0x00, sizeof(IPV6_T)); + if (WdsConnectionIPv6Handle == 0) + return 0; + } + + pRequest = ComposeQMUXMsg(QMIType, QMIWDS_GET_RUNTIME_SETTINGS_REQ, WdsGetRuntimeSettingReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pPCSCFIpv6Addr = (PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x2e); // 0x2e - pcscf ipv6 address + if (pPCSCFIpv6Addr) { + if (pPCSCFIpv6Addr->PCSCFNumber == 1) { + UCHAR *PCSCFIpv6Addr1 = (UCHAR *)(pPCSCFIpv6Addr + 1); + memcpy(profile->PCSCFIpv6Addr1, PCSCFIpv6Addr1, 16); + }else if (pPCSCFIpv6Addr->PCSCFNumber == 2) { + UCHAR *PCSCFIpv6Addr1 = (UCHAR *)(pPCSCFIpv6Addr + 1); + UCHAR *PCSCFIpv6Addr2 = PCSCFIpv6Addr1 + 16; + memcpy(profile->PCSCFIpv6Addr1, PCSCFIpv6Addr1, 16); + memcpy(profile->PCSCFIpv6Addr2, PCSCFIpv6Addr2, 16); + } + } + + pPCSCFIpv4Addr = (PQMIWDS_GET_RUNNING_SETTINGS_PCSCF_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x23); // 0x23 - pcscf ipv4 address + if (pPCSCFIpv4Addr) { + if (pPCSCFIpv4Addr->PCSCFNumber == 1) { + UCHAR *PCSCFIpv4Addr1 = (UCHAR *)(pPCSCFIpv4Addr + 1); + memcpy(&profile->PCSCFIpv4Addr1, PCSCFIpv4Addr1, 4); + }else if (pPCSCFIpv4Addr->PCSCFNumber == 2) { + UCHAR *PCSCFIpv4Addr1 = (UCHAR *)(pPCSCFIpv4Addr + 1); + UCHAR *PCSCFIpv4Addr2 = PCSCFIpv4Addr1 + 4; + memcpy(&profile->PCSCFIpv4Addr1, PCSCFIpv4Addr1, 4); + memcpy(&profile->PCSCFIpv4Addr2, PCSCFIpv4Addr2, 4); + } + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4PRIMARYDNS); + if (pIpv4Addr) { + pIpv4->DnsPrimary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SECONDARYDNS); + if (pIpv4Addr) { + pIpv4->DnsSecondary = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4GATEWAY); + if (pIpv4Addr) { + pIpv4->Gateway = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4SUBNET); + if (pIpv4Addr) { + pIpv4->SubnetMask = pIpv4Addr->IPV4Address; + } + + pIpv4Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV4_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV4); + if (pIpv4Addr) { + pIpv4->Address = pIpv4Addr->IPV4Address; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6PRIMARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsPrimary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6SECONDARYDNS); + if (pIpv6Addr) { + memcpy(pIpv6->DnsSecondary, pIpv6Addr->IPV6Address, 16); + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6GATEWAY); + if (pIpv6Addr) { + memcpy(pIpv6->Gateway, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthGateway = pIpv6Addr->PrefixLength; + } + + pIpv6Addr = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_IPV6_ADDR)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_IPV6); + if (pIpv6Addr) { + memcpy(pIpv6->Address, pIpv6Addr->IPV6Address, 16); + pIpv6->PrefixLengthIPAddr = pIpv6Addr->PrefixLength; + } + + pMtu = (PQMIWDS_GET_RUNTIME_SETTINGS_TLV_MTU)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, QMIWDS_GET_RUNTIME_SETTINGS_TLV_TYPE_MTU); + if (pMtu) { + if (curIpFamily == IpFamilyV4) + pIpv4->Mtu = le32_to_cpu(pMtu->Mtu); + else + pIpv6->Mtu = le32_to_cpu(pMtu->Mtu); + } + + free(pResponse); + return 0; +} + +#ifdef CONFIG_APN +static int requestSetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + const char *new_apn = profile->apn ? profile->apn : ""; + const char *new_user = profile->user ? profile->user : ""; + const char *new_pd = profile->pd ? profile->pd : ""; + const char *ipStr[] = {"IPV4", "NULL", "IPV6", "IPV4V6"}; + + dbg_time("%s[pdp:%d index:%d] %s/%s/%s/%d/%s", __func__, profile->pdp, profile->profile_index, profile->apn, profile->user, profile->pd, profile->auth,ipStr[profile->iptype]); + if (!profile->profile_index) + return -1; + + if ( !strcmp(profile->old_apn, new_apn) && !strcmp(profile->old_user, new_user) + && !strcmp(profile->old_pd, new_pd) + && profile->old_iptype == profile->iptype + && profile->old_auth == profile->auth) + { + dbg_time("no need to set skip the rest"); + return 0; + } + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_MODIFY_PROFILE_SETTINGS_REQ, WdsModifyProfileSettingsReq, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 1; +} + +static int requestGetProfile(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + PQMIWDS_APNNAME pApnName; + PQMIWDS_USERNAME pUserName; + PQMIWDS_PD pPd; + PQMIWDS_AUTH_PREFERENCE pAuthPref; + PQMIWDS_IPTYPE pIpType; + PQMIWDS_PDPCONTEXT pPdpContext; + PQMIWDS_PROFILELIST pProfileList; + + const char *ipStr[] = {"IPV4", "NULL", "IPV6", "IPV4V6"}; + + profile->old_apn[0] = profile->old_user[0] = profile->old_pd[0] = '\0'; + profile->old_auth = 0; + profile->old_iptype = 0; + if (profile->enable_ipv4 && profile->enable_ipv6) + profile->iptype = 3; + else if (profile->enable_ipv6) + profile->iptype = 2; + else + profile->iptype = 0; + + if (!profile->pdp) + return 0; + +_re_check: + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_LIST_REQ, WdsGetProfileListReqSend, profile); + err = QmiThreadSendQMI(pRequest, &pResponse);s_pResponse = malloc(le16_to_cpu(pResponse->QMIHdr.Length) + 1); + qmi_rsp_check_and_return(); + + pProfileList = (PQMIWDS_PROFILELIST)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + uint8 profile_indexs[42] = {0}; + uint8 profile_num = pProfileList->ProfileList[0]; + uint8 i; + if(profile_num >= 1) + { + uint8 j = 0; + uint8 k = 2; + for(i=0; iProfileList[k]; + if(pProfileList->ProfileList[++k] == 0) + k+=2; + else + k+=2+pProfileList->ProfileList[k]; + } + } + free(pResponse); + + for(i=0; iprofile_index = profile_indexs[i]; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_GET_PROFILE_SETTINGS_REQ, WdsGetProfileSettingsReqSend, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pPdpContext = (PQMIWDS_PDPCONTEXT)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x25); + if(pPdpContext->pdp_context == profile->pdp) + break; + else + free(pResponse); + + if(i == profile_num-1) + { + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS, QMIWDS_CREATE_PROFILE_REQ, WdsCreateProfileSettingsReqSend, profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + free(pResponse); + goto _re_check; + } + } + + + pApnName = (PQMIWDS_APNNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + pUserName = (PQMIWDS_USERNAME)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1B); + pPd = (PQMIWDS_PD)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1C); + pAuthPref = (PQMIWDS_AUTH_PREFERENCE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1D); + pIpType = (PQMIWDS_IPTYPE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + + if (pApnName/* && le16_to_cpu(pApnName->TLVLength)*/) + uchar2char(profile->old_apn, sizeof(profile->old_apn), &pApnName->ApnName, le16_to_cpu(pApnName->TLVLength)); + if (pUserName/* && pUserName->UserName*/) + uchar2char(profile->old_user, sizeof(profile->old_user), &pUserName->UserName, le16_to_cpu(pUserName->TLVLength)); + if (pPd/* && le16_to_cpu(pPd->TLVLength)*/) + uchar2char(profile->old_pd, sizeof(profile->old_pd), &pPd->Pd, le16_to_cpu(pPd->TLVLength)); + if (pAuthPref/* && le16_to_cpu(pAuthPref->TLVLength)*/) { + profile->old_auth = pAuthPref->AuthPreference; + } + if (pIpType) { + profile->old_iptype = pIpType->IPType; + } + + dbg_time("%s[pdp:%d index:%d] %s/%s/%s/%d/%s", __func__, profile->pdp, profile->profile_index, profile->old_apn, profile->old_user, profile->old_pd, profile->old_auth, ipStr[profile->old_iptype]); + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_SIGNALINFO +static int requestGetSignalInfo(void) +{ + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_SIG_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + + // CDMA + { + PQMINAS_SIG_INFO_CDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_CDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s CDMA: RSSI %d dBm, ECIO %.1lf dBm", __func__, + ptlv->rssi, (-0.5) * (double)ptlv->ecio); + } + } + + // HDR + { + PQMINAS_SIG_INFO_HDR_TLV_MSG ptlv = (PQMINAS_SIG_INFO_HDR_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s HDR: RSSI %d dBm, ECIO %.1lf dBm, IO %d dBm", __func__, + ptlv->rssi, (-0.5) * (double)ptlv->ecio, ptlv->io); + } + } + + // GSM + { + PQMINAS_SIG_INFO_GSM_TLV_MSG ptlv = (PQMINAS_SIG_INFO_GSM_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s GSM: RSSI %d dBm", __func__, ptlv->rssi); + } + } + + // WCDMA + { + PQMINAS_SIG_INFO_WCDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_WCDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x13); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s WCDMA: RSSI %d dBm, ECIO %.1lf dBm", __func__, + ptlv->rssi, (-0.5) * (double)ptlv->ecio); + } + } + + // LTE + { + PQMINAS_SIG_INFO_LTE_TLV_MSG ptlv = (PQMINAS_SIG_INFO_LTE_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s LTE: RSSI %d dBm, RSRQ %d dB, RSRP %d dBm, SNR %.1lf dB", __func__, + ptlv->rssi, ptlv->rsrq, ptlv->rsrp, (0.1) * (double)ptlv->snr); + } + } + + // TDSCDMA + { + PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_TDSCDMA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x15); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s LTE: RSCP %d dBm", __func__, ptlv->rscp); + } + } + + // 5G_NSA + if (s_5g_type == WWAN_DATA_CLASS_5G_NSA) + { + PQMINAS_SIG_INFO_5G_NSA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_5G_NSA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x17); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s 5G_NSA: RSRP %d dBm, SNR %.1lf dB", __func__, ptlv->rsrp, (0.1) * (double)ptlv->snr); + } + } + + // 5G_SA + if (s_5g_type == WWAN_DATA_CLASS_5G_SA) + { + PQMINAS_SIG_INFO_5G_SA_TLV_MSG ptlv = (PQMINAS_SIG_INFO_5G_SA_TLV_MSG)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x18); + if (ptlv && ptlv->TLVLength) + { + dbg_time("%s 5G_SA: NR5G_RSRQ %d dB", __func__, ptlv->nr5g_rsrq); + } + } + + free(pResponse); + return 0; +} +#endif + +#ifdef CONFIG_VERSION +static int requestBaseBandVersion(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PDEVICE_REV_ID revId; + int err; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_GET_DEVICE_REV_ID_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + revId = (PDEVICE_REV_ID)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + + if (revId && le16_to_cpu(revId->TLVLength)) + { + uchar2char(profile->BaseBandVersion, sizeof(profile->BaseBandVersion), &revId->RevisionID, le16_to_cpu(revId->TLVLength)); + dbg_time("%s %s", __func__, profile->BaseBandVersion); + } + + free(pResponse); + return 0; +} +#endif + +static USHORT DmsSetOperatingModeReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->SetOperatingModeReq.TLVType = 0x01; + pMUXMsg->SetOperatingModeReq.TLVLength = cpu_to_le16(1); + pMUXMsg->SetOperatingModeReq.OperatingMode = *((UCHAR *)arg); + + return sizeof(QMIDMS_SET_OPERATING_MODE_REQ_MSG); +} + +static USHORT UimSetCardSlotReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->UIMSetCardSlotReq.TLVType = 0x01; + pMUXMsg->UIMSetCardSlotReq.TLVLength = cpu_to_le16(1); + pMUXMsg->UIMSetCardSlotReq.slot = *((UCHAR *)arg); + + return sizeof(QMIUIM_SET_CARD_SLOT_REQ_MSG); +} + +static int requestRadioPower(int state) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + UCHAR OperatingMode = (!!state) ? DMS_OP_MODE_ONLINE : DMS_OP_MODE_LOW_POWER; + USHORT SimOp = (!!state) ? QMIUIM_POWER_UP : QMIUIM_POWER_DOWN; + UCHAR cardSlot = 0x01; + + dbg_time("%s(%d)", __func__, state); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_DMS, QMIDMS_SET_OPERATING_MODE_REQ, DmsSetOperatingModeReq, &OperatingMode); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + free(pResponse); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_UIM, SimOp, UimSetCardSlotReq, &cardSlot); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + free(pResponse); + + return 0; +} + +static USHORT WdaSetLoopBackReq(PQMUX_MSG pMUXMsg, void *arg) { + (void)arg; + pMUXMsg->SetLoopBackReq.loopback_state.TLVType = 0x01; + pMUXMsg->SetLoopBackReq.loopback_state.TLVLength = cpu_to_le16(1); + + pMUXMsg->SetLoopBackReq.replication_factor.TLVType = 0x10; + pMUXMsg->SetLoopBackReq.replication_factor.TLVLength = cpu_to_le16(4); + + return sizeof(QMI_WDA_SET_LOOPBACK_CONFIG_REQ_MSG); +} + +static int requestSetLoopBackState(UCHAR loopback_state, ULONG replication_factor) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + + dbg_time("%s(loopback_state=%d, replication_factor=%u)", __func__, loopback_state, replication_factor); + + pRequest = ComposeQMUXMsg(QMUX_TYPE_WDS_ADMIN, QMI_WDA_SET_LOOPBACK_CONFIG_REQ, WdaSetLoopBackReq, NULL); + pRequest->MUXMsg.SetLoopBackReq.loopback_state.TLVVaule = loopback_state; + pRequest->MUXMsg.SetLoopBackReq.replication_factor.TLVVaule = cpu_to_le16(replication_factor); + + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + free(pResponse); + return 0; +} + +#ifdef CONFIG_ENABLE_QOS +static USHORT QosSetBindMuxDataPort(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->QosBindDataPortReq.EpIdTlv.TLVType = 0x10; + pMUXMsg->QosBindDataPortReq.EpIdTlv.TLVLength = cpu_to_le16(8); + pMUXMsg->QosBindDataPortReq.EpIdTlv.ep_type = cpu_to_le32(profile->rmnet_info.ep_type); + pMUXMsg->QosBindDataPortReq.EpIdTlv.iface_id = cpu_to_le32(profile->rmnet_info.iface_id); + pMUXMsg->QosBindDataPortReq.MuxIdTlv.TLVType = 0x11; + pMUXMsg->QosBindDataPortReq.MuxIdTlv.TLVLength = cpu_to_le16(1); + pMUXMsg->QosBindDataPortReq.MuxIdTlv.mux_id = profile->muxid; + return sizeof(QMI_QOS_BIND_DATA_PORT_REQ_MSG); +} + +#ifdef CONFIG_REG_QOS_IND +static USHORT QosIndRegReq(PQMUX_MSG pMUXMsg, void *arg) { + pMUXMsg->QosIndRegReq.ReportGlobalQosFlowTlv.TLVType = 0x10; + pMUXMsg->QosIndRegReq.ReportGlobalQosFlowTlv.TLVLength = cpu_to_le16(1); + pMUXMsg->QosIndRegReq.ReportGlobalQosFlowTlv.report_global_qos_flows = 1; + return sizeof(QMI_QOS_INDICATION_REGISTER_REQ_MSG); +} +#endif + +static int requestRegisterQos(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse = NULL; + PQMUX_MSG pMUXMsg; + int err; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_QOS, QMI_QOS_BIND_DATA_PORT_REQ , QosSetBindMuxDataPort, (void *)profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + dbg_time("%s QosSetBindMuxDataPort", __func__); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); + +#ifdef CONFIG_REG_QOS_IND + pRequest = ComposeQMUXMsg(QMUX_TYPE_QOS, QMI_QOS_INDICATION_REGISTER_REQ , QosIndRegReq, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + dbg_time("%s QosIndRegReq", __func__); + qmi_rsp_check_and_return(); + if (pResponse) free(pResponse); +#endif + return 0; +} + +#ifdef CONFIG_GET_QOS_INFO +UCHAR ql_get_qos_info_data_rate(PQCQMIMSG pResponse, void *max_data_rate) +{ + PQMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW qos_tx_granted_flow = NULL; + PQMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW qos_rx_granted_flow = NULL; + qos_tx_granted_flow = (PQMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if(qos_tx_granted_flow != NULL) + { + *(ULONG64 *)(max_data_rate) = le64_to_cpu(qos_tx_granted_flow->data_rate_max); + dbg_time("GET_QOS_INFO: tx_data_rate_max=%llu", *(ULONG64 *)(max_data_rate+0)); + } + else + dbg_time("GET_QOS_INFO: No qos_tx_granted_flow"); + qos_rx_granted_flow = (PQMI_QOS_GET_QOS_INFO_TLV_GRANTED_FLOW)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if(qos_rx_granted_flow != NULL) + { + *(ULONG64 *)(max_data_rate+sizeof(ULONG64)) = le64_to_cpu(qos_rx_granted_flow->data_rate_max); + dbg_time("GET_QOS_INFO: rx_data_rate_max=%llu", *(ULONG64 *)(max_data_rate+sizeof(ULONG64))); + } + else + dbg_time("GET_QOS_INFO: No qos_rx_granted_flow"); + if(qos_tx_granted_flow != NULL || qos_rx_granted_flow != NULL) + return 0; + else + return 1; +} + +static USHORT QosGetQosInfoReq(PQMUX_MSG pMUXMsg, void *arg) { + PROFILE_T *profile = (PROFILE_T *)arg; + pMUXMsg->QosGetQosInfoReq.QosIdTlv.TLVType = 0x01; + pMUXMsg->QosGetQosInfoReq.QosIdTlv.TLVLength = cpu_to_le16(4); + pMUXMsg->QosGetQosInfoReq.QosIdTlv.qos_id = cpu_to_le32(profile->qos_id); + return sizeof(QMI_QOS_GET_QOS_INFO_REQ_MSG); +} + +static int requestGetQosInfo(PROFILE_T *profile) { + PQCQMIMSG pRequest; + PQCQMIMSG pResponse = NULL; + PQMUX_MSG pMUXMsg; + int err; + + if(profile->qos_id == 0) + { + dbg_time("%s request not send: invalid qos_id", __func__); + return 0; + } + pRequest = ComposeQMUXMsg(QMUX_TYPE_QOS, QMI_QOS_GET_QOS_INFO_REQ , QosGetQosInfoReq, (void *)profile); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + if (pResponse) + { +#ifdef CONFIG_GET_QOS_DATA_RATE + ULONG64 max_data_rate[2] = {0}; + if(ql_get_qos_info_data_rate(pResponse, (void *)max_data_rate) == 0){} +#endif + free(pResponse); + } + return 0; +} +#endif //#ifdef CONFIG_GET_QOS_INFO + +#ifdef CONFIG_REG_QOS_IND +UCHAR ql_get_global_qos_flow_ind_qos_id(PQCQMIMSG pResponse, UINT *qos_id) +{ + PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_STATE qos_flow_state = NULL; + qos_flow_state = (PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_STATE)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if(qos_flow_state != NULL) + { + if(le32_to_cpu(qos_flow_state->state_change) == QOS_IND_FLOW_STATE_ACTIVATED && qos_flow_state->new_flow == 1) + { + *qos_id = le32_to_cpu(qos_flow_state->qos_id); + dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: qos_id=%u state=QOS_IND_FLOW_STATE_ACTIVATED", *qos_id); + } + return (qos_flow_state->new_flow); + } + return (0); +} + +#ifdef CONFIG_GET_QOS_DATA_RATE +UCHAR ql_get_global_qos_flow_ind_data_rate(PQCQMIMSG pResponse, void *max_data_rate) +{ + PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED qos_tx_flow_granted = NULL; + PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED qos_rx_flow_granted = NULL; + qos_tx_flow_granted = (PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x10); + if(qos_tx_flow_granted != NULL) + { + *(ULONG64 *)(max_data_rate) = le64_to_cpu(qos_tx_flow_granted->data_rate_max); + dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: tx_data_rate_max=%llu", *(ULONG64 *)(max_data_rate+0)); + } + else + dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: No qos_tx_flow_granted"); + qos_rx_flow_granted = (PQMI_QOS_GLOBAL_QOS_FLOW_TLV_FLOW_GRANTED)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if(qos_rx_flow_granted != NULL) + { + *(ULONG64 *)(max_data_rate+sizeof(ULONG64)) = le64_to_cpu(qos_rx_flow_granted->data_rate_max); + dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: rx_data_rate_max=%llu", *(ULONG64 *)(max_data_rate+sizeof(ULONG64))); + } + else + dbg_time("QMI_QOS_GLOBAL_QOS_FLOW_IND: No qos_rx_flow_granted"); + if(qos_tx_flow_granted != NULL || qos_rx_flow_granted != NULL) + return 0; + else + return 1; +} +#endif +#endif //#ifdef CONFIG_REG_QOS_IND +#endif //#ifdef CONFIG_ENABLE_QOS + +#ifdef CONFIG_CELLINFO +/* + at+qeng="servingcell" and at+qeng="neighbourcell" + https://gitlab.freedesktop.org/mobile-broadband/libqmi/-/blob/master/src/qmicli/qmicli-nas.c +*/ +static int nas_get_cell_location_info(void); +static int nas_get_rf_band_information(void); + +static int requestGetCellInfoList(void) { + dbg_time("%s", __func__); + nas_get_cell_location_info(); + nas_get_rf_band_information(); + return 0; +} +#endif + +const struct request_ops qmi_request_ops = { +#ifdef CONFIG_VERSION + .requestBaseBandVersion = requestBaseBandVersion, +#endif + .requestSetEthMode = requestSetEthMode, +#ifdef CONFIG_SIM + .requestGetSIMStatus = requestGetSIMStatus, + .requestEnterSimPin = requestEnterSimPin, +#endif +#ifdef CONFIG_IMSI_ICCID + .requestGetICCID = requestGetICCID, + .requestGetIMSI = requestGetIMSI, +#endif +#ifdef CONFIG_APN + .requestSetProfile = requestSetProfile, + .requestGetProfile = requestGetProfile, +#endif + .requestRegistrationState = requestRegistrationState, + .requestSetupDataCall = requestSetupDataCall, + .requestQueryDataCall = requestQueryDataCall, + .requestDeactivateDefaultPDP = requestDeactivateDefaultPDP, + .requestGetIPAddress = requestGetIPAddress, +#ifdef CONFIG_SIGNALINFO + .requestGetSignalInfo = requestGetSignalInfo, +#endif +#ifdef CONFIG_CELLINFO + .requestGetCellInfoList = requestGetCellInfoList, +#endif + .requestSetLoopBackState = requestSetLoopBackState, + .requestRadioPower = requestRadioPower, +#ifdef CONFIG_ENABLE_QOS + .requestRegisterQos = requestRegisterQos, +#endif +#ifdef CONFIG_GET_QOS_INFO + .requestGetQosInfo = requestGetQosInfo, +#endif +#ifdef CONFIG_COEX_WWAN_STATE + .requestGetCoexWWANState = requestGetCoexWWANState, +#endif +}; + +#ifdef CONFIG_CELLINFO +static char *str_from_bcd_plmn (uint8 plmn[3]) +{ + const char bcd_chars[] = "0123456789*#abc\0\0"; + static char str[12]; + int i; + int j = 0; + + for (i = 0; i < 3; i++) { + str[j] = bcd_chars[plmn[i]&0xF]; + if (str[j]) j++; + str[j] = bcd_chars[plmn[i]>>4]; + if (str[j]) j++; + } + + str[j++] = 0; + + return str; +} + +typedef struct { + UINT type; + const char *name; +} ENUM_NAME_T; + +#define enum_name(type) {type, #type} +#define N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) + +static const ENUM_NAME_T QMI_NAS_ACTIVE_BAND_NAME[] = { + enum_name(QMI_NAS_ACTIVE_BAND_BC_0), + enum_name(QMI_NAS_ACTIVE_BAND_BC_1), + enum_name(QMI_NAS_ACTIVE_BAND_BC_2), + enum_name(QMI_NAS_ACTIVE_BAND_BC_3), + enum_name(QMI_NAS_ACTIVE_BAND_BC_4), + enum_name(QMI_NAS_ACTIVE_BAND_BC_5), + enum_name(QMI_NAS_ACTIVE_BAND_BC_6), + enum_name(QMI_NAS_ACTIVE_BAND_BC_7), + enum_name(QMI_NAS_ACTIVE_BAND_BC_8), + enum_name(QMI_NAS_ACTIVE_BAND_BC_9), + enum_name(QMI_NAS_ACTIVE_BAND_BC_10), + enum_name(QMI_NAS_ACTIVE_BAND_BC_11), + enum_name(QMI_NAS_ACTIVE_BAND_BC_12), + enum_name(QMI_NAS_ACTIVE_BAND_BC_13), + enum_name(QMI_NAS_ACTIVE_BAND_BC_14), + enum_name(QMI_NAS_ACTIVE_BAND_BC_15), + enum_name(QMI_NAS_ACTIVE_BAND_BC_16), + enum_name(QMI_NAS_ACTIVE_BAND_BC_17), + enum_name(QMI_NAS_ACTIVE_BAND_BC_18), + enum_name(QMI_NAS_ACTIVE_BAND_BC_19), + enum_name(QMI_NAS_ACTIVE_BAND_GSM_450), + enum_name(QMI_NAS_ACTIVE_BAND_GSM_480), + enum_name(QMI_NAS_ACTIVE_BAND_GSM_750), + enum_name(QMI_NAS_ACTIVE_BAND_GSM_850), + enum_name(QMI_NAS_ACTIVE_BAND_GSM_900_EXTENDED), + enum_name(QMI_NAS_ACTIVE_BAND_GSM_900_PRIMARY), + enum_name(QMI_NAS_ACTIVE_BAND_GSM_900_RAILWAYS), + enum_name(QMI_NAS_ACTIVE_BAND_GSM_DCS_1800), + enum_name(QMI_NAS_ACTIVE_BAND_GSM_PCS_1900), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_2100), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_PCS_1900), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_DCS_1800), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_1700_US), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_850), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_800), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_2600), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_900), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_1700_JAPAN), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_1500_JAPAN), + enum_name(QMI_NAS_ACTIVE_BAND_WCDMA_850_JAPAN), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_1), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_2), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_3), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_4), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_5), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_6), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_7), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_8), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_9), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_10), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_11), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_12), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_13), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_14), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_17), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_18), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_19), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_20), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_21), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_23), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_24), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_25), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_26), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_27), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_28), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_29), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_30), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_31), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_32), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_33), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_34), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_35), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_36), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_37), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_38), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_39), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_40), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_41), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_42), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_43), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_46), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_47), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_48), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_66), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_71), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_125), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_126), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_127), + enum_name(QMI_NAS_ACTIVE_BAND_EUTRAN_250), + enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_A), + enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_B), + enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_C), + enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_D), + enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_E), + enum_name(QMI_NAS_ACTIVE_BAND_TDSCDMA_F), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_1 ), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_2 ), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_3 ), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_5 ), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_7 ), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_8 ), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_20), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_28), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_38), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_41), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_50), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_51), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_66), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_70), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_71), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_74), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_75), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_76), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_77), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_78), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_79), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_80), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_81), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_82), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_83), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_84), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_85), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_257), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_258), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_259), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_260), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_261), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_12), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_25), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_34), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_39), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_40), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_65), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_86), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_48), + enum_name(QMI_NAS_ACTIVE_BAND_NR5G_BAND_14), +}; + +static const char *qmi_nas_radio_interface_get_string(uint8 radio_if) +{ + const char *str = NULL; + + switch (radio_if) { + case QMI_NAS_RADIO_INTERFACE_CDMA_1X: str = "cdma-1x"; break; + case QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO: str = "cdma-1xevdo"; break; + case QMI_NAS_RADIO_INTERFACE_AMPS: str = "amps"; break; + case QMI_NAS_RADIO_INTERFACE_GSM: str = "gsm"; break; + case QMI_NAS_RADIO_INTERFACE_UMTS: str = "umts"; break; + case QMI_NAS_RADIO_INTERFACE_LTE: str = "lte"; break; + case QMI_NAS_RADIO_INTERFACE_TD_SCDMA: str = "td-scdma"; break; + case QMI_NAS_RADIO_INTERFACE_5GNR: str = "5gnr"; break; + default: str = NULL; break; + } + + return str ? str : "unknown"; +} + +static const char *qmi_nas_active_band_get_string(uint32 active_band) +{ + size_t i; + + for (i = 0; i < N_ELEMENTS(QMI_NAS_ACTIVE_BAND_NAME); i++) { + if (active_band == QMI_NAS_ACTIVE_BAND_NAME[i].type) + return QMI_NAS_ACTIVE_BAND_NAME[i].name + strlen("QMI_NAS_ACTIVE_BAND_"); + } + + return "unknown"; +} + +typedef struct { + uint16 min; + uint16 max; + const char *name; +} EarfcnRange; + +/* http://niviuk.free.fr/lte_band.php */ +static const EarfcnRange earfcn_ranges[] = { + { 0, 599, "E-UTRA band 1: 2100" }, + { 600, 1199, "E-UTRA band 2: 1900 PCS" }, + { 1200, 1949, "E-UTRA band 3: 1800+" }, + { 1950, 2399, "E-UTRA band 4: AWS-1" }, + { 2400, 2649, "E-UTRA band 5: 850" }, + { 2650, 2749, "E-UTRA band 6: UMTS only" }, + { 2750, 3449, "E-UTRA band 7: 2600" }, + { 3450, 3799, "E-UTRA band 8: 900" }, + { 3800, 4149, "E-UTRA band 9: 1800" }, + { 4150, 4749, "E-UTRA band 10: AWS-1+" }, + { 4750, 4999, "E-UTRA band 11: 1500 Lower" }, + { 5000, 5179, "E-UTRA band 12: 700 a" }, + { 5180, 5279, "E-UTRA band 13: 700 c" }, + { 5280, 5379, "E-UTRA band 14: 700 PS" }, + { 5730, 5849, "E-UTRA band 17: 700 b" }, + { 5850, 5999, "E-UTRA band 18: 800 Lower" }, + { 6000, 6149, "E-UTRA band 19: 800 Upper" }, + { 6150, 6449, "E-UTRA band 20: 800 DD" }, + { 6450, 6599, "E-UTRA band 21: 1500 Upper" }, + { 6600, 7399, "E-UTRA band 22: 3500" }, + { 7500, 7699, "E-UTRA band 23: 2000 S-band" }, + { 7700, 8039, "E-UTRA band 24: 1600 L-band" }, + { 8040, 8689, "E-UTRA band 25: 1900+" }, + { 8690, 9039, "E-UTRA band 26: 850+" }, + { 9040, 9209, "E-UTRA band 27: 800 SMR" }, + { 9210, 9659, "E-UTRA band 28: 700 APT" }, + { 9660, 9769, "E-UTRA band 29: 700 d" }, + { 9770, 9869, "E-UTRA band 30: 2300 WCS" }, + { 9870, 9919, "E-UTRA band 31: 450" }, + { 9920, 10359, "E-UTRA band 32: 1500 L-band" }, + { 36000, 36199, "E-UTRA band 33: TD 1900" }, + { 36200, 36349, "E-UTRA band 34: TD 2000" }, + { 36350, 36949, "E-UTRA band 35: TD PCS Lower" }, + { 36950, 37549, "E-UTRA band 36: TD PCS Upper" }, + { 37550, 37749, "E-UTRA band 37: TD PCS Center" }, + { 37750, 38249, "E-UTRA band 38: TD 2600" }, + { 38250, 38649, "E-UTRA band 39: TD 1900+" }, + { 38650, 39649, "E-UTRA band 40: TD 2300" }, + { 39650, 41589, "E-UTRA band 41: TD 2500" }, + { 41590, 43589, "E-UTRA band 42: TD 3500" }, + { 43590, 45589, "E-UTRA band 43: TD 3700" }, + { 45590, 46589, "E-UTRA band 44: TD 700" }, +}; + +static const char * earfcn_to_eutra_band_string (uint16 earfcn) +{ + size_t i; + + for (i = 0; i < N_ELEMENTS (earfcn_ranges); i++) { + if (earfcn <= earfcn_ranges[i].max && earfcn >= earfcn_ranges[i].min) + return earfcn_ranges[i].name; + } + + return "unknown"; +} + +static int nas_get_cell_location_info(void) +{ + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + PQMI_TLV pV; + int err; + int i, j; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_CELL_LOCATION_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + pV = (PQMI_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x2E); + if (pV && pV->TLVLength) { + printf ("5GNR ARFCN: '%u'\n", pV->u32); + } + + { + NasGetCellLocationNr5gServingCell *ptlv = (NasGetCellLocationNr5gServingCell *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x2F); + if (ptlv && ptlv->TLVLength) + { + printf ("5GNR cell information:\n" + "\tPLMN: '%s'\n" + "\tTracking Area Code: '%u'\n" + "\tGlobal Cell ID: '%" PRIu64 "'\n" + "\tPhysical Cell ID: '%u'\n" + "\tRSRQ: '%.1lf dB'\n" + "\tRSRP: '%.1lf dBm'\n" + "\tSNR: '%.1lf dB'\n", + str_from_bcd_plmn(ptlv->plmn), + ptlv->tac[0]<<16 | ptlv->tac[1]<<8 | ptlv->tac[2] , + ptlv->global_cell_id, + ptlv->physical_cell_id, + (0.1) * ((double)ptlv->rsrq), + (0.1) * ((double)ptlv->rsrp), + (0.1) * ((double)ptlv->snr)); + } + } + + { + NasGetCellLocationLteInfoIntrafrequency *ptlv = (NasGetCellLocationLteInfoIntrafrequency *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x13); + if (ptlv && ptlv->TLVLength) + { + printf ("Intrafrequency LTE Info:\n" + "\tUE In Idle: '%s'\n" + "\tPLMN: '%s'\n" + "\tTracking Area Code: '%u'\n" + "\tGlobal Cell ID: '%u'\n" + "\tEUTRA Absolute RF Channel Number: '%u' (%s)\n" + "\tServing Cell ID: '%u'\n", + ptlv->ue_in_idle ? "yes" : "no", + str_from_bcd_plmn(ptlv->plmn), + ptlv->tracking_area_code, + ptlv->global_cell_id, + ptlv->absolute_rf_channel_number, earfcn_to_eutra_band_string(ptlv->absolute_rf_channel_number), + ptlv->serving_cell_id); + + if (ptlv->ue_in_idle) + printf ("\tCell Reselection Priority: '%u'\n" + "\tS Non Intra Search Threshold: '%u'\n" + "\tServing Cell Low Threshold: '%u'\n" + "\tS Intra Search Threshold: '%u'\n", + ptlv->cell_reselection_priority, + ptlv->s_non_intra_search_threshold, + ptlv->serving_cell_low_threshold, + ptlv->s_intra_search_threshold); + + + for (i = 0; i < ptlv->cells_len; i++) { + NasGetCellLocationLteInfoCell *cell = &ptlv->cells_array[i]; + + printf ("\tCell [%u]:\n" + "\t\tPhysical Cell ID: '%u'\n" + "\t\tRSRQ: '%.1lf' dB\n" + "\t\tRSRP: '%.1lf' dBm\n" + "\t\tRSSI: '%.1lf' dBm\n", + i, + cell->physical_cell_id, + (double) cell->rsrq * 0.1, + (double) cell->rsrp * 0.1, + (double) cell->rssi * 0.1); + + if (ptlv->ue_in_idle) + printf ("\t\tCell Selection RX Level: '%d'\n", + cell->cell_selection_rx_level); + } + } + } + + { + NasGetCellLocationLteInfoInterfrequency *ptlv = (NasGetCellLocationLteInfoInterfrequency *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x14); + if (ptlv && ptlv->TLVLength) + { + int off = offsetof(NasGetCellLocationLteInfoInterfrequency, freqs[0]); + printf ("Interfrequency LTE Info:\n" + "\tUE In Idle: '%s'\n", ptlv->ue_in_idle ? "yes" : "no"); + + for (i = 0; i < ptlv->freqs_len; i++) { + NasGetCellLocationLteInfoInterfrequencyFrequencyElement *freq = (((void *)ptlv) + off); + + off += sizeof(*freq); + printf ("\tFrequency [%u]:\n" + "\t\tEUTRA Absolute RF Channel Number: '%u' (%s)\n" + "\t\tSelection RX Level Low Threshold: '%u'\n" + "\t\tCell Selection RX Level High Threshold: '%u'\n", + i, + freq->eutra_absolute_rf_channel_number, earfcn_to_eutra_band_string(freq->eutra_absolute_rf_channel_number), + freq->cell_selection_rx_level_low_threshold, + freq->cell_selection_rx_level_high_threshold); + if (ptlv->ue_in_idle) + printf ("\t\tCell Reselection Priority: '%u'\n", + freq->cell_reselection_priority); + + + for (j = 0; j < freq->cells_len; j++) { + NasGetCellLocationLteInfoCell *cell = &freq->cells_array[j]; + + off += sizeof(*cell); + printf ("\t\tCell [%u]:\n" + "\t\t\tPhysical Cell ID: '%u'\n" + "\t\t\tRSRQ: '%.1lf' dB\n" + "\t\t\tRSRP: '%.1lf' dBm\n" + "\t\t\tRSSI: '%.1lf' dBm\n" + "\t\t\tCell Selection RX Level: '%u'\n", + j, + cell->physical_cell_id, + (double) cell->rsrq * 0.1, + (double) cell->rsrp * 0.1, + (double) cell->rssi * 0.1, + cell->cell_selection_rx_level); + } + } + } + } + + pV = (PQMI_TLV)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x1E); + if (pV && pV->TLVLength) { + if (pV->u32 == 0xFFFFFFFF) + printf ("LTE Timing Advance: 'unavailable'\n"); + else + printf ("LTE Timing Advance: '%u'\n", pV->u32); + } + + free(pResponse); + return 0; +} + +static int nas_get_rf_band_information(void) +{ + PQCQMIMSG pRequest; + PQCQMIMSG pResponse; + PQMUX_MSG pMUXMsg; + int err; + int i; + + pRequest = ComposeQMUXMsg(QMUX_TYPE_NAS, QMINAS_GET_RF_BAND_INFO_REQ, NULL, NULL); + err = QmiThreadSendQMI(pRequest, &pResponse); + qmi_rsp_check_and_return(); + + { + NasGetRfBandInfoList *ptlv = (NasGetRfBandInfoList *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x01); + if (ptlv && ptlv->TLVLength) + { + printf ("Band Information:\n"); + for (i = 0; i < ptlv->num_instances; i++) { + NasGetRfBandInfo *band = &ptlv->bands_array[i]; + + printf ("\tRadio Interface: '%s'\n" + "\tActive Band Class: '%s'\n" + "\tActive Channel: '%u'\n", + qmi_nas_radio_interface_get_string (band->radio_if), + qmi_nas_active_band_get_string (band->active_band), + band->active_channel); + } + } + } + + { + NasGetRfBandInfoExtendedList *ptlv = (NasGetRfBandInfoExtendedList *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x11); + if (ptlv && ptlv->TLVLength) + { + printf ("Band Information (Extended):\n"); + for (i = 0; i < ptlv->num_instances; i++) { + NasGetRfBandInfoExtended *band = &ptlv->bands_array[i]; + + printf ("\tRadio Interface: '%s'\n" + "\tActive Band Class: '%s'\n" + "\tActive Channel: '%u'\n", + qmi_nas_radio_interface_get_string (band->radio_if), + qmi_nas_active_band_get_string (band->active_band), + band->active_channel); + } + } + } + + { + NasGetRfBandInfoBandWidthList *ptlv = (NasGetRfBandInfoBandWidthList *)GetTLV(&pResponse->MUXMsg.QMUXMsgHdr, 0x12); + if (ptlv && ptlv->TLVLength) + { + printf ("Bandwidth:\n"); + for (i = 0; i < ptlv->num_instances; i++) { + NasGetRfBandInfoBandWidth *band = &ptlv->bands_array[i]; + + printf ("\tRadio Interface: '%s'\n" + "\tBandwidth: '%u'\n", + qmi_nas_radio_interface_get_string (band->radio_if), + (band->bandwidth)); + } + } + } + + free(pResponse); + return 0; +} +#endif diff --git a/RM520N-5G/文件/source/QMIThread.h b/RM520N-5G/文件/source/QMIThread.h new file mode 100644 index 0000000..249336f --- /dev/null +++ b/RM520N-5G/文件/source/QMIThread.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#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 diff --git a/RM520N-5G/文件/source/QmiWwanCM.c b/RM520N-5G/文件/source/QmiWwanCM.c new file mode 100644 index 0000000..2959c58 --- /dev/null +++ b/RM520N-5G/文件/source/QmiWwanCM.c @@ -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 +#include +#include +#include +#include +#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 + diff --git a/RM520N-5G/文件/source/Quectel_QConnectManager_Linux_User_Guide_V1.0.pdf b/RM520N-5G/文件/source/Quectel_QConnectManager_Linux_User_Guide_V1.0.pdf new file mode 100644 index 0000000..a6ee18c Binary files /dev/null and b/RM520N-5G/文件/source/Quectel_QConnectManager_Linux_User_Guide_V1.0.pdf differ diff --git a/RM520N-5G/文件/source/ReleaseNote.txt b/RM520N-5G/文件/source/ReleaseNote.txt new file mode 100644 index 0000000..76a63be --- /dev/null +++ b/RM520N-5G/文件/source/ReleaseNote.txt @@ -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_ +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<E_QConnectManager_Linux&Android_V1.5.4] +Date: 12/17/2019 +enhancement: + 1. Add copyright + 2. auto detect pcie mhi /dev/mhi* +fix: + +[WCDMA<E_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<E_QConnectManager_Linux&Android_V1.5.2] +Date: 12/2/2019 +enhancement: + 1. support requestGetSignalInfo() +fix: + +[WCDMA<E_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<E_QConnectManager_Linux&Android_V1.3.10] +Date: 10/14/2019 +enhancement: + 1. increase retry interval +fix: + +[WCDMA<E_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<E_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<E_QConnectManager_Linux&Android_V1.1.45] +Date: 2018/09/13 +enhancement: +1. support EG12 PCIE interface + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.44] +Date: 2018/09/10 +enhancement: +1. support setup IPV4&IPV6 data call. + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.43] +[WCDMA<E_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<E_QConnectManager_Linux&Android_V1.1.41] +Date: 2018/05/24 +enhancement: +1. fix a cdma data call error + +[WCDMA<E_QConnectManager_Linux&Android_V1.1.40] +Date: 2018/05/12 +enhancement: +1. support GobiNet's QMAP fucntion and bridge mode. + 'Quectel_WCDMA<E_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 + diff --git a/RM520N-5G/文件/source/at_tok.c b/RM520N-5G/文件/source/at_tok.c new file mode 100644 index 0000000..6736cc8 --- /dev/null +++ b/RM520N-5G/文件/source/at_tok.c @@ -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 +#include +#include +#include +#include + +/** + * 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; +} diff --git a/RM520N-5G/文件/source/at_tok.h b/RM520N-5G/文件/source/at_tok.h new file mode 100644 index 0000000..2fcb683 --- /dev/null +++ b/RM520N-5G/文件/source/at_tok.h @@ -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 */ + diff --git a/RM520N-5G/文件/source/atc.c b/RM520N-5G/文件/source/atc.c new file mode 100644 index 0000000..d6b9777 --- /dev/null +++ b/RM520N-5G/文件/source/atc.c @@ -0,0 +1,1056 @@ +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int asprintf(char **s, const char *fmt, ...); + +#include "QMIThread.h" + +#include "atchannel.h" +#include "at_tok.h" + +static int asr_style_atc = 0; +static int s_pdp; +#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) + +static int atc_init(PROFILE_T *profile) { + int err; + char *cmd; + ATResponse *p_response = NULL; + + if (profile->proxy[0]) { + s_pdp = profile->pdp; + err = 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); + + return err; + } + + err = at_handshake(); + if (err) { + dbg_time("handshake fail, TODO ... "); + goto exit; + } + + s_pdp = profile->pdp; + 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 + + err = 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); + + if(profile->usb_dev.idProduct == 0x6007) + asr_style_atc = 1; + + 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); + if (!err) + g_donot_exit_when_modem_hangup = 1; + //reboot to take effect + } + safe_at_response_free(p_response); + } + err = 0; + } + safe_at_response_free(p_response); + +exit: + return err; +} + +static int atc_deinit(void) { + return 0; +} + +/** + * Called by atchannel when an unsolicited line appears + * This is called on atchannel's reader thread. AT commands may + * not be issued here + */ +static void onUnsolicited (const char *s, const char *sms_pdu) +{ + (void)sms_pdu; + + if (strStartsWith(s, "+QNETDEVSTATUS:")) { + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + } + else if (strStartsWith(s, "+CGREG:") + || strStartsWith(s, "+CEREG:") + || strStartsWith(s, "+C5GREG:")) { + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } +} + +static void onTimeout(void) { + dbg_time("%s", __func__); + //TODO +} + +static void onClose(void) { + dbg_time("%s", __func__); +} + +static void * atc_read_thread(void *param) { + PROFILE_T *profile = (PROFILE_T *)param; + const char *cdc_wdm = (const char *)profile->qmichannel; + int wait_for_request_quit = 0; + int atc_fd; + + atc_fd = cm_open_dev(cdc_wdm); + if (atc_fd <= 0) { + dbg_time("fail to open (%s), errno: %d (%s)", cdc_wdm, errno, strerror(errno)); + goto __quit; + } + + dbg_time("atc_fd = %d", atc_fd); + + if (at_open(atc_fd, onUnsolicited, 0)) + goto __quit; + + at_set_on_timeout(onTimeout); + at_set_on_reader_closed(onClose); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (atc_fd > 0) { + struct pollfd pollfds[] = {{atc_fd, POLLIN, 0}, {qmidevice_control_fd[1], POLLIN, 0}}; + int ne, ret, nevents = 2; + + ret = poll(pollfds, nevents, wait_for_request_quit ? 1000 : -1); + + if (ret == 0 && wait_for_request_quit) { + break; + } + + 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 (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (atc_fd == fd) { + usleep(10*1000); //let atchannel.c read at response. + } + else if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //dbg_time("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; + } + } + } + } + } + +__quit: + at_close(); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + dbg_time("%s exit", __func__); + + return NULL; +} + +const struct qmi_device_ops atc_dev_ops = { + .init = atc_init, + .deinit = atc_deinit, + .read = atc_read_thread, +}; + +static int requestBaseBandVersion(PROFILE_T *profile) { + int retVal = -1; + int err; + ATResponse *p_response = NULL; + + (void)profile; + + err = at_send_command_multiline("AT+CGMR", "\0", &p_response); + if (at_response_error(err, p_response)) + goto exit; + + if (p_response->p_intermediates && p_response->p_intermediates->line) { + strncpy(profile->BaseBandVersion, p_response->p_intermediates->line, sizeof(profile->BaseBandVersion) - 1); + retVal = 0; + } + +exit: + safe_at_response_free(p_response); + return retVal; +} + +static int requestGetSIMStatus(SIM_Status *pSIMStatus) +{ + int err; + ATResponse *p_response = NULL; + char *cpinLine; + char *cpinResult; + int ret = SIM_NOT_READY; + + err = at_send_command_singleline("AT+CPIN?", "+CPIN:", &p_response); + if (at_response_error(err, p_response)) + goto done; + + switch (at_get_cme_error(p_response)) + { + case CME_SUCCESS: + break; + + case CME_SIM_NOT_INSERTED: + case CME_OPERATION_NOT_ALLOWED: + case CME_FAILURE: + ret = SIM_ABSENT; + goto done; + + default: + ret = SIM_NOT_READY; + goto done; + } + + cpinLine = p_response->p_intermediates->line; + err = at_tok_start (&cpinLine); + + if (err < 0) + { + ret = SIM_NOT_READY; + goto done; + } + + err = at_tok_nextstr(&cpinLine, &cpinResult); + + if (err < 0) + { + ret = SIM_NOT_READY; + goto done; + } + + if (0 == strcmp (cpinResult, "SIM PIN")) + { + ret = SIM_PIN; + goto done; + } + else if (0 == strcmp (cpinResult, "SIM PUK")) + { + ret = SIM_PUK; + goto done; + } + else if (0 == strcmp (cpinResult, "PH-NET PIN")) + { + return SIM_NETWORK_PERSONALIZATION; + } + else if (0 != strcmp (cpinResult, "READY")) + { + /* we're treating unsupported lock types as "sim absent" */ + ret = SIM_ABSENT; + goto done; + } + + ret = SIM_READY; + +done: + safe_at_response_free(p_response); + *pSIMStatus = ret; + return err; +} + +static int requestEnterSimPin(const char *pPinCode) { + int retVal = -1; + int err; + ATResponse *p_response = NULL; + char *cmd = NULL; + + asprintf(&cmd, "AT+CPIN=%s", pPinCode); + err = at_send_command(cmd, NULL); + safe_free(cmd); + + if (!at_response_error(err, p_response)) { + retVal = 0; + } + + safe_at_response_free(p_response); + return retVal; +} + +static int requestSetProfile(PROFILE_T *profile) { + int err; + ATResponse *p_response = NULL; + char *cmd = NULL; + const char *new_apn = profile->apn ? profile->apn : ""; + const char *new_user = profile->user ? profile->user : ""; + const char *new_pd = profile->pd ? profile->pd : ""; + const char *ipStr[] = {"NULL", "IPV4", "IPV6", "IPV4V6"}; + + dbg_time("%s[%d] %s/%s/%s/%d/%s", __func__, + profile->pdp, profile->apn, profile->user, profile->pd, + profile->auth,ipStr[profile->iptype]); + + if ( !strcmp(profile->old_apn, new_apn) && !strcmp(profile->old_user, new_user) + && !strcmp(profile->old_pd, new_pd) + && profile->old_iptype == profile->iptype + && profile->old_auth == profile->auth) + { + dbg_time("no need to set skip the rest"); + return 0; + } + + asprintf(&cmd, "AT+QICSGP=%d,%d,\"%s\",\"%s\",\"%s\",%d", + profile->pdp, profile->iptype, new_apn, new_user, new_pd, profile->auth); + err = at_send_command(cmd, &p_response); + safe_free(cmd); + if (at_response_error(err, p_response)) { + safe_at_response_free(p_response); + asprintf(&cmd, "AT+CGDCONT=%d,\"%s\",\"%s\"", profile->pdp, ipStr[profile->iptype], new_apn); + err = at_send_command(cmd, &p_response); + safe_free(cmd); + } + + safe_at_response_free(p_response); + return 1; +} + +static int requestGetProfile(PROFILE_T *profile) { + int retVal = -1; + int err; + ATResponse *p_response = NULL; + char *cmd = NULL; + int pdp; + int old_iptype = 1; // 1 ~ IPV4, 2 ~ IPV6, 3 ~ IPV4V6 + char *old_apn = "", *old_user = "", *old_pd = ""; + int old_auth = 0; + const char *ipStr[] = {"NULL", "IPV4", "IPV6", "IPV4V6"}; + + if (profile->enable_ipv4 && profile->enable_ipv6) + profile->iptype = 3; + else if (profile->enable_ipv6) + profile->iptype = 2; + else + profile->iptype = 1; + +_re_check: + asprintf(&cmd, "AT+QICSGP=%d", profile->pdp); + err = at_send_command_singleline(cmd, "+QICSGP:", &p_response); + safe_free(cmd); + if (err == AT_ERROR_INVALID_RESPONSE && p_response == NULL) { + //bug of RG801H + safe_at_response_free(p_response); + asprintf(&cmd, "AT+QICSGP=%d,%d,\"\",\"\",\"\",0", profile->pdp, profile->iptype); + err = at_send_command(cmd, &p_response); + safe_free(cmd); + if (!at_response_error(err, p_response)) { + safe_at_response_free(p_response); + goto _re_check; + } + } + + if (!at_response_error(err, p_response)) { + err = at_tok_scanf(p_response->p_intermediates->line, + "%d%s%s%s%d", &old_iptype, &old_apn, &old_user, &old_pd, &old_auth); + + if (err != 4 || pdp != profile->pdp) + goto _error; + } + else { + ATLine *atLine = NULL; + char *cgdcont_iptype = NULL; + + safe_at_response_free(p_response); + err = at_send_command_multiline("AT+CGDCONT?", "+CGDCONT:", &p_response); + if (at_response_error(err, p_response)) + goto _error; + + atLine = p_response->p_intermediates; + while (atLine) { + err = at_tok_scanf(atLine->line, "%d%s%s", &pdp, &cgdcont_iptype, &old_apn); + if (err == 3 && pdp == profile->pdp) { + if (!strcasecmp(cgdcont_iptype, ipStr[3])) + old_iptype = 3; + else if (!strcasecmp(cgdcont_iptype, ipStr[2])) + old_iptype = 2; + else + old_iptype = 1; + break; + } + old_apn = NULL; + atLine = atLine->p_next; + } + } + + retVal = 0; + +_error: + if (!old_apn) old_apn = ""; + if (!old_user) old_user = ""; + if (!old_pd) old_pd = ""; + + strncpy(profile->old_apn, old_apn, sizeof(profile->old_apn)); + strncpy(profile->old_user, old_user, sizeof(profile->old_user)); + strncpy(profile->old_pd, old_pd, sizeof(profile->old_pd)); + profile->old_auth = old_auth; + profile->old_iptype = old_iptype; + + dbg_time("%s[%d] %s/%s/%s/%d/%s", __func__, + profile->pdp, profile->old_apn, profile->old_user, profile->old_pd, + profile->old_auth, ipStr[profile->old_iptype]); + + safe_at_response_free(p_response); + + return retVal; +} + +static int requestRegistrationState(UCHAR *pPSAttachedState) { + int retVal = -1; + int err; + ATResponse *p_response = NULL; + ATLine *p_cur; + int i; + int cops_act = -1; + int state = 0, lac = 0, cid = 0, act = 0; + int commas; + char *line; + + *pPSAttachedState = 0; + + err = at_send_command_multiline( + "AT+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?", + "+COPS:", &p_response); + if (at_response_error(err, p_response)) + goto error; + +/* +AT< +COPS: 0,0,"CHINA MOBILE",13 +AT< +COPS: 0,1,"CMCC",13 +AT< +COPS: 0,2,"46000",13 +AT< OK +*/ + retVal = 0; + for (i = 0, p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next, i++) { + err = at_tok_scanf(p_cur->line, "%d%d%s%d", NULL, NULL, NULL, &cops_act); + if (err != 4) goto error; + + break; + } + + safe_at_response_free(p_response); + switch (cops_act) { + case 2: //UTRAN + case 3: //GSM W/EGPRS + case 4: //UTRAN W/HSDPA + case 5: //UTRAN W/HSUPA + case 6: //UTRAN W/HSDPA and HSUPA + //AT+CGREG GPRS Network Registration Status + err = at_send_command_singleline("AT+CGREG?", "+CGREG:", &p_response); + break; + + case 7: //E-UTRAN + case 13: //E-UTRAN-NR dual connectivity + //AT+CEREG EPS Network Registration Status + err = at_send_command_singleline("AT+CEREG?", "+CEREG:", &p_response); + break; + + case 10: //E-UTRAN connected to a 5GCN + case 11: //NR connected to a 5GCN + case 12: //NG-RAN + //AT+C5GREG 5GS Network Registration Status + err = at_send_command_singleline("AT+C5GREG?", "+C5GREG:", &p_response); + break; + + default: + goto error; + break; + } + + if (at_response_error(err, p_response)) + goto error; + if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto error; + + line = p_response->p_intermediates->line; + commas = at_tok_count(line); + switch (commas) { + case 0: /* +CREG: */ + err = at_tok_nextint(&line, &state); + if (err < 0) goto error; + break; + + case 1: /* +CREG: , */ + err = at_tok_scanf(line, "%d%d", NULL, &state); + if (err != 2) goto error; + break; + + case 2: /* +CREG: , , */ + err = at_tok_scanf(line, "%d%x%x", NULL, &state, &lac, &cid); + if (err != 3) goto error; + break; + + case 3: /* +CREG: , , , */ + err = at_tok_scanf(line, "%d%d%x%x", NULL, &state, &lac, &cid); + if (err != 4) goto error; + break; + + case 4: //, , , , */ + case 5: + case 6: + case 7: + err = at_tok_scanf(line, "%d%d%x%x%d", NULL, &state, &lac, &cid, &act); + if (err != 5) goto error; + break; + + default: + goto error; + } + + //dbg_time("state=%d", state); + + if (state == 1 || state == 5) { //Registered, home network / roaming + *pPSAttachedState = 1; + } + +error: + safe_at_response_free(p_response); + return retVal; +} + +static int requestSetupDataCall(PROFILE_T *profile, int curIpFamily) { + int err; + ATResponse *p_response = NULL; + char *cmd = NULL; + ATLine *p_cur = NULL; + int pdp = profile->pdp; + int state = 0; + + (void)curIpFamily; + + if (strStartsWith(profile->BaseBandVersion, "RG801H") || strStartsWith(profile->BaseBandVersion, "EC200H")) { + //RG801H will miss USB_CDC_NOTIFY_NETWORK_CONNECTION + asprintf(&cmd, "ifconfig %s up", profile->usbnet_adapter); + if (system(cmd)) {}; + safe_free(cmd); + } + + if (asr_style_atc) { + err = at_send_command_multiline("AT+CGACT?", "+CGACT:", &p_response); + if (at_response_error(err, p_response)) + goto _error; + + for (p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next) { + int cid = 0; + state = 0; + + err = at_tok_scanf(p_cur->line, "%d%d", &cid, &state); + if (cid == pdp) + break; + else if(state) + state = 0; + } + safe_at_response_free(p_response); + + if (state == 0) { + asprintf(&cmd, "AT+CGACT=1,%d", pdp); + err = at_send_command(cmd, &p_response); + safe_free(cmd); + if (at_response_error(err, p_response)) + goto _error; + } + } + + if(asr_style_atc) + asprintf(&cmd, "AT+QNETDEVCTL=1,%d,%d", pdp, 1); + else + asprintf(&cmd, "AT+QNETDEVCTL=%d,1,%d", pdp, 1); + err = at_send_command(cmd, &p_response); + safe_free(cmd); + + if (at_response_error(err, p_response)) + goto _error; + + if (!asr_style_atc) { //TODO some modems do not sync return setup call resule + int t = 0; + + while (t++ < 15) { + asprintf(&cmd, "AT+QNETDEVSTATUS=%d", pdp); + err = at_send_command_singleline(cmd, "+QNETDEVSTATUS", &p_response); + safe_free(cmd); + if (err) goto _error; + + if (!at_response_error(err, p_response)) { + break; + } + safe_at_response_free(p_response); + sleep(1); + } + } + + //some modem do not report URC + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + +_error: + safe_at_response_free(p_response); + //dbg_time("%s err=%d", __func__, err); + return err; +} + +static int at_netdevstatus(int pdp, unsigned int *pV4Addr) { + int err; + ATResponse *p_response = NULL; + char *cmd = NULL; + char *ipv4_address = NULL; + char *ipv4_gate = NULL; + char *ipv4_DHCP = NULL; + char *ipv4_pDNS = NULL; + char *ipv4_sDNS = NULL; + char *ipv6_address = NULL; + char *ipv6_gate = NULL; + char *ipv6_DHCP = NULL; + char *ipv6_pDNS = NULL; + char *ipv6_sDNS = NULL; + + *pV4Addr = 0; + + asprintf(&cmd, "AT+QNETDEVSTATUS=%d", pdp); + err = at_send_command_singleline(cmd, "+QNETDEVSTATUS", &p_response); + safe_free(cmd); + if (at_response_error(err, p_response)) goto _error; + if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error; + + err = at_tok_scanf(p_response->p_intermediates->line, "%s%s%s%s%s%s%s%s%s%s", + &ipv4_address, &ipv4_gate, &ipv4_DHCP, &ipv4_pDNS, &ipv4_sDNS, + &ipv6_address, &ipv6_gate, &ipv6_DHCP, &ipv6_pDNS, &ipv6_sDNS); + if (err > 0) { +#if 0 + dbg_time("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + ipv4_address, ipv4_gate, ipv4_DHCP, ipv4_pDNS, ipv4_sDNS, + ipv6_address, ipv6_gate, ipv6_DHCP, ipv6_pDNS, ipv6_sDNS); +#endif + + if (ipv4_address && ipv4_address[0]) { + int addr[4] = {0, 0, 0, 0}; + + if (strstr(ipv4_address, ".")) { + sscanf(ipv4_address, "%d.%d.%d.%d", &addr[0], &addr[1], &addr[2], &addr[3]); + } + else { + sscanf(ipv4_address, "%02X%02X%02X%02X", &addr[3], &addr[2], &addr[1], &addr[0]); + } + *pV4Addr = (addr[0]) | (addr[1]<<8) | (addr[2]<<16) | (addr[3]<<24); + } + } + +_error: + safe_at_response_free(p_response); + return 0; +} + +static int requestQueryDataCall(UCHAR *pConnectionStatus, int curIpFamily) { + int err; + ATResponse *p_response = NULL; + ATLine *p_cur = NULL; + int state = 0; + int bind = 0; + int cid; + int pdp = s_pdp; + unsigned int v4Addr = 0; + + (void)curIpFamily; + + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + + if (!asr_style_atc) { + err = at_netdevstatus(pdp, &v4Addr); + if (!err && v4Addr) { + *pConnectionStatus = QWDS_PKT_DATA_CONNECTED; + //if (profile->ipv4.Address == v4Addr) {} //TODO + } + return err; + } + + err = at_send_command_multiline("AT+QNETDEVCTL?", "+QNETDEVCTL:", &p_response); + if (at_response_error(err, p_response)) + goto _error; + + for (p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next) + { + //+QNETDECTL:,,, + err = at_tok_scanf(p_cur->line, "%d%d%d%d", &bind, &cid, NULL, &state); + if (err != 4 || cid != pdp) + continue; + if (bind != 1) + bind = 0; + } + safe_at_response_free(p_response); + + if (bind == 0 || state == 0) + goto _error; + + err = at_send_command_multiline("AT+CGACT?", "+CGACT:", &p_response); + if (at_response_error(err, p_response)) + goto _error; + + for (p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next) + { + state = 0; + err = at_tok_scanf(p_cur->line, "%d%d", &cid, &state); + if (cid == pdp) + break; + else if(state) + state = 0; + } + safe_at_response_free(p_response); + + if (bind && state) + *pConnectionStatus = QWDS_PKT_DATA_CONNECTED; + +_error: + safe_at_response_free(p_response); + //dbg_time("%s err=%d, call_state=%d", __func__, err, *pConnectionStatus); + return 0; +} + +static int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily) { + char *cmd = NULL; + int pdp = profile->pdp; + + (void)curIpFamily; + + if (asr_style_atc) + asprintf(&cmd, "AT+QNETDEVCTL=0,%d,%d", pdp, 0); + else + asprintf(&cmd, "AT+QNETDEVCTL=%d,0,%d", pdp, 0); + at_send_command(cmd, NULL); + safe_free(cmd); + + //dbg_time("%s err=%d", __func__, err); + return 0; +} + +static int requestGetIPAddress(PROFILE_T *profile, int curIpFamily) { + int err; + ATResponse *p_response = NULL; + char *cmd = NULL; + ATLine *p_cur = NULL; + int pdp = profile->pdp; + unsigned int v4Addr = 0; + unsigned char *v4 = (unsigned char *)&v4Addr; + + (void)curIpFamily; + + if (!asr_style_atc) { + err = at_netdevstatus(pdp, &v4Addr); + goto _error; + } + + asprintf(&cmd, "AT+CGPADDR=%d", profile->pdp); + err = at_send_command_singleline(cmd, "+CGPADDR:", &p_response); + safe_free(cmd); + if (at_response_error(err, p_response)) + goto _error; + + //+CGPADDR: 1,"10.201.80.91","2409:8930:4B3:41C7:F9B8:3D9B:A2F7:CA96" + for (p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next) + { + char *ipv4 = NULL; + char *ipv6 = NULL; + + err = at_tok_scanf(p_cur->line, "%d%s%s", &pdp, &ipv4, &ipv6); + if (err < 2 || pdp != profile->pdp) + continue; + + if (ipv4) { + int addr[4] = {0, 0, 0, 0}; + + sscanf(ipv4, "%d.%d.%d.%d", &addr[0], &addr[1], &addr[2], &addr[3]); + v4[0] = addr[0]; + v4[1] = addr[1]; + v4[2] = addr[2]; + v4[3] = addr[3]; + break; + } + } + +_error: + if (v4Addr && profile->ipv4.Address != v4Addr) { + profile->ipv4.Address = v4Addr; + dbg_time("%s %d.%d.%d.%d", __func__, v4[0], v4[1], v4[2], v4[3]); + } + + //dbg_time("%s err=%d", __func__, err); + return v4Addr ? 0 : -1; +} + +static int requestGetSignalInfo(void) { + int retVal = -1; + int err; + ATResponse *p_response = NULL; + int i; + ATLine *p_cur = NULL; + char *rat = NULL; + int cops_act = 0; + int is_nr5g_nsa = 0, nr5g_sa = 0; + int verbose = 0; + + err = at_send_command_singleline("at+cops?", "+COPS:", &p_response); + if (at_response_error(err, p_response)) goto _error; + if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error; + + retVal = 0; + err = at_tok_scanf(p_response->p_intermediates->line, "%d%d%s%d", NULL, NULL, NULL, &cops_act); + if (err != 4) goto _error; + + nr5g_sa = (cops_act == 11); + + safe_at_response_free(p_response); + err = at_send_command_multiline("at+qeng=\"servingcell\"", "+QENG:", &p_response); + if (at_response_error(err, p_response)) + goto _error; + + for (i = 0, p_cur = p_response->p_intermediates; p_cur != NULL; p_cur = p_cur->p_next, i++) { + char *type, *state; + + err = at_tok_scanf(p_cur->line, "%s%s", &type, &state); + if (err != 2 || strcmp(type, "servingcell")) + continue; + + if (!strcmp(state, "SEARCH") || !strcmp(state, "LIMSRV")) + continue; + + if (!strcmp(state, "NOCONN") || !strcmp(state, "CONNECT")) { + err = at_tok_scanf(p_cur->line, "%s%s%s", &type, &state, &rat); + if (err != 3) + continue; + } + else { + rat = state; + } + + if (!strcmp(rat, "NR5G-SA")) + { + //+QENG: "servingcell",,"NR5G-SA",,,,,,,,,,,,,, + //+QENG: "servingcell","NOCONN","NR5G-SA","TDD", 454,12,0,21,4ED,636576,78,3,-85,-11,32,0,5184 + struct qeng_servingcell_nr5g_sa { + char *cell_type, *state, *rat, *is_tdd; + int MCC, MNC, cellID/*hex*/; + int PCID, TAC/*hex*/, ARFCN; + int band, NR_DL_bandwidth; + int RSRP, RSRQ, RSSI, SINR; + }; + struct qeng_servingcell_nr5g_sa nr5g_sa; + + memset(&nr5g_sa, 0, sizeof(nr5g_sa)); + err = at_tok_scanf(p_cur->line, "%s,%s,%s,%s,%d,%d,%x,%d,%x,%d,%d,%d,%d,%d,%d,%d", + &nr5g_sa.cell_type, &nr5g_sa.state, &nr5g_sa.rat, &nr5g_sa.is_tdd, + &nr5g_sa.MCC, &nr5g_sa.MNC, &nr5g_sa.cellID, &nr5g_sa.PCID, &nr5g_sa.TAC, + &nr5g_sa.ARFCN, &nr5g_sa.band, &nr5g_sa.NR_DL_bandwidth, + &nr5g_sa.RSRP, &nr5g_sa.RSRQ, &nr5g_sa.RSSI, &nr5g_sa.SINR); + + if (err >= 13 && verbose) { + dbg_time("%s,%s,%s,%s,%d,%d,%x,%d,%x,%d,%d,%d,%d,%d,%d,%d", + nr5g_sa.cell_type, nr5g_sa.state, nr5g_sa.rat, nr5g_sa.is_tdd, + nr5g_sa.MCC, nr5g_sa.MNC, nr5g_sa.cellID, nr5g_sa.PCID, nr5g_sa.TAC, + nr5g_sa.ARFCN, nr5g_sa.band, nr5g_sa.NR_DL_bandwidth, + nr5g_sa.RSRP, nr5g_sa.RSRQ, nr5g_sa.RSSI, nr5g_sa.SINR); + } + } + else if (!strcmp(rat, "NR5G-NSA")) + { + //+QENG: "NR5G-NSA",,,,,< SINR>,,, + struct qeng_servingcell_nr5g_nsa { + char *mcc, *mnc; + int pcid, rsrp, sinr, rsrq; + }; + struct qeng_servingcell_nr5g_nsa nr5g_nsa; + + memset(&nr5g_nsa, 0, sizeof(nr5g_nsa)); + err = at_tok_scanf(p_cur->line, "%s%s%s%s%d%d%d%dd", + NULL, NULL, &nr5g_nsa.mcc, &nr5g_nsa.mnc, &nr5g_nsa.pcid, &nr5g_nsa.rsrp, &nr5g_nsa.sinr, &nr5g_nsa.rsrq); + if (err == 8 && verbose) + { + dbg_time("mcc=%s, mnc=%s, pcid=%d, rsrp=%d, sinr=%d, rsrq=%d", + nr5g_nsa.mcc, nr5g_nsa.mnc, nr5g_nsa.pcid, nr5g_nsa.rsrp, nr5g_nsa.sinr, nr5g_nsa.rsrq); + } + + is_nr5g_nsa = 1; + } + else if (!strcmp(rat, "LTE")) + { + //+QENG: "LTE",,,,,,,,,,,,,,,,, + struct qeng_servingcell_lte { + char *is_tdd, *mcc, *mnc; + int cellID/*hex*/, pcid, earfcn, freq_band_ind, ul_bandwidth, dl_bandwidth; + int tac/*hex*/, rsrp, rsrq, rssi, sinr, cqi,tx_power,srxlev; + }; + struct qeng_servingcell_lte lte; + + memset(<e, 0, sizeof(lte)); + if (!strcmp(rat, state)) + err = at_tok_scanf(p_cur->line, "%s%s%s%s%s%x%d%d%d%d%d%x%d%d%d%d%d%d%d", + NULL, NULL, <e.is_tdd, <e.mcc, <e.mnc, + <e.cellID, <e.pcid, <e.earfcn, <e.freq_band_ind, <e.ul_bandwidth, <e.dl_bandwidth, + <e.tac, <e.rsrp, <e.rsrq, <e.rssi, <e.sinr, <e.cqi, <e.tx_power, <e.srxlev); + else + err = at_tok_scanf(p_cur->line, "%s%s%s%s%s%s%x%d%d%d%d%d%x%d%d%d%d%d%d%d", + NULL, NULL, NULL, <e.is_tdd, <e.mcc, <e.mnc, + <e.cellID, <e.pcid, <e.earfcn, <e.freq_band_ind, <e.ul_bandwidth, <e.dl_bandwidth, + <e.tac, <e.rsrp, <e.rsrq, <e.rssi, <e.sinr, <e.cqi, <e.tx_power, <e.srxlev); + + if (err >= 18 && verbose) + { + dbg_time("is_tdd=%s, mcc=%s, mnc=%s", lte.is_tdd, lte.mcc, lte.mnc); + dbg_time("cellID=%x, pcid=%d, earfcn=%d", lte.cellID, lte.pcid, lte.earfcn); + dbg_time("freq_band_ind=%d, ul_bandwidth=%d, dl_bandwidth=%d", lte.freq_band_ind, lte.ul_bandwidth, lte.dl_bandwidth); + dbg_time("tac=%x, rsrp=%d, rsrq=%d, rssi=%d, sinr=%d", lte.tac, lte.rsrp, lte.rsrq, lte.rssi, lte.sinr); + dbg_time("cqi=%d, tx_power=%d, earfcn=%d", lte.cqi, lte.tx_power, lte.srxlev); + } + } + } + + if (is_nr5g_nsa) { + int endc_avl, plmn_info_list_r15_avl, endc_rstr, nr5g_basic; + + is_nr5g_nsa = 0; + safe_at_response_free(p_response); + err = at_send_command_multiline("at+qendc", "+QENDC:", &p_response); + if (at_response_error(err, p_response)) goto _error; + if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error; + + err = at_tok_scanf(p_response->p_intermediates->line, "%d%d%d%d", + &endc_avl, &plmn_info_list_r15_avl, &endc_rstr, &nr5g_basic); + if (err == 4 && nr5g_basic) { + is_nr5g_nsa = 1; + } + } + + if (verbose) + dbg_time("cops_act=%d, nr5g_nsa=%d, nr5g_sa=%d", cops_act, is_nr5g_nsa, nr5g_sa); + +_error: + safe_at_response_free(p_response); + return retVal; +} + +static int requestGetICCID(void) { + int retVal = -1; + int err; + ATResponse *p_response = NULL; + char *iccid; + + err = at_send_command_singleline("AT+QCCID", "+QCCID:", &p_response); + if (at_response_error(err, p_response)) goto _error; + if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error; + + err = at_tok_scanf(p_response->p_intermediates->line, "%s", &iccid); + if (err != 1) goto _error; + + if (iccid && iccid[0]) { + dbg_time("%s %s", __func__, iccid); + retVal = 0; + } + +_error: + safe_at_response_free(p_response); + return retVal; +} + +static int requestGetIMSI(void) { + int retVal = -1; + int err; + ATResponse *p_response = NULL; + char *imsi; + + err = at_send_command_numeric("AT+CIMI", &p_response); + if (at_response_error(err, p_response)) goto _error; + if (!p_response->p_intermediates || !p_response->p_intermediates->line) goto _error; + + imsi = p_response->p_intermediates->line; + if (imsi) { + dbg_time("%s %s", __func__, imsi); + retVal = 0; + } + +_error: + safe_at_response_free(p_response); + return retVal; +} + +const struct request_ops atc_request_ops = { + .requestBaseBandVersion = requestBaseBandVersion, + .requestGetSIMStatus = requestGetSIMStatus, + .requestEnterSimPin = requestEnterSimPin, + .requestSetProfile = requestSetProfile, + .requestGetProfile = requestGetProfile, + .requestRegistrationState = requestRegistrationState, + .requestSetupDataCall = requestSetupDataCall, + .requestQueryDataCall = requestQueryDataCall, + .requestDeactivateDefaultPDP = requestDeactivateDefaultPDP, + .requestGetIPAddress = requestGetIPAddress, + .requestGetSignalInfo = requestGetSignalInfo, + .requestGetICCID = requestGetICCID, + .requestGetIMSI = requestGetIMSI, +}; + diff --git a/RM520N-5G/文件/source/atchannel.c b/RM520N-5G/文件/source/atchannel.c new file mode 100644 index 0000000..90aa1c4 --- /dev/null +++ b/RM520N-5G/文件/source/atchannel.c @@ -0,0 +1,1037 @@ +/* //device/system/reference-ril/atchannel.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 "atchannel.h" +#include "at_tok.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QMIThread.h" +#define LOGE dbg_time +#define LOGD dbg_time + +#define NUM_ELEMS(x) (sizeof(x)/sizeof(x[0])) + +#define MAX_AT_RESPONSE sizeof(cm_recv_buf) +#define HANDSHAKE_RETRY_COUNT 8 +#define HANDSHAKE_TIMEOUT_MSEC 1000 + +static pthread_t s_tid_reader; +static int s_fd = -1; /* fd of the AT channel */ +static ATUnsolHandler s_unsolHandler; +static int s_atc_proxy = 0; /* fd of the AT channel */ + +/* for input buffering */ + +static char *s_ATBuffer = (char *)cm_recv_buf; +static char *s_ATBufferCur = (char *)cm_recv_buf; + +static int s_readCount = 0; + +#if AT_DEBUG +void AT_DUMP(const char* prefix, const char* buff, int len) +{ + if (len < 0) + len = strlen(buff); + LOGD("%.*s", len, buff); +} +#endif + +/* + * for current pending command + * these are protected by s_commandmutex + */ +static ATCommandType s_type; +static const char *s_responsePrefix = NULL; +static const char *s_smsPDU = NULL; +static const char *s_raw_data = NULL; +static size_t s_raw_len; +static ATResponse *sp_response = NULL; + +static void (*s_onTimeout)(void) = NULL; +static void (*s_onReaderClosed)(void) = NULL; +static int s_readerClosed; + +static void onReaderClosed(); +static int writeCtrlZ (const char *s); +static int writeline (const char *s); +static int writeraw (const char *s, size_t len); + +static void sleepMsec(long long msec) +{ + struct timespec ts; + int err; + + ts.tv_sec = (msec / 1000); + ts.tv_nsec = (msec % 1000) * 1000 * 1000; + + do { + err = nanosleep (&ts, &ts); + } while (err < 0 && errno == EINTR); +} + +/** returns 1 if line starts with prefix, 0 if it does not */ +int strStartsWith(const char *line, const char *prefix) +{ + for ( ; *line != '\0' && *prefix != '\0' ; line++, prefix++) { + if (*line != *prefix) { + return 0; + } + } + + return *prefix == '\0'; +} + +/** add an intermediate response to sp_response*/ +static void addIntermediate(const char *line) +{ + ATLine *p_new; + + p_new = (ATLine *) malloc(sizeof(ATLine)); + + p_new->line = strdup(line); + + /* note: this adds to the head of the list, so the list + will be in reverse order of lines received. the order is flipped + again before passing on to the command issuer */ + p_new->p_next = sp_response->p_intermediates; + sp_response->p_intermediates = p_new; +} + + +/** + * returns 1 if line is a final response indicating error + * See 27.007 annex B + * WARNING: NO CARRIER and others are sometimes unsolicited + */ +static const char * s_finalResponsesError[] = { + "ERROR", + "+CMS ERROR:", + "+CME ERROR:", + "NO CARRIER", /* sometimes! */ + "NO ANSWER", + "NO DIALTONE", + "COMMAND NOT SUPPORT", +}; +static int isFinalResponseError(const char *line) +{ + size_t i; + + for (i = 0 ; i < NUM_ELEMS(s_finalResponsesError) ; i++) { + if (strStartsWith(line, s_finalResponsesError[i])) { + return 1; + } + } + + return 0; +} + +/** + * returns 1 if line is a final response indicating success + * See 27.007 annex B + * WARNING: NO CARRIER and others are sometimes unsolicited + */ +static const char * s_finalResponsesSuccess[] = { + "OK", + "+QIND: \"FOTA\",\"END\",0", + "CONNECT" /* some stacks start up data on another channel */ +}; + +static int isFinalResponseSuccess(const char *line) +{ + size_t i; + + for (i = 0 ; i < NUM_ELEMS(s_finalResponsesSuccess) ; i++) { + if (strStartsWith(line, s_finalResponsesSuccess[i])) { + return 1; + } + } + + return 0; +} + +#if 0 +/** + * returns 1 if line is a final response, either error or success + * See 27.007 annex B + * WARNING: NO CARRIER and others are sometimes unsolicited + */ +static int isFinalResponse(const char *line) +{ + return isFinalResponseSuccess(line) || isFinalResponseError(line); +} +#endif + +/** + * returns 1 if line is the first line in (what will be) a two-line + * SMS unsolicited response + */ +static const char * s_smsUnsoliciteds[] = { + "+CMT:", + "+CDS:", + "+CBM:", + "+CMTI:" +}; +static int isSMSUnsolicited(const char *line) +{ + size_t i; + + for (i = 0 ; i < NUM_ELEMS(s_smsUnsoliciteds) ; i++) { + if (strStartsWith(line, s_smsUnsoliciteds[i])) { + return 1; + } + } + + return 0; +} + + +/** assumes s_commandmutex is held */ +static void handleFinalResponse(const char *line) +{ + sp_response->finalResponse = strdup(line); + + pthread_cond_signal(&cm_command_cond); +} + +static void handleUnsolicited(const char *line) +{ + if (s_unsolHandler != NULL) { + s_unsolHandler(line, NULL); + } +} + +static void processLine(const char *line) +{ + pthread_mutex_lock(&cm_command_mutex); + + if (sp_response == NULL) { + /* no command pending */ + handleUnsolicited(line); + } else if (s_raw_data != NULL && 0 == strcmp(line, "CONNECT")) { + usleep(500*1000); //for EC20 + writeraw(s_raw_data, s_raw_len); + s_raw_data = NULL; + } else if (isFinalResponseSuccess(line)) { + if(s_atc_proxy) + handleUnsolicited(line); + sp_response->success = 1; + handleFinalResponse(line); + } else if (isFinalResponseError(line)) { + if(s_atc_proxy) + handleUnsolicited(line); + sp_response->success = 0; + handleFinalResponse(line); + } else if (s_smsPDU != NULL && 0 == strcmp(line, "> ")) { + // See eg. TS 27.005 4.3 + // Commands like AT+CMGS have a "> " prompt + writeCtrlZ(s_smsPDU); + s_smsPDU = NULL; + } else switch (s_type) { + case NO_RESULT: + handleUnsolicited(line); + break; + case NUMERIC: + if (sp_response->p_intermediates == NULL + && isdigit(line[0]) + ) { + addIntermediate(line); + } else { + /* either we already have an intermediate response or + the line doesn't begin with a digit */ + handleUnsolicited(line); + } + break; + case SINGLELINE: + if (sp_response->p_intermediates == NULL + && strStartsWith (line, s_responsePrefix) + ) { + addIntermediate(line); + } else { + /* we already have an intermediate response */ + handleUnsolicited(line); + } + break; + case MULTILINE: + if (strStartsWith (line, s_responsePrefix)) { + addIntermediate(line); + } else { + handleUnsolicited(line); + } + break; + + default: /* this should never be reached */ + LOGE("Unsupported AT command type %d\n", s_type); + handleUnsolicited(line); + break; + } + + pthread_mutex_unlock(&cm_command_mutex); +} + + +/** + * Returns a pointer to the end of the next line + * special-cases the "> " SMS prompt + * + * returns NULL if there is no complete line + */ +static char * findNextEOL(char *cur) +{ + if (cur[0] == '>' && cur[1] == ' ' && cur[2] == '\0') { + /* SMS prompt character...not \r terminated */ + return cur+2; + } + + // Find next newline + while (*cur != '\0' && *cur != '\r' && *cur != '\n') cur++; + + return *cur == '\0' ? NULL : cur; +} + + +/** + * Reads a line from the AT channel, returns NULL on timeout. + * Assumes it has exclusive read access to the FD + * + * This line is valid only until the next call to readline + * + * This function exists because as of writing, android libc does not + * have buffered stdio. + */ + +static const char *readline() +{ + ssize_t count; + + char *p_read = NULL; + char *p_eol = NULL; + char *ret; + + /* this is a little odd. I use *s_ATBufferCur == 0 to + * mean "buffer consumed completely". If it points to a character, than + * the buffer continues until a \0 + */ + if (*s_ATBufferCur == '\0') { + /* empty buffer */ + s_ATBufferCur = s_ATBuffer; + *s_ATBufferCur = '\0'; + p_read = s_ATBuffer; + } else { /* *s_ATBufferCur != '\0' */ + /* there's data in the buffer from the last read */ + + // skip over leading newlines + while (*s_ATBufferCur == '\r' || *s_ATBufferCur == '\n') + s_ATBufferCur++; + + p_eol = findNextEOL(s_ATBufferCur); + + if (p_eol == NULL) { + /* a partial line. move it up and prepare to read more */ + size_t len; + + len = strlen(s_ATBufferCur); + + memmove(s_ATBuffer, s_ATBufferCur, len + 1); + p_read = s_ATBuffer + len; + s_ATBufferCur = s_ATBuffer; + } + /* Otherwise, (p_eol !- NULL) there is a complete line */ + /* that will be returned the while () loop below */ + } + + while (p_eol == NULL) { + if (0 == MAX_AT_RESPONSE - (p_read - s_ATBuffer)) { + LOGE("ERROR: Input line exceeded buffer\n"); + /* ditch buffer and start over again */ + s_ATBufferCur = s_ATBuffer; + *s_ATBufferCur = '\0'; + p_read = s_ATBuffer; + } + + do { + while (s_fd > 0) { + struct pollfd pollfds[1] = {{s_fd, POLLIN, 0}}; + int ret; + + do { + ret = poll(pollfds, 1, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { + break; + } else if (pollfds[0].revents & (POLLIN)) { + break; + } + }; + + count = (s_fd == -1) ? 0 : read(s_fd, p_read, + MAX_AT_RESPONSE - (p_read - s_ATBuffer)); + } while (count < 0 && errno == EINTR); + + if (count > 0) { + AT_DUMP( "<< ", p_read, count ); + s_readCount += count; + + p_read[count] = '\0'; + + // skip over leading newlines + while (*s_ATBufferCur == '\r' || *s_ATBufferCur == '\n') + s_ATBufferCur++; + + p_eol = findNextEOL(s_ATBufferCur); + p_read += count; + } else if (count <= 0) { + /* read error encountered or EOF reached */ + if(count == 0) { + LOGD("atchannel: EOF reached"); + } else { + LOGD("atchannel: read error %s", strerror(errno)); + } + return NULL; + } + } + + /* a full line in the buffer. Place a \0 over the \r and return */ + + ret = s_ATBufferCur; + *p_eol = '\0'; + s_ATBufferCur = p_eol + 1; /* this will always be <= p_read, */ + /* and there will be a \0 at *p_read */ + + LOGD("AT< %s", ret); + return ret; +} + + +static void onReaderClosed() +{ + LOGE("%s", __func__); + if (s_onReaderClosed != NULL && s_readerClosed == 0) { + + pthread_mutex_lock(&cm_command_mutex); + + s_readerClosed = 1; + + pthread_cond_signal(&cm_command_cond); + + pthread_mutex_unlock(&cm_command_mutex); + + s_onReaderClosed(); + } +} + + +static void *readerLoop(void *arg) +{ + (void)arg; + + for (;;) { + const char * line; + + line = readline(); + + if (line == NULL) { + break; + } + + if(isSMSUnsolicited(line)) { + char *line1; + const char *line2; + + // The scope of string returned by 'readline()' is valid only + // till next call to 'readline()' hence making a copy of line + // before calling readline again. + line1 = strdup(line); + line2 = readline(); + + if (line2 == NULL) { + break; + } + + if (s_unsolHandler != NULL) { + s_unsolHandler (line1, line2); + } + free(line1); + } else { + processLine(line); + } + } + + onReaderClosed(); + + return NULL; +} + +/** + * Sends string s to the radio with a \r appended. + * Returns AT_ERROR_* on error, 0 on success + * + * This function exists because as of writing, android libc does not + * have buffered stdio. + */ +static int writeline (const char *s) +{ + size_t cur = 0; + size_t len = strlen(s); + ssize_t written; + static char at_command[64]; + + if (s_fd < 0 || s_readerClosed > 0) { + return AT_ERROR_CHANNEL_CLOSED; + } + + LOGD("AT> %s", s); + + AT_DUMP( ">> ", s, strlen(s) ); + +#if 1 //send '\r' maybe fail via USB controller: Intel Corporation 7 Series/C210 Series Chipset Family USB xHCI Host Controller (rev 04) + if (len < (sizeof(at_command) - 1)) { + strcpy(at_command, s); + at_command[len++] = '\r'; + s = (const char *)at_command; + } +#endif + + /* the main string */ + while (cur < len) { + do { + written = write (s_fd, s + cur, len - cur); + } while (written < 0 && errno == EINTR); + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + cur += written; + } + +#if 1 //Quectel send '\r' maybe fail via USB controller: Intel Corporation 7 Series/C210 Series Chipset Family USB xHCI Host Controller (rev 04) + if (s == (const char *)at_command) { + return 0; + } +#endif + + /* the \r */ + + do { + written = write (s_fd, "\r" , 1); + } while ((written < 0 && errno == EINTR) || (written == 0)); + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + return 0; +} +static int writeCtrlZ (const char *s) +{ + size_t cur = 0; + size_t len = strlen(s); + ssize_t written; + + if (s_fd < 0 || s_readerClosed > 0) { + return AT_ERROR_CHANNEL_CLOSED; + } + + LOGD("AT> %s^Z", s); + + AT_DUMP( ">* ", s, strlen(s) ); + + /* the main string */ + while (cur < len) { + do { + written = write (s_fd, s + cur, len - cur); + } while (written < 0 && errno == EINTR); + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + cur += written; + } + + /* the ^Z */ + + do { + written = write (s_fd, "\032" , 1); + } while ((written < 0 && errno == EINTR) || (written == 0)); + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + return 0; +} + +static int writeraw (const char *s, size_t len) { + size_t cur = 0; + ssize_t written; + + if (s_fd < 0 || s_readerClosed > 0) { + return AT_ERROR_CHANNEL_CLOSED; + } + + /* the main string */ + while (cur < len) { + struct pollfd pollfds[1] = {{s_fd, POLLOUT, 0}}; + int ret; + + ret = poll(pollfds, 1, -1); + if (ret <= 0) + break; + + do { + written = write (s_fd, s + cur, len - cur); + } while (written < 0 && errno == EINTR); + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + cur += written; + } + + if (written < 0) { + return AT_ERROR_GENERIC; + } + + return cur; +} + +static void clearPendingCommand() +{ + if (sp_response != NULL) { + at_response_free(sp_response); + } + + sp_response = NULL; + s_responsePrefix = NULL; + s_smsPDU = NULL; +} + + +/** + * Starts AT handler on stream "fd' + * returns 0 on success, -1 on error + */ +int at_open(int fd, ATUnsolHandler h, int proxy) +{ + int ret; + pthread_attr_t attr; + + s_fd = fd; + s_unsolHandler = h; + s_readerClosed = 0; + s_atc_proxy = proxy; + + s_responsePrefix = NULL; + s_smsPDU = NULL; + sp_response = NULL; + + pthread_attr_init (&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + ret = pthread_create(&s_tid_reader, &attr, readerLoop, NULL); + + if (ret < 0) { + LOGE("readerLoop create fail!"); + perror ("pthread_create\n"); + return -1; + } + + return 0; +} + +/* FIXME is it ok to call this from the reader and the command thread? */ +void at_close() +{ + dbg_time("at_close"); + if (s_fd >= 0) { + close(s_fd); + } + s_fd = -1; + + pthread_mutex_lock(&cm_command_mutex); + + s_readerClosed = 1; + + pthread_cond_signal(&cm_command_cond); + + pthread_mutex_unlock(&cm_command_mutex); + + /* the reader thread should eventually die */ +} + +static ATResponse * at_response_new() +{ + return (ATResponse *) calloc(1, sizeof(ATResponse)); +} + +void at_response_free(ATResponse *p_response) +{ + ATLine *p_line; + + if (p_response == NULL) return; + + p_line = p_response->p_intermediates; + + while (p_line != NULL) { + ATLine *p_toFree; + + p_toFree = p_line; + p_line = p_line->p_next; + + free(p_toFree->line); + free(p_toFree); + } + + free (p_response->finalResponse); + free (p_response); +} + +/** + * The line reader places the intermediate responses in reverse order + * here we flip them back + */ +static void reverseIntermediates(ATResponse *p_response) +{ + ATLine *pcur,*pnext; + + pcur = p_response->p_intermediates; + p_response->p_intermediates = NULL; + + while (pcur != NULL) { + pnext = pcur->p_next; + pcur->p_next = p_response->p_intermediates; + p_response->p_intermediates = pcur; + pcur = pnext; + } +} + +/** + * Internal send_command implementation + * Doesn't lock or call the timeout callback + * + * timeoutMsec == 0 means infinite timeout + */ +static int at_send_command_full_nolock (const char *command, ATCommandType type, + const char *responsePrefix, const char *smspdu, + long long timeoutMsec, ATResponse **pp_outResponse) +{ + int err = 0; + + if (!timeoutMsec) + timeoutMsec = 15000; + + if(sp_response != NULL) { + err = AT_ERROR_COMMAND_PENDING; + goto error; + } + + if (command != NULL) + err = writeline (command); + + if (err < 0) { + printf("%s errno: %d (%s)\n", __func__, errno, strerror(errno)); + goto error; + } + + s_type = type; + s_responsePrefix = responsePrefix; + s_smsPDU = smspdu; + sp_response = at_response_new(); + + while (sp_response->finalResponse == NULL && s_readerClosed == 0) { + err = pthread_cond_timeout_np(&cm_command_cond, &cm_command_mutex, timeoutMsec); + + if (err == ETIMEDOUT) { + err = AT_ERROR_TIMEOUT; + goto error; + } + } + + if (pp_outResponse == NULL) { + at_response_free(sp_response); + } else { + /* line reader stores intermediate responses in reverse order */ + reverseIntermediates(sp_response); + *pp_outResponse = sp_response; + } + + sp_response = NULL; + + if(s_readerClosed > 0) { + err = AT_ERROR_CHANNEL_CLOSED; + goto error; + } + + err = 0; +error: + clearPendingCommand(); + + return err; +} + +/** + * Internal send_command implementation + * + * timeoutMsec == 0 means infinite timeout + */ +static int at_send_command_full (const char *command, ATCommandType type, + const char *responsePrefix, const char *smspdu, + long long timeoutMsec, ATResponse **pp_outResponse) +{ + int err; + + if (0 != pthread_equal(s_tid_reader, pthread_self())) { + /* cannot be called from reader thread */ + return AT_ERROR_INVALID_THREAD; + } + + pthread_mutex_lock(&cm_command_mutex); + + err = at_send_command_full_nolock(command, type, + responsePrefix, smspdu, + timeoutMsec, pp_outResponse); + + pthread_mutex_unlock(&cm_command_mutex); + + if (err == AT_ERROR_TIMEOUT && s_onTimeout != NULL) { + s_onTimeout(); + } + + return err; +} + + +/** + * Issue a single normal AT command with no intermediate response expected + * + * "command" should not include \r + * pp_outResponse can be NULL + * + * if non-NULL, the resulting ATResponse * must be eventually freed with + * at_response_free + */ +int at_send_command (const char *command, ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, NO_RESULT, NULL, + NULL, 0, pp_outResponse); + + return err; +} + + +int at_send_command_singleline (const char *command, + const char *responsePrefix, + ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, SINGLELINE, responsePrefix, + NULL, 0, pp_outResponse); + + if (err == 0 && pp_outResponse != NULL + && (*pp_outResponse)->success > 0 + && (*pp_outResponse)->p_intermediates == NULL + ) { + /* successful command must have an intermediate response */ + at_response_free(*pp_outResponse); + *pp_outResponse = NULL; + return AT_ERROR_INVALID_RESPONSE; + } + + return err; +} + + +int at_send_command_numeric (const char *command, + ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, NUMERIC, NULL, + NULL, 0, pp_outResponse); + + if (err == 0 && pp_outResponse != NULL + && (*pp_outResponse)->success > 0 + && (*pp_outResponse)->p_intermediates == NULL + ) { + /* successful command must have an intermediate response */ + at_response_free(*pp_outResponse); + *pp_outResponse = NULL; + return AT_ERROR_INVALID_RESPONSE; + } + + return err; +} + + +int at_send_command_sms (const char *command, + const char *pdu, + const char *responsePrefix, + ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, SINGLELINE, responsePrefix, + pdu, 0, pp_outResponse); + + if (err == 0 && pp_outResponse != NULL + && (*pp_outResponse)->success > 0 + && (*pp_outResponse)->p_intermediates == NULL + ) { + /* successful command must have an intermediate response */ + at_response_free(*pp_outResponse); + *pp_outResponse = NULL; + return AT_ERROR_INVALID_RESPONSE; + } + + return err; +} + +int at_send_command_multiline (const char *command, + const char *responsePrefix, + ATResponse **pp_outResponse) +{ + int err; + + err = at_send_command_full (command, MULTILINE, responsePrefix, + NULL, 0, pp_outResponse); + + return err; +} + +int at_send_command_raw (const char *command, + const char *raw_data, unsigned int raw_len, + const char *responsePrefix, + ATResponse **pp_outResponse) +{ + int err; + + s_raw_data = raw_data; + s_raw_len = raw_len; + err = at_send_command_full (command, SINGLELINE, responsePrefix, + NULL, 0, pp_outResponse); + + return err; +} + +/** + * Periodically issue an AT command and wait for a response. + * Used to ensure channel has start up and is active + */ + +int at_handshake() +{ + int i; + int err = 0; + + if (0 != pthread_equal(s_tid_reader, pthread_self())) { + /* cannot be called from reader thread */ + return AT_ERROR_INVALID_THREAD; + } + + pthread_mutex_lock(&cm_command_mutex); + + for (i = 0 ; i < HANDSHAKE_RETRY_COUNT ; i++) { + /* some stacks start with verbose off */ + err = at_send_command_full_nolock ("ATE0Q0V1", NO_RESULT, + NULL, NULL, HANDSHAKE_TIMEOUT_MSEC, NULL); + + if (err == 0) { + break; + } + } + + pthread_mutex_unlock(&cm_command_mutex); + + if (err == 0) { + /* pause for a bit to let the input buffer drain any unmatched OK's + (they will appear as extraneous unsolicited responses) */ + + sleepMsec(HANDSHAKE_TIMEOUT_MSEC); + } + + return err; +} + +AT_CME_Error at_get_cme_error(const ATResponse *p_response) +{ + int ret; + int err; + char *p_cur; + + if (p_response == NULL) + return CME_ERROR_NON_CME; + + if (p_response->success > 0) { + return CME_SUCCESS; + } + + if (p_response->finalResponse == NULL + || !strStartsWith(p_response->finalResponse, "+CME ERROR:") + ) { + return CME_ERROR_NON_CME; + } + + p_cur = p_response->finalResponse; + err = at_tok_start(&p_cur); + + if (err < 0) { + return CME_ERROR_NON_CME; + } + + err = at_tok_nextint(&p_cur, &ret); + + if (err < 0) { + return CME_ERROR_NON_CME; + } + + return (AT_CME_Error) ret; +} + +/** This callback is invoked on the command thread */ +void at_set_on_timeout(void (*onTimeout)(void)) +{ + s_onTimeout = onTimeout; +} + +/** + * 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() + */ +void at_set_on_reader_closed(void (*onClose)(void)) +{ + s_onReaderClosed = onClose; +} diff --git a/RM520N-5G/文件/source/atchannel.h b/RM520N-5G/文件/source/atchannel.h new file mode 100644 index 0000000..cce28b1 --- /dev/null +++ b/RM520N-5G/文件/source/atchannel.h @@ -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*/ diff --git a/RM520N-5G/文件/source/device.c b/RM520N-5G/文件/source/device.c new file mode 100644 index 0000000..eada281 --- /dev/null +++ b/RM520N-5G/文件/source/device.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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); +} diff --git a/RM520N-5G/文件/source/ethtool-copy.h b/RM520N-5G/文件/source/ethtool-copy.h new file mode 100644 index 0000000..a61b454 --- /dev/null +++ b/RM520N-5G/文件/source/ethtool-copy.h @@ -0,0 +1,1110 @@ +/* + * ethtool.h: Defines for Linux ethtool. + * + * Copyright (C) 1998 David S. Miller (davem@redhat.com) + * Copyright 2001 Jeff Garzik + * Portions Copyright 2001 Sun Microsystems (thockin@sun.com) + * Portions Copyright 2002 Intel (eli.kupermann@intel.com, + * christopher.leech@intel.com, + * scott.feldman@intel.com) + * Portions Copyright (C) Sun Microsystems 2008 + + 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 _LINUX_ETHTOOL_H +#define _LINUX_ETHTOOL_H + +#include +#include + +/* This should work for both 32 and 64 bit userland. */ +struct ethtool_cmd { + __u32 cmd; + __u32 supported; /* Features this interface supports */ + __u32 advertising; /* Features this interface advertises */ + __u16 speed; /* The forced speed (lower bits) in + * Mbps. Please use + * ethtool_cmd_speed()/_set() to + * access it */ + __u8 duplex; /* Duplex, half or full */ + __u8 port; /* Which connector port */ + __u8 phy_address; /* MDIO PHY address (PRTAD for clause 45). + * May be read-only or read-write + * depending on the driver. + */ + __u8 transceiver; /* Which transceiver to use */ + __u8 autoneg; /* Enable or disable autonegotiation */ + __u8 mdio_support; /* MDIO protocols supported. Read-only. + * Not set by all drivers. + */ + __u32 maxtxpkt; /* Tx pkts before generating tx int */ + __u32 maxrxpkt; /* Rx pkts before generating rx int */ + __u16 speed_hi; /* The forced speed (upper + * bits) in Mbps. Please use + * ethtool_cmd_speed()/_set() to + * access it */ + __u8 eth_tp_mdix; /* twisted pair MDI-X status */ + __u8 eth_tp_mdix_ctrl; /* twisted pair MDI-X control, when set, + * link should be renegotiated if necessary + */ + __u32 lp_advertising; /* Features the link partner advertises */ + __u32 reserved[2]; +}; + +static __inline__ void ethtool_cmd_speed_set(struct ethtool_cmd *ep, + __u32 speed) +{ + + ep->speed = (__u16)speed; + ep->speed_hi = (__u16)(speed >> 16); +} + +static __inline__ __u32 ethtool_cmd_speed(const struct ethtool_cmd *ep) +{ + return (ep->speed_hi << 16) | ep->speed; +} + +/* Device supports clause 22 register access to PHY or peripherals + * using the interface defined in . This should not be + * set if there are known to be no such peripherals present or if + * the driver only emulates clause 22 registers for compatibility. + */ +#define ETH_MDIO_SUPPORTS_C22 1 + +/* Device supports clause 45 register access to PHY or peripherals + * using the interface defined in and . + * This should not be set if there are known to be no such peripherals + * present. + */ +#define ETH_MDIO_SUPPORTS_C45 2 + +#define ETHTOOL_FWVERS_LEN 32 +#define ETHTOOL_BUSINFO_LEN 32 +/* these strings are set to whatever the driver author decides... */ +struct ethtool_drvinfo { + __u32 cmd; + char driver[32]; /* driver short name, "tulip", "eepro100" */ + char version[32]; /* driver version string */ + char fw_version[ETHTOOL_FWVERS_LEN]; /* firmware version string */ + char bus_info[ETHTOOL_BUSINFO_LEN]; /* Bus info for this IF. */ + /* For PCI devices, use pci_name(pci_dev). */ + char reserved1[32]; + char reserved2[12]; + /* + * Some struct members below are filled in + * using ops->get_sset_count(). Obtaining + * this info from ethtool_drvinfo is now + * deprecated; Use ETHTOOL_GSSET_INFO + * instead. + */ + __u32 n_priv_flags; /* number of flags valid in ETHTOOL_GPFLAGS */ + __u32 n_stats; /* number of u64's from ETHTOOL_GSTATS */ + __u32 testinfo_len; + __u32 eedump_len; /* Size of data from ETHTOOL_GEEPROM (bytes) */ + __u32 regdump_len; /* Size of data from ETHTOOL_GREGS (bytes) */ +}; + +#define SOPASS_MAX 6 +/* wake-on-lan settings */ +struct ethtool_wolinfo { + __u32 cmd; + __u32 supported; + __u32 wolopts; + __u8 sopass[SOPASS_MAX]; /* SecureOn(tm) password */ +}; + +/* for passing single values */ +struct ethtool_value { + __u32 cmd; + __u32 data; +}; + +/* for passing big chunks of data */ +struct ethtool_regs { + __u32 cmd; + __u32 version; /* driver-specific, indicates different chips/revs */ + __u32 len; /* bytes */ + __u8 data[0]; +}; + +/* for passing EEPROM chunks */ +struct ethtool_eeprom { + __u32 cmd; + __u32 magic; + __u32 offset; /* in bytes */ + __u32 len; /* in bytes */ + __u8 data[0]; +}; + +/** + * struct ethtool_eee - Energy Efficient Ethernet information + * @cmd: ETHTOOL_{G,S}EEE + * @supported: Mask of %SUPPORTED_* flags for the speed/duplex combinations + * for which there is EEE support. + * @advertised: Mask of %ADVERTISED_* flags for the speed/duplex combinations + * advertised as eee capable. + * @lp_advertised: Mask of %ADVERTISED_* flags for the speed/duplex + * combinations advertised by the link partner as eee capable. + * @eee_active: Result of the eee auto negotiation. + * @eee_enabled: EEE configured mode (enabled/disabled). + * @tx_lpi_enabled: Whether the interface should assert its tx lpi, given + * that eee was negotiated. + * @tx_lpi_timer: Time in microseconds the interface delays prior to asserting + * its tx lpi (after reaching 'idle' state). Effective only when eee + * was negotiated and tx_lpi_enabled was set. + */ +struct ethtool_eee { + __u32 cmd; + __u32 supported; + __u32 advertised; + __u32 lp_advertised; + __u32 eee_active; + __u32 eee_enabled; + __u32 tx_lpi_enabled; + __u32 tx_lpi_timer; + __u32 reserved[2]; +}; + +/** + * struct ethtool_modinfo - plugin module eeprom information + * @cmd: %ETHTOOL_GMODULEINFO + * @type: Standard the module information conforms to %ETH_MODULE_SFF_xxxx + * @eeprom_len: Length of the eeprom + * + * This structure is used to return the information to + * properly size memory for a subsequent call to %ETHTOOL_GMODULEEEPROM. + * The type code indicates the eeprom data format + */ +struct ethtool_modinfo { + __u32 cmd; + __u32 type; + __u32 eeprom_len; + __u32 reserved[8]; +}; + +/** + * struct ethtool_coalesce - coalescing parameters for IRQs and stats updates + * @cmd: ETHTOOL_{G,S}COALESCE + * @rx_coalesce_usecs: How many usecs to delay an RX interrupt after + * a packet arrives. + * @rx_max_coalesced_frames: Maximum number of packets to receive + * before an RX interrupt. + * @rx_coalesce_usecs_irq: Same as @rx_coalesce_usecs, except that + * this value applies while an IRQ is being serviced by the host. + * @rx_max_coalesced_frames_irq: Same as @rx_max_coalesced_frames, + * except that this value applies while an IRQ is being serviced + * by the host. + * @tx_coalesce_usecs: How many usecs to delay a TX interrupt after + * a packet is sent. + * @tx_max_coalesced_frames: Maximum number of packets to be sent + * before a TX interrupt. + * @tx_coalesce_usecs_irq: Same as @tx_coalesce_usecs, except that + * this value applies while an IRQ is being serviced by the host. + * @tx_max_coalesced_frames_irq: Same as @tx_max_coalesced_frames, + * except that this value applies while an IRQ is being serviced + * by the host. + * @stats_block_coalesce_usecs: How many usecs to delay in-memory + * statistics block updates. Some drivers do not have an + * in-memory statistic block, and in such cases this value is + * ignored. This value must not be zero. + * @use_adaptive_rx_coalesce: Enable adaptive RX coalescing. + * @use_adaptive_tx_coalesce: Enable adaptive TX coalescing. + * @pkt_rate_low: Threshold for low packet rate (packets per second). + * @rx_coalesce_usecs_low: How many usecs to delay an RX interrupt after + * a packet arrives, when the packet rate is below @pkt_rate_low. + * @rx_max_coalesced_frames_low: Maximum number of packets to be received + * before an RX interrupt, when the packet rate is below @pkt_rate_low. + * @tx_coalesce_usecs_low: How many usecs to delay a TX interrupt after + * a packet is sent, when the packet rate is below @pkt_rate_low. + * @tx_max_coalesced_frames_low: Maximum nuumber of packets to be sent before + * a TX interrupt, when the packet rate is below @pkt_rate_low. + * @pkt_rate_high: Threshold for high packet rate (packets per second). + * @rx_coalesce_usecs_high: How many usecs to delay an RX interrupt after + * a packet arrives, when the packet rate is above @pkt_rate_high. + * @rx_max_coalesced_frames_high: Maximum number of packets to be received + * before an RX interrupt, when the packet rate is above @pkt_rate_high. + * @tx_coalesce_usecs_high: How many usecs to delay a TX interrupt after + * a packet is sent, when the packet rate is above @pkt_rate_high. + * @tx_max_coalesced_frames_high: Maximum number of packets to be sent before + * a TX interrupt, when the packet rate is above @pkt_rate_high. + * @rate_sample_interval: How often to do adaptive coalescing packet rate + * sampling, measured in seconds. Must not be zero. + * + * Each pair of (usecs, max_frames) fields specifies this exit + * condition for interrupt coalescing: + * (usecs > 0 && time_since_first_completion >= usecs) || + * (max_frames > 0 && completed_frames >= max_frames) + * It is illegal to set both usecs and max_frames to zero as this + * would cause interrupts to never be generated. To disable + * coalescing, set usecs = 0 and max_frames = 1. + * + * Some implementations ignore the value of max_frames and use the + * condition: + * time_since_first_completion >= usecs + * This is deprecated. Drivers for hardware that does not support + * counting completions should validate that max_frames == !rx_usecs. + * + * Adaptive RX/TX coalescing is an algorithm implemented by some + * drivers to improve latency under low packet rates and improve + * throughput under high packet rates. Some drivers only implement + * one of RX or TX adaptive coalescing. Anything not implemented by + * the driver causes these values to be silently ignored. + * + * When the packet rate is below @pkt_rate_high but above + * @pkt_rate_low (both measured in packets per second) the + * normal {rx,tx}_* coalescing parameters are used. + */ +struct ethtool_coalesce { + __u32 cmd; + __u32 rx_coalesce_usecs; + __u32 rx_max_coalesced_frames; + __u32 rx_coalesce_usecs_irq; + __u32 rx_max_coalesced_frames_irq; + __u32 tx_coalesce_usecs; + __u32 tx_max_coalesced_frames; + __u32 tx_coalesce_usecs_irq; + __u32 tx_max_coalesced_frames_irq; + __u32 stats_block_coalesce_usecs; + __u32 use_adaptive_rx_coalesce; + __u32 use_adaptive_tx_coalesce; + __u32 pkt_rate_low; + __u32 rx_coalesce_usecs_low; + __u32 rx_max_coalesced_frames_low; + __u32 tx_coalesce_usecs_low; + __u32 tx_max_coalesced_frames_low; + __u32 pkt_rate_high; + __u32 rx_coalesce_usecs_high; + __u32 rx_max_coalesced_frames_high; + __u32 tx_coalesce_usecs_high; + __u32 tx_max_coalesced_frames_high; + __u32 rate_sample_interval; +}; + +/* for configuring RX/TX ring parameters */ +struct ethtool_ringparam { + __u32 cmd; /* ETHTOOL_{G,S}RINGPARAM */ + + /* Read only attributes. These indicate the maximum number + * of pending RX/TX ring entries the driver will allow the + * user to set. + */ + __u32 rx_max_pending; + __u32 rx_mini_max_pending; + __u32 rx_jumbo_max_pending; + __u32 tx_max_pending; + + /* Values changeable by the user. The valid values are + * in the range 1 to the "*_max_pending" counterpart above. + */ + __u32 rx_pending; + __u32 rx_mini_pending; + __u32 rx_jumbo_pending; + __u32 tx_pending; +}; + +/** + * struct ethtool_channels - configuring number of network channel + * @cmd: ETHTOOL_{G,S}CHANNELS + * @max_rx: Read only. Maximum number of receive channel the driver support. + * @max_tx: Read only. Maximum number of transmit channel the driver support. + * @max_other: Read only. Maximum number of other channel the driver support. + * @max_combined: Read only. Maximum number of combined channel the driver + * support. Set of queues RX, TX or other. + * @rx_count: Valid values are in the range 1 to the max_rx. + * @tx_count: Valid values are in the range 1 to the max_tx. + * @other_count: Valid values are in the range 1 to the max_other. + * @combined_count: Valid values are in the range 1 to the max_combined. + * + * This can be used to configure RX, TX and other channels. + */ + +struct ethtool_channels { + __u32 cmd; + __u32 max_rx; + __u32 max_tx; + __u32 max_other; + __u32 max_combined; + __u32 rx_count; + __u32 tx_count; + __u32 other_count; + __u32 combined_count; +}; + +/* for configuring link flow control parameters */ +struct ethtool_pauseparam { + __u32 cmd; /* ETHTOOL_{G,S}PAUSEPARAM */ + + /* If the link is being auto-negotiated (via ethtool_cmd.autoneg + * being true) the user may set 'autoneg' here non-zero to have the + * pause parameters be auto-negotiated too. In such a case, the + * {rx,tx}_pause values below determine what capabilities are + * advertised. + * + * If 'autoneg' is zero or the link is not being auto-negotiated, + * then {rx,tx}_pause force the driver to use/not-use pause + * flow control. + */ + __u32 autoneg; + __u32 rx_pause; + __u32 tx_pause; +}; + +#define ETH_GSTRING_LEN 32 +enum ethtool_stringset { + ETH_SS_TEST = 0, + ETH_SS_STATS, + ETH_SS_PRIV_FLAGS, + ETH_SS_NTUPLE_FILTERS, /* Do not use, GRXNTUPLE is now deprecated */ + ETH_SS_FEATURES, +}; + +/* for passing string sets for data tagging */ +struct ethtool_gstrings { + __u32 cmd; /* ETHTOOL_GSTRINGS */ + __u32 string_set; /* string set id e.c. ETH_SS_TEST, etc*/ + __u32 len; /* number of strings in the string set */ + __u8 data[0]; +}; + +struct ethtool_sset_info { + __u32 cmd; /* ETHTOOL_GSSET_INFO */ + __u32 reserved; + __u64 sset_mask; /* input: each bit selects an sset to query */ + /* output: each bit a returned sset */ + __u32 data[0]; /* ETH_SS_xxx count, in order, based on bits + in sset_mask. One bit implies one + __u32, two bits implies two + __u32's, etc. */ +}; + +/** + * enum ethtool_test_flags - flags definition of ethtool_test + * @ETH_TEST_FL_OFFLINE: if set perform online and offline tests, otherwise + * only online tests. + * @ETH_TEST_FL_FAILED: Driver set this flag if test fails. + * @ETH_TEST_FL_EXTERNAL_LB: Application request to perform external loopback + * test. + * @ETH_TEST_FL_EXTERNAL_LB_DONE: Driver performed the external loopback test + */ + +enum ethtool_test_flags { + ETH_TEST_FL_OFFLINE = (1 << 0), + ETH_TEST_FL_FAILED = (1 << 1), + ETH_TEST_FL_EXTERNAL_LB = (1 << 2), + ETH_TEST_FL_EXTERNAL_LB_DONE = (1 << 3), +}; + +/* for requesting NIC test and getting results*/ +struct ethtool_test { + __u32 cmd; /* ETHTOOL_TEST */ + __u32 flags; /* ETH_TEST_FL_xxx */ + __u32 reserved; + __u32 len; /* result length, in number of u64 elements */ + __u64 data[0]; +}; + +/* for dumping NIC-specific statistics */ +struct ethtool_stats { + __u32 cmd; /* ETHTOOL_GSTATS */ + __u32 n_stats; /* number of u64's being returned */ + __u64 data[0]; +}; + +struct ethtool_perm_addr { + __u32 cmd; /* ETHTOOL_GPERMADDR */ + __u32 size; + __u8 data[0]; +}; + +/* boolean flags controlling per-interface behavior characteristics. + * When reading, the flag indicates whether or not a certain behavior + * is enabled/present. When writing, the flag indicates whether + * or not the driver should turn on (set) or off (clear) a behavior. + * + * Some behaviors may read-only (unconditionally absent or present). + * If such is the case, return EINVAL in the set-flags operation if the + * flag differs from the read-only value. + */ +enum ethtool_flags { + ETH_FLAG_TXVLAN = (1 << 7), /* TX VLAN offload enabled */ + ETH_FLAG_RXVLAN = (1 << 8), /* RX VLAN offload enabled */ + ETH_FLAG_LRO = (1 << 15), /* LRO is enabled */ + ETH_FLAG_NTUPLE = (1 << 27), /* N-tuple filters enabled */ + ETH_FLAG_RXHASH = (1 << 28), +}; + +/* The following structures are for supporting RX network flow + * classification and RX n-tuple configuration. Note, all multibyte + * fields, e.g., ip4src, ip4dst, psrc, pdst, spi, etc. are expected to + * be in network byte order. + */ + +/** + * struct ethtool_tcpip4_spec - flow specification for TCP/IPv4 etc. + * @ip4src: Source host + * @ip4dst: Destination host + * @psrc: Source port + * @pdst: Destination port + * @tos: Type-of-service + * + * This can be used to specify a TCP/IPv4, UDP/IPv4 or SCTP/IPv4 flow. + */ +struct ethtool_tcpip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be16 psrc; + __be16 pdst; + __u8 tos; +}; + +/** + * struct ethtool_ah_espip4_spec - flow specification for IPsec/IPv4 + * @ip4src: Source host + * @ip4dst: Destination host + * @spi: Security parameters index + * @tos: Type-of-service + * + * This can be used to specify an IPsec transport or tunnel over IPv4. + */ +struct ethtool_ah_espip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 spi; + __u8 tos; +}; + +#define ETH_RX_NFC_IP4 1 + +/** + * struct ethtool_usrip4_spec - general flow specification for IPv4 + * @ip4src: Source host + * @ip4dst: Destination host + * @l4_4_bytes: First 4 bytes of transport (layer 4) header + * @tos: Type-of-service + * @ip_ver: Value must be %ETH_RX_NFC_IP4; mask must be 0 + * @proto: Transport protocol number; mask must be 0 + */ +struct ethtool_usrip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 l4_4_bytes; + __u8 tos; + __u8 ip_ver; + __u8 proto; +}; + +union ethtool_flow_union { + struct ethtool_tcpip4_spec tcp_ip4_spec; + struct ethtool_tcpip4_spec udp_ip4_spec; + struct ethtool_tcpip4_spec sctp_ip4_spec; + struct ethtool_ah_espip4_spec ah_ip4_spec; + struct ethtool_ah_espip4_spec esp_ip4_spec; + struct ethtool_usrip4_spec usr_ip4_spec; + struct ethhdr ether_spec; + __u8 hdata[52]; +}; + +/** + * struct ethtool_flow_ext - additional RX flow fields + * @h_dest: destination MAC address + * @vlan_etype: VLAN EtherType + * @vlan_tci: VLAN tag control information + * @data: user defined data + * + * Note, @vlan_etype, @vlan_tci, and @data are only valid if %FLOW_EXT + * is set in &struct ethtool_rx_flow_spec @flow_type. + * @h_dest is valid if %FLOW_MAC_EXT is set. + */ +struct ethtool_flow_ext { + __u8 padding[2]; + unsigned char h_dest[ETH_ALEN]; + __be16 vlan_etype; + __be16 vlan_tci; + __be32 data[2]; +}; + +/** + * struct ethtool_rx_flow_spec - classification rule for RX flows + * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW + * @h_u: Flow fields to match (dependent on @flow_type) + * @h_ext: Additional fields to match + * @m_u: Masks for flow field bits to be matched + * @m_ext: Masks for additional field bits to be matched + * Note, all additional fields must be ignored unless @flow_type + * includes the %FLOW_EXT or %FLOW_MAC_EXT flag + * (see &struct ethtool_flow_ext description). + * @ring_cookie: RX ring/queue index to deliver to, or %RX_CLS_FLOW_DISC + * if packets should be discarded + * @location: Location of rule in the table. Locations must be + * numbered such that a flow matching multiple rules will be + * classified according to the first (lowest numbered) rule. + */ +struct ethtool_rx_flow_spec { + __u32 flow_type; + union ethtool_flow_union h_u; + struct ethtool_flow_ext h_ext; + union ethtool_flow_union m_u; + struct ethtool_flow_ext m_ext; + __u64 ring_cookie; + __u32 location; +}; + +/** + * struct ethtool_rxnfc - command to get or set RX flow classification rules + * @cmd: Specific command number - %ETHTOOL_GRXFH, %ETHTOOL_SRXFH, + * %ETHTOOL_GRXRINGS, %ETHTOOL_GRXCLSRLCNT, %ETHTOOL_GRXCLSRULE, + * %ETHTOOL_GRXCLSRLALL, %ETHTOOL_SRXCLSRLDEL or %ETHTOOL_SRXCLSRLINS + * @flow_type: Type of flow to be affected, e.g. %TCP_V4_FLOW + * @data: Command-dependent value + * @fs: Flow classification rule + * @rule_cnt: Number of rules to be affected + * @rule_locs: Array of used rule locations + * + * For %ETHTOOL_GRXFH and %ETHTOOL_SRXFH, @data is a bitmask indicating + * the fields included in the flow hash, e.g. %RXH_IP_SRC. The following + * structure fields must not be used. + * + * For %ETHTOOL_GRXRINGS, @data is set to the number of RX rings/queues + * on return. + * + * For %ETHTOOL_GRXCLSRLCNT, @rule_cnt is set to the number of defined + * rules on return. If @data is non-zero on return then it is the + * size of the rule table, plus the flag %RX_CLS_LOC_SPECIAL if the + * driver supports any special location values. If that flag is not + * set in @data then special location values should not be used. + * + * For %ETHTOOL_GRXCLSRULE, @fs.@location specifies the location of an + * existing rule on entry and @fs contains the rule on return. + * + * For %ETHTOOL_GRXCLSRLALL, @rule_cnt specifies the array size of the + * user buffer for @rule_locs on entry. On return, @data is the size + * of the rule table, @rule_cnt is the number of defined rules, and + * @rule_locs contains the locations of the defined rules. Drivers + * must use the second parameter to get_rxnfc() instead of @rule_locs. + * + * For %ETHTOOL_SRXCLSRLINS, @fs specifies the rule to add or update. + * @fs.@location either specifies the location to use or is a special + * location value with %RX_CLS_LOC_SPECIAL flag set. On return, + * @fs.@location is the actual rule location. + * + * For %ETHTOOL_SRXCLSRLDEL, @fs.@location specifies the location of an + * existing rule on entry. + * + * A driver supporting the special location values for + * %ETHTOOL_SRXCLSRLINS may add the rule at any suitable unused + * location, and may remove a rule at a later location (lower + * priority) that matches exactly the same set of flows. The special + * values are: %RX_CLS_LOC_ANY, selecting any location; + * %RX_CLS_LOC_FIRST, selecting the first suitable location (maximum + * priority); and %RX_CLS_LOC_LAST, selecting the last suitable + * location (minimum priority). Additional special values may be + * defined in future and drivers must return -%EINVAL for any + * unrecognised value. + */ +struct ethtool_rxnfc { + __u32 cmd; + __u32 flow_type; + __u64 data; + struct ethtool_rx_flow_spec fs; + __u32 rule_cnt; + __u32 rule_locs[0]; +}; + + +/** + * struct ethtool_rxfh_indir - command to get or set RX flow hash indirection + * @cmd: Specific command number - %ETHTOOL_GRXFHINDIR or %ETHTOOL_SRXFHINDIR + * @size: On entry, the array size of the user buffer, which may be zero. + * On return from %ETHTOOL_GRXFHINDIR, the array size of the hardware + * indirection table. + * @ring_index: RX ring/queue index for each hash value + * + * For %ETHTOOL_GRXFHINDIR, a @size of zero means that only the size + * should be returned. For %ETHTOOL_SRXFHINDIR, a @size of zero means + * the table should be reset to default values. This last feature + * is not supported by the original implementations. + */ +struct ethtool_rxfh_indir { + __u32 cmd; + __u32 size; + __u32 ring_index[0]; +}; + +/** + * struct ethtool_rx_ntuple_flow_spec - specification for RX flow filter + * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW + * @h_u: Flow field values to match (dependent on @flow_type) + * @m_u: Masks for flow field value bits to be ignored + * @vlan_tag: VLAN tag to match + * @vlan_tag_mask: Mask for VLAN tag bits to be ignored + * @data: Driver-dependent data to match + * @data_mask: Mask for driver-dependent data bits to be ignored + * @action: RX ring/queue index to deliver to (non-negative) or other action + * (negative, e.g. %ETHTOOL_RXNTUPLE_ACTION_DROP) + * + * For flow types %TCP_V4_FLOW, %UDP_V4_FLOW and %SCTP_V4_FLOW, where + * a field value and mask are both zero this is treated as if all mask + * bits are set i.e. the field is ignored. + */ +struct ethtool_rx_ntuple_flow_spec { + __u32 flow_type; + union { + struct ethtool_tcpip4_spec tcp_ip4_spec; + struct ethtool_tcpip4_spec udp_ip4_spec; + struct ethtool_tcpip4_spec sctp_ip4_spec; + struct ethtool_ah_espip4_spec ah_ip4_spec; + struct ethtool_ah_espip4_spec esp_ip4_spec; + struct ethtool_usrip4_spec usr_ip4_spec; + struct ethhdr ether_spec; + __u8 hdata[72]; + } h_u, m_u; + + __u16 vlan_tag; + __u16 vlan_tag_mask; + __u64 data; + __u64 data_mask; + + __s32 action; +#define ETHTOOL_RXNTUPLE_ACTION_DROP (-1) /* drop packet */ +#define ETHTOOL_RXNTUPLE_ACTION_CLEAR (-2) /* clear filter */ +}; + +/** + * struct ethtool_rx_ntuple - command to set or clear RX flow filter + * @cmd: Command number - %ETHTOOL_SRXNTUPLE + * @fs: Flow filter specification + */ +struct ethtool_rx_ntuple { + __u32 cmd; + struct ethtool_rx_ntuple_flow_spec fs; +}; + +#define ETHTOOL_FLASH_MAX_FILENAME 128 +enum ethtool_flash_op_type { + ETHTOOL_FLASH_ALL_REGIONS = 0, +}; + +/* for passing firmware flashing related parameters */ +struct ethtool_flash { + __u32 cmd; + __u32 region; + char data[ETHTOOL_FLASH_MAX_FILENAME]; +}; + +/** + * struct ethtool_dump - used for retrieving, setting device dump + * @cmd: Command number - %ETHTOOL_GET_DUMP_FLAG, %ETHTOOL_GET_DUMP_DATA, or + * %ETHTOOL_SET_DUMP + * @version: FW version of the dump, filled in by driver + * @flag: driver dependent flag for dump setting, filled in by driver during + * get and filled in by ethtool for set operation. + * flag must be initialized by macro ETH_FW_DUMP_DISABLE value when + * firmware dump is disabled. + * @len: length of dump data, used as the length of the user buffer on entry to + * %ETHTOOL_GET_DUMP_DATA and this is returned as dump length by driver + * for %ETHTOOL_GET_DUMP_FLAG command + * @data: data collected for get dump data operation + */ + +#define ETH_FW_DUMP_DISABLE 0 + +struct ethtool_dump { + __u32 cmd; + __u32 version; + __u32 flag; + __u32 len; + __u8 data[0]; +}; + +/* for returning and changing feature sets */ + +/** + * struct ethtool_get_features_block - block with state of 32 features + * @available: mask of changeable features + * @requested: mask of features requested to be enabled if possible + * @active: mask of currently enabled features + * @never_changed: mask of features not changeable for any device + */ +struct ethtool_get_features_block { + __u32 available; + __u32 requested; + __u32 active; + __u32 never_changed; +}; + +/** + * struct ethtool_gfeatures - command to get state of device's features + * @cmd: command number = %ETHTOOL_GFEATURES + * @size: in: number of elements in the features[] array; + * out: number of elements in features[] needed to hold all features + * @features: state of features + */ +struct ethtool_gfeatures { + __u32 cmd; + __u32 size; + struct ethtool_get_features_block features[0]; +}; + +/** + * struct ethtool_set_features_block - block with request for 32 features + * @valid: mask of features to be changed + * @requested: values of features to be changed + */ +struct ethtool_set_features_block { + __u32 valid; + __u32 requested; +}; + +/** + * struct ethtool_sfeatures - command to request change in device's features + * @cmd: command number = %ETHTOOL_SFEATURES + * @size: array size of the features[] array + * @features: feature change masks + */ +struct ethtool_sfeatures { + __u32 cmd; + __u32 size; + struct ethtool_set_features_block features[0]; +}; + +/** + * struct ethtool_ts_info - holds a device's timestamping and PHC association + * @cmd: command number = %ETHTOOL_GET_TS_INFO + * @so_timestamping: bit mask of the sum of the supported SO_TIMESTAMPING flags + * @phc_index: device index of the associated PHC, or -1 if there is none + * @tx_types: bit mask of the supported hwtstamp_tx_types enumeration values + * @rx_filters: bit mask of the supported hwtstamp_rx_filters enumeration values + * + * The bits in the 'tx_types' and 'rx_filters' fields correspond to + * the 'hwtstamp_tx_types' and 'hwtstamp_rx_filters' enumeration values, + * respectively. For example, if the device supports HWTSTAMP_TX_ON, + * then (1 << HWTSTAMP_TX_ON) in 'tx_types' will be set. + */ +struct ethtool_ts_info { + __u32 cmd; + __u32 so_timestamping; + __s32 phc_index; + __u32 tx_types; + __u32 tx_reserved[3]; + __u32 rx_filters; + __u32 rx_reserved[3]; +}; + +/* + * %ETHTOOL_SFEATURES changes features present in features[].valid to the + * values of corresponding bits in features[].requested. Bits in .requested + * not set in .valid or not changeable are ignored. + * + * Returns %EINVAL when .valid contains undefined or never-changeable bits + * or size is not equal to required number of features words (32-bit blocks). + * Returns >= 0 if request was completed; bits set in the value mean: + * %ETHTOOL_F_UNSUPPORTED - there were bits set in .valid that are not + * changeable (not present in %ETHTOOL_GFEATURES' features[].available) + * those bits were ignored. + * %ETHTOOL_F_WISH - some or all changes requested were recorded but the + * resulting state of bits masked by .valid is not equal to .requested. + * Probably there are other device-specific constraints on some features + * in the set. When %ETHTOOL_F_UNSUPPORTED is set, .valid is considered + * here as though ignored bits were cleared. + * %ETHTOOL_F_COMPAT - some or all changes requested were made by calling + * compatibility functions. Requested offload state cannot be properly + * managed by kernel. + * + * Meaning of bits in the masks are obtained by %ETHTOOL_GSSET_INFO (number of + * bits in the arrays - always multiple of 32) and %ETHTOOL_GSTRINGS commands + * for ETH_SS_FEATURES string set. First entry in the table corresponds to least + * significant bit in features[0] fields. Empty strings mark undefined features. + */ +enum ethtool_sfeatures_retval_bits { + ETHTOOL_F_UNSUPPORTED__BIT, + ETHTOOL_F_WISH__BIT, + ETHTOOL_F_COMPAT__BIT, +}; + +#define ETHTOOL_F_UNSUPPORTED (1 << ETHTOOL_F_UNSUPPORTED__BIT) +#define ETHTOOL_F_WISH (1 << ETHTOOL_F_WISH__BIT) +#define ETHTOOL_F_COMPAT (1 << ETHTOOL_F_COMPAT__BIT) + + +/* CMDs currently supported */ +#define ETHTOOL_GSET 0x00000001 /* Get settings. */ +#define ETHTOOL_SSET 0x00000002 /* Set settings. */ +#define ETHTOOL_GDRVINFO 0x00000003 /* Get driver info. */ +#define ETHTOOL_GREGS 0x00000004 /* Get NIC registers. */ +#define ETHTOOL_GWOL 0x00000005 /* Get wake-on-lan options. */ +#define ETHTOOL_SWOL 0x00000006 /* Set wake-on-lan options. */ +#define ETHTOOL_GMSGLVL 0x00000007 /* Get driver message level */ +#define ETHTOOL_SMSGLVL 0x00000008 /* Set driver msg level. */ +#define ETHTOOL_NWAY_RST 0x00000009 /* Restart autonegotiation. */ +/* Get link status for host, i.e. whether the interface *and* the + * physical port (if there is one) are up (ethtool_value). */ +#define ETHTOOL_GLINK 0x0000000a +#define ETHTOOL_GEEPROM 0x0000000b /* Get EEPROM data */ +#define ETHTOOL_SEEPROM 0x0000000c /* Set EEPROM data. */ +#define ETHTOOL_GCOALESCE 0x0000000e /* Get coalesce config */ +#define ETHTOOL_SCOALESCE 0x0000000f /* Set coalesce config. */ +#define ETHTOOL_GRINGPARAM 0x00000010 /* Get ring parameters */ +#define ETHTOOL_SRINGPARAM 0x00000011 /* Set ring parameters. */ +#define ETHTOOL_GPAUSEPARAM 0x00000012 /* Get pause parameters */ +#define ETHTOOL_SPAUSEPARAM 0x00000013 /* Set pause parameters. */ +#define ETHTOOL_GRXCSUM 0x00000014 /* Get RX hw csum enable (ethtool_value) */ +#define ETHTOOL_SRXCSUM 0x00000015 /* Set RX hw csum enable (ethtool_value) */ +#define ETHTOOL_GTXCSUM 0x00000016 /* Get TX hw csum enable (ethtool_value) */ +#define ETHTOOL_STXCSUM 0x00000017 /* Set TX hw csum enable (ethtool_value) */ +#define ETHTOOL_GSG 0x00000018 /* Get scatter-gather enable + * (ethtool_value) */ +#define ETHTOOL_SSG 0x00000019 /* Set scatter-gather enable + * (ethtool_value). */ +#define ETHTOOL_TEST 0x0000001a /* execute NIC self-test. */ +#define ETHTOOL_GSTRINGS 0x0000001b /* get specified string set */ +#define ETHTOOL_PHYS_ID 0x0000001c /* identify the NIC */ +#define ETHTOOL_GSTATS 0x0000001d /* get NIC-specific statistics */ +#define ETHTOOL_GTSO 0x0000001e /* Get TSO enable (ethtool_value) */ +#define ETHTOOL_STSO 0x0000001f /* Set TSO enable (ethtool_value) */ +#define ETHTOOL_GPERMADDR 0x00000020 /* Get permanent hardware address */ +#define ETHTOOL_GUFO 0x00000021 /* Get UFO enable (ethtool_value) */ +#define ETHTOOL_SUFO 0x00000022 /* Set UFO enable (ethtool_value) */ +#define ETHTOOL_GGSO 0x00000023 /* Get GSO enable (ethtool_value) */ +#define ETHTOOL_SGSO 0x00000024 /* Set GSO enable (ethtool_value) */ +#define ETHTOOL_GFLAGS 0x00000025 /* Get flags bitmap(ethtool_value) */ +#define ETHTOOL_SFLAGS 0x00000026 /* Set flags bitmap(ethtool_value) */ +#define ETHTOOL_GPFLAGS 0x00000027 /* Get driver-private flags bitmap */ +#define ETHTOOL_SPFLAGS 0x00000028 /* Set driver-private flags bitmap */ + +#define ETHTOOL_GRXFH 0x00000029 /* Get RX flow hash configuration */ +#define ETHTOOL_SRXFH 0x0000002a /* Set RX flow hash configuration */ +#define ETHTOOL_GGRO 0x0000002b /* Get GRO enable (ethtool_value) */ +#define ETHTOOL_SGRO 0x0000002c /* Set GRO enable (ethtool_value) */ +#define ETHTOOL_GRXRINGS 0x0000002d /* Get RX rings available for LB */ +#define ETHTOOL_GRXCLSRLCNT 0x0000002e /* Get RX class rule count */ +#define ETHTOOL_GRXCLSRULE 0x0000002f /* Get RX classification rule */ +#define ETHTOOL_GRXCLSRLALL 0x00000030 /* Get all RX classification rule */ +#define ETHTOOL_SRXCLSRLDEL 0x00000031 /* Delete RX classification rule */ +#define ETHTOOL_SRXCLSRLINS 0x00000032 /* Insert RX classification rule */ +#define ETHTOOL_FLASHDEV 0x00000033 /* Flash firmware to device */ +#define ETHTOOL_RESET 0x00000034 /* Reset hardware */ +#define ETHTOOL_SRXNTUPLE 0x00000035 /* Add an n-tuple filter to device */ +#define ETHTOOL_GRXNTUPLE 0x00000036 /* deprecated */ +#define ETHTOOL_GSSET_INFO 0x00000037 /* Get string set info */ +#define ETHTOOL_GRXFHINDIR 0x00000038 /* Get RX flow hash indir'n table */ +#define ETHTOOL_SRXFHINDIR 0x00000039 /* Set RX flow hash indir'n table */ + +#define ETHTOOL_GFEATURES 0x0000003a /* Get device offload settings */ +#define ETHTOOL_SFEATURES 0x0000003b /* Change device offload settings */ +#define ETHTOOL_GCHANNELS 0x0000003c /* Get no of channels */ +#define ETHTOOL_SCHANNELS 0x0000003d /* Set no of channels */ +#define ETHTOOL_SET_DUMP 0x0000003e /* Set dump settings */ +#define ETHTOOL_GET_DUMP_FLAG 0x0000003f /* Get dump settings */ +#define ETHTOOL_GET_DUMP_DATA 0x00000040 /* Get dump data */ +#define ETHTOOL_GET_TS_INFO 0x00000041 /* Get time stamping and PHC info */ +#define ETHTOOL_GMODULEINFO 0x00000042 /* Get plug-in module information */ +#define ETHTOOL_GMODULEEEPROM 0x00000043 /* Get plug-in module eeprom */ +#define ETHTOOL_GEEE 0x00000044 /* Get EEE settings */ +#define ETHTOOL_SEEE 0x00000045 /* Set EEE settings */ + +/* compatibility with older code */ +#define SPARC_ETH_GSET ETHTOOL_GSET +#define SPARC_ETH_SSET ETHTOOL_SSET + +/* Indicates what features are supported by the interface. */ +#define SUPPORTED_10baseT_Half (1 << 0) +#define SUPPORTED_10baseT_Full (1 << 1) +#define SUPPORTED_100baseT_Half (1 << 2) +#define SUPPORTED_100baseT_Full (1 << 3) +#define SUPPORTED_1000baseT_Half (1 << 4) +#define SUPPORTED_1000baseT_Full (1 << 5) +#define SUPPORTED_Autoneg (1 << 6) +#define SUPPORTED_TP (1 << 7) +#define SUPPORTED_AUI (1 << 8) +#define SUPPORTED_MII (1 << 9) +#define SUPPORTED_FIBRE (1 << 10) +#define SUPPORTED_BNC (1 << 11) +#define SUPPORTED_10000baseT_Full (1 << 12) +#define SUPPORTED_Pause (1 << 13) +#define SUPPORTED_Asym_Pause (1 << 14) +#define SUPPORTED_2500baseX_Full (1 << 15) +#define SUPPORTED_Backplane (1 << 16) +#define SUPPORTED_1000baseKX_Full (1 << 17) +#define SUPPORTED_10000baseKX4_Full (1 << 18) +#define SUPPORTED_10000baseKR_Full (1 << 19) +#define SUPPORTED_10000baseR_FEC (1 << 20) +#define SUPPORTED_20000baseMLD2_Full (1 << 21) +#define SUPPORTED_20000baseKR2_Full (1 << 22) +#define SUPPORTED_40000baseKR4_Full (1 << 23) +#define SUPPORTED_40000baseCR4_Full (1 << 24) +#define SUPPORTED_40000baseSR4_Full (1 << 25) +#define SUPPORTED_40000baseLR4_Full (1 << 26) + +/* Indicates what features are advertised by the interface. */ +#define ADVERTISED_10baseT_Half (1 << 0) +#define ADVERTISED_10baseT_Full (1 << 1) +#define ADVERTISED_100baseT_Half (1 << 2) +#define ADVERTISED_100baseT_Full (1 << 3) +#define ADVERTISED_1000baseT_Half (1 << 4) +#define ADVERTISED_1000baseT_Full (1 << 5) +#define ADVERTISED_Autoneg (1 << 6) +#define ADVERTISED_TP (1 << 7) +#define ADVERTISED_AUI (1 << 8) +#define ADVERTISED_MII (1 << 9) +#define ADVERTISED_FIBRE (1 << 10) +#define ADVERTISED_BNC (1 << 11) +#define ADVERTISED_10000baseT_Full (1 << 12) +#define ADVERTISED_Pause (1 << 13) +#define ADVERTISED_Asym_Pause (1 << 14) +#define ADVERTISED_2500baseX_Full (1 << 15) +#define ADVERTISED_Backplane (1 << 16) +#define ADVERTISED_1000baseKX_Full (1 << 17) +#define ADVERTISED_10000baseKX4_Full (1 << 18) +#define ADVERTISED_10000baseKR_Full (1 << 19) +#define ADVERTISED_10000baseR_FEC (1 << 20) +#define ADVERTISED_20000baseMLD2_Full (1 << 21) +#define ADVERTISED_20000baseKR2_Full (1 << 22) +#define ADVERTISED_40000baseKR4_Full (1 << 23) +#define ADVERTISED_40000baseCR4_Full (1 << 24) +#define ADVERTISED_40000baseSR4_Full (1 << 25) +#define ADVERTISED_40000baseLR4_Full (1 << 26) + +/* The following are all involved in forcing a particular link + * mode for the device for setting things. When getting the + * devices settings, these indicate the current mode and whether + * it was forced up into this mode or autonegotiated. + */ + +/* The forced speed, 10Mb, 100Mb, gigabit, 2.5Gb, 10GbE. */ +#define SPEED_10 10 +#define SPEED_100 100 +#define SPEED_1000 1000 +#define SPEED_2500 2500 +#define SPEED_10000 10000 +#define SPEED_UNKNOWN -1 + +/* Duplex, half or full. */ +#define DUPLEX_HALF 0x00 +#define DUPLEX_FULL 0x01 +#define DUPLEX_UNKNOWN 0xff + +/* Which connector port. */ +#define PORT_TP 0x00 +#define PORT_AUI 0x01 +#define PORT_MII 0x02 +#define PORT_FIBRE 0x03 +#define PORT_BNC 0x04 +#define PORT_DA 0x05 +#define PORT_NONE 0xef +#define PORT_OTHER 0xff + +/* Which transceiver to use. */ +#define XCVR_INTERNAL 0x00 +#define XCVR_EXTERNAL 0x01 +#define XCVR_DUMMY1 0x02 +#define XCVR_DUMMY2 0x03 +#define XCVR_DUMMY3 0x04 + +/* Enable or disable autonegotiation. If this is set to enable, + * the forced link modes above are completely ignored. + */ +#define AUTONEG_DISABLE 0x00 +#define AUTONEG_ENABLE 0x01 + +/* MDI or MDI-X status/control - if MDI/MDI_X/AUTO is set then + * the driver is required to renegotiate link + */ +#define ETH_TP_MDI_INVALID 0x00 /* status: unknown; control: unsupported */ +#define ETH_TP_MDI 0x01 /* status: MDI; control: force MDI */ +#define ETH_TP_MDI_X 0x02 /* status: MDI-X; control: force MDI-X */ +#define ETH_TP_MDI_AUTO 0x03 /* control: auto-select */ + +/* Wake-On-Lan options. */ +#define WAKE_PHY (1 << 0) +#define WAKE_UCAST (1 << 1) +#define WAKE_MCAST (1 << 2) +#define WAKE_BCAST (1 << 3) +#define WAKE_ARP (1 << 4) +#define WAKE_MAGIC (1 << 5) +#define WAKE_MAGICSECURE (1 << 6) /* only meaningful if WAKE_MAGIC */ + +/* L2-L4 network traffic flow types */ +#define TCP_V4_FLOW 0x01 /* hash or spec (tcp_ip4_spec) */ +#define UDP_V4_FLOW 0x02 /* hash or spec (udp_ip4_spec) */ +#define SCTP_V4_FLOW 0x03 /* hash or spec (sctp_ip4_spec) */ +#define AH_ESP_V4_FLOW 0x04 /* hash only */ +#define TCP_V6_FLOW 0x05 /* hash only */ +#define UDP_V6_FLOW 0x06 /* hash only */ +#define SCTP_V6_FLOW 0x07 /* hash only */ +#define AH_ESP_V6_FLOW 0x08 /* hash only */ +#define AH_V4_FLOW 0x09 /* hash or spec (ah_ip4_spec) */ +#define ESP_V4_FLOW 0x0a /* hash or spec (esp_ip4_spec) */ +#define AH_V6_FLOW 0x0b /* hash only */ +#define ESP_V6_FLOW 0x0c /* hash only */ +#define IP_USER_FLOW 0x0d /* spec only (usr_ip4_spec) */ +#define IPV4_FLOW 0x10 /* hash only */ +#define IPV6_FLOW 0x11 /* hash only */ +#define ETHER_FLOW 0x12 /* spec only (ether_spec) */ +/* Flag to enable additional fields in struct ethtool_rx_flow_spec */ +#define FLOW_EXT 0x80000000 +#define FLOW_MAC_EXT 0x40000000 + +/* L3-L4 network traffic flow hash options */ +#define RXH_L2DA (1 << 1) +#define RXH_VLAN (1 << 2) +#define RXH_L3_PROTO (1 << 3) +#define RXH_IP_SRC (1 << 4) +#define RXH_IP_DST (1 << 5) +#define RXH_L4_B_0_1 (1 << 6) /* src port in case of TCP/UDP/SCTP */ +#define RXH_L4_B_2_3 (1 << 7) /* dst port in case of TCP/UDP/SCTP */ +#define RXH_DISCARD (1 << 31) + +#define RX_CLS_FLOW_DISC 0xffffffffffffffffULL + +/* Special RX classification rule insert location values */ +#define RX_CLS_LOC_SPECIAL 0x80000000 /* flag */ +#define RX_CLS_LOC_ANY 0xffffffff +#define RX_CLS_LOC_FIRST 0xfffffffe +#define RX_CLS_LOC_LAST 0xfffffffd + +/* EEPROM Standards for plug in modules */ +#define ETH_MODULE_SFF_8079 0x1 +#define ETH_MODULE_SFF_8079_LEN 256 +#define ETH_MODULE_SFF_8472 0x2 +#define ETH_MODULE_SFF_8472_LEN 512 + +/* Reset flags */ +/* The reset() operation must clear the flags for the components which + * were actually reset. On successful return, the flags indicate the + * components which were not reset, either because they do not exist + * in the hardware or because they cannot be reset independently. The + * driver must never reset any components that were not requested. + */ +enum ethtool_reset_flags { + /* These flags represent components dedicated to the interface + * the command is addressed to. Shift any flag left by + * ETH_RESET_SHARED_SHIFT to reset a shared component of the + * same type. + */ + ETH_RESET_MGMT = 1 << 0, /* Management processor */ + ETH_RESET_IRQ = 1 << 1, /* Interrupt requester */ + ETH_RESET_DMA = 1 << 2, /* DMA engine */ + ETH_RESET_FILTER = 1 << 3, /* Filtering/flow direction */ + ETH_RESET_OFFLOAD = 1 << 4, /* Protocol offload */ + ETH_RESET_MAC = 1 << 5, /* Media access controller */ + ETH_RESET_PHY = 1 << 6, /* Transceiver/PHY */ + ETH_RESET_RAM = 1 << 7, /* RAM shared between + * multiple components */ + + ETH_RESET_DEDICATED = 0x0000ffff, /* All components dedicated to + * this interface */ + ETH_RESET_ALL = 0xffffffff, /* All components used by this + * interface, even if shared */ +}; +#define ETH_RESET_SHARED_SHIFT 16 + +#endif /* _LINUX_ETHTOOL_H */ diff --git a/RM520N-5G/文件/source/main.c b/RM520N-5G/文件/source/main.c new file mode 100644 index 0000000..4d5ff1c --- /dev/null +++ b/RM520N-5G/文件/source/main.c @@ -0,0 +1,1197 @@ +/* + 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" +#include +#include +#include +#include + +#include "util.h" +//#define CONFIG_PID_FILE_FORMAT "/var/run/quectel-CM-%s.pid" //for example /var/run/quectel-CM-wwan0.pid + +static PROFILE_T s_profile; +int debug_qmi = 0; +int qmidevice_control_fd[2]; +static int signal_control_fd[2]; +#ifdef USE_IPC_MSG_STATUS_IND +static int qcm_status_indication_fd[2]; +#endif +int g_donot_exit_when_modem_hangup = 0; +extern int ql_ifconfig(int argc, char *argv[]); +extern int ql_get_netcard_driver_info(const char*); +extern int ql_capture_usbmon_log(PROFILE_T *profile, const char *log_path); +extern void ql_stop_usbmon_log(PROFILE_T *profile); +//UINT ifc_get_addr(const char *ifname); +extern int cdc_wdm_fd; +static int s_link = -1; +static void usbnet_link_change(int link, PROFILE_T *profile) { + if (s_link == link) + return; + + s_link = link; + + if (!(link & (1<ipv4, 0, sizeof(IPV4_T)); + + if (!(link & (1<ipv6, 0, sizeof(IPV6_T)); + + if (link) { + udhcpc_start(profile); + } else { + udhcpc_stop(profile); + } +} + +static int check_ipv4_address(PROFILE_T *profile) { + uint32_t oldAddress = profile->ipv4.Address; + + if (profile->request_ops == &mbim_request_ops) + return 1; //we will get a new ipv6 address per requestGetIPAddress() + if (profile->request_ops == &atc_request_ops) { + if (!profile->udhcpc_ip) return 1; + oldAddress = profile->udhcpc_ip; + } + + if (profile->request_ops->requestGetIPAddress(profile, IpFamilyV4) == 0) { + if (profile->ipv4.Address != oldAddress || debug_qmi) { + unsigned char *l = (unsigned char *)&oldAddress; + unsigned char *r = (unsigned char *)&profile->ipv4.Address; + dbg_time("localIP: %d.%d.%d.%d VS remoteIP: %d.%d.%d.%d", + l[3], l[2], l[1], l[0], r[3], r[2], r[1], r[0]); + } + return (profile->ipv4.Address == oldAddress); + } + + return 0; +} + +static void main_send_event_to_qmidevice(int triger_event) { + if (write(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)) == -1) {}; +} + +static void send_signo_to_main(int signo) { + if (write(signal_control_fd[0], &signo, sizeof(signo)) == -1) {}; +} + +#ifdef USE_IPC_MSG_STATUS_IND +static void main_send_status_to_fifo(uint8_t status) { + if (write(qcm_status_indication_fd[0], &status, sizeof(status)) == -1) {}; +} +#endif + +void qmidevice_send_event_to_main(int triger_event) { + if (write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)) == -1) {}; +} + +void qmidevice_send_event_to_main_ext(int triger_event, void *data, unsigned len) { + if (write(qmidevice_control_fd[1], &triger_event, sizeof(triger_event)) == -1) {}; + if (write(qmidevice_control_fd[1], data, len) == -1) {}; +} + +#ifdef USE_IPC_MSG_STATUS_IND +static int msg_get() +{ + key_t key = ftok(MSG_FILE, 'a'); + int msgid = msgget(key, IPC_CREAT | 0644); + + if (msgid < 0) + { + dbg_time("msgget fail: key %d, %s", key, strerror(errno)); + return -1; + } + return msgid; +} + +static int msg_rm(int msgid) +{ + return msgctl(msgid, IPC_RMID, 0); +} + +static int msg_send(int msgid, long type, const char *msg) +{ + struct message info; + info.mtype = type; + snprintf(info.mtext, MSGBUFFSZ, "%s", msg); + if (msgsnd(msgid, (void *)&info, MSGBUFFSZ, IPC_NOWAIT) < 0) + { + dbg_time("msgsnd faild: msg %s, %s", msg, strerror(errno)); + return -1; + } + return 0; +} + +void* qcm_status_thread(void *arg) +{ + uint8_t status = 0; + int msgid = -1; + char buf[MSGBUFFSZ] = {0}; + + uint8_t* tmp = arg; + if (tmp == NULL) + { + dbg_time("%s qcm start report status", __func__); + } + + if (access(MSG_FILE, F_OK)) + { + dbg_time("%s %s isn't exit", __func__, MSG_FILE); + pthread_exit(NULL); + return (void*)0; + } + else + { + msgid = msg_get(); + if (msgid < 0) + { + dbg_time("%s msg_get fail", __func__); + pthread_exit(NULL); + return (void*)0; + } + } + + while (1) + { + struct pollfd pollfds[] = {{qcm_status_indication_fd[1], POLLIN, 0}}; + int ret; + + do + { + ret = poll(pollfds, 1, -1); + } while ((ret < 0) && (errno == EINTR)); + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + break; + } + + if (pollfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) + { + dbg_time("%s poll err/hup", __func__); + dbg_time("epoll pollfds[0].fd = %d, pollfds[0].events = 0x%04x", pollfds[0].fd, pollfds[0].revents); + break; + } + + if ((pollfds[0].revents & POLLIN) == 0) + { + continue; + } + + if (read(pollfds[0].fd, &status, sizeof(status)) == sizeof(status)) + { + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf), "%d", status); + + if (msg_send(msgid, MSG_TYPE_IPC, buf) >= 0) + { + //dbg_time("%s msg_send success msgid = %d status = %d", __func__, msgid, status); + } + else + { + dbg_time("%s msg_send fail", __func__); + break; + } + + if (status == QCM_PROCESS_EXIT) + { + dbg_time("%s qcm process exit", __func__); + usleep(100000); + break; + } + } + else + { + dbg_time("%s read fail", __func__); + break; + } + } + + if (msg_rm(msgid) < 0) + { + dbg_time("%s msg_rm fail", __func__); + } + + dbg_time("%s exit", __func__); + pthread_exit(NULL); + return (void*)0; +} +#endif + +#define MAX_PATH 256 + +static int ls_dir(const char *dir, int (*match)(const char *dir, const char *file, void *argv[]), void *argv[]) +{ + DIR *pDir; + struct dirent* ent = NULL; + int match_times = 0; + + pDir = opendir(dir); + if (pDir == NULL) { + dbg_time("Cannot open directory: %s, errno: %d (%s)", dir, errno, strerror(errno)); + return 0; + } + + while ((ent = readdir(pDir)) != NULL) { + match_times += match(dir, ent->d_name, argv); + } + closedir(pDir); + + return match_times; +} + +static int is_same_linkfile(const char *dir, const char *file, void *argv[]) +{ + const char *qmichannel = (const char *)argv[1]; + char linkname[MAX_PATH*2+6]; + char filename[MAX_PATH]; + int linksize; + + snprintf(linkname, sizeof(linkname), "%.256s/%s", dir, file); + linksize = readlink(linkname, filename, sizeof(filename)); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + if (strcmp(filename, qmichannel)) + return 0; + + dbg_time("%s -> %s", linkname, filename); + return 1; +} + +static int is_brother_process(const char *dir, const char *file, void *argv[]) +{ + //const char *myself = (const char *)argv[0]; + char linkname[MAX_PATH*2+6]; + char filename[MAX_PATH]; + int linksize; + int i = 0, kill_timeout = 15; + pid_t pid; + + //dbg_time("%s", file); + while (file[i]) { + if (!isdigit(file[i])) + break; + i++; + } + + if (file[i]) { + //dbg_time("%s not digit", file); + return 0; + } + + snprintf(linkname, sizeof(linkname), "%s/%s/exe", dir, file); + linksize = readlink(linkname, filename, sizeof(filename)); + if (linksize <= 0) + return 0; + + filename[linksize] = 0; + + pid = atoi(file); + if (pid >= getpid()) + return 0; + + snprintf(linkname, sizeof(linkname), "%s/%s/fd", dir, file); + if (!ls_dir(linkname, is_same_linkfile, argv)) + return 0; + + dbg_time("%s/%s/exe -> %s", dir, file, filename); + while (kill_timeout-- && !kill(pid, 0)) + { + kill(pid, SIGTERM); + sleep(1); + } + if (!kill(pid, 0)) + { + dbg_time("force kill %s/%s/exe -> %s", dir, file, filename); + kill(pid, SIGKILL); + sleep(1); + } + + return 1; +} + +static int kill_brothers(const char *qmichannel) +{ + char myself[MAX_PATH]; + int filenamesize; + void *argv[2] = {myself, (void *)qmichannel}; + + filenamesize = readlink("/proc/self/exe", myself, sizeof(myself)); + if (filenamesize <= 0) + return 0; + myself[filenamesize] = 0; + + if (ls_dir("/proc", is_brother_process, argv)) + sleep(1); + + return 0; +} + +static int kill_data_call_pdp(int pdp, char *self) { + int pid; + char *p = NULL; + + p = self; + while (*self) { + if (*self == '/') + p = self+1; + self++; + } + + pid = getpid_by_pdp(pdp, p); + if (pid > 0) { + dbg_time("send SIGINT to process %d", pid); + return kill(pid, SIGINT); + } + + return -1; +} + +static void ql_sigaction(int signo) { + if (SIGALRM == signo) + send_signo_to_main(SIG_EVENT_START); + else + { + g_donot_exit_when_modem_hangup = 0; + send_signo_to_main(SIG_EVENT_STOP); + main_send_event_to_qmidevice(SIG_EVENT_STOP); //main may be wating qmi response + } +} + +static int usage(const char *progname) { + dbg_time("Usage: %s [options]", progname); + dbg_time("-s [apn [user password auth]] Set apn/user/password/auth get from your network provider. auth: 1~pap, 2~chap, 3~MsChapV2"); + dbg_time("-p pincode Verify sim card pin if sim card is locked"); + dbg_time("-p [quectel-][qmi|mbim]-proxy Request to use proxy"); + dbg_time("-f logfilename Save log message of this program to file"); + dbg_time("-u usbmonlog filename Save usbmon log to file"); + dbg_time("-i interface Specify which network interface to setup data call when multi-modems exits"); + dbg_time("-4 Setup IPv4 data call (default)"); + dbg_time("-6 Setup IPv6 data call"); + dbg_time("-n pdn Specify which pdn to setup data call (default 1 for QMI, 0 for MBIM)"); + dbg_time("-k pdn Specify which pdn to hangup data call (by send SIGINT to 'quectel-CM -n pdn')"); + dbg_time("-m iface-idx Bind QMI data call to wwan0_ when QMAP used. E.g '-n 7 -m 1' bind pdn-7 data call to wwan0_1"); + dbg_time("-b Enable network interface bridge function (default 0)"); + dbg_time("-v Verbose log mode, for debug purpose."); + dbg_time("-d Obtain the IP address and dns through qmi"); + dbg_time("-a 1:Device attempts to bring up a call with the APN name,if -a 1 need add -s apn_name ;2:Device attempts to bring up a call with the APN type,if -a 2 need add -y apn_type"); + dbg_time("-y Set APN type 0:APN type unspecified 1:internet traffic. 2:IMS"); + dbg_time("[Examples]"); + dbg_time("Example 1: %s ", progname); + dbg_time("Example 2: %s -s 3gnet ", progname); + dbg_time("Example 3: %s -s 3gnet carl 1234 1 -p 1234 -f gobinet_log.txt", progname); + return 0; +} + +static int qmi_main(PROFILE_T *profile) +{ + int triger_event = 0; + int signo; +#ifdef CONFIG_SIM + SIM_Status SIMStatus = SIM_ABSENT; +#endif + UCHAR PSAttachedState = 0; + UCHAR IPv4ConnectionStatus = QWDS_PKT_DATA_UNKNOW; + UCHAR IPv6ConnectionStatus = QWDS_PKT_DATA_UNKNOW; + unsigned SetupCallFail = 0; + unsigned long SetupCallAllowTime = clock_msec(); +#ifdef REBOOT_SIM_CARD_WHEN_LONG_TIME_NO_PS + unsigned PsAttachFail = 0; + unsigned long PsAttachTime = clock_msec(); +#endif + int qmierr = 0; + const struct request_ops *request_ops = profile ->request_ops; + pthread_t gQmiThreadID = 0; +#ifdef USE_IPC_MSG_STATUS_IND + pthread_t gQcmStatusThreadID = 0; +#endif + +//sudo apt-get install udhcpc +//sudo apt-get remove ModemManager + if (profile->reattach_flag) { + if (!reattach_driver(profile)) + sleep(2); + } + + g_donot_exit_when_modem_hangup = 0; + + /* try to recreate FDs*/ + if (socketpair( AF_LOCAL, SOCK_STREAM, 0, signal_control_fd) < 0 ) { + dbg_time("%s Faild to create main_control_fd: %d (%s)", __func__, errno, strerror(errno)); + return -1; + } + + if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, qmidevice_control_fd ) < 0 ) { + dbg_time("%s Failed to create thread control socket pair: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + +#ifdef USE_IPC_MSG_STATUS_IND + if ( socketpair( AF_LOCAL, SOCK_STREAM, 0, qcm_status_indication_fd ) < 0 ) { + dbg_time("%s Failed to create thread qcm_status_indication_fd socket pair: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } +#endif + + if ((profile->qmap_mode == 0 || profile->qmap_mode == 1) + && (!profile->proxy[0] || strstr(profile->qmichannel, "_IPCR"))) { + kill_brothers(profile->qmichannel); + } + +#ifdef USE_IPC_MSG_STATUS_IND + if (pthread_create( &gQcmStatusThreadID, 0, qcm_status_thread, NULL) != 0) { + dbg_time("%s Failed to create QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } +#endif + + if (pthread_create( &gQmiThreadID, 0, profile->qmi_ops->read, (void *)profile) != 0) { + dbg_time("%s Failed to create QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if ((read(qmidevice_control_fd[0], &triger_event, sizeof(triger_event)) != sizeof(triger_event)) + || (triger_event != RIL_INDICATE_DEVICE_CONNECTED)) { + dbg_time("%s Failed to init QMIThread: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + + if (profile->qmi_ops->init && profile->qmi_ops->init(profile)) { + dbg_time("%s Failed to qmi init: %d (%s)", __func__, errno, strerror(errno)); + return 0; + } + +#ifdef USE_IPC_MSG_STATUS_IND + main_send_status_to_fifo(QCM_PARA_CONFIG_QUERY); +#endif + + if (request_ops->requestBaseBandVersion) + request_ops->requestBaseBandVersion(profile); + + if (request_ops->requestSetEthMode) + request_ops->requestSetEthMode(profile); + + if (request_ops->requestSetLoopBackState && profile->loopback_state) { + qmierr = request_ops->requestSetLoopBackState(profile->loopback_state, profile->replication_factor); + if (qmierr != QMI_ERR_INVALID_QMI_CMD) //X20 return this error + profile->loopback_state = 0; //wait for RIL_UNSOL_LOOPBACK_CONFIG_IND + } + + if (request_ops->requestGetSIMStatus) { + qmierr = request_ops->requestGetSIMStatus(&SIMStatus); + + while (qmierr == QMI_ERR_OP_DEVICE_UNSUPPORTED) { + sleep(1); + qmierr = request_ops->requestGetSIMStatus(&SIMStatus); + } + + if ((SIMStatus == SIM_PIN) && profile->pincode && request_ops->requestEnterSimPin) { + request_ops->requestEnterSimPin(profile->pincode); + } + } + + if (SIMStatus == SIM_READY) { + if (request_ops->requestGetICCID) + request_ops->requestGetICCID(); + + if (request_ops->requestGetIMSI) + request_ops->requestGetIMSI(); + } +if(profile->usb_dev.idProduct != 0x0316) +{ + if (request_ops->requestGetProfile) + request_ops->requestGetProfile(profile); + + if (request_ops->requestSetProfile && (profile->apn || profile->user || profile->pd)) { + if (request_ops->requestSetProfile(profile) == 1) { +#ifdef REBOOT_SIM_CARD_WHEN_APN_CHANGE //enable at only when customer asked + if (request_ops->requestRadioPower) { + request_ops->requestRadioPower(0); + request_ops->requestRadioPower(1); + } +#endif + } + } +} + request_ops->requestRegistrationState(&PSAttachedState); + +#ifdef CONFIG_ENABLE_QOS + request_ops->requestRegisterQos(profile); +#endif + +#if 1 //USB disconnnect and re-connect, but not reboot modem, will get this bug + if (profile->enable_ipv4 + && profile->request_ops == &atc_request_ops + && !request_ops->requestQueryDataCall(&IPv4ConnectionStatus, IpFamilyV4) + && IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + request_ops->requestDeactivateDefaultPDP(profile, IpFamilyV4); + } +#endif + + send_signo_to_main(SIG_EVENT_CHECK); + + while (1) + { + struct pollfd pollfds[] = {{signal_control_fd[1], POLLIN, 0}, {qmidevice_control_fd[0], POLLIN, 0}}; + int ne, ret, nevents = sizeof(pollfds)/sizeof(pollfds[0]); + + do { + ret = poll(pollfds, nevents, 15*1000); + } while ((ret < 0) && (errno == EINTR)); + + if (ret == 0) + { + send_signo_to_main(SIG_EVENT_CHECK); + continue; + } + + if (ret <= 0) { + dbg_time("%s poll=%d, errno: %d (%s)", __func__, ret, errno, strerror(errno)); + goto __main_quit; + } + + 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", __func__); + dbg_time("epoll fd = %d, events = 0x%04x", fd, revents); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); +#ifdef USE_IPC_MSG_STATUS_IND + main_send_status_to_fifo(QCM_PROCESS_EXIT); +#endif + if (revents & POLLHUP) + goto __main_quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (fd == signal_control_fd[1]) + { + if (read(fd, &signo, sizeof(signo)) == sizeof(signo)) + { + alarm(0); + switch (signo) + { + case SIG_EVENT_START: + if (PSAttachedState != 1 && profile->loopback_state == 0) + break; + + if (SetupCallAllowTime > clock_msec()) { + alarm((SetupCallAllowTime - clock_msec()+999)/1000); + break; + } + +#ifdef USE_IPC_MSG_STATUS_IND + main_send_status_to_fifo(QCM_PREPARE_START_DAIL); +#endif + + if (profile->enable_ipv4 && IPv4ConnectionStatus != QWDS_PKT_DATA_CONNECTED) { + qmierr = request_ops->requestSetupDataCall(profile, IpFamilyV4); + + if ((qmierr > 0) && profile->user && profile->user[0] && profile->pd && profile->pd[0]) { + int old_auto = profile->auth; + + //may be fail because wrong auth mode, try pap->chap, or chap->pap + profile->auth = (profile->auth == 1) ? 2 : 1; + qmierr = request_ops->requestSetupDataCall(profile, IpFamilyV4); + + if (qmierr) + profile->auth = old_auto; //still fail, restore old auth moe + } + + if (!qmierr) { + qmierr = request_ops->requestGetIPAddress(profile, IpFamilyV4); + if (!qmierr) + { + IPv4ConnectionStatus = QWDS_PKT_DATA_CONNECTED; +#ifdef USE_IPC_MSG_STATUS_IND + main_send_status_to_fifo(QCM_NETWORK_IPV4_CONNECTED); +#endif + } + } + + } + + if (profile->enable_ipv6 && IPv6ConnectionStatus != QWDS_PKT_DATA_CONNECTED) { + if (profile->enable_ipv4 && profile->request_ops != &qmi_request_ops) { + IPv6ConnectionStatus = IPv4ConnectionStatus; + } + else { + qmierr = request_ops->requestSetupDataCall(profile, IpFamilyV6); + + if (!qmierr) { + qmierr = request_ops->requestGetIPAddress(profile, IpFamilyV6); + if (!qmierr) + { + IPv6ConnectionStatus = QWDS_PKT_DATA_CONNECTED; +#ifdef USE_IPC_MSG_STATUS_IND + main_send_status_to_fifo(QCM_NETWORK_IPV6_CONNECTED); +#endif + } + } + } + } + + if ((profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + || (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) { + const unsigned allow_time[] = {5, 10, 20, 40, 60}; + + if (SetupCallFail < (sizeof(allow_time)/sizeof(unsigned))) + SetupCallAllowTime = allow_time[SetupCallFail]; + else + SetupCallAllowTime = 60; + SetupCallFail++; + dbg_time("try to requestSetupDataCall %ld second later", SetupCallAllowTime); + alarm(SetupCallAllowTime); + SetupCallAllowTime = SetupCallAllowTime*1000 + clock_msec(); +#ifdef USE_IPC_MSG_STATUS_IND + main_send_status_to_fifo(QCM_PREPARE_RESTART_DAIL); +#endif + } + else if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED || IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + SetupCallFail = 0; + SetupCallAllowTime = clock_msec(); +#ifdef USE_IPC_MSG_STATUS_IND + main_send_status_to_fifo(QCM_PREPARE_DAIL_SUCC); +#endif + } + break; + + case SIG_EVENT_CHECK: + if (request_ops->requestGetSignalInfo) + request_ops->requestGetSignalInfo(); + + if (request_ops->requestGetCellInfoList) + request_ops->requestGetCellInfoList(); + + if (request_ops->requestGetCoexWWANState) + request_ops->requestGetCoexWWANState(); + + if (PSAttachedState != 1) + request_ops->requestRegistrationState(&PSAttachedState); + +#ifdef REBOOT_SIM_CARD_WHEN_LONG_TIME_NO_PS + if (PSAttachedState) { + PsAttachTime = clock_msec(); + PsAttachFail = 0; + } + else { + unsigned long diff = (clock_msec() - PsAttachTime) / 1000; + unsigned long threshold = REBOOT_SIM_CARD_WHEN_LONG_TIME_NO_PS << PsAttachFail; + + if (diff > threshold || diff > 960) { + //interval time is 60 -> 120 - > 240 - > 480 -> 960 + PsAttachTime = clock_msec(); + PsAttachFail++; + + if (request_ops->requestRadioPower) { + request_ops->requestRadioPower(0); + request_ops->requestRadioPower(1); + } + } + } +#endif + + if (profile->enable_ipv4 && IPv4ConnectionStatus != QWDS_PKT_DATA_DISCONNECTED + && !request_ops->requestQueryDataCall(&IPv4ConnectionStatus, IpFamilyV4)) + { + if (QWDS_PKT_DATA_CONNECTED == IPv4ConnectionStatus && profile->ipv4.Address == 0) { + //killall -9 quectel-CM for MBIM and ATC call + qmierr = request_ops->requestGetIPAddress(profile, IpFamilyV4); + if (qmierr) + IPv4ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + //local ip is different with remote ip + if (QWDS_PKT_DATA_CONNECTED == IPv4ConnectionStatus && check_ipv4_address(profile) == 0) { + request_ops->requestDeactivateDefaultPDP(profile, IpFamilyV4); + IPv4ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + } + else { + IPv4ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (profile->enable_ipv6 && IPv6ConnectionStatus != QWDS_PKT_DATA_DISCONNECTED) { + if (profile->enable_ipv4 && profile->request_ops != &qmi_request_ops) { + IPv6ConnectionStatus = IPv4ConnectionStatus; + } + else { + request_ops->requestQueryDataCall(&IPv6ConnectionStatus, IpFamilyV6); + } + } + else { + IPv6ConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + } + + if (IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) { + usbnet_link_change(0, profile); + } + else if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED || IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + int link = 0; + if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED) + link |= (1<enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) + { + main_send_status_to_fifo(QCM_NETWORK_IPV4_DISCONNECTED); + } + else + { + main_send_status_to_fifo(QCM_NETWORK_IPV4_CONNECTED); + } + + if ((profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) + { + main_send_status_to_fifo(QCM_NETWORK_IPV6_DISCONNECTED); + } + else + { + main_send_status_to_fifo(QCM_NETWORK_IPV6_CONNECTED); + } +#endif + + if ((profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + || (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) { + send_signo_to_main(SIG_EVENT_START); + } + break; + + case SIG_EVENT_STOP: + if (profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + request_ops->requestDeactivateDefaultPDP(profile, IpFamilyV4); + } + if (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + if (profile->enable_ipv4 && profile->request_ops != &qmi_request_ops) { + + } + else { + request_ops->requestDeactivateDefaultPDP(profile, IpFamilyV6); + } + } + usbnet_link_change(0, profile); +#ifdef USE_IPC_MSG_STATUS_IND + main_send_status_to_fifo(QCM_PROCESS_EXIT); +#endif + if (profile->qmi_ops->deinit) + profile->qmi_ops->deinit(); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + goto __main_quit; + break; + + default: + break; + } + } + } + + if (fd == qmidevice_control_fd[0]) { + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + switch (triger_event) { + case RIL_INDICATE_DEVICE_DISCONNECTED: + usbnet_link_change(0, profile); + goto __main_quit; + break; + + case RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED: + request_ops->requestRegistrationState(&PSAttachedState); + if (PSAttachedState == 1) { + if ((profile->enable_ipv4 && IPv4ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED) + || (profile->enable_ipv6 && IPv6ConnectionStatus == QWDS_PKT_DATA_DISCONNECTED)) { + send_signo_to_main(SIG_EVENT_START); + } + } else { + SetupCallAllowTime = clock_msec(); + } + break; + + case RIL_UNSOL_DATA_CALL_LIST_CHANGED: + if (IPv4ConnectionStatus == QWDS_PKT_DATA_CONNECTED || IPv6ConnectionStatus == QWDS_PKT_DATA_CONNECTED) { + SetupCallAllowTime = clock_msec() + 1000; //from connect -> disconnect, do not re-dail immediately, wait network stable + } + send_signo_to_main(SIG_EVENT_CHECK); + break; + + case MODEM_REPORT_RESET_EVENT: + { + dbg_time("main recv MODEM RESET SIGNAL"); + main_send_event_to_qmidevice(RIL_REQUEST_QUIT); + g_donot_exit_when_modem_hangup = 1; + goto __main_quit; + } + break; + + case RIL_UNSOL_LOOPBACK_CONFIG_IND: + { + QMI_WDA_SET_LOOPBACK_CONFIG_IND_MSG SetLoopBackInd; + if (read(fd, &SetLoopBackInd, sizeof(SetLoopBackInd)) == sizeof(SetLoopBackInd)) { + profile->loopback_state = SetLoopBackInd.loopback_state.TLVVaule; + profile->replication_factor = le32_to_cpu(SetLoopBackInd.replication_factor.TLVVaule); + dbg_time("SetLoopBackInd: loopback_state=%d, replication_factor=%u", + profile->loopback_state, profile->replication_factor); + if (profile->loopback_state) + send_signo_to_main(SIG_EVENT_START); + } + } + break; +#ifdef CONFIG_REG_QOS_IND + case RIL_UNSOL_GLOBAL_QOS_FLOW_IND_QOS_ID: + { + UINT qos_id = 0; + if (read(fd, &qos_id, sizeof(qos_id)) == sizeof(qos_id)) { + profile->qos_id = qos_id; + } + } + break; +#endif + default: + break; + } + } + } + } + } + +__main_quit: + usbnet_link_change(0, profile); + if (gQmiThreadID && pthread_join(gQmiThreadID, NULL)) { + dbg_time("%s Error joining to listener thread (%s)", __func__, strerror(errno)); + } + +#ifdef USE_IPC_MSG_STATUS_IND + if (gQcmStatusThreadID && pthread_join(gQcmStatusThreadID, NULL)) { + dbg_time("%s Error joining to gQcmStatusThreadID (%s)", __func__, strerror(errno)); + } +#endif + + close(signal_control_fd[0]); + close(signal_control_fd[1]); + close(qmidevice_control_fd[0]); + close(qmidevice_control_fd[1]); +#ifdef USE_IPC_MSG_STATUS_IND + close(qcm_status_indication_fd[0]); + close(qcm_status_indication_fd[1]); +#endif + dbg_time("%s exit", __func__); + + return 0; +} + +static int quectel_CM(PROFILE_T *profile) +{ + int ret = 0; + char qmichannel[32] = {'\0'}; + char usbnet_adapter[32] = {'\0'}; + + if (profile->expect_adapter[0]) + strncpy(usbnet_adapter, profile->expect_adapter, sizeof(usbnet_adapter)); + + if (qmidevice_detect(qmichannel, usbnet_adapter, sizeof(qmichannel), profile)) { + profile->hardware_interface = HARDWARE_USB; + } + else if (mhidevice_detect(qmichannel, usbnet_adapter, profile)) { + profile->hardware_interface = HARDWARE_PCIE; + } + else if (atdevice_detect(qmichannel, usbnet_adapter, profile)) { + profile->hardware_interface = HARDWARE_PCIE; + } +#ifdef CONFIG_QRTR + else if (!access("/sys/class/net/rmnet_mhi0", F_OK)) { + strcpy(usbnet_adapter, "rmnet_mhi0"); + profile->hardware_interface = HARDWARE_PCIE; + strcpy(qmichannel, "qrtr"); + profile->software_interface = SOFTWARE_QRTR; + } + else if (!access("/sys/class/net/rmnet_usb0", F_OK)) { + strcpy(usbnet_adapter, "rmnet_usb0"); + profile->hardware_interface = HARDWARE_USB; + strcpy(qmichannel, "qrtr"); + profile->software_interface = SOFTWARE_QRTR; + } + else if (!access("/sys/class/net/rmnet_ipa0", F_OK)) { + strcpy(usbnet_adapter, "rmnet_ipa0"); + profile->hardware_interface = HARDWARE_IPA; + strcpy(qmichannel, "qrtr"); + profile->software_interface = SOFTWARE_QRTR; + } +#endif + else { + dbg_time("qmidevice_detect failed"); + goto error; + } + + strncpy(profile->qmichannel, qmichannel, sizeof(profile->qmichannel)); + strncpy(profile->usbnet_adapter, usbnet_adapter, sizeof(profile->usbnet_adapter)); + ql_get_netcard_driver_info(profile->usbnet_adapter); + + if ((profile->hardware_interface == HARDWARE_USB) && profile->usblogfile) + ql_capture_usbmon_log(profile, profile->usblogfile); + + if (profile->hardware_interface == HARDWARE_USB) { + profile->software_interface = get_driver_type(profile); + } + + ql_qmap_mode_detect(profile); + + if (profile->software_interface == SOFTWARE_MBIM) { + dbg_time("Modem works in MBIM mode"); + profile->request_ops = &mbim_request_ops; + profile->qmi_ops = &mbim_dev_ops; + if (!profile->apn || !profile->apn[0]) { + //see FAE-51804 FAE-59811 + dbg_time("When MBIM mode, must specify APN with '-s', or setup data call may fail!"); + exit(-404); //if no such issue on your side, please comment this + } + ret = qmi_main(profile); + } + else if (profile->software_interface == SOFTWARE_QMI) { + dbg_time("Modem works in QMI mode"); + profile->request_ops = &qmi_request_ops; + 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; + ret = qmi_main(profile); + } + else if (profile->software_interface == SOFTWARE_ECM_RNDIS_NCM) { + dbg_time("Modem works in ECM_RNDIS_NCM mode"); + profile->request_ops = &atc_request_ops; + profile->qmi_ops = &atc_dev_ops; + ret = qmi_main(profile); + } +#ifdef CONFIG_QRTR + else if (profile->software_interface == SOFTWARE_QRTR) { + dbg_time("Modem works in QRTR mode"); + profile->request_ops = &qmi_request_ops; + profile->qmi_ops = &qrtr_qmidev_ops; + qmidev_send = profile->qmi_ops->send; + ret = qmi_main(profile); + } +#endif + else { + dbg_time("unsupport software_interface %d", profile->software_interface); + } + + ql_stop_usbmon_log(profile); + +error: + return ret; +} + +static int parse_user_input(int argc, char **argv, PROFILE_T *profile) { + int opt = 1; + int apn_name_or_type; + profile->pdp = CONFIG_DEFAULT_PDP; + profile->profile_index = CONFIG_DEFAULT_PDP; + + if (!strcmp(argv[argc-1], "&")) + argc--; + +#define has_more_argv() ((opt < argc) && (argv[opt][0] != '-')) + while (opt < argc) { + if (argv[opt][0] != '-') { + return usage(argv[0]); + } + + switch (argv[opt++][1]) + { + case 's': + profile->apn = profile->user = profile->pd = ""; + if (has_more_argv()) { + profile->apn = argv[opt++]; + } + if (has_more_argv()) { + profile->user = argv[opt++]; + } + if (has_more_argv()) { + profile->pd = argv[opt++]; + if (profile->pd && profile->pd[0]) + profile->auth = 2; //default chap, customers may miss auth + } + if (has_more_argv()) { + const char *auth = argv[opt++]; + + if (!strcmp(auth, "0") || !strcasecmp(auth, "none")) { + profile->auth = 0; + } + else if (!strcmp(auth, "1") || !strcasecmp(auth, "pap")) { + profile->auth = 1; + } + else if (!strcmp(auth, "2") || !strcasecmp(auth, "chap")) { + profile->auth = 2; + } + else if (!strcmp(auth, "3") || !strcasecmp(auth, "MsChapV2")) { + profile->auth = 3; + } + else { + dbg_time("unknow auth '%s'", auth); + return usage(argv[0]); + } + } + break; + + case 'p': + if (has_more_argv()) { + const char *arg = argv[opt++]; + + if (!strcmp(arg, QUECTEL_QMI_PROXY) || !strcmp(arg, QUECTEL_MBIM_PROXY) + || !strcmp(arg, LIBQMI_PROXY) || !strcmp(arg, LIBMBIM_PROXY) || !strcmp(arg, QUECTEL_ATC_PROXY)) { + strncpy(profile->proxy, arg, sizeof(profile->proxy) - 1); + } + else if ((999 < atoi(arg)) && (atoi(arg) < 10000)) { + profile->pincode = arg; + } + else { + dbg_time("unknow -p '%s'", arg); + return usage(argv[0]); + } + } + break; + + case 'm': + if (has_more_argv()) + profile->muxid = argv[opt++][0] - '0' + 0x80; + break; + + case 'n': + if (has_more_argv()) + profile->pdp = argv[opt++][0] - '0'; + break; + + case 'f': + if (has_more_argv()) { + profile->logfile = argv[opt++]; + } + break; + + case 'i': + if (has_more_argv()) { + strncpy(profile->expect_adapter, argv[opt++], sizeof(profile->expect_adapter) - 1); + } + break; + + case 'v': + debug_qmi = 1; + break; + + case 'l': + if (has_more_argv()) { + profile->replication_factor = atoi(argv[opt++]); + if (profile->replication_factor > 0) { + profile->loopback_state = 1; + } + } + break; + + case '4': + profile->enable_ipv4 = 1; + break; + + case '6': + profile->enable_ipv6 = 1; + break; + + case 'd': + profile->no_dhcp = 1; + break; + + case 'u': + if (has_more_argv()) { + profile->usblogfile = argv[opt++]; + } + break; + + case 'b': + profile->enable_bridge = 1; + break; + + case 'k': + if (has_more_argv()) { + profile->kill_pdp = argv[opt++][0] - '0'; + } + break; + + case 'a': + if (has_more_argv()) { + apn_name_or_type = argv[opt++][0] - '0'; + if(apn_name_or_type == 1) + profile->bring_up_by_apn_name = apn_name_or_type; + else if(apn_name_or_type == 2) + profile->bring_up_by_apn_type = apn_name_or_type; + } + break; + case 'y': + if (has_more_argv()) { + profile->apn_type = argv[opt++][0] - '0'; + } + break; + default: + return usage(argv[0]); + break; + } + } + + if (profile->enable_ipv4 != 1 && profile->enable_ipv6 != 1) { // default enable IPv4 + profile->enable_ipv4 = 1; + } + + return 1; +} + +int main(int argc, char *argv[]) +{ + int ret; + PROFILE_T *ctx = &s_profile; + + dbg_time("QConnectManager_Linux_V1.6.8"); + + ret = parse_user_input(argc, argv, ctx); + if (!ret) + return ret; + + if (ctx->kill_pdp) { + return kill_data_call_pdp(ctx->kill_pdp, argv[0]); + } + + if (ctx->logfile) { + logfilefp = fopen(ctx->logfile, "a+"); + if (!logfilefp) { + dbg_time("Fail to open %s, errno: %d(%s)", ctx->logfile, errno, strerror(errno)); + } + } + + signal(SIGINT, ql_sigaction); + signal(SIGTERM, ql_sigaction); + signal(SIGALRM, ql_sigaction); + + do { + ret = quectel_CM(ctx); + if (g_donot_exit_when_modem_hangup > 0) + sleep(3); + } while (g_donot_exit_when_modem_hangup > 0); + + if (logfilefp) { + fclose(logfilefp); + } + + return ret; +} diff --git a/RM520N-5G/文件/source/mbim-cm.c b/RM520N-5G/文件/source/mbim-cm.c new file mode 100644 index 0000000..67e802f --- /dev/null +++ b/RM520N-5G/文件/source/mbim-cm.c @@ -0,0 +1,2422 @@ +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "QMIThread.h" + +#define mbim_debug dbg_time + +#define UUID_BASIC_CONNECT "a289cc33-bcbb-8b4f-b6b0-133ec2aae6df" +//https://docs.microsoft.com/en-us/windows-hardware/drivers/network/mb-5g-data-class-support +#define UUID_BASIC_CONNECT_EXT "3d01dcc5-fef5-4d05-0d3a-bef7058e9aaf" +#define UUID_SMS "533fbeeb-14fe-4467-9f90-33a223e56c3f" +#define UUID_USSD "e550a0c8-5e82-479e-82f7-10abf4c3351f" +#define UUID_PHONEBOOK "4bf38476-1e6a-41db-b1d8-bed289c25bdb" +#define UUID_STK "d8f20131-fcb5-4e17-8602-d6ed3816164c" +#define UUID_AUTH "1d2b5ff7-0aa1-48b2-aa52-50f15767174e" +#define UUID_DSS "c08a26dd-7718-4382-8482-6e0d583c4d0e" +#define uuid_ext_qmux "d1a30bc2-f97a-6e43-bf65-c7e24fb0f0d3" +#define uuid_mshsd "883b7c26-985f-43fa-9804-27d7fb80959c" +#define uuid_qmbe "2d0c12c9-0e6a-495a-915c-8d174fe5d63c" +#define UUID_MSFWID "e9f7dea2-feaf-4009-93ce-90a3694103b6" +#define uuid_atds "5967bdcc-7fd2-49a2-9f5c-b2e70e527db3" +#define uuid_qdu "6427015f-579d-48f5-8c54-f43ed1e76f83" +#define UUID_MS_UICC_LOW_LEVEL "c2f6588e-f037-4bc9-8665-f4d44bd09367" +#define UUID_MS_SARControl "68223D04-9F6C-4E0F-822D-28441FB72340" +#define UUID_VOICEEXTENSIONS "8d8b9eba-37be-449b-8f1e-61cb034a702e" +#define UUID_LIBMBIM_PROXY "838cf7fb-8d0d-4d7f-871e-d71dbefbb39b" + +#define UUID_MBIMContextTypeInternet "7E5E2A7E-4E6F-7272-736B-656E7E5E2A7E" + +typedef unsigned char UINT8; +typedef unsigned short UINT16; +typedef unsigned int UINT32; +typedef unsigned long long UINT64; + +#pragma pack(4) +typedef enum { + MBIM_CID_CMD_TYPE_QUERY = 0, + MBIM_CID_CMD_TYPE_SET = 1, +} MBIM_CID_CMD_TYPE_E; + +typedef enum { + MBIM_CID_DEVICE_CAPS = 1, + MBIM_CID_SUBSCRIBER_READY_STATUS = 2, + MBIM_CID_RADIO_STATE = 3, MBIM_CID_PIN = 4, + MBIM_CID_PIN_LIS = 5, + MBIM_CID_HOME_PROVIDER = 6, + MBIM_CID_PREFERRED_PROVIDERS = 7, + MBIM_CID_VISIBLE_PROVIDERS = 8, + MBIM_CID_REGISTER_STATE = 9, + MBIM_CID_PACKET_SERVICE = 10, + MBIM_CID_SIGNAL_STATE = 11, + MBIM_CID_CONNECT = 12, + MBIM_CID_PROVISIONED_CONTEXTS = 13, + MBIM_CID_SERVICE_ACTIVATION = 14, + MBIM_CID_IP_CONFIGURATION = 15, + MBIM_CID_DEVICE_SERVICES = 16, + MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST = 19, + MBIM_CID_PACKET_STATISTICS = 20, + MBIM_CID_NETWORK_IDLE_HINT = 21, + MBIM_CID_EMERGENCY_MODE = 22, + MBIM_CID_IP_PACKET_FILTERS = 23, + MBIM_CID_MULTICARRIER_PROVIDERS = 24, +} UUID_BASIC_CONNECT_CID_E; + +typedef enum{ + MBIM_CID_MS_PROVISIONED_CONTEXT_V2 = 1, + MBIM_CID_MS_NETWORK_BLACKLIST = 2, + MBIM_CID_MS_LTE_ATTACH_CONFIG = 3, + MBIM_CID_MS_LTE_ATTACH_STATUS = 4, + MBIM_CID_MS_SYS_CAPS = 5, + MBIM_CID_MS_DEVICE_CAPS_V2 = 6, + MBIM_CID_MS_DEVICE_SLOT_MAPPING = 7, + MBIM_CID_MS_SLOT_INFO_STATUS = 8, + MBIM_CID_MS_PCO = 9, + MBIM_CID_MS_DEVICE_RESET = 10, + MBIM_CID_MS_BASE_STATIONS_INFO = 11, + MBIM_CID_MS_LOCATION_INFO_STATUS = 12, + MBIM_CID_NOT_DEFINED = 13, + MBIM_CID_MS_PIN_EX = 14, + MBIM_CID_MS_VERSION = 15, +} UUID_BASIC_CONNECT_EXT_CID_E; + +typedef enum { + MBIM_CID_SMS_CONFIGURATION = 1, // Y Y Y + MBIM_CID_SMS_READ = 2, // N Y Y + MBIM_CID_SMS_SEND = 3, // Y N N + MBIM_CID_SMS_DELETE = 4, // Y N N + MBIM_CID_SMS_MESSAGE_STORE_STATUS = 5, // N Y Y +} UUID_SMS_CID_E; + +typedef enum { + MBIM_CID_DSS_CONNECT = 1, // Y N N +} UUID_DSS_CID_E; + +typedef enum{ + MBIM_OPEN_MSG = 1, + MBIM_CLOSE_MSG = 2, + MBIM_COMMAND_MSG = 3, + MBIM_HOST_ERROR_MSG = 4, + MBIM_OPEN_DONE = 0x80000001, + MBIM_CLOSE_DONE = 0x80000002, + MBIM_COMMAND_DONE = 0x80000003, + MBIM_FUNCTION_ERROR_MSG = 0x80000004, + MBIM_INDICATE_STATUS_MSG = 0x80000007, +} MBIM_MSG_Type_E; + +typedef enum { /*< since=1.10 >*/ + MBIM_CID_PROXY_CONTROL_UNKNOWN = 0, + MBIM_CID_PROXY_CONTROL_CONFIGURATION = 1 +} UUID_LIBMBIM_PROXY_CID_E; + +typedef enum { + MBIM_CID_MS_UICC_ATR = 1, + MBIM_CID_MS_UICC_OPEN_CHANNEL = 2, + MBIM_CID_MS_UICC_CLOSE_CHANNEL = 3, + MBIM_CID_MS_UICC_APDU = 4, + MBIM_CID_MS_UICC_TERMINAL_CAPABILITY = 5, + MBIM_CID_MS_UICC_RESET = 6, + MBIM_CID_MS_APP_LIST = 7, +} UUID_MS_UICC_CID_E; + +typedef enum { + MBIM_ERROR_TIMEOUT_FRAGMENT = 1, + MBIM_ERROR_FRAGMENT_OUT_OF_SEQUENCE = 2, + MBIM_ERROR_LENGTH_MISMATCH = 3, + MBIM_ERROR_DUPLICATED_TID = 4, + MBIM_ERROR_NOT_OPENED = 5, + MBIM_ERROR_UNKNOWN = 6, + MBIM_ERROR_CANCEL = 7, + MBIM_ERROR_MAX_TRANSFER = 8, +} MBIM_ERROR_E; + +typedef enum { + MBIM_STATUS_SUCCESS = 0, + MBIM_STATUS_BUSY = 1, + MBIM_STATUS_FAILURE = 2, + MBIM_STATUS_SIM_NOT_INSERTED = 3, + MBIM_STATUS_BAD_SIM = 4, + MBIM_STATUS_PIN_REQUIRED = 5, + MBIM_STATUS_PIN_DISABLED = 6, + MBIM_STATUS_NOT_REGISTERED = 7, + MBIM_STATUS_PROVIDERS_NOT_FOUND = 8, + MBIM_STATUS_NO_DEVICE_SUPPORT = 9, + MBIM_STATUS_PROVIDER_NOT_VISIBLE = 10, + MBIM_STATUS_DATA_CLASS_NOT_AVAILABL = 11, + MBIM_STATUS_PACKET_SERVICE_DETACHED = 12, +} MBIM_STATUS_CODES_E; + +typedef enum { + MBIMPacketServiceActionAttach = 0, + MBIMPacketServiceActionDetach = 1, +} MBIM_PACKET_SERVICE_ACTION_E; + +typedef enum { + MBIMPacketServiceStateUnknown = 0, + MBIMPacketServiceStateAttaching = 1, + MBIMPacketServiceStateAttached = 2, + MBIMPacketServiceStateDetaching = 3, + MBIMPacketServiceStateDetached = 4, +} MBIM_PACKET_SERVICE_STATE_E; + +static const char *MBIMPacketServiceStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMPacketServiceStateUnknown, "Unknown"}, + {MBIMPacketServiceStateAttaching, "Attaching"}, + {MBIMPacketServiceStateAttached, "Attached"}, + {MBIMPacketServiceStateDetaching, "Detaching"}, + {MBIMPacketServiceStateDetached, "Detached"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef enum { + MBIMDataClassNone = 0x0, + MBIMDataClassGPRS = 0x1, + MBIMDataClassEDGE = 0x2, + MBIMDataClassUMTS = 0x4, + MBIMDataClassHSDPA = 0x8, + MBIMDataClassHSUPA = 0x10, + MBIMDataClassLTE = 0x20, + MBIMDataClass5G_NSA = 0x40, + MBIMDataClass5G_SA = 0x80, + MBIMDataClass1XRTT = 0x10000, + MBIMDataClass1XEVDO = 0x20000, + MBIMDataClass1XEVDORevA = 0x40000, + MBIMDataClass1XEVDV = 0x80000, + MBIMDataClass3XRTT = 0x100000, + MBIMDataClass1XEVDORevB = 0x200000, + MBIMDataClassUMB = 0x400000, + MBIMDataClassCustom = 0x80000000, +} MBIM_DATA_CLASS_E; + +static const char *MBIMDataClassStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMDataClassNone, "None"}, + {MBIMDataClassGPRS, "GPRS"}, + {MBIMDataClassEDGE, "EDGE"}, + {MBIMDataClassUMTS, "UMTS"}, + {MBIMDataClassHSDPA, "HSDPA"}, + {MBIMDataClassHSUPA, "HSUPA"}, + {MBIMDataClassLTE, "LTE"}, + {MBIMDataClass5G_NSA, "5G_NSA"}, + {MBIMDataClass5G_SA, "5G_SA"}, + {MBIMDataClass1XRTT, "1XRTT"}, + {MBIMDataClass1XEVDO, "1XEVDO"}, + {MBIMDataClass1XEVDORevA, "1XEVDORevA"}, + {MBIMDataClass1XEVDV, "1XEVDV"}, + {MBIMDataClass3XRTT, "3XRTT"}, + {MBIMDataClass1XEVDORevB, "1XEVDORevB"}, + {MBIMDataClassUMB, "UMB"}, + {MBIMDataClassCustom, "Custom"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Unknow"; +}; + +typedef struct { + UINT32 NwError; + UINT32 PacketServiceState; //MBIM_PACKET_SERVICE_STATE_E + UINT32 HighestAvailableDataClass; //MBIM_DATA_CLASS_E + UINT64 UplinkSpeed; + UINT64 DownlinkSpeed; +} MBIM_PACKET_SERVICE_INFO_T; + +typedef struct { + UINT32 NwError; + UINT32 PacketServiceState; //MBIM_PACKET_SERVICE_STATE_E + UINT32 CurrentDataClass; //MBIM_DATA_CLASS_E + UINT64 UplinkSpeed; + UINT64 DownlinkSpeed; + UINT32 FrequencyRange; +} MBIM_PACKET_SERVICE_INFO_V2_T; + +typedef enum { + MBIMSubscriberReadyStateNotInitialized = 0, + MBIMSubscriberReadyStateInitialized = 1, + MBIMSubscriberReadyStateSimNotInserted = 2, + MBIMSubscriberReadyStateBadSim = 3, + MBIMSubscriberReadyStateFailure = 4, + MBIMSubscriberReadyStateNotActivated = 5, + MBIMSubscriberReadyStateDeviceLocked = 6, +}MBIM_SUBSCRIBER_READY_STATE_E; + +static const char *MBIMSubscriberReadyStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMSubscriberReadyStateNotInitialized, "NotInitialized"}, + {MBIMSubscriberReadyStateInitialized, "Initialized"}, + {MBIMSubscriberReadyStateSimNotInserted, "NotInserted"}, + {MBIMSubscriberReadyStateBadSim, "BadSim"}, + {MBIMSubscriberReadyStateFailure, "Failure"}, + {MBIMSubscriberReadyStateNotActivated, "NotActivated"}, + {MBIMSubscriberReadyStateDeviceLocked, "DeviceLocked"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef struct { + UINT32 DeviceType; //MBIM_DEVICE_TYPE + UINT32 CellularClass; //MBIM_CELLULAR_CLASS + UINT32 VoiceClass; //MBIM_VOICE_CLASS + UINT32 SimClass; //MBIM_SIM_CLASS + UINT32 DataClass; //MBIM_DATA_CLASS + UINT32 SmsCaps; //MBIM_SMS_CAPS + UINT32 ControlCaps; //MBIM_CTRL_CAPS + UINT32 MaxSessions; + UINT32 CustomDataClassOffset; + UINT32 CustomDataClassSize; + UINT32 DeviceIdOffset; + UINT32 DeviceIdSize; + UINT32 FirmwareInfoOffset; + UINT32 FirmwareInfoSize; + UINT32 HardwareInfoOffset; + UINT32 HardwareInfoSize; + UINT8 DataBuffer[0]; //DeviceId FirmwareInfo HardwareInfo +} MBIM_DEVICE_CAPS_INFO_T; + +typedef enum { + MBIMRadioOff = 0, + MBIMRadioOn = 1, +} MBIM_RADIO_SWITCH_STATE_E; + +typedef struct { + MBIM_RADIO_SWITCH_STATE_E RadioState; +} MBIM_SET_RADIO_STATE_T; + +typedef struct { + MBIM_RADIO_SWITCH_STATE_E HwRadioState; + MBIM_RADIO_SWITCH_STATE_E SwRadioState; +} MBIM_RADIO_STATE_INFO_T; + +typedef enum { + MBIMReadyInfoFlagsNone, + MBIMReadyInfoFlagsProtectUniqueID, +}MBIM_UNIQUE_ID_FLAGS; + +typedef struct { + UINT32 ReadyState; + UINT32 SubscriberIdOffset; + UINT32 SubscriberIdSize; + UINT32 SimIccIdOffset; + UINT32 SimIccIdSize; + UINT32 ReadyInfo; + UINT32 ElementCount; + UINT8 *TelephoneNumbersRefList; + UINT8 *DataBuffer; +} MBIM_SUBSCRIBER_READY_STATUS_T; + +typedef enum { + MBIMRegisterActionAutomatic, + MBIMRegisterActionManual, +}MBIM_REGISTER_ACTION_E; + +typedef enum { + MBIMRegisterStateUnknown = 0, + MBIMRegisterStateDeregistered = 1, + MBIMRegisterStateSearching = 2, + MBIMRegisterStateHome = 3, + MBIMRegisterStateRoaming = 4, + MBIMRegisterStatePartner = 5, + MBIMRegisterStateDenied = 6, +}MBIM_REGISTER_STATE_E; + +typedef enum { + MBIMRegisterModeUnknown = 0, + MBIMRegisterModeAutomatic = 1, + MBIMRegisterModeManual = 2, +}MBIM_REGISTER_MODE_E; + +static const char *MBIMRegisterStateStr(int _val) { + struct { int val;char *name;} _enumstr[] ={ + {MBIMRegisterStateUnknown, "Unknown"}, + {MBIMRegisterStateDeregistered, "Deregistered"}, + {MBIMRegisterStateSearching, "Searching"}, + {MBIMRegisterStateHome, "Home"}, + {MBIMRegisterStateRoaming, "Roaming"}, + {MBIMRegisterStatePartner, "Partner"}, + {MBIMRegisterStateDenied, "Denied"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +static const char *MBIMRegisterModeStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMRegisterModeUnknown, "Unknown"}, + {MBIMRegisterModeAutomatic, "Automatic"}, + {MBIMRegisterModeManual, "Manual"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef enum { + MBIM_REGISTRATION_NONE, + MBIM_REGISTRATION_MANUAL_SELECTION_NOT_AVAILABLE, + MBIM_REGISTRATION_PACKET_SERVICE_AUTOMATIC_ATTACH, +}MBIM_REGISTRATION_FLAGS_E; + +typedef struct { + UINT32 NwError; + UINT32 RegisterState; //MBIM_REGISTER_STATE_E + UINT32 RegisterMode; + UINT32 AvailableDataClasses; + UINT32 CurrentCellularClass; + UINT32 ProviderIdOffset; + UINT32 ProviderIdSize; + UINT32 ProviderNameOffset; + UINT32 ProviderNameSize; + UINT32 RoamingTextOffset; + UINT32 RoamingTextSize; + UINT32 RegistrationFlag; + UINT8 *DataBuffer; +} MBIM_REGISTRATION_STATE_INFO_T; + +typedef struct { + UINT32 NwError; + UINT32 RegisterState; //MBIM_REGISTER_STATE_E + UINT32 RegisterMode; + UINT32 AvailableDataClasses; + UINT32 CurrentCellularClass; + UINT32 ProviderIdOffset; + UINT32 ProviderIdSize; + UINT32 ProviderNameOffset; + UINT32 ProviderNameSize; + UINT32 RoamingTextOffset; + UINT32 RoamingTextSize; + UINT32 RegistrationFlag; + UINT32 PreferredDataClass; + UINT8 *DataBuffer; +} MBIM_REGISTRATION_STATE_INFO_V2_T; + +typedef struct { + UINT32 MessageType; //Specifies the MBIM message type. + UINT32 MessageLength; //Specifies the total length of this MBIM message in bytes. + /* Specifies the MBIM message id value. This value is used to match host sent messages with function responses. + This value must be unique among all outstanding transactions. + For notifications, the TransactionId must be set to 0 by the function */ + UINT32 TransactionId; +} MBIM_MESSAGE_HEADER; + +typedef struct { + UINT32 TotalFragments; //this field indicates how many fragments there are intotal. + UINT32 CurrentFragment; //This field indicates which fragment this message is. Values are 0 to TotalFragments?\1 +} MBIM_FRAGMENT_HEADER; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 MaxControlTransfer; +} MBIM_OPEN_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 Status; //MBIM_STATUS_CODES_E +} MBIM_OPEN_DONE_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; +} MBIM_CLOSE_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 Status; +} MBIM_CLOSE_DONE_T; + +typedef struct { + UINT8 uuid[16]; +} UUID_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + MBIM_FRAGMENT_HEADER FragmentHeader; + UUID_T DeviceServiceId; //A 16 byte UUID that identifies the device service the following CID value applies. + UINT32 CID; //Specifies the CID that identifies the parameter being queried for + UINT32 CommandType; //0 for a query operation, 1 for a Set operation + UINT32 InformationBufferLength; //Size of the Total InformationBuffer, may be larger than current message if fragmented. + UINT8 InformationBuffer[0]; //Data supplied to device specific to the CID +} MBIM_COMMAND_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + MBIM_FRAGMENT_HEADER FragmentHeader; + UUID_T DeviceServiceId; //A 16 byte UUID that identifies the device service the following CID value applies. + UINT32 CID; //Specifies the CID that identifies the parameter being queried for + UINT32 Status; + UINT32 InformationBufferLength; //Size of the Total InformationBuffer, may be larger than current message if fragmented. + UINT8 InformationBuffer[0]; //Data supplied to device specific to the CID +} MBIM_COMMAND_DONE_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 ErrorStatusCode; +} MBIM_HOST_ERROR_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + UINT32 ErrorStatusCode; +} MBIM_FUNCTION_ERROR_MSG_T; + +typedef struct { + MBIM_MESSAGE_HEADER MessageHeader; + MBIM_FRAGMENT_HEADER FragmentHeader; + UUID_T DeviceServiceId; //A 16 byte UUID that identifies the device service the following CID value applies. + UINT32 CID; //Specifies the CID that identifies the parameter being queried for + UINT32 InformationBufferLength; //Size of the Total InformationBuffer, may be larger than current message if fragmented. + UINT8 InformationBuffer[0]; //Data supplied to device specific to the CID +} MBIM_INDICATE_STATUS_MSG_T; + +typedef struct { + UINT32 offset; + UINT32 size; +} OL_PAIR_LIST; + +typedef struct { + UUID_T DeviceServiceId; + UINT32 DssPayload; + UINT32 MaxDssInstances; + UINT32 CidCount; + UINT32 CidList[]; +} MBIM_DEVICE_SERVICE_ELEMENT_T; + +typedef struct { + UINT32 DeviceServicesCount; + UINT32 MaxDssSessions; + OL_PAIR_LIST DeviceServicesRefList[]; +} MBIM_DEVICE_SERVICES_INFO_T; + +typedef enum { + MBIMActivationCommandDeactivate = 0, + MBIMActivationCommandActivate = 1, +} MBIM_ACTIVATION_COMMAND_E; + +typedef enum { + MBIMCompressionNone = 0, + MBIMCompressionEnable = 1, +} MBIM_COMPRESSION_E; + +typedef enum { + MBIMAuthProtocolNone = 0, + MBIMAuthProtocolPap = 1, + MBIMAuthProtocolChap = 2, + MBIMAuthProtocolMsChapV2 = 3, +} MBIM_AUTH_PROTOCOL_E; + +typedef enum { + MBIMContextIPTypeDefault = 0, + MBIMContextIPTypeIPv4 = 1, + MBIMContextIPTypeIPv6 = 2, + MBIMContextIPTypeIPv4v6 = 3, + MBIMContextIPTypeIPv4AndIPv6 = 4, +} MBIM_CONTEXT_IP_TYPE_E; + +typedef enum { + MBIMActivationStateUnknown = 0, + MBIMActivationStateActivated = 1, + MBIMActivationStateActivating = 2, + MBIMActivationStateDeactivated = 3, + MBIMActivationStateDeactivating = 4, +} MBIM_ACTIVATION_STATE_E; + +typedef enum { + MBIMVoiceCallStateNone = 0, + MBIMVoiceCallStateInProgress = 1, + MBIMVoiceCallStateHangUp = 2, +} MBIM_VOICECALL_STATE_E; + +static const char *MBIMMSGTypeStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIM_OPEN_MSG, "MBIM_OPEN_MSG"}, + {MBIM_CLOSE_MSG, "MBIM_CLOSE_MSG"}, + {MBIM_COMMAND_MSG, "MBIM_COMMAND_MSG"}, + {MBIM_HOST_ERROR_MSG, "MBIM_HOST_ERROR_MSG"}, + {MBIM_OPEN_DONE, "MBIM_OPEN_DONE"}, + {MBIM_CLOSE_DONE, "MBIM_CLOSE_DONE"}, + {MBIM_COMMAND_DONE, "MBIM_COMMAND_DONE"}, + {MBIM_FUNCTION_ERROR_MSG, "MBIM_FUNCTION_ERROR_MSG"}, + {MBIM_INDICATE_STATUS_MSG, "MBIM_INDICATE_STATUS_MSG"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "MBIMMSGTypeUnknow"; +}; + +static const char *MBIMContextIPTypeStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMContextIPTypeDefault, "MBIMContextIPTypeDefault"}, + {MBIMContextIPTypeIPv4, "MBIMContextIPTypeIPv4"}, + {MBIMContextIPTypeIPv6, "MBIMContextIPTypeIPv6"}, + {MBIMContextIPTypeIPv4v6, "MBIMContextIPTypeIPv4v6"}, + {MBIMContextIPTypeIPv4AndIPv6, "MBIMContextIPTypeIPv4AndIPv6"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "MBIMContextIPTypeUnknow"; +} + +static const char *MBIMActivationStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMActivationStateUnknown, "Unknown"}, + {MBIMActivationStateActivated, "Activated"}, + {MBIMActivationStateActivating, "Activating"}, + {MBIMActivationStateDeactivated, "Deactivated"}, + {MBIMActivationStateDeactivating, "Deactivating"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +static const char *MBIMVoiceCallStateStr(int _val) { + struct { int val;char *name;} _enumstr[] = { + {MBIMVoiceCallStateNone, "None"}, + {MBIMVoiceCallStateInProgress, "InProgress"}, + {MBIMVoiceCallStateHangUp, "HangUp"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (_val == _enumstr[idx].val) + return _enumstr[idx].name; + } + + return "Undefined"; +}; + +typedef struct { + const char *uuid; + UINT32 cid; + const char *name; +} UUID_CID_STR; + +static const UUID_CID_STR uuid_cid_string[] = { + {UUID_BASIC_CONNECT, MBIM_CID_DEVICE_CAPS, "MBIM_CID_DEVICE_CAPS"}, + {UUID_BASIC_CONNECT, MBIM_CID_SUBSCRIBER_READY_STATUS, "MBIM_CID_SUBSCRIBER_READY_STATUS"}, + {UUID_BASIC_CONNECT, MBIM_CID_RADIO_STATE, "MBIM_CID_RADIO_STATE"}, + {UUID_BASIC_CONNECT, MBIM_CID_PIN, "MBIM_CID_PIN"}, + {UUID_BASIC_CONNECT, MBIM_CID_PIN_LIS, "MBIM_CID_PIN_LIS"}, + {UUID_BASIC_CONNECT, MBIM_CID_HOME_PROVIDER, "MBIM_CID_HOME_PROVIDER"}, + {UUID_BASIC_CONNECT, MBIM_CID_PREFERRED_PROVIDERS, "MBIM_CID_PREFERRED_PROVIDERS"}, + {UUID_BASIC_CONNECT, MBIM_CID_VISIBLE_PROVIDERS, "MBIM_CID_VISIBLE_PROVIDERS"}, + {UUID_BASIC_CONNECT, MBIM_CID_REGISTER_STATE, "MBIM_CID_REGISTER_STATE"}, + {UUID_BASIC_CONNECT, MBIM_CID_PACKET_SERVICE, "MBIM_CID_PACKET_SERVICE"}, + {UUID_BASIC_CONNECT, MBIM_CID_SIGNAL_STATE, "MBIM_CID_SIGNAL_STATE"}, + {UUID_BASIC_CONNECT, MBIM_CID_CONNECT, "MBIM_CID_CONNECT"}, + {UUID_BASIC_CONNECT, MBIM_CID_PROVISIONED_CONTEXTS, "MBIM_CID_PROVISIONED_CONTEXTS"}, + {UUID_BASIC_CONNECT, MBIM_CID_SERVICE_ACTIVATION, "MBIM_CID_SERVICE_ACTIVATION"}, + {UUID_BASIC_CONNECT, MBIM_CID_IP_CONFIGURATION, "MBIM_CID_IP_CONFIGURATION"}, + {UUID_BASIC_CONNECT, MBIM_CID_DEVICE_SERVICES, "MBIM_CID_DEVICE_SERVICES"}, + {UUID_BASIC_CONNECT, MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST, "MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST"}, + {UUID_BASIC_CONNECT, MBIM_CID_PACKET_STATISTICS, "MBIM_CID_PACKET_STATISTICS"}, + {UUID_BASIC_CONNECT, MBIM_CID_NETWORK_IDLE_HINT, "MBIM_CID_NETWORK_IDLE_HINT"}, + {UUID_BASIC_CONNECT, MBIM_CID_EMERGENCY_MODE, "MBIM_CID_EMERGENCY_MODE"}, + {UUID_BASIC_CONNECT, MBIM_CID_IP_PACKET_FILTERS, "MBIM_CID_IP_PACKET_FILTERS"}, + {UUID_BASIC_CONNECT, MBIM_CID_MULTICARRIER_PROVIDERS, "MBIM_CID_MULTICARRIER_PROVIDERS"}, + + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_PROVISIONED_CONTEXT_V2, "MBIM_CID_MS_PROVISIONED_CONTEXT_V2"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_NETWORK_BLACKLIST, "MBIM_CID_MS_NETWORK_BLACKLIST"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_LTE_ATTACH_CONFIG, "MBIM_CID_MS_LTE_ATTACH_CONFIG"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_LTE_ATTACH_STATUS, "MBIM_CID_MS_LTE_ATTACH_STATUS"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_SYS_CAPS, "MBIM_CID_MS_SYS_CAPS"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_DEVICE_CAPS_V2, "MBIM_CID_MS_DEVICE_CAPS_V2"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_DEVICE_SLOT_MAPPING, "MBIM_CID_MS_DEVICE_SLOT_MAPPING"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_SLOT_INFO_STATUS, "MBIM_CID_MS_SLOT_INFO_STATUS"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_PCO, "MBIM_CID_MS_PCO"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_DEVICE_RESET, "MBIM_CID_MS_DEVICE_RESET"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_BASE_STATIONS_INFO, "MBIM_CID_MS_BASE_STATIONS_INFO"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_LOCATION_INFO_STATUS, "MBIM_CID_MS_LOCATION_INFO_STATUS"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_NOT_DEFINED, "MBIM_CID_NOT_DEFINED"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_PIN_EX, "MBIM_CID_MS_PIN_EX"}, + {UUID_BASIC_CONNECT_EXT, MBIM_CID_MS_VERSION, "MBIM_CID_MS_VERSION"}, + + {UUID_MS_UICC_LOW_LEVEL, MBIM_CID_MS_UICC_ATR, "MBIM_CID_MS_UICC_ATR"}, + {UUID_MS_UICC_LOW_LEVEL, MBIM_CID_MS_UICC_OPEN_CHANNEL, "MBIM_CID_MS_UICC_OPEN_CHANNEL"}, + {UUID_MS_UICC_LOW_LEVEL, MBIM_CID_MS_UICC_CLOSE_CHANNEL, "MBIM_CID_MS_UICC_CLOSE_CHANNEL"}, + {UUID_MS_UICC_LOW_LEVEL, MBIM_CID_MS_UICC_APDU, "MBIM_CID_MS_UICC_APDU"}, + {UUID_MS_UICC_LOW_LEVEL, MBIM_CID_MS_UICC_TERMINAL_CAPABILITY, "MBIM_CID_MS_UICC_TERMINAL_CAPABILITY"}, + {UUID_MS_UICC_LOW_LEVEL, MBIM_CID_MS_UICC_RESET, "MBIM_CID_MS_UICC_RESET"}, + {UUID_MS_UICC_LOW_LEVEL, MBIM_CID_MS_APP_LIST, "MBIM_CID_MS_APP_LIST"}, +}; + +typedef struct { + UINT32 SessionId; + UINT32 ActivationCommand; //MBIM_ACTIVATION_COMMAND_E + UINT32 AccessStringOffset; + UINT32 AccessStringSize; + UINT32 UserNameOffset; + UINT32 UserNameSize; + UINT32 PasswordOffset; + UINT32 PasswordSize; + UINT32 Compression; //MBIM_COMPRESSION_E + UINT32 AuthProtocol; //MBIM_AUTH_PROTOCOL_E + UINT32 IPType; //MBIM_CONTEXT_IP_TYPE_E + UUID_T ContextType; + UINT8 DataBuffer[0]; /* apn, username, password */ +} MBIM_SET_CONNECT_T; + +typedef struct { + UINT32 SessionId; + UINT32 ActivationState; //MBIM_ACTIVATION_STATE_E + UINT32 VoiceCallState; + UINT32 IPType; //MBIM_CONTEXT_IP_TYPE_E + UUID_T ContextType; + UINT32 NwError; +} MBIM_CONNECT_T; + +typedef struct { + UINT32 OnLinkPrefixLength; + UINT8 IPv4Address[4]; +} MBIM_IPV4_ELEMENT_T; + +typedef struct { + UINT32 OnLinkPrefixLength; + UINT8 IPv6Address[16]; +} MBIM_IPV6_ELEMENT_T; + +typedef struct { + UINT32 SessionId; + UINT32 IPv4ConfigurationAvailable; //bit0~Address, bit1~gateway, bit2~DNS, bit3~MTU + UINT32 IPv6ConfigurationAvailable; //bit0~Address, bit1~gateway, bit2~DNS, bit3~MTU + UINT32 IPv4AddressCount; + UINT32 IPv4AddressOffset; + UINT32 IPv6AddressCount; + UINT32 IPv6AddressOffset; + UINT32 IPv4GatewayOffset; + UINT32 IPv6GatewayOffset; + UINT32 IPv4DnsServerCount; + UINT32 IPv4DnsServerOffset; + UINT32 IPv6DnsServerCount; + UINT32 IPv6DnsServerOffset; + UINT32 IPv4Mtu; + UINT32 IPv6Mtu; + UINT8 DataBuffer[]; +} MBIM_IP_CONFIGURATION_INFO_T; + +typedef struct { + UINT32 RSRP; + UINT32 SNR; + UINT32 RSRPThreshold; + UINT32 SNRThreshold; + UINT32 SystemType; +} MBIM_RSRP_SNR_INFO_T; + +typedef struct { + UINT32 Elementcount; + MBIM_RSRP_SNR_INFO_T RsrpSnr[0]; +} MBIM_RSRP_SNR_T; + +typedef struct { + UINT32 Rssi; + UINT32 ErrorRate; + UINT32 SignalStrengthInterval; + UINT32 RssiThreshold; + UINT32 ErrorRateThreshold; +} MBIM_SIGNAL_STATE_INFO_T; + +typedef struct { + UINT32 Rssi; + UINT32 ErrorRate; + UINT32 SignalStrengthInterval; + UINT32 RssiThreshold; + UINT32 ErrorRateThreshold; + UINT32 RsrpSnrOffset; + UINT32 RsrpSnrSize; + UINT8 DataBuffer[]; +} MBIM_SIGNAL_STATE_INFO_V2_T; + +typedef struct { + UINT32 SignalStrengthInterval; + UINT32 RssiThreshold; + UINT32 ErrorRateThreshold; +} MBIM_SET_SIGNAL_STATE_T; + +typedef struct { + UINT32 DevicePathOffset; + UINT32 DevicePathSize; + UINT32 Timeout; + UINT8 DataBuffer[]; +} MBIM_LIBQMI_PROXY_CONFIG_T; + +typedef struct { + UINT32 AtrSize; + UINT32 AtrOffset; + UINT8 DataBuffer[]; +} MBIM_MS_ATR_INFO_T; + +#pragma pack() + +static pthread_t s_tid_reader = 0; +static int mbim_verbose = 0; +static UINT32 TransactionId = 1; +static unsigned mbim_default_timeout = 30000; +static const char *mbim_apn = NULL; +static const char *mbim_user = NULL; +static const char *mbim_pd = NULL; +static int mbim_iptype = MBIMContextIPTypeDefault; +static int mbim_auth = MBIMAuthProtocolNone; +static int mbim_sessionID = 0; +static int mbim_fd = -1; +static MBIM_MESSAGE_HEADER *mbim_pRequest; +static MBIM_MESSAGE_HEADER *mbim_pResponse; + +static unsigned int qmi_over_mbim_support = 0; +static int qmi_over_mbim_sk[2] = {-1, -1}; +static pthread_mutex_t mbim_command_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t mbim_command_cond = PTHREAD_COND_INITIALIZER; +static int mbim_ms_version = 1; +static uint8_t qmi_over_mbim_nas = 0; +int qmi_over_mbim_qmidev_send(PQCQMIMSG pQMI); + +static const UUID_T * str2uuid(const char *str) { + static UUID_T uuid; + UINT32 d[16]; + char tmp[16*2+4+1]; + unsigned i = 0; + + while (str[i]) { + tmp[i] = tolower(str[i]); + i++; + } + tmp[i] = '\0'; + + sscanf(tmp, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + &d[0], &d[1], &d[2], &d[3], &d[4], &d[5], &d[6], &d[7], + &d[8], &d[9], &d[10], &d[11], &d[12], &d[13], &d[14], &d[15]); + + for (i = 0; i < 16; i++) { + uuid.uuid[i] = d[i]&0xFF; + } + + return &uuid; +} + +static void wchar2char(const char *src, size_t src_size, char *dst, size_t dst_len) { + size_t i; + + for (i = 0; i < (dst_len-1) && i < (src_size/2); i++) + dst[i] = src[i*2]; + dst[i] = 0; +} + +static size_t char2wchar(const char *src, size_t src_len, uint8_t *dst, size_t dst_len) { + size_t i; + + if (src_len > (dst_len/2)) + src_len = (dst_len/2); + + for (i = 0; i < src_len; i++) { + *dst++ = *src++; + *dst++ = 0; + } + + return i*2; +} + +#define mbim_alloc( _size) malloc(_size) +#define mbim_free(_mem) do { if (_mem) { free(_mem); _mem = NULL;}} while(0) + +static int mbim_open_state = 0; +static MBIM_SUBSCRIBER_READY_STATE_E ReadyState = MBIMSubscriberReadyStateNotInitialized; +static MBIM_REGISTER_STATE_E RegisterState = MBIMRegisterStateUnknown; +static MBIM_PACKET_SERVICE_STATE_E PacketServiceState = MBIMPacketServiceStateUnknown; +static MBIM_ACTIVATION_STATE_E ActivationState = MBIMActivationStateUnknown; +static MBIM_SUBSCRIBER_READY_STATE_E oldReadyState = MBIMSubscriberReadyStateNotInitialized; +static MBIM_REGISTER_STATE_E oldRegisterState = MBIMRegisterStateUnknown; +static MBIM_PACKET_SERVICE_STATE_E oldPacketServiceState = MBIMPacketServiceStateUnknown; +static MBIM_ACTIVATION_STATE_E oldActivationState = MBIMActivationStateUnknown; +static int mbim_update_state(void); + +static __inline uint32_t mbim2qmi_ipv4addr(uint32_t addr) { + return (addr>>24) | (addr>>8&0xff00) | (addr<<8&0xff0000) | (addr<<24); +} + +static __inline void mbim2qmi_ipv6addr(const unsigned char *src, unsigned char *dst) { + int i; + + for (i = 0; i < 16 ; i++) { + dst[i] = src[i]; + } +} + +static MBIM_MESSAGE_HEADER *compose_open_command(UINT32 MaxControlTransfer) +{ + MBIM_OPEN_MSG_T *pRequest = (MBIM_OPEN_MSG_T *)mbim_alloc(sizeof(MBIM_OPEN_MSG_T)); + + if(!pRequest) + return NULL; + + pRequest->MessageHeader.MessageType = htole32(MBIM_OPEN_MSG); + pRequest->MessageHeader.MessageLength = htole32(sizeof(MBIM_OPEN_MSG_T)); + pRequest->MessageHeader.TransactionId = htole32(TransactionId++); + pRequest->MaxControlTransfer = htole32(MaxControlTransfer); + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_close_command(void) +{ + MBIM_CLOSE_MSG_T *pRequest = (MBIM_CLOSE_MSG_T *)mbim_alloc(sizeof(MBIM_CLOSE_MSG_T)); + + if(!pRequest) + return NULL; + + pRequest->MessageHeader.MessageType = htole32(MBIM_CLOSE_MSG); + pRequest->MessageHeader.MessageLength = htole32(sizeof(MBIM_CLOSE_MSG_T)); + pRequest->MessageHeader.TransactionId = htole32(TransactionId++); + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_basic_connect_command(UINT32 CID, UINT32 CommandType, void *pInformationBuffer, UINT32 InformationBufferLength) +{ + MBIM_COMMAND_MSG_T *pRequest = (MBIM_COMMAND_MSG_T *)mbim_alloc(sizeof(MBIM_COMMAND_MSG_T) + InformationBufferLength); + + if (!pRequest) + return NULL; + + pRequest->MessageHeader.MessageType = htole32(MBIM_COMMAND_MSG); + pRequest->MessageHeader.MessageLength = htole32((sizeof(MBIM_COMMAND_MSG_T) + InformationBufferLength)); + pRequest->MessageHeader.TransactionId = htole32(TransactionId++); + + pRequest->FragmentHeader.TotalFragments = htole32(1); + pRequest->FragmentHeader.CurrentFragment= htole32(0); + + memcpy(pRequest->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16); + + pRequest->CID = htole32(CID); + pRequest->CommandType = htole32(CommandType); + if (InformationBufferLength && pInformationBuffer) { + pRequest->InformationBufferLength = htole32(InformationBufferLength); + memcpy(pRequest->InformationBuffer, pInformationBuffer, InformationBufferLength); + } else { + pRequest->InformationBufferLength = htole32(0); + } + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_basic_connect_ext_command(UINT32 CID, UINT32 CommandType, void *pInformationBuffer, UINT32 InformationBufferLength) +{ + MBIM_COMMAND_MSG_T *pRequest = (MBIM_COMMAND_MSG_T *)compose_basic_connect_command(CID, CommandType, pInformationBuffer, InformationBufferLength); + + if (!pRequest) + return NULL; + + memcpy(pRequest->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT_EXT), 16); + + return &pRequest->MessageHeader; +} + +static MBIM_MESSAGE_HEADER *compose_qmi_over_mbim_command(UINT32 CID, UINT32 CommandType, void *pInformationBuffer, UINT32 InformationBufferLength) +{ + MBIM_COMMAND_MSG_T *pRequest = (MBIM_COMMAND_MSG_T *)compose_basic_connect_command(CID, CommandType, pInformationBuffer, InformationBufferLength); + + if (!pRequest) + return NULL; + + memcpy(pRequest->DeviceServiceId.uuid, str2uuid(uuid_ext_qmux), 16); + + return &pRequest->MessageHeader; +} + +static const char * uuid2str(const UUID_T *pUUID) { + static char str[16*2+4+1]; + const UINT8 *d = pUUID->uuid; + + snprintf(str, sizeof(str), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], + d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15]); + + return str; +} + +static const char *DeviceServiceId2str(const UUID_T *pUUID) { + const char *str = uuid2str(pUUID); + + struct { char *val;char *name;} _enumstr[] = { + {UUID_BASIC_CONNECT, "UUID_BASIC_CONNECT"}, + {UUID_BASIC_CONNECT_EXT, "UUID_BASIC_CONNECT_EXT"}, + {UUID_SMS, "UUID_SMS"}, + {UUID_USSD, "UUID_USSD"}, + {UUID_PHONEBOOK, "UUID_PHONEBOOK"}, + {UUID_STK, "UUID_STK"}, + {UUID_AUTH, "UUID_AUTH"}, + {UUID_DSS, "UUID_DSS"}, + {uuid_ext_qmux, "uuid_ext_qmux"}, + {uuid_mshsd, "uuid_mshsd"}, + {uuid_qmbe, "uuid_qmbe"}, + {UUID_MSFWID, "UUID_MSFWID"}, + {uuid_atds, "uuid_atds"}, + {uuid_qdu, "uuid_qdu"}, + {UUID_MS_UICC_LOW_LEVEL, "UUID_MS_UICC_LOW_LEVEL"}, + {UUID_MS_SARControl, "UUID_MS_SARControl"}, + {UUID_VOICEEXTENSIONS, "UUID_VOICEEXTENSIONS"}, + {UUID_LIBMBIM_PROXY, "UUID_LIBMBIM_PROXY"}, + }; + int idx; + + for (idx = 0; idx < (int)(sizeof(_enumstr)/sizeof(_enumstr[0])); idx++) { + if (!strcasecmp(str, _enumstr[idx].val)) + return _enumstr[idx].name; + } + + return str; +} + +static const char *mbim_get_segment(void *_pMsg, UINT32 offset, UINT32 len) +{ + int idx; + static char buff[256] = {'\0'}; + UINT8 *pMsg = (UINT8*)_pMsg; + + for (idx = 0; idx < (int)(len/2); idx++) + buff[idx] = pMsg[offset+idx*2]; + buff[idx] = '\0'; + return buff; +} + +static void mbim_dump_header(MBIM_MESSAGE_HEADER *pMsg, const char *direction) { + mbim_debug("%s Header:", direction); + mbim_debug("%s MessageLength = %u", direction, le32toh(pMsg->MessageLength)); + mbim_debug("%s MessageType = %s (0x%08x)", direction, MBIMMSGTypeStr(le32toh(pMsg->MessageType)), le32toh(pMsg->MessageType)); + mbim_debug("%s TransactionId = %u", direction, le32toh(pMsg->TransactionId)); + mbim_debug("%s Contents:", direction); +} + +static void mbim_dump_uuid_cid(const UUID_T *pUUID, UINT32 CID, const char *direction) { + size_t idx; + const char *uuidStr = uuid2str(pUUID); + const char *cidStr = "unknow"; + + for (idx = 0; idx < (sizeof(uuid_cid_string)/sizeof(uuid_cid_string[0])); idx++) { + if (!strcmp(uuidStr, uuid_cid_string[idx].uuid) && uuid_cid_string[idx].cid == CID) { + cidStr = uuid_cid_string[idx].name; + } + } + + mbim_debug("%s DeviceServiceId = %s (%s)", direction, DeviceServiceId2str(pUUID), uuidStr); + mbim_debug("%s CID = %s (%u)", direction, cidStr, le32toh(CID)); +} + + +static void mbim_dump_command_msg(MBIM_COMMAND_MSG_T *pCmdMsg, const char *direction) { + mbim_dump_uuid_cid(&pCmdMsg->DeviceServiceId, le32toh(pCmdMsg->CID), direction); + mbim_debug("%s CommandType = %s (%u)", direction, le32toh(pCmdMsg->CommandType) ? "set" : "query", le32toh(pCmdMsg->CommandType)); + mbim_debug("%s InformationBufferLength = %u", direction, le32toh(pCmdMsg->InformationBufferLength)); +} + +static void mbim_dump_command_done(MBIM_COMMAND_DONE_T *pCmdDone, const char *direction) { + mbim_dump_uuid_cid(&pCmdDone->DeviceServiceId, le32toh(pCmdDone->CID), direction); + mbim_debug("%s Status = %u", direction, le32toh(pCmdDone->Status)); + mbim_debug("%s InformationBufferLength = %u", direction, le32toh(pCmdDone->InformationBufferLength)); +} + +static void mbim_dump_indicate_msg(MBIM_INDICATE_STATUS_MSG_T *pIndMsg, const char *direction) { + mbim_dump_uuid_cid(&pIndMsg->DeviceServiceId, le32toh(pIndMsg->CID), direction); + mbim_debug("%s InformationBufferLength = %u", direction, le32toh(pIndMsg->InformationBufferLength)); +} + +static void mbim_dump_connect(MBIM_CONNECT_T *pInfo, const char *direction) { + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + mbim_debug("%s ActivationState = %s (%u)", direction, MBIMActivationStateStr(le32toh(pInfo->ActivationState)), le32toh(pInfo->ActivationState)); + mbim_debug("%s IPType = %s", direction, MBIMContextIPTypeStr(le32toh(pInfo->IPType))); + mbim_debug("%s VoiceCallState = %s", direction, MBIMVoiceCallStateStr(le32toh(pInfo->VoiceCallState))); + mbim_debug("%s ContextType = %s", direction, uuid2str(&pInfo->ContextType)); + mbim_debug("%s NwError = %u", direction, le32toh(pInfo->NwError)); +} + +static void mbim_dump_signal_state(MBIM_SIGNAL_STATE_INFO_T *pInfo, const char *direction) +{ + mbim_debug("%s Rssi = %u", direction, le32toh(pInfo->Rssi)); + mbim_debug("%s ErrorRate = %u", direction, le32toh(pInfo->ErrorRate)); + mbim_debug("%s SignalStrengthInterval = %u", direction, le32toh(pInfo->SignalStrengthInterval)); + mbim_debug("%s RssiThreshold = %u", direction, le32toh(pInfo->RssiThreshold)); + mbim_debug("%s ErrorRateThreshold = %u", direction, le32toh(pInfo->ErrorRateThreshold)); +} + +static void mbim_dump_packet_service(MBIM_PACKET_SERVICE_INFO_T *pInfo, const char *direction) +{ + mbim_debug("%s NwError = %u", direction, le32toh(pInfo->NwError)); + mbim_debug("%s PacketServiceState = %s", direction, MBIMPacketServiceStateStr(le32toh(pInfo->PacketServiceState))); + mbim_debug("%s HighestAvailableDataClass = %s", direction, MBIMDataClassStr(le32toh(pInfo->HighestAvailableDataClass))); + mbim_debug("%s UplinkSpeed = %ld", direction, (long)le64toh(pInfo->UplinkSpeed)); + mbim_debug("%s DownlinkSpeed = %ld", direction, (long)le64toh(pInfo->DownlinkSpeed)); +} + +static void mbim_dump_subscriber_status(MBIM_SUBSCRIBER_READY_STATUS_T *pInfo, const char *direction) +{ + mbim_debug("%s ReadyState = %s", direction, MBIMSubscriberReadyStateStr(le32toh(pInfo->ReadyState))); + mbim_debug("%s SIMICCID = %s", direction, mbim_get_segment(pInfo, le32toh(pInfo->SimIccIdOffset), le32toh(pInfo->SimIccIdSize))); + mbim_debug("%s SubscriberID = %s", direction, mbim_get_segment(pInfo, le32toh(pInfo->SubscriberIdOffset), le32toh(pInfo->SubscriberIdSize))); + /* maybe more than one number */ + uint32_t idx; + for (idx = 0; idx < le32toh(pInfo->ElementCount); idx++) { + UINT32 offset = ((UINT32*)((UINT8*)pInfo+offsetof(MBIM_SUBSCRIBER_READY_STATUS_T, TelephoneNumbersRefList)))[0]; + UINT32 length = ((UINT32*)((UINT8*)pInfo+offsetof(MBIM_SUBSCRIBER_READY_STATUS_T, TelephoneNumbersRefList)))[1]; + mbim_debug("%s Number = %s", direction, mbim_get_segment(pInfo, le32toh(offset), le32toh(length))); + } +} + +static void mbim_dump_regiester_status(MBIM_REGISTRATION_STATE_INFO_T *pInfo, const char *direction) +{ + mbim_debug("%s NwError = %u", direction, le32toh(pInfo->NwError)); + mbim_debug("%s RegisterState = %s", direction, MBIMRegisterStateStr(le32toh(pInfo->RegisterState))); + mbim_debug("%s RegisterMode = %s", direction, MBIMRegisterModeStr(le32toh(pInfo->RegisterMode))); +} + +static void mbim_dump_ipconfig(MBIM_IP_CONFIGURATION_INFO_T *pInfo, const char *direction) +{ + UINT8 prefix = 0, *ipv4=NULL, *ipv6=NULL, *gw=NULL, *dns1=NULL, *dns2=NULL; + + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + mbim_debug("%s IPv4ConfigurationAvailable = 0x%x", direction, le32toh(pInfo->IPv4ConfigurationAvailable)); + mbim_debug("%s IPv6ConfigurationAvailable = 0x%x", direction, le32toh(pInfo->IPv6ConfigurationAvailable)); + mbim_debug("%s IPv4AddressCount = 0x%x", direction, le32toh(pInfo->IPv4AddressCount)); + mbim_debug("%s IPv4AddressOffset = 0x%x", direction, le32toh(pInfo->IPv4AddressOffset)); + mbim_debug("%s IPv6AddressCount = 0x%x", direction, le32toh(pInfo->IPv6AddressCount)); + mbim_debug("%s IPv6AddressOffset = 0x%x", direction, le32toh(pInfo->IPv6AddressOffset)); + + /* IPv4 */ + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x1) { + MBIM_IPV4_ELEMENT_T *pAddress = (MBIM_IPV4_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv4 = pAddress->IPv4Address; + mbim_debug("%s IPv4 = %u.%u.%u.%u/%u", direction, ipv4[0], ipv4[1], ipv4[2], ipv4[3], prefix); + } + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x2) { + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s gw = %u.%u.%u.%u", direction, gw[0], gw[1], gw[2], gw[3]); + } + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x4) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4DnsServerOffset) -sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s dns1 = %u.%u.%u.%u", direction, dns1[0], dns1[1], dns1[2], dns1[3]); + if (le32toh(pInfo->IPv4DnsServerCount) == 2) { + dns2 = dns1 + 4; + mbim_debug("%s dns2 = %u.%u.%u.%u", direction, dns2[0], dns2[1], dns2[2], dns2[3]); + } + } + if (le32toh(pInfo->IPv4Mtu)) mbim_debug("%s ipv4 mtu = %u", direction, le32toh(pInfo->IPv4Mtu)); + + /* IPv6 */ + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x1) { + MBIM_IPV6_ELEMENT_T *pAddress = (MBIM_IPV6_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv6 = pAddress->IPv6Address; + mbim_debug("%s IPv6 = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x/%d", \ + direction, ipv6[0], ipv6[1], ipv6[2], ipv6[3], ipv6[4], ipv6[5], ipv6[6], ipv6[7], \ + ipv6[8], ipv6[9], ipv6[10], ipv6[11], ipv6[12], ipv6[13], ipv6[14], ipv6[15], prefix); + } + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x2) { + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s gw = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", \ + direction, gw[0], gw[1], gw[2], gw[3], gw[4], gw[5], gw[6], gw[7], \ + gw[8], gw[9], gw[10], gw[11], gw[12], gw[13], gw[14], gw[15]); + } + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x4) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6DnsServerOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + mbim_debug("%s dns1 = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", \ + direction, dns1[0], dns1[1], dns1[2], dns1[3], dns1[4], dns1[5], dns1[6], dns1[7], \ + dns1[8], dns1[9], dns1[10], dns1[11], dns1[12], dns1[13], dns1[14], dns1[15]); + if (le32toh(pInfo->IPv6DnsServerCount) == 2) { + dns2 = dns1 + 16; + mbim_debug("%s dns2 = %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", \ + direction, dns2[0], dns2[1], dns2[2], dns2[3], dns1[4], dns1[5], dns1[6], dns1[7], + dns2[8], dns2[9], dns2[10], dns2[11], dns2[12], dns2[13], dns2[14], dns2[15]); + } + } + if (le32toh(pInfo->IPv6Mtu)) mbim_debug("%s ipv6 mtu = %u", direction, le32toh(pInfo->IPv6Mtu)); +} + +static void mbim_dump(MBIM_MESSAGE_HEADER *pMsg, int mbim_verbose) { + unsigned char *data = (unsigned char *)pMsg; + const char *direction = (le32toh(pMsg->MessageType) & 0x80000000) ? "<" : ">"; + + if (!mbim_verbose) + return; + + if (mbim_verbose) { + unsigned i; + static char *_tmp = NULL; + + if (!_tmp) + _tmp = (char *)malloc(4096); + + if (_tmp) { + _tmp[0] = (le32toh(pMsg->MessageType) & 0x80000000) ? '<' : '>'; + _tmp[1] = '\0'; + for (i = 0; i < le32toh(pMsg->MessageLength) && i < 4096; i++) + snprintf(_tmp + strlen(_tmp), 4096 - strlen(_tmp), "%02X:", data[i]); + mbim_debug("%s", _tmp); + } + } + + mbim_dump_header(pMsg, direction); + + switch (le32toh(pMsg->MessageType)) { + case MBIM_OPEN_MSG: { + MBIM_OPEN_MSG_T *pOpenMsg = (MBIM_OPEN_MSG_T *)pMsg; + mbim_debug("%s MaxControlTransfer = %u", direction, le32toh(pOpenMsg->MaxControlTransfer)); + } + break; + case MBIM_OPEN_DONE: { + MBIM_OPEN_DONE_T *pOpenDone = (MBIM_OPEN_DONE_T *)pMsg; + mbim_debug("%s Status = %u", direction, le32toh(pOpenDone->Status)); + } + break; + case MBIM_CLOSE_MSG: { + + } + break; + case MBIM_CLOSE_DONE: { + MBIM_CLOSE_DONE_T *pCloseDone = (MBIM_CLOSE_DONE_T *)pMsg; + mbim_debug("%s Status = %u", direction, le32toh(pCloseDone->Status)); + } + break; + case MBIM_COMMAND_MSG: { + MBIM_COMMAND_MSG_T *pCmdMsg = (MBIM_COMMAND_MSG_T *)pMsg; + + mbim_dump_command_msg(pCmdMsg, direction); + if (!memcmp(pCmdMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) { + switch (le32toh(pCmdMsg->CID)) { + case MBIM_CID_CONNECT: { + MBIM_SET_CONNECT_T *pInfo = (MBIM_SET_CONNECT_T *)pCmdMsg->InformationBuffer; + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + } + break; + case MBIM_CID_IP_CONFIGURATION: { + MBIM_IP_CONFIGURATION_INFO_T *pInfo = (MBIM_IP_CONFIGURATION_INFO_T *)pCmdMsg->InformationBuffer; + mbim_debug("%s SessionId = %u", direction, le32toh(pInfo->SessionId)); + } + break; + default: + break; + } + } + } + break; + case MBIM_COMMAND_DONE: { + MBIM_COMMAND_DONE_T *pCmdDone = (MBIM_COMMAND_DONE_T *)pMsg; + + mbim_dump_command_done(pCmdDone, direction); + if (le32toh(pCmdDone->InformationBufferLength) == 0) + return; + + if (!memcmp(pCmdDone->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) { + switch (le32toh(pCmdDone->CID)) { + case MBIM_CID_CONNECT: { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pCmdDone->InformationBuffer; + mbim_dump_connect(pInfo, direction); + } + break; + case MBIM_CID_IP_CONFIGURATION: { + //MBIM_IP_CONFIGURATION_INFO_T *pInfo = (MBIM_IP_CONFIGURATION_INFO_T *)pCmdDone->InformationBuffer; + //mbim_dump_ipconfig(pInfo, direction); + } + break; + case MBIM_CID_PACKET_SERVICE: { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pCmdDone->InformationBuffer; + mbim_dump_packet_service(pInfo, direction); + } + break; + case MBIM_CID_SUBSCRIBER_READY_STATUS: { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pCmdDone->InformationBuffer; + mbim_dump_subscriber_status(pInfo, direction); + } + break; + case MBIM_CID_REGISTER_STATE: { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pCmdDone->InformationBuffer; + mbim_dump_regiester_status(pInfo, direction); + } + break; + default: + break; + } + } + } + break; + case MBIM_INDICATE_STATUS_MSG: { + MBIM_INDICATE_STATUS_MSG_T *pIndMsg = (MBIM_INDICATE_STATUS_MSG_T *)pMsg; + + mbim_dump_indicate_msg(pIndMsg, direction); + if (le32toh(pIndMsg->InformationBufferLength) == 0) + return; + + if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) { + switch (le32toh(pIndMsg->CID)) { + case MBIM_CID_CONNECT: { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pIndMsg->InformationBuffer; + mbim_dump_connect(pInfo, direction); + } + break; + case MBIM_CID_SIGNAL_STATE: { + MBIM_SIGNAL_STATE_INFO_T *pInfo = (MBIM_SIGNAL_STATE_INFO_T *)pIndMsg->InformationBuffer; + mbim_dump_signal_state(pInfo, direction); + } + break; + case MBIM_CID_SUBSCRIBER_READY_STATUS: { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pIndMsg->InformationBuffer; + mbim_dump_subscriber_status(pInfo, direction); + } + break; + case MBIM_CID_REGISTER_STATE: { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pIndMsg->InformationBuffer; + mbim_dump_regiester_status(pInfo, direction); + } + break; + case MBIM_CID_PACKET_SERVICE: { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pIndMsg->InformationBuffer; + mbim_dump_packet_service(pInfo, direction); + } + break; + default: + break; + } + } + else if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT_EXT), 16)) { + } + } + break; + case MBIM_FUNCTION_ERROR_MSG: { + MBIM_FUNCTION_ERROR_MSG_T *pErrMsg = (MBIM_FUNCTION_ERROR_MSG_T*)pMsg; + mbim_debug("%s ErrorStatusCode = %u", direction, le32toh(pErrMsg->ErrorStatusCode)); + } + break; + default: + break; + } +} + +static void mbim_recv_command(MBIM_MESSAGE_HEADER *pResponse, unsigned size) +{ + (void)size; + pthread_mutex_lock(&mbim_command_mutex); + + if (pResponse) + mbim_dump(pResponse, mbim_verbose); + + if (pResponse == NULL) { + pthread_cond_signal(&mbim_command_cond); + } + else if (mbim_pRequest && le32toh(mbim_pRequest->TransactionId) == le32toh(pResponse->TransactionId)) { + mbim_pResponse = mbim_alloc(le32toh(pResponse->MessageLength)); + if (mbim_pResponse) + memcpy(mbim_pResponse, pResponse, le32toh(pResponse->MessageLength)); + pthread_cond_signal(&mbim_command_cond); + } + else if (le32toh(pResponse->MessageType) == MBIM_INDICATE_STATUS_MSG) { + MBIM_INDICATE_STATUS_MSG_T *pIndMsg = (MBIM_INDICATE_STATUS_MSG_T *)pResponse; + + if (!memcmp(pIndMsg->DeviceServiceId.uuid, str2uuid(UUID_BASIC_CONNECT), 16)) + { + switch (le32toh(pIndMsg->CID)) { + case MBIM_CID_SUBSCRIBER_READY_STATUS: { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pIndMsg->InformationBuffer; + if (oldReadyState != le32toh(pInfo->ReadyState)) + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } + break; + case MBIM_CID_REGISTER_STATE: { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pIndMsg->InformationBuffer; + if (oldRegisterState != le32toh(pInfo->RegisterState)) + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } + break; + case MBIM_CID_PACKET_SERVICE: { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pIndMsg->InformationBuffer; + MBIM_PACKET_SERVICE_STATE_E state = le32toh(pInfo->PacketServiceState); + + if (oldPacketServiceState != state + && (1 || MBIMPacketServiceStateAttached == state || MBIMPacketServiceStateDetached == state)) + qmidevice_send_event_to_main(RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + } + break; + case MBIM_CID_CONNECT: { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pIndMsg->InformationBuffer; + if (pInfo->SessionId == (uint32_t)mbim_sessionID) { + MBIM_ACTIVATION_STATE_E state = le32toh(pInfo->ActivationState); + + if (oldActivationState != state + && (1 || MBIMActivationStateActivated == state || MBIMActivationStateDeactivated == state)) + qmidevice_send_event_to_main(RIL_UNSOL_DATA_CALL_LIST_CHANGED); + } + } + break; + default: + break; + } + } + } + + pthread_mutex_unlock(&mbim_command_mutex); +} + +static int mbim_send_command(MBIM_MESSAGE_HEADER *pRequest, MBIM_COMMAND_DONE_T **ppCmdDone, unsigned msecs) { + int ret; + + if (ppCmdDone) + *ppCmdDone = NULL; + + if (mbim_fd <= 0) + return -ENODEV; + + if (s_tid_reader == 0) + return -EINVAL; + + if (!pRequest) + return -ENOMEM; + + pthread_mutex_lock(&mbim_command_mutex); + + if (pRequest) { + if (pRequest->TransactionId == (0xFFFFFF + 1)) { //quectel-mbim-proxy need 0xFF000000 to indicat client + TransactionId = 1; + pRequest->TransactionId = htole32(TransactionId++); + } + mbim_dump(pRequest, mbim_verbose); + } + + mbim_pRequest = pRequest; + mbim_pResponse = NULL; + + ret = write(mbim_fd, pRequest, le32toh(pRequest->MessageLength)); + + if (ret > 0 && (uint32_t)ret == le32toh(pRequest->MessageLength)) { + ret = pthread_cond_timeout_np(&mbim_command_cond, &mbim_command_mutex, msecs); + if (!ret) { + if (mbim_pResponse && ppCmdDone) { + *ppCmdDone = (MBIM_COMMAND_DONE_T *)mbim_pResponse; + } + } + } else { + mbim_debug("%s pthread_cond_timeout_np=%d", __func__, ret); + } + + mbim_pRequest = mbim_pResponse = NULL; + + pthread_mutex_unlock(&mbim_command_mutex); + + return ret; +} + +static ssize_t mbim_proxy_read (int fd, MBIM_MESSAGE_HEADER *pResponse, size_t size) { + ssize_t nreads; + + nreads = read(fd, pResponse, sizeof(MBIM_MESSAGE_HEADER)); + if (nreads == sizeof(MBIM_MESSAGE_HEADER) && le32toh(pResponse->MessageLength) <= size) { + nreads += read(fd, pResponse+1, le32toh(pResponse->MessageLength) - sizeof(MBIM_MESSAGE_HEADER)); + } + + return nreads; +} + +static void * mbim_read_thread(void *param) { + PROFILE_T *profile = (PROFILE_T *)param; + const char *cdc_wdm = (const char *)profile->qmichannel; + int wait_for_request_quit = 0; + + mbim_verbose = debug_qmi; + s_tid_reader = pthread_self(); + + if (profile->qmap_mode > 1 && profile->qmapnet_adapter[0]) { + if (!profile->proxy[0]) + sprintf(profile->proxy, "%s", QUECTEL_MBIM_PROXY); + mbim_sessionID = profile->pdp; + } + + if (profile->proxy[0]) { + mbim_fd = cm_open_proxy(profile->proxy); + } + else { + mbim_fd = cm_open_dev(cdc_wdm); + } + + if (mbim_fd <= 0) { + mbim_debug("fail to open (%s), errno: %d (%s)", cdc_wdm, errno, strerror(errno)); + goto __quit; + } + + dbg_time("cdc_wdm_fd = %d", mbim_fd); + + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_CONNECTED); + + while (mbim_fd > 0) { + struct pollfd pollfds[] = {{mbim_fd, POLLIN, 0}, {qmidevice_control_fd[1], POLLIN, 0}, {qmi_over_mbim_sk[1], POLLIN, 0}}; + int ne, ret, nevents = 2; + + if (pollfds[nevents].fd != -1) + nevents++; + + ret = poll(pollfds, nevents, wait_for_request_quit ? 1000 : -1); + + if (ret == 0 && wait_for_request_quit) { + break; + } + + if (ret < 0) { + mbim_debug("%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)) { + mbim_debug("%s poll err/hup/inval", __func__); + mbim_debug("epoll fd = %d, events = 0x%04x", fd, revents); + if (revents & (POLLERR | POLLHUP | POLLNVAL)) + goto __quit; + } + + if ((revents & POLLIN) == 0) + continue; + + if (mbim_fd == fd) { + ssize_t nreads; + MBIM_MESSAGE_HEADER *pResponse = (MBIM_MESSAGE_HEADER *) cm_recv_buf; + + if (profile->proxy[0]) + nreads = mbim_proxy_read(fd, pResponse, sizeof(cm_recv_buf)); + else + nreads = read(fd, pResponse, sizeof(cm_recv_buf)); + + if (nreads <= 0) { + mbim_debug("%s read=%d errno: %d (%s)", __func__, (int)nreads, errno, strerror(errno)); + break; + } + + mbim_recv_command(pResponse, nreads); + } + else if (fd == qmidevice_control_fd[1]) { + int triger_event; + if (read(fd, &triger_event, sizeof(triger_event)) == sizeof(triger_event)) { + //mbim_debug("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 == qmi_over_mbim_sk[1]) { + ssize_t nreads = read(fd, cm_recv_buf, sizeof(cm_recv_buf)); + if (nreads > 0) + QmiThreadRecvQMI((PQCQMIMSG)cm_recv_buf); + } + } + } + +__quit: + if (mbim_fd != -1) { close(mbim_fd); mbim_fd = -1; } + mbim_recv_command(NULL, 0); + qmidevice_send_event_to_main(RIL_INDICATE_DEVICE_DISCONNECTED); + mbim_debug("%s exit", __func__); + s_tid_reader = 0; + + return NULL; +} + +static int mbim_status_code(MBIM_MESSAGE_HEADER *pMsgHdr) { + int status = 0; + + if (!pMsgHdr) + return 0; + + switch (le32toh(pMsgHdr->MessageType)) { + case MBIM_OPEN_DONE: { + MBIM_OPEN_DONE_T *pOpenDone = (MBIM_OPEN_DONE_T *)pMsgHdr; + status = le32toh(pOpenDone->Status); + } + break; + case MBIM_CLOSE_DONE: { + MBIM_CLOSE_DONE_T *pCloseDone = (MBIM_CLOSE_DONE_T *)pMsgHdr; + status = le32toh(pCloseDone->Status); + } + break; + case MBIM_COMMAND_DONE: { + MBIM_COMMAND_DONE_T *pCmdDone = (MBIM_COMMAND_DONE_T *)pMsgHdr; + status = le32toh(pCmdDone->Status); + } + break; + case MBIM_FUNCTION_ERROR_MSG: { + MBIM_FUNCTION_ERROR_MSG_T *pErrMsg = (MBIM_FUNCTION_ERROR_MSG_T *)pMsgHdr; + status = le32toh(pErrMsg->ErrorStatusCode); + if (status == MBIM_ERROR_NOT_OPENED) + mbim_open_state = 0; //EM06ELAR03A05M4G when suspend/resume, may get this error + } + break; + default: + break; + } + + return status; +} + +#define mbim_check_err(err, pRequest, pCmdDone) do { \ + int _status = mbim_status_code(pCmdDone ? &pCmdDone->MessageHeader : NULL); \ + if (err || _status || !pCmdDone) { \ + if (pCmdDone) { mbim_dump(&pCmdDone->MessageHeader, (mbim_verbose == 0)); } \ + mbim_free(pRequest); mbim_free(pCmdDone); \ + mbim_debug("%s:%d err=%d, Status=%d", __func__, __LINE__, err, _status); \ + if (err) return err; \ + if (_status) return _status; \ + return 8888; \ + } \ +} while(0) + +/* + * MBIM device can be open repeatly without error + * So, we can call the function, no matter it have been opened or not + */ +static int mbim_open_device(uint32_t MaxControlTransfer) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_OPEN_DONE_T *pOpenDone = NULL; + int err = 0; + + mbim_debug("%s()", __func__); + pRequest = compose_open_command(MaxControlTransfer); + err = mbim_send_command(pRequest, (MBIM_COMMAND_DONE_T **)&pOpenDone, 3*1000); //EM06ELAR03A09M4G take about 2.5 seconds + mbim_check_err(err, pRequest, pOpenDone); + + err = le32toh(pOpenDone->Status); + mbim_free(pRequest); mbim_free(pOpenDone); + + return err; +} + +static int mbim_close_device(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_CLOSE_DONE_T *pCloseDone = NULL; + int err = 0; + + mbim_debug("%s()", __func__); + pRequest = compose_close_command(); + err = mbim_send_command(pRequest, (MBIM_COMMAND_DONE_T **)&pCloseDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCloseDone); + + err = le32toh(pCloseDone->Status); + mbim_free(pRequest); mbim_free(pCloseDone); + + return err; +} + +static int mbim_query_connect(int sessionID) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_SET_CONNECT_T set_connect; + int err; + + if (ActivationState != MBIMActivationStateActivated || mbim_verbose) + mbim_debug("%s(sessionID=%d)", __func__, sessionID); //avoid two many log + set_connect.SessionId = htole32(sessionID); + pRequest = compose_basic_connect_command(MBIM_CID_CONNECT, MBIM_CID_CMD_TYPE_QUERY, &set_connect, sizeof(set_connect)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) + { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pCmdDone->InformationBuffer; + ActivationState = le32toh(pInfo->ActivationState); + mbim_update_state(); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_ms_version_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + struct _bc_ext_version { + UINT8 ver_minor; + UINT8 ver_major; + UINT8 ext_ver_minor; + UINT8 ext_ver_major; + } __attribute__ ((packed)) bc_ext_version; + + bc_ext_version.ver_major = 1; + bc_ext_version.ver_minor = 0; + bc_ext_version.ext_ver_major = 2; + bc_ext_version.ext_ver_minor = 0; + + pRequest = compose_basic_connect_ext_command(MBIM_CID_MS_VERSION, MBIM_CID_CMD_TYPE_QUERY, &bc_ext_version, sizeof(bc_ext_version)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + struct _bc_ext_version *pInfo = (struct _bc_ext_version *)pCmdDone->InformationBuffer; + //mbim_debug("%s ext_rel_ver major=%d, minor=%d", __func__, pInfo->ext_ver_major, pInfo->ext_ver_minor); + mbim_ms_version = pInfo->ext_ver_major; + } + + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_device_services_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + int mbim_v2_support = 0; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_DEVICE_SERVICES, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (pCmdDone->InformationBufferLength) { + MBIM_DEVICE_SERVICES_INFO_T *pInfo = (MBIM_DEVICE_SERVICES_INFO_T *)pCmdDone->InformationBuffer; + UINT32 i; + + for (i = 0; i < le32toh(pInfo->DeviceServicesCount) ; i++) { + //UINT32 size = pInfo->DeviceServicesRefList[i].size; + UINT32 offset = le32toh(pInfo->DeviceServicesRefList[i].offset); + MBIM_DEVICE_SERVICE_ELEMENT_T *pSrvEle = (MBIM_DEVICE_SERVICE_ELEMENT_T *)((void *)pInfo + offset); + + //mbim_debug("\t[%2d] %s (%s)", i, DeviceServiceId2str(&pSrvEle->DeviceServiceId), uuid2str(&pSrvEle->DeviceServiceId)); + if (!strcasecmp(UUID_BASIC_CONNECT_EXT, uuid2str(&pSrvEle->DeviceServiceId))) { + UINT32 cid = 0; + + for (cid = 0; cid < le32toh(pSrvEle->CidCount); cid++) { + if (MBIM_CID_MS_VERSION == le32toh(pSrvEle->CidList[cid])) { + mbim_v2_support = 1; + } + } + } + else if (!strcasecmp(uuid_ext_qmux, uuid2str(&pSrvEle->DeviceServiceId))) { + qmi_over_mbim_support = 1; + } + } + } + mbim_free(pRequest); mbim_free(pCmdDone); + + if (mbim_v2_support) { + mbim_ms_version_query(); + } + + return err; +} + +static int mbim_device_caps_query(PROFILE_T *profile) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_DEVICE_CAPS, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_DEVICE_CAPS_INFO_T *pInfo = (MBIM_DEVICE_CAPS_INFO_T *)pCmdDone->InformationBuffer; + char tmp[32]; + + if (le32toh(pInfo->DeviceIdOffset) && le32toh(pInfo->DeviceIdSize)) { + wchar2char((const char *)pInfo + le32toh(pInfo->DeviceIdOffset), le32toh(pInfo->DeviceIdSize), tmp, sizeof(tmp)); + mbim_debug("DeviceId: %s", tmp); + } + if (le32toh(pInfo->FirmwareInfoOffset) && le32toh(pInfo->FirmwareInfoSize)) { + wchar2char((const char *)pInfo + le32toh(pInfo->FirmwareInfoOffset), le32toh(pInfo->FirmwareInfoSize), tmp, sizeof(tmp)); + strncpy(profile->BaseBandVersion, tmp, sizeof(profile->BaseBandVersion)); + mbim_debug("FirmwareInfo: %s", tmp); + } + if (le32toh(pInfo->HardwareInfoOffset) && le32toh(pInfo->HardwareInfoSize)) { + wchar2char((const char *)pInfo + le32toh(pInfo->HardwareInfoOffset), le32toh(pInfo->HardwareInfoSize), tmp, sizeof(tmp)); + mbim_debug("HardwareInfo: %s", tmp); + } + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +#if 0 +static int mbim_radio_state_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_RADIO_STATE, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (pCmdDone->InformationBufferLength) { + MBIM_RADIO_STATE_INFO_T *pInfo = (MBIM_RADIO_STATE_INFO_T *)pCmdDone->InformationBuffer; + mbim_debug("HwRadioState: %d, SwRadioState: %d", pInfo->HwRadioState, pInfo->SwRadioState); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} +#endif + +static int mbim_set_radio_state(MBIM_RADIO_SWITCH_STATE_E RadioState) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + UINT32 value = htole32(RadioState); + int err; + + mbim_debug("%s( %d )", __func__, RadioState); + pRequest = compose_basic_connect_command(MBIM_CID_RADIO_STATE, MBIM_CID_CMD_TYPE_SET, &value, sizeof(value)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_RADIO_STATE_INFO_T *pInfo = (MBIM_RADIO_STATE_INFO_T *)pCmdDone->InformationBuffer; + mbim_debug("HwRadioState: %d, SwRadioState: %d", le32toh(pInfo->HwRadioState), le32toh(pInfo->SwRadioState)); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_subscriber_status_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_SUBSCRIBER_READY_STATUS, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_SUBSCRIBER_READY_STATUS_T *pInfo = (MBIM_SUBSCRIBER_READY_STATUS_T *)pCmdDone->InformationBuffer; + char tmp[32]; + + wchar2char((const char *)pInfo + le32toh(pInfo->SubscriberIdOffset), le32toh(pInfo->SubscriberIdSize), tmp, sizeof(tmp)); + mbim_debug("SubscriberId: %s", tmp); + wchar2char((const char *)pInfo + le32toh(pInfo->SimIccIdOffset), le32toh(pInfo->SimIccIdSize), tmp, sizeof(tmp)); + mbim_debug("SimIccId: %s", tmp); + ReadyState = le32toh(pInfo->ReadyState); + mbim_update_state(); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_register_state_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_REGISTER_STATE, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_REGISTRATION_STATE_INFO_T *pInfo = (MBIM_REGISTRATION_STATE_INFO_T *)pCmdDone->InformationBuffer;; + RegisterState = le32toh(pInfo->RegisterState); + mbim_update_state(); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_packet_service_query(void) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_PACKET_SERVICE, MBIM_CID_CMD_TYPE_QUERY, NULL, 0); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pCmdDone->InformationBuffer; + PacketServiceState = le32toh(pInfo->PacketServiceState); + mbim_update_state(); + + if (le32toh(pCmdDone->InformationBufferLength) == sizeof(MBIM_PACKET_SERVICE_INFO_V2_T)) { + MBIM_PACKET_SERVICE_INFO_V2_T *pInfo = (MBIM_PACKET_SERVICE_INFO_V2_T *)pCmdDone->InformationBuffer; + mbim_debug("CurrentDataClass = %s", MBIMDataClassStr(le32toh(pInfo->CurrentDataClass))); + } + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_packet_service_set(MBIM_PACKET_SERVICE_ACTION_E action) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + UINT32 value = htole32(action); + int err; + + mbim_debug("%s()", __func__); + pRequest = compose_basic_connect_command(MBIM_CID_PACKET_SERVICE, MBIM_CID_CMD_TYPE_SET, &value, sizeof(value)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_PACKET_SERVICE_INFO_T *pInfo = (MBIM_PACKET_SERVICE_INFO_T *)pCmdDone->InformationBuffer; + PacketServiceState = le32toh(pInfo->PacketServiceState); + mbim_update_state(); + } + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +#define _align_32(len) {len += (len % 4) ? (4 - (len % 4)) : 0;} +static int mbim_populate_connect_data(MBIM_SET_CONNECT_T **connect_req_ptr) { + int offset; + int buflen = 0; + + if (mbim_apn && strlen(mbim_apn) > 0) buflen += 2*strlen(mbim_apn) ; + _align_32(buflen); + if (mbim_user && strlen(mbim_user) > 0) buflen += 2*strlen(mbim_user); + _align_32(buflen); + if (mbim_pd && strlen(mbim_pd) > 0) buflen += 2*strlen(mbim_pd); + _align_32(buflen); + + *connect_req_ptr = (MBIM_SET_CONNECT_T*)malloc(sizeof(MBIM_SET_CONNECT_T) + buflen); + if (! *connect_req_ptr) { + mbim_debug("not enough memory\n"); + return -1; + } + memset(*connect_req_ptr, 0, sizeof(MBIM_SET_CONNECT_T) + buflen); + + offset = 0; + if (mbim_apn && strlen(mbim_apn) > 0) { + (*connect_req_ptr)->AccessStringSize = htole32(2*strlen(mbim_apn)); + (*connect_req_ptr)->AccessStringOffset = htole32(offset + sizeof(MBIM_SET_CONNECT_T)); + offset = char2wchar(mbim_apn, strlen(mbim_apn), &(*connect_req_ptr)->DataBuffer[offset], buflen - offset); + _align_32(offset); + } + + if (mbim_user && strlen(mbim_user) > 0) { + (*connect_req_ptr)->UserNameSize = htole32(2*strlen(mbim_user)); + (*connect_req_ptr)->UserNameOffset = htole32(offset + sizeof(MBIM_SET_CONNECT_T)); + offset = char2wchar(mbim_user, strlen(mbim_user), &(*connect_req_ptr)->DataBuffer[offset], buflen - offset); + _align_32(offset); + } + + if (mbim_pd && strlen(mbim_pd) > 0) { + (*connect_req_ptr)->PasswordSize = htole32(2*strlen(mbim_pd)); + (*connect_req_ptr)->PasswordOffset = htole32(offset + sizeof(MBIM_SET_CONNECT_T)); + offset = char2wchar(mbim_pd, strlen(mbim_pd), &(*connect_req_ptr)->DataBuffer[offset], buflen - offset); + } + + return buflen; +} + +static int mbim_set_connect(int onoff, int sessionID) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_SET_CONNECT_T *set_connect = NULL; + int err; + + mbim_debug("%s(onoff=%d, sessionID=%d)", __func__, onoff, sessionID); + /* alloc memory then populate APN USERNAME PASSWORD */ + int buflen = mbim_populate_connect_data(&set_connect); + if (buflen < 0) { + return ENOMEM; + } + + set_connect->SessionId = htole32(sessionID); + if (onoff == 0) + set_connect->ActivationCommand = htole32(MBIMActivationCommandDeactivate); + else + set_connect->ActivationCommand = htole32(MBIMActivationCommandActivate); + + set_connect->Compression = htole32(MBIMCompressionNone); + set_connect->AuthProtocol = htole32(mbim_auth); + set_connect->IPType = htole32(mbim_iptype); + memcpy(set_connect->ContextType.uuid, str2uuid(UUID_MBIMContextTypeInternet), 16); + + pRequest = compose_basic_connect_command(MBIM_CID_CONNECT, MBIM_CID_CMD_TYPE_SET, set_connect, sizeof(MBIM_SET_CONNECT_T) + buflen); + mbim_free(set_connect); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout*10); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + MBIM_CONNECT_T *pInfo = (MBIM_CONNECT_T *)pCmdDone->InformationBuffer; + ActivationState = le32toh(pInfo->ActivationState); + mbim_update_state(); + } + + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_ip_config(PROFILE_T *profile, int sessionID) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_IP_CONFIGURATION_INFO_T ip_info; + int err; + + if (profile->ipv4.Address == 0 || mbim_verbose) + mbim_debug("%s(sessionID=%d)", __func__, sessionID); + ip_info.SessionId = htole32(sessionID); + pRequest = compose_basic_connect_command(MBIM_CID_IP_CONFIGURATION, MBIM_CID_CMD_TYPE_QUERY, &ip_info, sizeof(ip_info)); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + if (le32toh(pCmdDone->InformationBufferLength)) { + UINT8 prefix, *ipv4=NULL, *ipv6=NULL, *gw=NULL, *dns1=NULL, *dns2=NULL; + UINT32 mtu = 1500; + MBIM_IP_CONFIGURATION_INFO_T *pInfo = (MBIM_IP_CONFIGURATION_INFO_T *)pCmdDone->InformationBuffer; + + /* IPv4 network configration */ + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x1) { + MBIM_IPV4_ELEMENT_T *pAddress = (MBIM_IPV4_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv4 = pAddress->IPv4Address; + + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x2) + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x4) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv4DnsServerOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + if (le32toh(pInfo->IPv4DnsServerCount) == 2) + dns2 = dns1 + 4; + } + + if (le32toh(pInfo->IPv4ConfigurationAvailable)&0x8) + mtu = le32toh(pInfo->IPv4Mtu); + + if (profile->ipv4.Address != mbim2qmi_ipv4addr(*(uint32_t *)ipv4) || mbim_verbose) { + mbim_dump_ipconfig(pInfo, "<"); + profile->ipv4.Address = mbim2qmi_ipv4addr(*(uint32_t *)ipv4); + } + + if(gw != NULL) + profile->ipv4.Gateway = mbim2qmi_ipv4addr(*(uint32_t *)gw); + profile->ipv4.SubnetMask = mbim2qmi_ipv4addr(0xFFFFFFFF>>(32-prefix)<<(32-prefix)); + if(dns1 != NULL) + profile->ipv4.DnsPrimary = mbim2qmi_ipv4addr(*(uint32_t *)dns1); + if(dns2 != NULL) + profile->ipv4.DnsSecondary = mbim2qmi_ipv4addr(*(uint32_t *)dns2); + profile->ipv4.Mtu = mbim2qmi_ipv4addr(mtu); + } + + /* IPv6 network configration */ + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x1) { + gw = NULL; dns1 = NULL; dns2 = NULL; + MBIM_IPV6_ELEMENT_T *pAddress = (MBIM_IPV6_ELEMENT_T *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6AddressOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + prefix = le32toh(pAddress->OnLinkPrefixLength); + ipv6 = pAddress->IPv6Address; + + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x2) + gw = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6GatewayOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x4) { + dns1 = (UINT8 *)(&pInfo->DataBuffer[le32toh(pInfo->IPv6DnsServerOffset)-sizeof(MBIM_IP_CONFIGURATION_INFO_T)]); + if (le32toh(pInfo->IPv6DnsServerCount) == 2) + dns2 = dns1 + 16; + } + + if (le32toh(pInfo->IPv6ConfigurationAvailable)&0x8) + mtu = le32toh(pInfo->IPv6Mtu); + + if(ipv6 != NULL) + mbim2qmi_ipv6addr(ipv6, profile->ipv6.Address); + if(gw != NULL) + mbim2qmi_ipv6addr(gw, profile->ipv6.Gateway); + if(dns1 != NULL) + mbim2qmi_ipv6addr(dns1, profile->ipv6.DnsPrimary); + if(dns2 != NULL) + mbim2qmi_ipv6addr(dns2, profile->ipv6.DnsSecondary); + profile->ipv6.PrefixLengthIPAddr = prefix; + profile->ipv6.PrefixLengthGateway = prefix; + profile->ipv6.Mtu = mbim2qmi_ipv4addr(mtu); + } + } + return err; +} + +int mbim_proxy_configure(const char *dev) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + MBIM_LIBQMI_PROXY_CONFIG_T *cfg; + int err; + + pRequest = compose_basic_connect_command( + MBIM_CID_PROXY_CONTROL_CONFIGURATION, + MBIM_CID_CMD_TYPE_SET, + NULL, + sizeof(*cfg) + strlen(dev)*2); + if (pRequest) { + memcpy(((MBIM_COMMAND_MSG_T *)pRequest)->DeviceServiceId.uuid, str2uuid(UUID_LIBMBIM_PROXY), 16); + cfg = (MBIM_LIBQMI_PROXY_CONFIG_T *)((MBIM_COMMAND_MSG_T *)pRequest)->InformationBuffer; + + cfg->DevicePathOffset = sizeof(*cfg); + cfg->DevicePathSize = char2wchar(dev, strlen(dev), cfg->DataBuffer, strlen(dev)*2); + cfg->Timeout = 15; + } + + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} + +static int mbim_update_state(void) { + int chages = 0; + + if (oldReadyState != ReadyState) { + mbim_debug("SubscriberReadyState %s -> %s ", MBIMSubscriberReadyStateStr(oldReadyState), MBIMSubscriberReadyStateStr(ReadyState)); + oldReadyState = ReadyState; chages++; + } + if (oldRegisterState != RegisterState) { + mbim_debug("RegisterState %s -> %s ", MBIMRegisterStateStr(oldRegisterState), MBIMRegisterStateStr(RegisterState)); + oldRegisterState = RegisterState; chages++; + } + if (oldPacketServiceState != PacketServiceState) { + mbim_debug("PacketServiceState %s -> %s ", MBIMPacketServiceStateStr(oldPacketServiceState), MBIMPacketServiceStateStr(PacketServiceState)); + oldPacketServiceState = PacketServiceState; chages++; + } + if (oldActivationState != ActivationState) { + mbim_debug("ActivationState %s -> %s ", MBIMActivationStateStr(oldActivationState), MBIMActivationStateStr(ActivationState)); + oldActivationState = ActivationState; chages++; + } + + return chages; +} + +static int mbim_init(PROFILE_T *profile) { + int retval; + int t = 0; + + if (profile->proxy[0] && !strcmp(profile->proxy, LIBMBIM_PROXY)) { + retval = mbim_proxy_configure(profile->qmichannel); + if (retval) goto exit; + } + + while (t++ < 10) { + retval = mbim_open_device(4096); + if (retval != ETIMEDOUT) + break; + } + if (retval) goto exit; + retval = mbim_device_caps_query(profile); + if (retval) goto exit; + mbim_update_state(); + retval = mbim_device_services_query(); + if (retval) goto exit; + mbim_update_state(); + retval = mbim_set_radio_state(MBIMRadioOn); + if (retval) goto exit; + mbim_update_state(); + + if (qmi_over_mbim_support) { + if (!socketpair( AF_LOCAL, SOCK_STREAM, 0, qmi_over_mbim_sk)) { + qmidev_send = qmi_over_mbim_qmidev_send; +#ifdef CONFIG_CELLINFO //by now, only this function need QMI OVER MBIM + qmi_over_mbim_nas = qmi_over_mbim_get_client_id(QMUX_TYPE_NAS); +#endif + } + } + + return 0; + +exit: + return retval; +} + +static int mbim_deinit(void) { + if (qmi_over_mbim_nas) { + qmi_over_mbim_release_client_id(QMUX_TYPE_NAS, qmi_over_mbim_nas); + qmi_over_mbim_nas = 0; + } + + mbim_close_device(); + + if (qmi_over_mbim_sk[0] != -1) { + close(qmi_over_mbim_sk[0]); + close(qmi_over_mbim_sk[1]); + } + + return 0; +} + +const struct qmi_device_ops mbim_dev_ops = { + .init = mbim_init, + .deinit = mbim_deinit, + .read = mbim_read_thread, +}; + +static int requestBaseBandVersion(PROFILE_T *profile) { + (void)profile; + return 0; +} + +static int requestGetSIMStatus(SIM_Status *pSIMStatus) +{ + int retval; + + *pSIMStatus = SIM_ABSENT; + retval = mbim_subscriber_status_query(); + if (retval) + goto exit; + mbim_update_state(); + + switch(ReadyState) { + case MBIMSubscriberReadyStateNotInitialized: *pSIMStatus = SIM_NOT_READY; break; + case MBIMSubscriberReadyStateInitialized: *pSIMStatus = SIM_READY; break; + case MBIMSubscriberReadyStateSimNotInserted: *pSIMStatus = SIM_ABSENT; break; + case MBIMSubscriberReadyStateBadSim: *pSIMStatus = SIM_BAD; break; + case MBIMSubscriberReadyStateFailure: *pSIMStatus = SIM_ABSENT; break; + case MBIMSubscriberReadyStateNotActivated: *pSIMStatus = SIM_ABSENT; break; + case MBIMSubscriberReadyStateDeviceLocked: *pSIMStatus = SIM_PIN; break; + default: *pSIMStatus = SIM_ABSENT; break; + } + +exit: + return retval; +} + +static int requestRegistrationState(UCHAR *pPSAttachedState) { + int retval; + + *pPSAttachedState = 0; + retval = mbim_register_state_query(); + if (retval) + goto exit; + mbim_update_state(); + + switch (RegisterState) { + case MBIMRegisterStateUnknown: *pPSAttachedState = 0; break; + case MBIMRegisterStateDeregistered: *pPSAttachedState = 0; break; + case MBIMRegisterStateSearching: *pPSAttachedState = 0; break; + case MBIMRegisterStateHome: *pPSAttachedState = 1; break; + case MBIMRegisterStateRoaming: *pPSAttachedState = 1; break; + case MBIMRegisterStatePartner: *pPSAttachedState = 0; break; + case MBIMRegisterStateDenied: *pPSAttachedState = 0; break; + default: *pPSAttachedState = 0; break; + } + + if (*pPSAttachedState == 0) + goto exit; + + retval = mbim_packet_service_query(); + if (retval) + goto exit; + + switch (PacketServiceState) { + case MBIMPacketServiceStateUnknown: *pPSAttachedState = 0; break; + case MBIMPacketServiceStateAttaching: *pPSAttachedState = 0; break; + case MBIMPacketServiceStateAttached: *pPSAttachedState = 1; break; + case MBIMPacketServiceStateDetaching: *pPSAttachedState = 0; break; + case MBIMPacketServiceStateDetached: *pPSAttachedState = 0; break; + default: *pPSAttachedState = 0; break; + } + + if (*pPSAttachedState == 0) + mbim_packet_service_set(MBIMPacketServiceActionAttach); + +exit: + return retval; +} + +static int requestSetupDataCall(PROFILE_T *profile, int curIpFamily) { + int retval; + + (void)curIpFamily; + + if (profile->apn) + mbim_apn = profile->apn; + if (profile->user) + mbim_user = profile->user; + if (profile->pd) + mbim_pd = profile->pd; + if (profile->auth) + mbim_auth = profile->auth; + if (profile->enable_ipv4) + mbim_iptype = MBIMContextIPTypeIPv4; + if (profile->enable_ipv6) + mbim_iptype = MBIMContextIPTypeIPv6; + if (profile->enable_ipv4 && profile->enable_ipv6) + mbim_iptype = MBIMContextIPTypeIPv4AndIPv6; + + retval = mbim_set_connect(1, mbim_sessionID); + if (retval) + goto exit; + +exit: + return retval; +} + +static int requestQueryDataCall(UCHAR *pConnectionStatus, int curIpFamily) { + int retval; + + (void)curIpFamily; + + *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; + + retval = mbim_query_connect(mbim_sessionID); + if (retval) + goto exit; + + switch(ActivationState) { + case MBIMActivationStateUnknown: *pConnectionStatus = QWDS_PKT_DATA_UNKNOW; break; + case MBIMActivationStateActivated: *pConnectionStatus = QWDS_PKT_DATA_CONNECTED; break; + case MBIMActivationStateActivating: *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; break; + case MBIMActivationStateDeactivated: *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; break; + case MBIMActivationStateDeactivating: *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; break; + default: *pConnectionStatus = QWDS_PKT_DATA_DISCONNECTED; break; + } + +exit: + return retval; +} + +static int requestDeactivateDefaultPDP(PROFILE_T *profile, int curIpFamily) { + int retval; + + (void)profile; + (void)curIpFamily; + + retval = mbim_set_connect(0, mbim_sessionID); + if (retval) + goto exit; + +exit: + return retval; +} + +static int requestGetIPAddress(PROFILE_T *profile, int curIpFamily) { + int retval; + + (void)curIpFamily; + retval = mbim_ip_config(profile, mbim_sessionID); + if (retval) + goto exit; + +exit: + return retval; +} + +#ifdef CONFIG_CELLINFO +static int requestGetCellInfoList(void) { + if (qmi_over_mbim_nas) { + if (qmi_request_ops.requestGetCellInfoList) + return qmi_request_ops.requestGetCellInfoList(); + } + + return 0; +} +#endif + +const struct request_ops mbim_request_ops = { + .requestBaseBandVersion = requestBaseBandVersion, + .requestGetSIMStatus = requestGetSIMStatus, + .requestRegistrationState = requestRegistrationState, + .requestSetupDataCall = requestSetupDataCall, + .requestQueryDataCall = requestQueryDataCall, + .requestDeactivateDefaultPDP = requestDeactivateDefaultPDP, + .requestGetIPAddress = requestGetIPAddress, +#ifdef CONFIG_CELLINFO + .requestGetCellInfoList = requestGetCellInfoList, +#endif +}; + +int qmi_over_mbim_qmidev_send(PQCQMIMSG pQMI) { + MBIM_MESSAGE_HEADER *pRequest = NULL; + MBIM_COMMAND_DONE_T *pCmdDone = NULL; + int err; + size_t len = le16toh(pQMI->QMIHdr.Length) + 1; + + if (pQMI->QMIHdr.QMIType != QMUX_TYPE_CTL) { + if (pQMI->QMIHdr.QMIType == QMUX_TYPE_NAS) + pQMI->QMIHdr.ClientId = qmi_over_mbim_nas; + + if (pQMI->QMIHdr.ClientId == 0) { + dbg_time("QMIType %d has no clientID", pQMI->QMIHdr.QMIType); + return -ENODEV; + } + } + + pRequest = compose_qmi_over_mbim_command(1, MBIM_CID_CMD_TYPE_SET, pQMI, len); + err = mbim_send_command(pRequest, &pCmdDone, mbim_default_timeout); + mbim_check_err(err, pRequest, pCmdDone); + + err = -1; + len = le32toh(pCmdDone->InformationBufferLength); + if (len) { + if (write(qmi_over_mbim_sk[0], pCmdDone->InformationBuffer, len) == (long)len) { + err = 0; + }; + } + + mbim_free(pRequest); mbim_free(pCmdDone); + return err; +} diff --git a/RM520N-5G/文件/source/qendian.h b/RM520N-5G/文件/source/qendian.h new file mode 100644 index 0000000..0219ca0 --- /dev/null +++ b/RM520N-5G/文件/source/qendian.h @@ -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 + +#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__ + diff --git a/RM520N-5G/文件/source/qlist.h b/RM520N-5G/文件/source/qlist.h new file mode 100644 index 0000000..891d50d --- /dev/null +++ b/RM520N-5G/文件/source/qlist.h @@ -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 \ No newline at end of file diff --git a/RM520N-5G/文件/source/qmap_bridge_mode.c b/RM520N-5G/文件/source/qmap_bridge_mode.c new file mode 100644 index 0000000..82cfa8a --- /dev/null +++ b/RM520N-5G/文件/source/qmap_bridge_mode.c @@ -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; +} diff --git a/RM520N-5G/文件/source/qrtr.c b/RM520N-5G/文件/source/qrtr.c new file mode 100644 index 0000000..26a7898 --- /dev/null +++ b/RM520N-5G/文件/source/qrtr.c @@ -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 +#include +#include +#include +#include +#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 + +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 +#include +#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, +}; + diff --git a/RM520N-5G/文件/source/qrtr.h b/RM520N-5G/文件/source/qrtr.h new file mode 100644 index 0000000..6db00d5 --- /dev/null +++ b/RM520N-5G/文件/source/qrtr.h @@ -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 +#include + +#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 */ diff --git a/RM520N-5G/文件/source/quectel-atc-proxy.c b/RM520N-5G/文件/source/quectel-atc-proxy.c new file mode 100644 index 0000000..bbe6951 --- /dev/null +++ b/RM520N-5G/文件/source/quectel-atc-proxy.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 A valid atc device\n" + " default /dev/ttyUSB2, but /dev/ttyUSB2 may be invalid\n" + " -i 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; +} \ No newline at end of file diff --git a/RM520N-5G/文件/source/quectel-mbim-proxy.c b/RM520N-5G/文件/source/quectel-mbim-proxy.c new file mode 100644 index 0000000..dbda963 --- /dev/null +++ b/RM520N-5G/文件/source/quectel-mbim-proxy.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/RM520N-5G/文件/source/quectel-qmi-proxy.c b/RM520N-5G/文件/source/quectel-qmi-proxy.c new file mode 100644 index 0000000..4cb4c9f --- /dev/null +++ b/RM520N-5G/文件/source/quectel-qmi-proxy.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 A valid qmi device\n" + " default /dev/cdc-wdm0, but cdc-wdm0 may be invalid\n" + " -i 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; +} diff --git a/RM520N-5G/文件/source/quectel-qrtr-proxy.c b/RM520N-5G/文件/source/quectel-qrtr-proxy.c new file mode 100644 index 0000000..77a6914 --- /dev/null +++ b/RM520N-5G/文件/source/quectel-qrtr-proxy.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 A valid qrtr device\n" + " default /dev/mhi_IPCR, but mhi_IPCR may be invalid\n" + " -i 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; +} diff --git a/RM520N-5G/文件/source/rmnetctl.c b/RM520N-5G/文件/source/rmnetctl.c new file mode 100644 index 0000000..1ccc59d --- /dev/null +++ b/RM520N-5G/文件/source/rmnetctl.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} + diff --git a/RM520N-5G/文件/source/udhcpc.c b/RM520N-5G/文件/source/udhcpc.c new file mode 100644 index 0000000..c2e92a2 --- /dev/null +++ b/RM520N-5G/文件/source/udhcpc.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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<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 +} diff --git a/RM520N-5G/文件/source/udhcpc_netlink.c b/RM520N-5G/文件/source/udhcpc_netlink.c new file mode 100644 index 0000000..163a580 --- /dev/null +++ b/RM520N-5G/文件/source/udhcpc_netlink.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/RM520N-5G/文件/source/udhcpc_script.c b/RM520N-5G/文件/source/udhcpc_script.c new file mode 100644 index 0000000..2cc114d --- /dev/null +++ b/RM520N-5G/文件/source/udhcpc_script.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/RM520N-5G/文件/source/util.c b/RM520N-5G/文件/source/util.c new file mode 100644 index 0000000..7d9c2a8 --- /dev/null +++ b/RM520N-5G/文件/source/util.c @@ -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 +#include +typedef unsigned short sa_family_t; +#include + +#if defined(__STDC__) +#include +#define __V(x) x +#else +#include +#define __V(x) (va_alist) va_dcl +#define const +#define volatile +#endif + +#include + +#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, >); + 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(>); + 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(>); + + return pid; + } + } + } + + globfree(>); + 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; +} diff --git a/RM520N-5G/文件/source/util.h b/RM520N-5G/文件/source/util.h new file mode 100644 index 0000000..eea1756 --- /dev/null +++ b/RM520N-5G/文件/source/util.h @@ -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 +#include + +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 diff --git a/RM520N-5G/文件/多WAN配置/90-pcie_mhi.txt b/RM520N-5G/文件/多WAN配置/90-pcie_mhi.txt new file mode 100644 index 0000000..dfc0992 --- /dev/null +++ b/RM520N-5G/文件/多WAN配置/90-pcie_mhi.txt @@ -0,0 +1 @@ +pcie_mhi mhi_mbim_enabled=1 diff --git a/RM520N-5G/文件/多WAN配置/README-多WAN.md b/RM520N-5G/文件/多WAN配置/README-多WAN.md new file mode 100644 index 0000000..8366166 --- /dev/null +++ b/RM520N-5G/文件/多WAN配置/README-多WAN.md @@ -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.cn(kmods 源 curl 22 失败,但不影响纯脚本包安装)。 +- 备份:原 `/etc/config/network`(eth0 曾 DHCP 拿 110.135)可回滚。 diff --git a/RM520N-5G/文件/多WAN配置/init.d_modem5g b/RM520N-5G/文件/多WAN配置/init.d_modem5g new file mode 100644 index 0000000..1a62b89 --- /dev/null +++ b/RM520N-5G/文件/多WAN配置/init.d_modem5g @@ -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 +} diff --git a/RM520N-5G/文件/多WAN配置/init.d_wan5g-route b/RM520N-5G/文件/多WAN配置/init.d_wan5g-route new file mode 100644 index 0000000..b023e86 --- /dev/null +++ b/RM520N-5G/文件/多WAN配置/init.d_wan5g-route @@ -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 +} diff --git a/RM520N-5G/文件/多WAN配置/network.txt b/RM520N-5G/文件/多WAN配置/network.txt new file mode 100644 index 0000000..6fda699 --- /dev/null +++ b/RM520N-5G/文件/多WAN配置/network.txt @@ -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' + diff --git a/RM520N-5G/文件/多WAN配置/rc.local b/RM520N-5G/文件/多WAN配置/rc.local new file mode 100644 index 0000000..617c32e --- /dev/null +++ b/RM520N-5G/文件/多WAN配置/rc.local @@ -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 diff --git a/RM520N-5G/文档/RM520N-MBIM拨号成功路线.md b/RM520N-5G/文档/RM520N-MBIM拨号成功路线.md new file mode 100644 index 0000000..1fff6eb --- /dev/null +++ b/RM520N-5G/文档/RM520N-MBIM拨号成功路线.md @@ -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.8(musl 静态编译)** 通过 **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 vlan(MBIM 模式走 `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-wdm,mmcli 扫不到。 +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 下能用 MBIM,QMI 通道是死的**(至少 pcie_mhi v1.3.8 + 此固件下如此)。 +- MBIM 模式**不需要 proxy**,`quectel-CM -s ` 直接拨号即可。 +- iStoreOS 是 **musl libc**,必须用 **musl-gcc 静态编译** quectel-CM(glibc 静态编译的 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-CM(musl 静态编译 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-CM(V1.6.8,QMI 分支) +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` 参数。 diff --git a/RM520N-5G/文档/RM520N-故障排查与自启链路.md b/RM520N-5G/文档/RM520N-故障排查与自启链路.md new file mode 100644 index 0000000..05fdda6 --- /dev/null +++ b/RM520N-5G/文档/RM520N-故障排查与自启链路.md @@ -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.x,ping 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 autoload(modalias 拉起的),禁 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 检测可能因此误判:若主路由用国外站点/双栈做健康检查会误报"外网不通" diff --git a/RM520N-5G/文档/RM520N-硬件结构与网络拓扑.md b/RM520N-5G/文档/RM520N-硬件结构与网络拓扑.md new file mode 100644 index 0000000..6726de6 --- /dev/null +++ b/RM520N-5G/文档/RM520N-硬件结构与网络拓扑.md @@ -0,0 +1,131 @@ +# 硬件结构与网络拓扑(2026-08-19) + +> 峰的家用 NAS/软路由硬件拓扑与 RM520N-GL 5G 模块直通结构 +> 整理日期:2026-08-19 + +--- + +## 一、硬件结构 + +### 宿主机(飞牛 Fnos) +| 项 | 值 | +|---|---| +| 系统 | 飞牛 fnOS(云主机/软路由宿主) | +| CPU | Intel Core i5-8250U @ 1.60GHz(4核8线程) | +| 内存 | 15 GiB | +| 网卡 | 4 × Intel I226-V(enp1s0 / enp2s0 / enp3s0 / enp4s0) | +| 网卡现状 | enp1s0 = UP,enp4s0 = UP;enp2s0 / enp3s0 = 未接(DOWN) | +| 虚拟化 | KVM/QEMU(libvirt,`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 89860125801671811893,IMSI 460092810130406) | + +### istoreos 虚拟机(5G 路由器) +| 项 | 值 | +|---|---| +| VM 名称 | adwi9jj4(libvirt domain-1) | +| 系统 | iStoreOS(OpenWrt 定制版,musl libc) | +| 登录 IP | 192.168.110.200(root,SSH) | +| 虚拟网卡 | 2 × virtio-net-pci(tap 桥接) | +| 直通 | 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.1,istoreos eth0 = 110.200 | +| 192.168.200.0/24 | 主路由 wan2 ↔ istoreos eth1 | 主路由 wan2 = 200.2,istoreos 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` diff --git a/lede b/lede new file mode 160000 index 0000000..a09ade8 --- /dev/null +++ b/lede @@ -0,0 +1 @@ +Subproject commit a09ade85b34407039303bbe55131d2b8b1328465 diff --git a/luci-feed-inspect b/luci-feed-inspect new file mode 160000 index 0000000..ee9eee0 --- /dev/null +++ b/luci-feed-inspect @@ -0,0 +1 @@ +Subproject commit ee9eee076753591af594cac548881a5feca8ca82