Diag翻译中文名为诊断,因此Android Qualcomm Diag是高通自己独立研发的一套诊断系统。
那么诊断是什么意思呢?当我看到诊断这两个字的时候,首先想到的就是汽车诊断仪,就是汽车出现发动机故障的时候,维修工程师会拿一台手持仪器(汽车诊断仪),通过接口插在汽车MCU上面,汽车诊断仪和汽车MCU进行通信,讲一些故障码读取出来。因此Qualcomm Diag其实也是这样的工作逻辑。
本篇针对Qualcomm Diag机制来剖析高通的架构,Diag设计原理,Diag路由分发,Diag示例代码。
一、Qualcomm NON-HLOS架构

如上图在网上找的一张高通平台的架构图,可以分为如下三部分:
- 其中右侧就是我们熟知的Android部分,由google开发设计,包含了linux kernel。按照高通的定义叫做HLOS。
- 其中左侧部分就是AMSS,我们在MTK平台或者AOSP代码中都没有见过的东西。涉及到MPSS(medem),aDSP&cDSP(音频DSP和计算DSP),XBL(Boot build),AOP(实时处理器),TZ(trustzone),WLAN,BTFM(蓝牙),video,WiGig,VENUS,SLPI(传感器),CPE,Secure processor。前面都是开源,从TZ开始后面的都是给bin。
- 最后就是底层的一些硬件驱动的支持,这些模块其实就有Modem、ADSP等模块,说明这些模块都是高通自己研发设计的一些东西。
1、Qualcomm子系统
因此把非HLOS的部分统称为NON-HLOS,这些模块都属于高通自己独立研发设计,高通源代码如下:

