Android Qualcomm Diag

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/

并且提供了两个示例程序如何使用libdiag.so

  • 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等日志

相关推荐
hy15687861 小时前
Flink 延时数据处理
android·java·flink
TeleostNaCl1 小时前
Android TV | 一种不跳出应用指定页面的类 Monkey 的 Android TV 压测脚本
android·经验分享·压力测试
k***08291 小时前
mysql中general_log日志详解
android·数据库·mysql
2501_937154931 小时前
酷秒神马 9.0 版源码系统实测
android·源码·源代码管理·机顶盒
r***11332 小时前
【MySQL】MySQL库的操作
android·数据库·mysql
ljt27249606612 小时前
Compose笔记(五十九)--BadgedBox
android·笔记·android jetpack
用户41659673693552 小时前
ExoPlayer 播放花屏与跳跃?我们如何像 QuickTime 一样优雅处理音频时间戳错误
android
Y***h1872 小时前
MySQL不使用子查询的原因
android·数据库·mysql
p***93032 小时前
Java进阶之泛型
android·前端·后端