ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

高通平台QMI接口开发指南:从协议原理到增加新接口实战

高通平台QMI接口开发指南:从协议原理到增加新接口实战 简介高通平台新增QMI接口时IDL生成后的消息表与类型表位置核对是常见痛点这份Word说明文档面向需要扩展或定制QMI消息的驱动、通信和定位开发工程师清晰梳理了从接口定义到实际表项的定位思路。资源仅包含一个Word文档大小291KB内容精炼目前已有12212人学习下载。文档以定位驱动流程为背景重点拆解QMI的IDL表消息流宏的展开过程解释如何借助继承对象、类型表、引用表和消息表之间的关联关系一步步锁定新增消息在远程消息表等表中的具体位置同时用具体索引号演示消息在表项中的映射方式例如指定写用户数据响应消息如何被定位到引用表对应位置并单独标注消息与类型两个关键位置方便对照源码逐行研读。最终给出两个关键结论新消息必须追加到消息表末尾消息类型值完全由位置决定。读者按图索骥即可快速完成QMI接口扩展规避因IDL生成后表项错位导致的编译或运行异常并能根据实际报错快速反查表项定义显著提升排错效率。1. 先把QMI接口在高通平台里的位置放对刚拿到一块高通平台板子的开发同学多半会碰到这个场景系统侧的应用或守护进程需要跟Modem里的某个能力对话比如读信号强度、拿设备序列号、查SIM卡状态或者干脆让Modem跑一个私有协议。这时候你大概率会听说一句话——去走QMI接口。QMI全称Qualcomm MSM Interface是高通在AP应用处理器和Modem调制解调器之间定义的自定义消息交互协议。做高通平台开发这几年我加过自研服务、调过现成服务在QMI这层踩过的坑一只手数不过来。这篇文章不打算把QMI协议栈从头到尾复述一遍只把“增加接口或者真正把一条QMI链路跑通”时最容易被忽略的关键点拉出来讲清楚。1.1 QMI是一条“消息软总线”不是一个普通串口协议很多人第一次接触QMI会下意识把它和AT指令放到一起对比。这俩解决的是相似的场景但思路完全不同。AT指令是文本行的典型代表一条命令一行字符串简单直接可表达复杂结构时就很痛苦。QMI则是一套二进制消息协议底层承载在共享内存通道上消息内部用TLVType-Length-Value结构组织传N个参数就是拼接N个TLV解析时按Type索引扩展字段也不影响老版本解析。所以你在平台上增加QMI接口本质上做的不是“加一个驱动”“写一个节点”而是在已有的Modem消息总线上注册一个新的分支然后按协议把数据拆包、打包、收发。搞清楚这一点后面的很多操作才能看明白。1.2 SMD和QRTR新旧平台的通道差异这是高通平台开发者特别容易掉进去的坑。老平台比如MSM8909、MSM8916时代AP和Modem之间的共享内存通道靠SMDShared Memory Driver设备节点以字符设备的形式暴露常见的有/dev/smd_qmi、/dev/smd7之类代码里也能看到直接open /dev/smd*然后read/write的方式。到SDM660之后尤其在845、865这些新平台上高通大力推QRTRQualcomm IPC Router。它相当于在AP这边引入了一套“用户态总线接口”节点信息变了通道的发现方式也变了。同一个QMI服务在老平台可能直接绑定在固定SMD端点上新平台则要通过QRTR做服务发现和路由目标地址带node/port概念。这不只是代码习惯变化更影响你对问题的判断方向。比如业务收不到响应老平台大概率检查SMD内核配置和端点是否粘连新平台就要先看QRTR bus上有没有发布对应的service。增加接口之前先确认手上平台属于哪一代通道方案能少走很多弯路。1.3 什么情况下才需要你去“增加”接口实践里“增加QMI接口”主要有两类诉求。一类是纯粹的上层使用侧Modem侧已经存在某个服务但AP侧系统还没开放对应的接口需要补一个客户端使能通路让它能被上层SDK或自研进程调用。另一类是需要在Modem侧实现新的服务比如把私有节流的控制命令、定制的运营商特性塞进Modem然后AP侧再写对应的客户端来对接。这两类的工作量差异非常大。前者通常只需要确认服务ID、配好通道、写客户端逻辑。后者则牵涉Modem侧固件、IDL接口定义编译、服务注册、甚至要同步调整QMI编解码库。如果你的项目需求书只说了一句“加个QMI接口”第一件事就是先搞清楚你处在上面哪一侧不要一上来就改协议。2. 开始前必须敲定的几个关键点真正写代码之前有几个设计点必须提前定下来。这些点如果中途变卦返工量会很大尤其是牵涉Modem固件的改动一轮验证周期很长。2.1 搞清楚服务ID和CID机制QMI协议里有个基础概念叫服务Service每个服务有唯一ID。AP侧客户端要使用某个服务得先通过QMI_CTL控制服务Service ID为0x00发起分配请求Modem侧会回给你一个客户端ID也就是CID。之后这条客户会话的业务消息都带上这个CIDModem才能知道这个请求属于谁。高通平台常见的服务号要记住QMI_DMS设备管理0x02、QMI_NAS网络接入0x03、QMI_WDS数据业务0x04、QMI_UIM卡管理0x0B、QMI_PDC射频配置0x15。做自研服务时服务ID要在Modem侧统一注册不能自己随便编一个数字就发出去。我见过有人把服务ID错填成0x01和无线数据服务冲突调了一下午才发现是ID没对齐。2.2 QMI消息的TLV结构要刻进脑子里QMI消息的通用封装不算复杂但字段含义必须清楚。通常前4个字节是消息头第一字节为控制标志请求、响应、指示分别对应0x00、0x02、0x04第二、三字节是事务ID用24位小端表示作用是把同一笔请求和它对应的响应关联起来第四字节是消息编号。后面跟着的是一组TLV。TLV每一项由三部分组成Type占1字节Length占2字节Value是可变的业务数据。整个QMI协议最核心的操作就是编码连续的TLV和解码连续的TLV。你新增加一个接口本质上就是定义一组新的“消息编号TLV集合”。很多莫名其妙的联调问题到最后都是TLV类型写错、长度字段少算了一位、或者必选TLV没带上。2.3 IDL文件决定了上层和Modem怎么对齐在高通成熟的工程流程里QMI服务的协议不是靠C代码硬维护的而是通过IDLInterface Description Language文件定义。IDL里会清楚地描述服务名、服务版本、消息编号、每个消息里的TLV字段类型、是必选还是可选。高通的IDL编译器会据此生成C结构体和编解码函数。这意味着你添加接口的第一步很可能是新增或修改.idl文件再重新生成客户端代码。不要在生成的代码里手动改结构体后续重新生成一次就会把手工修改冲掉而且半夜排查“为什么上传的字段读出来是乱的”会非常痛苦。协议以IDL为准代码从IDL生成这条纪律比什么都重要。2.4 内核开关、SELinux和设备节点一个都不能少曾有同事在Android侧写完QMI服务调用run起来一直ECONNREFUSED。查了半天才发现内核没开CONFIG_QCOM_QMI_HELPERSSMD节点根本没注册。这是新手特别容易忽略的环节。添加QMI接口不只是上层代码的事内核配置、设备树、SELinux策略、用户态服务进程权限都链在一条线上。具体来说老平台确认内核CONFIG_QCOM_SMD、CONFIG_QCOM_SMEM等选项打开新平台确认QRTR相关配置打开。Android平台还需要在vndservice的SELinux policy里允许你的进程访问对应的QMI设备常见表现是logcat里刷avc: denied。还有设备节点权限如果进程不是root而是system用户没有chmod或user/group配置也会让open失败。这些环节里有一项没弄好业务层代码再完美也白搭。3. 实操全过程怎么把一个QMI请求真正发出去理论说够了下面走一遍完整流程。我会从通道检查开始到控制服务分配CID再到发送业务消息并解析响应最后给你一个可用于项目的最小示例框架。3.1 先确认通道通不通不管用哪种通道先确认AP和Modem之间有没有一条可达路径。老平台可以看一下/dev下面有没有smd相关节点再用命令行工具读一下QXDM日志或SMD状态。新平台可以用高通调试工具查QRTR上有没有挂载QMI服务。常见的用户态开源工具qrtr-lookup能列出当前bus上的服务ID、节点和端口类似ip route show的效果。这一步不能省。我亲眼见过同一个镜像文件刷到不同硬件版本上有的平台QRTR服务注册正常有的就是少了一个QMI服务原因藏在Modem启动阶段的固件配置里。改了板级配置之后一定要先做通道检查再进业务联调。3.2 用控制服务分配CID要通过QMI访问任何一个业务服务第一步都是通过QMI_CTL分配客户端。控制服务的逻辑可以理解为“管家”所有服务都由它来分配资源。请求报文逻辑如下构造一条发给QMI_CTL的请求消息消息头标识服务类型和请求动作TLV里带上你要访问的业务服务ID。Modem处理成功后响应报文里拷贝出一个客户端ID后续业务消息都使用这个CID作为会话标识。在libqmi这类用户态库里对应的操作已经被封装好了。现代开发更推荐用这种库而不是自己拼报文字节原因是中国。自己拼字节要自己维护事务ID、TLV长度、响应超时重传等状态机而libqmi已经处理好了这些细节代码量和出bug的概率都会显著下降。但我也建议至少手工拼过一次报文这样遇到库封装之外的协议问题时你才不至于无从下手。3.3 组织业务消息并解析响应拿到CID后业务消息的发送模式是固定的复用之前建立好的通道在消息头里填上服务类型和CID对应的通道信息消息体按照该服务IDL文件里的消息编号一步步填充TLV。以读取设备信息为例就是先查QMI_DMS的IDL定义找到消息编号然后按定义填充必选TLV。收到响应后第一件事不是急着解析数据而是检查事务ID是否符合预期。你把请求的事务ID记下来了响应里的事务ID必须一致。接着再看响应TLV里有没有携带错误码常见错误码会以特定TLV形式返回。如果业务成功就把数据TLV解析出来。解析时一定要注意字节序QMI报文里的整数通常是小端存储直接用本地结构体强转读出来容易遇到平台差异。3.4 一个最小请求示例的现场走读下面给一段逻辑示例用来展示从分配客户端到发业务消息的最小流程。这不是某个具体高通sdk的完整API但结构是通用的你可以按平台实际头文件替换。struct qmi_handle h; struct qmi_txn txn; struct qmi_ctl_alloc_client_req req; struct qmi_ctl_alloc_client_resp resp; // 1. 打开QMI控制通道 qmi_handle_init(h, default_ctl_endpoint()); // 2. 报文填充控制服务分配DMS服务的客户端 req.service_type QMI_DMS_SERVICE; qmi_ctl_alloc_client(h, req, resp, txn); // 3. 得到CID后后续业务消息需要用它标识会话 uint8_t cid resp.client_id; // 4. 构造业务请求比如查询设备信息 struct qmi_dms_get_device_info_req dev_req {0}; struct qmi_dms_get_device_info_resp dev_resp; // 5. 发送业务消息按IDL生成的编解码函数自动完成TLV组装 qmi_dms_get_device_info(h, cid, dev_req, dev_resp, txn);这里每一步都要检查返回值和错误码。不要在一个失败的分支上继续往下走更不要忽略Modem返回的“不支持”类错误。很多坑都不是通信坏了而是尽管响应成功服务端的内部逻辑仍然要求使用某种先决条件。4. 踩过的坑和排查方法汇总QMI增加接口的调试过程最常见的感觉是“报文看起来都对但Modem就是不理我”。下面把典型问题按现象整理成方法论遇到直接对照排查。4.1 请求发出去了却迟迟收不到响应这是最磨人的问题。第一反应查通道看看SMD/QRTR节点是否工作。其次查权限尤其Android的SELinux限制。然后是消息头本身看服务ID、消息编号是否在Modem侧已经注册。最后查事务ID如果多条请求共用了同一个事务IDModem可能只会响应一条其余全部超时。提示事务ID是高通平台上高频踩坑点。建议调一段时间窗口内每笔新请求都必须递增不能并行请求复用同一事务ID。低层协议栈不会帮你维护必须自己管理。4.2 CID分配失败或者返回异常CID分配失败常见原因有三个访问了未在Modem注册的服务、控制服务通道写错、请求TLV里服务类型填得不规范。还有一种隐藏情况系统里已经有人用同一个服务ID分配过客户端但你另一个进程也在同时用这时候更合理的做法是改成共享已有会话而不是重复分配。调试时先在logcat或串口日志里确认“服务端是否收到了报文”。如果Modem侧日志一片空白说明报文根本没到Modem问题在网络通道层如果Modem侧有响应但返回码是“服务不支持”则优先怀疑服务ID或Modem固件版本不匹配。4.3 Modem回复INVALID_ARGUMENT之类错误这是好消息至少通道和服务都通了现在是参数的问题。最常见的四类TLV长度字段写错、TLV类型编号和IDL不一致、必选TLV遗漏、整数大小端映射错误。这种问题很好查你要在组包前打出TLV的hex值对照IDL定义的byte layout逐字节比对。我自己的排查习惯是先用单一的必选TLV跑通再加入可选字段。这样可以快速定位是哪一项字段导致的非法参数避免同时改了一大片配置最后不知道是哪个引入的。4.4 版本协商不过导致消息被拒QMI服务有版本号概念版本由主版本号和次版本号组成。AP侧客户端和服务端Modem各自用IDL定义了自己的版本。当客户端调用服务时控制服务往往要先做版本协商。如果客户端版本高于Modem固件实际支持的版本Modem可能拒绝一部分新消息。这在高通平台升级固件时特别常见。客户应用代码没变但Modem版本降级或升级QMI服务版本随之变化应用侧却还是按旧IDL编译。解决方向是每次更新Modem固件同步重新生成并核对IDL文件别只更新libqmi的二进制。4.5 常见问题速查对照表现象可能原因排查方向open设备节点失败节点不存在、权限不足、SELinux拦截dmesg/logcat查avc denied检查节点权限和内核config报文发出无响应通道不通、服务未注册、事务ID冲突用工具查SMD/QRTR服务确认服务ID和事务管理CID分配失败服务ID错误、服务不存在、重复分配核对IDL服务ID查看Modem日志和固件版本返回非法参数错误TLV长度或类型不对、必选字段缺失对照IDL逐字节比对报文版本协商失败客户端与Modem版本不匹配同步更新IDL重新生成编解码代码5. 一点个人体会如果让我总结“增加QMI接口”最有价值的一句话那就是先把协议IDL和通道确认清楚再动手写代码。早期我拿到需求就急着写业务逻辑结果一半时间浪费在查服务和通道上。后来养成了固定工作流——先查QRTR/SMD服务注册再拉IDL文件最后写业务代码整个开发周期明显变稳。另外一个建议是调试期别仰仗着“看一下logcat就完事”。QMI报文是二进制的有条件的一定要对请求和响应做hex日志打印和IDL定义做逐字节比对。很多看似诡异的联调问题其实只是多了一个字段号一位之差。最后修改任意一侧的IDL一定要把生成的代码完整放源不要手改生成文件这是长期维护的基本功。本文还有配套的精品资源点击获取
返回列表