-
BOOT.MXF.1.0 :是高通平台Bootloader镜像的版本标识
-
KERNEL.PLATFORM.5.0.r6 : 是高通提供的Linux内核平台代码
-
LA.QSSI.16.0.r1 :是通用系统镜像的核心代码,包含了系统级应用和框架,旨在为多个设备型号提供统一的软件基础。
-
LA.QISI.16.0.r1 : 通常包含设备特定的配置和驱动,比如内核配置、设备树文件和硬件抽象层实现。
-
LA.VENDOR.16.2.1.r1 : 存放厂商定制化的软件和闭源组件,例如GPU驱动、DSP固件和各种硬件加速库。
-
MPSS.HI.4.3.3.c6.5:是高通平台**调制解调器子系统(Modem Processing Subsystem)**的固件版本,属于调制解调器相关组件。
-
QCM6490.LA.6.0:是高通CPU芯片某型号,该目录存放了芯片相关的一些组件。
-
QCM6490_wlan_hsp:是高通QCM6490芯片的Wi-Fi 6E与蓝牙5.2通信模块,属于其无线连接子系统的核心组件
-
ADSP.HT.5.5.C10: 是高通Hexagon DSP(数字信号处理器)的固件文件。这是高通平台上专门用于处理音频、传感器数据和其他低功耗任务的硬件加速单元 。此模块生成物是一个adsp.mbn文件。
-
AOP.HO.3.0: 是高通Hexagon DSP(数字信号处理器)的固件版本号,属于Hexagon SDK的一部分,用于处理音频、传感器等低功耗任务 。此模块生成物是一个aop.mbn文件。
-
CDSP.HT.2.5:是高通Hexagon DSP(数字信号处理器)的固件版本号,属于Hexagon Tensor Processor(HTP)系列。该版本可能针对特定功能(如AI计算或音频处理)进行了优化。此模块生成物是一个cdsp.mbn文件。
-
AUDIO_IOT.LA.11.0.r2: 是高通音频物联网(Audio IoT)平台的固件版本标识,属于高通Hexagon DSP生态的组件之一。在高通目录架构中,该组件主要服务于以下功能:为物联网设备提供音频处理能力,支持语音唤醒、降噪等实时处理;与Hexagon DSP协同工作,实现低功耗音频流水线;通过ADM(Audio Driver Model)模块与上层应用交互。其作用类似于ADSP.HT.5.5.C10,但针对物联网场景优化了功耗和实时性需求。
-
VIDEO.LA.5.1.r1: 是高通平台中负责视频编解码与显示输出的固件组件,属于Adreno GPU显示子系统的一部分。其核心功能包括:支持HDR10/杜比视界等主流HDR格式的硬件解码;管理视频帧缓冲区的分配与同步;与CPU/GPU协同实现低延迟视频渲染。
-
BTFW.HSP.2.0.0 :是高通蓝牙相关固件,HSP表示该固件针对高速传输或耳机应用场景优化。BTFW模块生成物是一个BTFM.bin文件。
-
BTFW.MOSELLE.1.1.3 :是高通蓝牙相关固件,MOSELLE表示高通某款蓝牙芯片的代号。BTFW模块生成物是一个BTFM.bin文件。
-
WLAN.HSP.1.1.2:是高通平台中与**无线局域网(WLAN)**相关的子系统。HSP指"High-Speed Protocol"或硬件支持包,侧重性能优化。
-
WLAN.MSL.1.0.1:是高通平台中与**无线局域网(WLAN)**相关的子系统。MSL指"Modem Subsystem Layer"或"Multi-Service Layer",涉及多协议支持或基础功能模块。
-
CPUCP.FW.1.0:是高通平台中CPU协处理器(CPU Co-Processor) 的固件版本。该组件通常负责低功耗任务管理、传感器数据处理等辅助计算功能,与主CPU协同工作以提升能效 。此模块生成物是一个cpucp.elf文件。
-
GRAPHICS.LA.16.1.r2:是高通平台**图形处理单元(GPU)**的固件版本,属于Adreno GPU系列。该版本可能针对图形渲染、显示输出或功耗管理进行了优化。
-
TZ.APPS.1.0:全称TrustZone Applications,是高通平台中与TrustZone安全环境相关的固件组件,管理运行在TrustZone安全环境中的应用程序,如安全支付、生物识别等敏感操作。TZ模块生成物是一个tz.mbn文件。
-
TZ.XF.5.0:全称TrustZone eXecution Framework,是高通平台中与TrustZone安全环境 相关的固件组件,提供安全执行框架,支持硬件级加密、密钥存储等高级安全功能 。TZ模块生成物是一个tz.mbn文件。
2、Qualcomm Diag机制
高通架构可以了解到高通基线的代码,运行的不止一个android系统,除了android系统之外,还有一系列高通定制的子系统,包括Modem、LPASS、WCNSS、CDSP、NPU等。
因此想要对高通设备进行诊断,那么诊断的是高通系统中的Android、Modem、LPASS等子系统,那么必然涉及到各个子系统之间的通信。那么DIAG机制必现实现各子进程之间通信的这种能力。
再重新回顾诊断,就跟前文的汽车诊断仪一样,需要HOST外部主机设备外部设备,能够诊断高通设备的各个子系统(Android、LPASS、CDSP、MODEM等)。
因此可以总结DIAG机制就是为了实现HOST设备与高通各个子系统之间的通信。完整架构图可以参考如下:
bash
┌─────────────────────────────────────────────────────────┐
│ 外部主机 (Host - PC/开发工具) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ QPST工具 │ │ QXDM工具 │ │ 其他诊断工具 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┴─────────────────┘ │
│ USB/UART/Socket │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ Android设备 (运行diag-router) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ diag-router (运行在APPS上) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ USB接口 │ │Socket接口│ │Unix接口 │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ └─────────────┴─────────────┘ │ │
│ │ 路由/分发层 │ │
│ └───────┬──────────────────────────────────────────┘ │
│ │ IPC通信 (QRTR/RPMSG) │
│ ┌───────┴──────────────────────────────────────────┐ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ MODEM │ │ LPASS │ │ CDSP │ │ │
│ │ │ (基带FW) │ │ (音频FW) │ │ (DSP FW) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ NPU │ │ WCNSS │ │ 其他... │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
-
外部HOST主机诊断设备:最常见的就是在PC端开发诊断工具,QPST和QXDM就是高通开发好的PC端工具
-
外部HOST主机连接方式:最常见的就是通过usb线连接android设备,通过uart线连接android设备,或者通过网络的方式连接android设备
-
诊断对象: peripheral 代表高通 SoC 上的不同子系统/处理器,每个负责特定功能,常见的有android系统、modem系统
-
诊断目的:调试各子系统,收集日志和事件,发送控制命令,监控系统状态
-
diag-router:运行在android系统的native层进程,该进程主要目的是为了拦截所有的diag指令,并进行路由分发,例如来决策当前指令是发送给modem系统处理,还是LAPSS系统处理
-
IPC通信:diag-router运行在android子系统,通过内核诊断驱动程序来实现与各个子系统进行通信
1)DIAG支持哪些传输方式?
HSOT主机设备与高通android设备有哪些方式可以进行传输和链接?在diag-router的代码中有如下定义:

-
USB_MODE:USB模式,外部HOST设备可以通过USB连接进行命令传输
-
MEMORY_DEVICE_MODE:内存设备模式,将诊断数据写入设备内存(如 SD 卡、eMMC),不依赖外部连接,适合离线日志收集
-
NO_LOGGING_MODE:无日志模式,禁用日志输出,仅处理命令/响应,减少系统开销
-
UART_MODE:串口传输模式,外部HOST设备可以通过串口线进行命令传输
-
SOCKET_MODE:Socket/TCP传输模式,可以通过网络支持远程诊断
-
CALLBACK_MODE:回调模式,不依赖特定传输介质,使用回调函数处理数据,Android 应用通过回调接收诊断数据。
-
PCIE_MODE:PCIe 传输模式,通过 PCIe 接口传输,使用 MHI 协议适合高速数据传输
-
QRTR_SOCKET_MODE:QRTR Socket 模式,使用 QRTR (Qualcomm IPC Router) 协议,用于与 peripheral 子系统通信,基于 Socket 的 IPC 机制

2)DIAG支持哪些子系统?
DIAG支持那些子系统,在peripheral.h中进行了如下定义:

- PERIPHERAL_APPS:核心应用处理器,Application Processor Subsystem(应用处理器子系统)运行 Android 系统的主处理器,唯一运行 Android 五层架构的子系统
- PERIPHERAL_MODEM:通信相关子系统,Modem Processor Subsystem(调制解调器子系统),处理蜂窝通信(2G/3G/4G/5G)的基带协议栈
- PERIPHERAL_WCNSS:通信相关子系统,Wireless Connectivity Subsystem(无线连接子系统),Wi‑Fi、蓝牙、FM 等无线连接
- PERIPHERAL_LPASS:音频/多媒体子系统,Low Power Audio Subsystem(低功耗音频子系统),音频处理、编解码、音频路由
- PERIPHERAL_WDSP:音频/多媒体子系统,Wireless DSP(无线 DSP),音频/语音处理专用 DSP
- PERIPHERAL_SLATE_ADSP:音频/多媒体子系统,Slate Audio DSP(Slate 音频 DSP),特定平台(如平板)的音频 DSP
- PERIPHERAL_SENSORS:Sensors Subsystem(传感器子系统),管理各类传感器(加速度、陀螺仪、磁力计、环境传感器等)
- PERIPHERAL_CDSP:AI/计算加速子系统,Compute DSP(计算 DSP),计算密集型任务、AI/ML 加速
- PERIPHERAL_NPU:AI/计算加速子系统,Neural Processing Unit(神经网络处理单元),专用 AI/ML 加速器
- PERIPHERAL_NSP1:AI/计算加速子系统,Network Service Processor(网络服务处理器),网络相关处理
- PERIPHERAL_GPDSP0:图形处理子系统,Graphics DSP 0(图形 DSP 0),图形处理相关 DSP
- PERIPHERAL_GPDSP1:图形处理子系统,Graphics DSP 1(图形 DSP 1),第二个图形 DSP(多 GPU 场景)
- PERIPHERAL_HELIOS_M55:特定平台/协处理器,Helios M55(可能是特定平台或协处理器)
- PERIPHERAL_SLATE_APPS:特定平台/协处理器,Slate APPS(Slate 应用处理器),特定设备形态的应用处理器
- PERIPHERAL_TELE_GVM:虚拟化子系统,Telephony Guest VM(电话功能虚拟机),运行电话功能的虚拟机
- PERIPHERAL_FOTA_GVM:虚拟化子系统,FOTA Guest VM(Firmware Over The Air 虚拟机),运行 FOTA 功能的虚拟机
- PERIPHERAL_SOCCP:可能是System On Chip Control Processor(片上系统控制处理器),系统控制相关
- PERIPHERAL_AWM:特定功能模块(需结合具体平台文档),可能是特定芯片的功能模块
- PERIPHERAL_SWM:特定功能模块(需结合具体平台文档),可能是特定芯片的功能模块
二、DIAG服务器端进程diag-router
diag router是运行在android子系统上面的一个native进程,并在开机完成后通过rc启动运行。其实可以把他理解对应于C-S架构里面的服务端进程。
源代码路径为:la.um/vendor/qcom/proprietary/diag-router/

根据如上的简介,可以了解到diag-router主要实现HOST主机与各子系统自检的消息传输,下面还介绍了依赖USB GADGET功能,需要kernel配置CONFIG_CONFIGFS_FS宏控,以便支持可以通过usb传输diag指令。
1、diag-router开机自启
diag-router有如下rc配置,因此系统开机,该进程自动被调起:
bash
# A14代码路径la.um/vendor/qcom/proprietary/diag-router/vendor.qti.diag.rc
on boot
start vendor.diag-router
service vendor.diag-router /vendor/bin/diag-router
class hal
user system
group system
capabilities KILL
disabled
# A16代码路径la.um/vendor/qcom/proprietary/coretech-config-vendor/diag/vendor.qti.diag.rc
on early-boot
start vendor.diag-router
service vendor.diag-router /vendor/bin/diag-router
class hal
user system
group system
capabilities KILL
disabled
# A16代码路径la.um/vendor/qcom/proprietary/coretech-config-vendor/diag/vendor.qti.diag_userdebug.rc
on early-init
chown system system /sys/devices/soc0/select_image
chown system system /sys/devices/soc0/image_version
chown system system /sys/devices/soc0/image_variant
chown system system /sys/devices/soc0/image_crm_version
on early-boot
start vendor.diag-router
service vendor.diag-router /vendor/bin/diag-router
class hal
user system
group system
capabilities KILL
disabled
2、diag-router程序架构
bash
┌─────────────────────────────────────────────────────────┐
│ 客户端层 (Clients) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 本地应用 │ │ 主机工具(USB) │ │ 主机工具(UART)│ │
│ │ mdlog等 │ │ QXDM/QPST │ │ 串口工具 │ │
│ │ (Unix Socket)│ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼──────────────────┼──────────────────┼──────────┘
│ │ │
└──────────────────┼──────────────────┘
│
┌────────▼────────┐
│ diag-router │
│ (dm.c管理) │
└────────┬────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Modem │ │ LPASS │ │ WCNSS │
│(Peripheral)│ │(Peripheral)│ │(Peripheral)│
└───────────┘ └───────────┘ └───────────┘
如上图diag-router主要实现的逻辑如下:main函数先初始化传输接口,初始化各个子系统通信方式,通过dm管理来自usb/uart/sockt/unix socket客户端进程的请求,并进行统一的路由分发决策给哪个子系统,最后通过diaghal像各个子系统发送接收消息。
1)diag-router入口函数
diag.c中的main函数初始化传输接口(USB/UART/Socket/Unix Socket),初始化外设设备(高通子系统),创建客户端dm管理器,然后进入事件轮询中,主要轮询来自客户端dm请求。
cpp
//la.um/vendor/qcom/proprietary/diag-router/router/diag.c
int main(int argc, char **argv)
{
/* 根据命令行选项初始化传输接口 */
if (host_address) {
/* 通过TCP/IP socket连接到远程主机 */
ret = diag_sock_connect(host_address, host_port);
if (ret < 0)
err(1, "failed to connect to client");
} else if (uartdev) {
/* 打开UART设备用于串口通信 */
ret = diag_uart_open(uartdev, baudrate);
if (ret < 0)
errx(1, "failed to open uart\n");
}
// ..........
// 核心流程1:初始化客户端的请求方式,主要包括usb uart socket unixSocket
/* 初始化QDSS(Qualcomm调试和统计子系统)节点, QDSS用于硬件跟踪和调试 */
qdss_nodes_init();
/* 初始化MHI(Modem主机接口)传输 MHI用于与modem的高速通信 */
diag_mhi_init();
/* 打开USB FunctionFS接口用于USB通信 /dev/ffs-diag是诊断数据的FunctionFS端点 */
diag_usb_open("/dev/ffs-diag");
/* 创建Unix域套接字用于本地客户端连接, 这是使用libdiag的应用程序的主要接口, 客户端通过抽象套接字 "\0diag" 连接 */
ret = diag_unix_open();
if (ret < 0)errx(1, "failed to create unix socket dm\n");
//核心流程2:初始化外设管理子系统
//设置了与各种子系统的通信通道: Modem (MPSS) 音频 (LPASS) WiFi/蓝牙 (WCNSS) 计算DSP (CDSP) 神经网络处理单元 (NPU) 以及其他外设
ret = peripheral_init();
if (ret < 0)
errx(1, "failed to initialize peripherals\n");
/* 初始化掩码管理系统 掩码控制接收哪些日志、事件和消息 */
diag_masks_init();
// ..........
/* 初始化内存设备(MD)日志子系统 MD模式允许记录到设备上的存储 */
diag_md_init();
/* 初始化MUX(多路复用器)层 MUX管理不同的日志传输方式(USB、MD、Socket、PCIe) */
diag_mux_init();
/* 初始化STM(同步跟踪和测量)支持 STM用于实时硬件跟踪 */
diag_stm_init();
/* 初始化实时模式信息
* 实时模式控制数据是立即发送还是缓冲
*/
diag_real_time_info_init();
/* 初始化APPS处理器特性标志
* 特性包括HDLC支持、缓冲模式等
*/
diag_apps_feature_init();
#ifndef FEATURE_LE_DIAG
/* 注册DiagHAL服务用于Android HAL接口
* 这为Android服务提供基于AIDL的接口
*/
register_diaghal_service();
/* 创建DiagHAL服务处理线程 */
create_diaghal_thread();
#endif /* FEATURE_LE_DIAG */
/* 初始化所有外设的Diag-ID v2特性掩码
* Diag-ID v2提供增强的命令路由能力
*/
for (i = 0; i < NUM_PERIPHERALS; i++) {
diagmem->feature[i].diagid_v2_feature_mask = 0;
}
/* 初始化DCI(诊断消费者接口)子系统
* DCI允许客户端拥有独立的诊断通道
*/
diagmem->dci_state = diag_dci_init();
/* 注册应用级命令处理器
* 这些命令由diag-router自身处理
*/
register_app_cmds();
/* 注册通用命令处理器
* 通用命令在所有子系统之间共享
*/
register_common_cmds();
#ifndef FEATURE_LE_DIAG
/* 初始化镜像版本信息
* 用于版本报告和兼容性检查
*/
image_version_init();
/* 初始化QDSS诊断数据包处理器
* 处理QDSS特定的诊断数据包
*/
qdss_diag_pkt_hdlr_init();
/* 初始化硬件加速数据包处理器
* 处理STM/ATB的硬件加速命令
*/
hw_accel_diag_pkt_hdlr_init();
#endif
/* 处理APPS处理器的Diag-ID v2特性掩码
* 这启用基于Diag-ID的命令路由和硬件加速
*/
diagid_mask = (BITMASK_DIAGID_FMASK | BITMASK_HW_ACCEL_STM_V1);
process_diagid_v2_feature_mask(DIAG_ID_APPS, diagid_mask);
/* 记录成功初始化信息,包含进程ID */
KMSGI("diag-router with PID: %d is instantiated\n", getpid());
/* 初始化监督框架(systemd看门狗支持)
* 这允许systemd监控diag-router的健康状态
*/
diag_supervision_init();
//核心流程3:启动主事件循环,diag-router的核心 - 它处理所有I/O事件,使用epoll并在客户端和外设之间处理消息路由
watch_run();
//核心流程4:diag-router退出循环体进行资源释放
/* 清理:反初始化QDSS节点 */
qdss_nodes_deinit();
/* 释放全局diag驱动结构体 */
free(diagmem);
diagmem = NULL;
return 0;
}
2)diag-router客户请求
dm.c中管理了所有客户端的请求,常见的就是外部host设备的连接。例如在检测到usb连接的时候就会调用dm.c中的dm_add来创建一个客户端。

除了来自usb、uart、socket的客户端,其实还包括本地进程,即回调模式 CALLBACK_MODE。
后文通过callback_sample示例应用可以看看集成在android系统中的apk或者native进程如何创建dm客户端发送diag指令给其他子系统。

3)diag-router路由分发
router.c中的diag_cmd_dispatch函数就是diag-router进程进行diag指令分发的核心代码:
cpp
static int diag_cmd_dispatch(struct diag_client *client, uint8_t *ptr, size_t len, int pid){
// ......
//核心流程1:检测是否diag指令,如果是打印diag: %s: valid diag-id based cmd found with id %d日志
ret = diag_check_diag_id_based_cmd(ptr, &diag_id, DIAG_ID_FLAG_GET_DIAGID);
if (ret) {
if (ret < 0) {
return ret;
} else {
ALOGM(DIAG_DBG_MASK_CMD, "diag: %s: valid diag-id based cmd found with id %d\n", __func__, diag_id);
/* update ptr to point to starting of legacy header */
ptr += sizeof(struct diag_id_cmd_req_header_t);
len -= sizeof(struct diag_id_cmd_req_header_t);
}
}
// ......
//核心流程2:如果是MODE命令且非RESET,直接跳转到periph_send向子系统发送消息
if (ptr[0] == MODE_CMD && ptr[1] != MODE_CMD_RESET) {
goto periph_send;
}
//核心流程3:通用命令处理,例如日志配置命令/消息配置等命令,这些命令不需要转发给子系统只需要本地处理
list_for_each(item, &common_cmds) {
dc = container_of(item, struct diag_cmd, node);
if (key < dc->first || key > dc->last) continue;
//如果判断命令类型大于DIAG_ID_APPS,打印异常并退出
if (diag_id > DIAG_ID_APPS) {
/* diag_id based cmd should not be used for common commands */
ALOGE("diag: %s: diag-d based cmd not supported for common cmd's key = %d\n", __func__, key);
return -EINVAL;
}
//如果判断命令类型等于DIAG_ID_APPS,就进行命令回调
return dc->cb(client, ptr, len, pid);
}
//命令类型有两种:DIAG_ID_APPS表示android层处理,大于DIAG_ID_APPS表示需要发送给子系统处理
//核心流程4:其他命令处理,大于DIAG_ID_APPS需要通过periph_send发送子系统处理
if (!vm_enabled && diag_id > DIAG_ID_APPS)
goto periph_send;
//核心流程5:遍历所有来自本地app的命令,即android系统有进程可以通过libdiag.so注册为unix socket客户端
list_for_each(item, &apps_cmds) {
dc = container_of(item, struct diag_cmd, node);
if (key < dc->first || key > dc->last) continue;
//核心流程6:如果是DIAG_ID_APPS命令,调用pvm_cmd进行处理,这里没有向子系统发送消息
if (diag_id == DIAG_ID_APPS)
goto pvm_cmd;
//核心流程7:如果非DIAG_ID_APPS命令,有两种模式,发送给子系统
//.......逻辑繁琐,没什么研究意义
}
pvm_cmd:
//......处理 DIAG_ID_APPS类型的命令,android系统中的进程进行处理
periph_send:
//......处理 非DIAG_ID_APPS类型的命令,类似modem这样的子系统中进行处理
}
//核心流程3中注册的通用命令,包括日志配置命令/消息配置命令/事件掩码获取/设置命令/事件报告控制命令
void register_common_cmds(void)
{
register_common_cmd(DIAG_CMD_LOGGING_CONFIGURATION, handle_logging_configuration);
register_common_cmd(DIAG_CMD_EXTENDED_MESSAGE_CONFIGURATION, handle_extended_message_configuration);
register_common_cmd(DIAG_CMD_GET_MASK, handle_event_get_mask);
register_common_cmd(DIAG_CMD_SET_MASK, handle_event_set_mask);
register_common_cmd(DIAG_CMD_EVENT_REPORT_CONTROL, handle_event_report_control);
register_common_subsys_cmd(DIAG_CMD_DIAG_SUBSYS, DIAG_CMD_MS_LOG_SUBSYS_CMD, handle_ms_logging_configuration);
register_common_subsys_cmd(DIAG_CMD_DIAG_SUBSYS, DIAG_CMD_MS_MSG_SUBSYS_CMD, handle_ms_extended_message_configuration);
register_common_subsys_cmd(DIAG_CMD_DIAG_SUBSYS, DIAG_CMD_MS_EVENT_SUBSYS_CMD, handle_ms_event_report_control);
}
这段代码可以总结如下:
- 如果解析为DIAG_ID_APPS类型的命令,通过pvm_cmd交到android子系统进行处理并接受返回数据回调给客户端
- 如果解析为非DIAG_ID_APPS类型的命令,通过periph_send发送到其他子系统中进行处理并接受返回数据回调给客户端
- DIAG_ID_APPS
三、DIAG客户端进程libdiag.so
libdiag.so是高通提供的一个so库,虽然它不像diag-router那样是一个独立的守护进程持续运行,但它封装并提供了一系列可以操作diag相关的库,所以我们可以开发android native/app进程持有该库,操作diag指令。
因此本章主要讨论的就是diag的客户端进程:

如上代码路径实现了libdiag.so,主要代码路径为la.um/vendor/qcom/proprietary/diag/src/
还通过jni的方式为fw层提供了java接口,主要代码路径为 /vendor/qcom/proprietary/diag/java/
- callback_sample:演示了callback方式单线程发送diag指令的示例程序
- dci_sample:演示了dci方式多线程发送diag指令的示例程序
- PktRspTest:演示了接收处理来自diag-router分发过来的diag指令的示例程序
1、libdiag.so
libdiag.so的定义为共享库:

libdiag提供的函数接口如下:
1)初始化和清理函数
Diag_LSM_Init(void *params)
- 初始化诊断库,连接到 diag-router
- 返回:boolean(成功/失败)
- 示例:status = Diag_LSM_Init(NULL);
Diag_LSM_DeInit(void)
- 清理资源,断开连接
- 返回:boolean
- 示例:status = Diag_LSM_DeInit();
2)回调模式相关函数
diag_register_callback(int (*callback_func)(unsigned char *ptr, int len, void *context_data), void *context_data)
- 注册回调函数接收诊断数据(MSM/APPS)
- 示例:diag_register_callback(process_diag_data, &data_primary);
- diag_register_remote_callback(int (*callback_func)(unsigned char *ptr, int len, void *context_data), int proc, void *context_data)
- 注册回调函数接收远程处理器数据(MDM、MDM_2 等)
- 示例:diag_register_remote_callback(process_diag_data, data_remote, &data_remote);
diag_switch_logging(int mode, void *params)
- 切换传输模式,如果需要回调模式,这里需要传递CALLBACK_MODE
- 模式:USB_MODE / CALLBACK_MODE / UART_MODE / SOCKET_MODE /MEMORY_DEVICE_MODE 等
- 示例:diag_switch_logging(CALLBACK_MODE, NULL);
diag_callback_send_data(int proc, unsigned char buf[], int bytes)
- 在回调模式下发送命令数据
- proc:处理器类型(MSM, MDM, MDM_2 等)
- 示例:diag_callback_send_data(MSM, req_modem_loopback, REQ_LOOPBACK_LEN);
3)DCI方式相关函数
diag_register_dci_client(int *client_id, diag_dci_peripherals *peripheral_list, int proc, int *signal_type)
- 注册 DCI 客户端
- 返回:DIAG_DCI_NO_ERROR 或错误码
- 示例:err = diag_register_dci_client(&client_id, &list, channel, &signal_type);
diag_register_dci_signal_data(int client_id, int signal)
- 注册信号用于接收 DCI 数据
- 示例:err = diag_register_dci_signal_data(client_id, DIAG_SAMPLE_SIGNAL);
diag_deinit_dci_client(int client_id)
- 注销 DCI 客户端
diag_register_dci_stream_proc(int client_id, void (*log_cb)(unsigned char *buf, int len), void (*event_cb)(unsigned char *buf, int len))
- 注册DCI日志/事件流回调
- 示例:err = diag_register_dci_stream_proc(client_id, ch_info[channel].dci_log_cb, ch_info[channel].dci_event_cb);
diag_log_stream_config(int client_id, int enable, uint16 *log_codes_array, int array_length)
- 配置日志流(启用/禁用特定日志码)
- 示例:err = diag_log_stream_config(client_id, ENABLE, log_codes_array, TOTAL_LOG_CODES);
diag_event_stream_config(int client_id, int enable, int *event_codes_array, int array_length)
- 配置事件流(启用/禁用特定事件码)
- 示例:err = diag_event_stream_config(client_id, ENABLE, event_codes_array, TOTAL_EVENT_CODES);
diag_send_dci_async_req(int client_id, unsigned char buf[], int bytes, unsigned char *rsp_ptr, int rsp_len, void (*func_ptr)(unsigned char *ptr, int len, void *data_ptr), void *data_ptr)
- DCI 命令异步发送
- 示例:err = diag_send_dci_async_req(client_id, buf, 24, dci_rsp_pkt, DIAG_MAX_RX_PKT_SIZ, ch_info[channel].dci_cmd_rsp_cb, NULL);
diag_get_dci_support_list_proc(int proc, diag_dci_peripherals *list)
- 查询处理器支持的 DCI 外设列表
- 示例:err = diag_get_dci_support_list_proc(channel, &list);
diag_get_health_stats_proc(int client_id, struct diag_dci_health_stats *health_stats, int peripheral)
- 查询 DCI 健康统计信息
- 示例:err = diag_get_health_stats_proc(client_id, dci_health_stats, DIAG_MODEM_PROC);
diag_dci_set_version(int client_id, int version)
- 设置 DCI 版本
- 示例:err = diag_dci_set_version(client_id, 1);
diag_dci_configure_buffering_mode(int proc, int peripheral, int mode, int high_wm_val, int low_wm_val)
- 配置 DCI 缓冲模式
- 示例:diag_dci_configure_buffering_mode(MSM, DIAG_MODEM_PROC, mode, 20, 80);
diag_dci_register_for_buffering_mode_status(int proc, void (*buffering_mode_status_cb)(int mode))
- 注册缓冲模式状态回调
- 示例:diag_dci_register_for_buffering_mode_status(MSM, diag_dci_buffering_mode_status_cb);
diag_dci_drain_buffer(int proc, int peripheral)
- 清空 DCI 缓冲区
- 示例:diag_dci_drain_buffer(MSM, DIAG_MODEM_PROC);
4)回调模式 VS DCI模式
回调模式示例代码:
cpp
void main(){
Diag_LSM_Init(NULL) // 初始化
diag_register_callback(...) // 注册回调
diag_switch_logging(CALLBACK_MODE, NULL) // 切换到回调模式
diag_callback_send_data(...) // 发送命令
// 在回调函数中接收响应
diag_switch_logging(USB_MODE, NULL) // 切换回 USB 模式
Diag_LSM_DeInit() // 清理
}
DCI模式示例代码:
cpp
void main(){
Diag_LSM_Init(NULL) // 初始化
diag_register_dci_client(...) // 注册 DCI 客户端
diag_register_dci_signal_data(...) // 注册信号
diag_register_dci_stream_proc(...) // 注册流回调
diag_log_stream_config(...) // 配置日志流
diag_event_stream_config(...) // 配置事件流
diag_send_dci_async_req(...) // 发送异步命令
// 在回调函数中接收日志/事件/响应
diag_deinit_dci_client(...) // 清理 DCI 客户端
Diag_LSM_DeInit() // 清理
}
使用场景对比

- 回调模式相对简单,一个单独的线程,针对不同子系统返回的数据,也都在这个线程之中
- DCI模式相对负责,可以开启多个线程,不同线程可以指定独立的回调函数,适应于多客户端场景
2、callback_sample
callback_sample就是高通提供的一个回调模式的demo程序:
1)callback初始化和注册

2)callback发送modem指令

3)callback接收数据处理

从接收的代码可以看到,MSM被定义为0,通常会被diag-router路由给Android子系统处理,MDM被定义为1,可能会被diag-router路由给modem子系统处理,但是接收数据的地方居然在同一个回调函数中,因此简单的diag数据发送可以通过此方式。
3、dci_sample
dci_sample的代码就是高通提供的一个dci方式的示例代码,我没有仔细阅读这段代码,但是从main函数中可以看到,通过多线程的方式去进行diag指令发送:

4、PktRspTest
PktRspTest是高通提供了如何注册diag-router路由表用来接收diag指令的示例程序。
1)注册路由表DIAGPKT_DISPATCH_TABLE_REGISTER

如上代码可以先看看DIAGPKT_DISPATCH_TABLE_REGISTER宏控用来实现给diag-router路由表进行注册,即diag-router解析到DIAG_ID_APPS类型的指令,并解析到对应的功能码,就会讲diag命令路由分发到该进程中。
2)PktRspTest无子系统注册
cpp
//la.um/vendor/qcom/proprietary/diag/PktRspTest/PktRspTest.c
int main(void){
boolean bInit_Success = FALSE;
bInit_Success = Diag_LSM_Init(NULL);
/* Registering diag packet with no subsystem id.
* This is so that an empty request to the app. gets a response back
* and we can ensure that the diag is working as well as the app. is
* responding subsys id = 255, table = test_tb1_1 ....
* To execute on QXDM :: "send_data 143 0 0 0 0 0"
*/
//DIAGPKT_NO_SUBSYS_ID表示无子系统注册
//test_tbl_1指定的命令码为143,回调函数dummy_func_no_subsys
DIAGPKT_DISPATCH_TABLE_REGISTER(DIAGPKT_NO_SUBSYS_ID, test_tbl_1);
// ......
Diag_LSM_DeInit();
return 0;
}
#define DIAG_TEST_APP_MT_NO_SUBSYS 143
static const diagpkt_user_table_entry_type test_tbl_1[] =
{ /* subsys cmd low, subsys cmd code high, call back function */
{DIAG_TEST_APP_MT_NO_SUBSYS, DIAG_TEST_APP_MT_NO_SUBSYS,
dummy_func_no_subsys},
};
//回调函数处理143命令码的diag指令
void *dummy_func_no_subsys(void *req_pkt, uint16 pkt_len)
{
void *rsp = NULL;
/* Allocate the same length as the request. */
rsp = diagpkt_alloc(DIAG_TEST_APP_MT_NO_SUBSYS, pkt_len);
if (rsp != NULL) {
memcpy((void *) rsp, (void *) req_pkt, pkt_len);
} else {
printf("FTM Test APP: diagpkt_subsys_alloc failed");
}
return rsp;
}
3)PktRspTest子系统码注册
子系统ID通常为75,这块需要去研究一下diag协议,这里就不深入展开了。

4、ftmdaemon
ftmdaemon是无意中看到高通提供的一个工模进程
1)ftmdaemon定义

2)ftmdaemon注册


3)ftmdaemon指令解析


四、DIAG相关工具
前文基本上就介绍了diag大部分相关模块,这里介绍la.um/vendor/qcom/proprietary/diag/目录下的几个diag工具,他们既不是diag服务端进程也不是diag客户端进程。单纯的为diag提供了一些功能的工具。
- klog
- uart_log
- socket_log
- mdlog

1、klog
klog是高通开发的一个工具,用于将内核日志转换为诊断消息并发送到诊断diag系统。
1)klog主函数
如下代码klog主函数在初始化diag之后,进入while循环,听过scan_buffer持续读取内核日志,然后通过print_string转换为diag命令,并发送到diag诊断系统中,diag-router对齐数据进行分发。

2)scan_buffer读取内核日志
在死循环中通过klogctl读取内核日志到buffer缓冲中,然后通过scan_buffer进行解析
count_bytes = klogctl(2, buffer, NUM_BYTES);
内核产生日志,写入内核环形缓冲区 (/dev/kmsg),可以通过klogctl(2, buffer, NUM_BYTES) 读取

3)print_string发送到诊断系统

2、uart_log
uart_log是高通提供的一个通过uart串口的方式来进行诊断调试
1)uart_log初始化串口配置

2)uart_log切换UART_MODE

3)uart_log串口数据转发到诊断系统

3、socket_log
socket_log同前面的uart_log基本一致,前者是通过串口进行数据转发到诊断系统中,后者是通过网络进行数据转发到诊断系统中。
1)socket_log初始化网络配置

2)socket_log切换SOCKET_MODE

3)socket_log网络数据转发到诊断系统

4、mdlog
mdlog是高通开发的一个工具,用于讲高通多个子系统产生的日志写入到文件中,目前支持 Modem (MPSS) 、LPASS、WCNSS、Sensors 等子系统的日志存储。
1)mdlog设置子网掩码关联子系统diag消息
子网掩码相关的逻辑没有深入研究,但根据AI大概猜测子网掩码设置为modem等子系统相关,就可以定向接收来自对应子系统的diag指令,有些类似于注册diag向量表的方式一样,diag-router路由分发会根据子网掩码来分发diag指令到mdlog模块。

2)mdlog切换MEMORY_DEVICE_MODE
mdlog切换为内存设备模式,这样在接收来自modem等子系统发送过来的diag消息之后,就可以直接讲这些消息写入到文件中进行保存。

3)MEMORY_DEVICE_MODE终极奥义

- MEMORY_DEVICE_MODE模式是被mdlog进程单独使用
- mdlog进程为高通开发的抓取高通子系统的诊断日志数据
- mdlog进程通过子网掩码关联高通多个子系统
- diag-router会把这些子系统产生的日志数据,直接分发到mdlog进程处理
总结:mdlog作为客户端进程,其持有的libdiag.so的代码会判断当前模式为MEMORY_DEVICE_MODE之后,会把这些diag数据默认写入到/sdcard/diag_logs目录,从而实现了抓取modem等日志。