ARTICLE DETAIL

资讯详情

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

高通Camx架构调试实操:UMD/KMD日志开启与离线合并

高通Camx架构调试实操:UMD/KMD日志开启与离线合并 早些年做高通平台Camera调试最耗时的往往不是解问题本身而是在一堆堆不明所以的日志里找方向。Log打少了现场复现不充分问题就越查越糊涂Log开多了又密到让人看不进去反而把关键点淹没掉。更麻烦的是用户态和内核态的日志如果不在一起一个问题要来回横跳两个终端时间戳还对不上等整明白流程半天已经没了。这篇内容主要围绕高通Camx架构下的调试基本功展开UMD用户态驱动层日志怎么开、KMD内核态驱动层日志怎么开、Camx的图像Dump怎么做最后分享一个我用过的离线日志合成脚本思路能把用户态和内核态的log按时间顺序合并到一起。做Camera驱动、系统集成或者图像效果调试的朋友应该都能用得上。1. 先搞清楚要打哪层日志Camx架构的UMD与KMD1.1 Camx在高通Camera栈里的位置高通平台从SM8250骁龙865这一代开始就把老的mm-camera架构逐步往CamxCamera eXtension上迁移。到现在Camx已经是高通主流平台的标准Camera框架上接Android Camera HAL层下连内核的Camera驱动。日常调试时我们通常把整个链路拆成三层来看App与Camera Framework、HAL与Camx CoreUMD层、内核Camera驱动KMD层。Camx Core用户态这部分实际会加载libcamxhwl、libcamxswl这类库还会配合Chi-CDKCamera Hardware Interface去做node的定制编排。KMD层则是Camera Subsystem的内核驱动部分包括cam_sync、cam_req_mgr、cam_isp、cam_sensor、cam_cpas这些模块负责更底层的中断、寄存器操作和sensor的I2C配置。单看某一层日志往往只能看到问题的一半。比如预览黑屏问题可能出在sensor上下电、也可能出在ISP的streamon失败、还可能出在HAL侧的pipeline没有真正start。所以我一般会建议调试开始前先决定要开哪几层日志而不是一口气全开。1.2 UMD与KMD分工为什么两侧日志都要开Camx里有个非常核心的设计理念叫“Pipeline-based”底层通过Request机制驱动sensor、ISP、IPE等节点。用户态Camx Core负责构建pipeline、分发request、处理HAL回调内核态则负责硬件调度、中断处理、buffer管理。以一条出图链路为例HAL发起processCaptureRequestCamx Core会给pipeline的各node填好配置通过IOCTL下发到KMDKMD的cam_req_mgr收到后登记request再逐级调度给ISP硬件。如果用户态日志显示request已经发出去了但内核日志里没看到对应的request arrival那基本可以确认问题出在KMD入口之前。反过来如果内核日志已经出图完成但HAL侧迟迟收不到回调那大概率是UMD的event分发或者buffer状态机卡住了。所以调试时我习惯把UMD Log和KMD Log一起开哪怕先不分析内容也要保证时间戳能对齐。这里需要提一下高通在车规平台上会把Camx和安全相关的补丁合在一起内核版本也会跟着CAF Kernel走不同平台Log开关的位置和格式会有差异但核心思路不会变。1.3 日志链路整体策略开日志之前先想清楚两个问题问题大概在哪个模块可复现性怎么样如果问题可稳定复现优先开目标模块的详细日志周边模块保持默认级别。如果问题像幽灵一样偶发那建议直接开全链路日志并且在复现后立刻抓取。全开日志会对帧率有一点影响特别是ISP统计相关的日志打多了会延长出图耗时复现出来的现象可能和用户崩溃时的现象不完全一样这个要有心理准备。2. UMD Log开启实操从环境变量到CamxOverrides2.1 UMD日志的打印路径Camx UMD层打印日志底层走的是系统Log机制。Android平台上Camx相关日志会打到logcat里TAG一般是CamX、ChiNode、CHICONTEXT这些。用adb logcat直接抓也能看到但因为Camx日志量实在太大了系统通常会把大部分日志打到独立的文件节点再通过属性开关控制。常用的获取方式adb shell logcat -d -v threadtime | grep -E CamX|ChiNode|Camx camx_umd.log如果平台上有独立的Camx log文件路径一般在/vendor/logs或者/data/vendor/camera这类文件是Camx侧的转储日志比logcat里的内容更完整字段也更规整。2.2 camxoverridesettings.txt 关键配置项Camx提供了一套运行时配置机制集中在camxoverridesettings.txt文件里平时就放在vendor/etc/camera目录下。这个文件本质上是键值对可以配置很多调试开关和内部参数。需要先确认相机进程有没有读这个文件有些平台会用generateoverride脚本把配置合到/vendor/etc/camera/camxoverridesettings.txt如果改动没生效大概率就是文件路径不对或者权限不对。下面是我常用的几个Log相关的配置配置项作用推荐值camxLoggingEnable总开关让Camx内部输出日志TRUEcamxLoggingMode日志模式1表示输出到logcat2表示输出到文件3表示都输出3camxLoggingLevel全局日志级别0为error1为warning2为info3为debug2camxLoggingMask按模块bitmask控制可按需组合0xFFFFFFFFCamxLogDumpIspISP相关dump节点开关TRUE实际配置时不要上来就全开这会非常吵而且影响性能。建议按模块拆。调试sensor对焦时就开sensor和AF相关的配置调试3A统计时就开stats和AFDump相关的配置。2.3 配置完成后如何验证改完配置文件后重启camearaProvider进程。adb shell pkill -f camera.provider adb shell pkill -f cameraserver然后重新打开相机App观察logcat里CamX的日志是否明显增多。如果增加量不明显先用一个简单的Exposure补偿命令确认配置是否被加载adb shell getprop | grep -i camx如果这个属性没有输出对应调试属性那可能是Camx native层没读取overrides文件需要把配置放到init加载完的路径下确保属性权限正确。我在某些平台上踩过坑改完配置总不生效折腾半天发现是文件放到了/vendor/etc/camera/下但平台实际读取的是/odm/etc/camera/。所以拿到一个新平台第一件事就是确认camxoverridesettings.txt默认路径。3. KMD Log开启实操内核侧到底怎么打开3.1 KMD日志节点与动态调试KMD层的高通Camera驱动里各个模块都有自己的打印默认情况下有很多是关闭的需要打开动态调试开关。内核日志查看基础命令adb shell dmesg kmd.log adb shell cat /proc/kmsg kmd.log模块化的打印推荐用内核的dynamic_debug机制可以在运行时打开某个文件的pr_debug不需要重新编译内核。以cam_isp模块为例adb shell echo file cam_isp.c p /sys/kernel/debug/dynamic_debug/control如果debugfs没有挂载需要先挂载adb shell mount -t debugfs none /sys/kernel/debug打开之后cam_isp.c里所有pr_debug的日志就会输出到dmesg里。内核对Camera模块驱动的文件名通常是cam_xxx.c对应模块cam_req_mgr.c、cam_isp.c、cam_sensor.c、cam_sync.c、cam_cpas.c。3.2 使能trace event打点比dynamic_debug更强大的是高通Camera驱动里的trace event。这套机制在内核的tracefs里挂了一组Camera相关的事件节点采集成perfetto或者trace文件后可以做很精细的时间线分析。开启trace eventadb shell echo 0 /sys/kernel/debug/tracing/tracing_on adb shell echo /sys/kernel/debug/tracing/trace adb shell echo 1 /sys/kernel/debug/tracing/events/camera/enable adb shell echo 1 /sys/kernel/debug/tracing/tracing_on复现问题后停止抓取adb shell echo 0 /sys/kernel/debug/tracing/tracing_on adb shell cat /sys/kernel/debug/tracing/trace trace_camera.txt这套trace几乎能钩到每次request从UMD下来的完整流转路径包括哪个时刻进了cam_req_mgr、哪个时刻ISP开始处理、哪个时刻产生sof/ef。排查帧率波动、出图时序问题时作用非常突出。3.3 高级技巧panic与超时场景的last_kmsg系统卡死或者camera进程崩溃时往往来不及手动抓dmesg。这种情况要看last_kmsg或者pstore。部分平台会把上次启动时的内核日志保存在/sys/fs/pstore/console-ramoops重启后还能读出来adb shell cat /sys/fs/pstore/console-ramoops last_kmsg_pstore.txt还有一种常见做法是打开高通平台的t32抓取内核log不过这项操作对大多数调试场景来说开销过高实际生产环境不建议直接上。需要先确认平台是否支持pstore如果支持能省下很多反复复现的时间。4. Dump图像让平台把数据吐出来4.1 图像dump的应用场景与数据链路Log再全也只是间接反映状态。遇到图像效果类问题、ISP的统计值异常问题、sensor输出异常问题时直接用dump图说话会更有说服力。Camx的dumplog分成几个维度Sensor raw dump直接dump sensor输出的raw图检查sensor出图是否正常ISP统计dumpdump AF、AEC、AWB等统计信息用来分析3A收敛过程YUV/RGB中间图dump在ISP处理链路的不同node上dump出图定位问题发生在哪个节点raw dump的数据量很大一片raw图动辄几十MB调试前要确认sensor分辨率同时注意dump时间不要过长免得存储空间被写满。4.2 ChiDump与各个节点配置Camx的Dump开关通常也在camxoverridesettings.txt里配置。比如开启raw dump可以配置配置项作用推荐值ChiDump总开关控制用户态dump节点数据TRUEChiDumpModedump模式按7位bit控制哪些node对应节点位的值ChiDumpNodeMask需要dump的node的mask按node填入ChiDumpPathdump文件的输出目录/data/vendor/cameraAfDumpAF统计dump开关TRUEAecDumpAEC统计dump开关TRUE这里的ChiDumpMode和ChiDumpNodeMask需要对照Chi节点ID去配置。最直观的办法是先把Mode设成全F把NodeMask也设成全Fdump一轮看看有哪些目录和文件产生再根据文件名反推节点ID缩小范围。需要提醒的是Camx的dump路径默认是/data/vendor/camera如果dumpserver进程没有权限写这个目录dump会静默失败。遇到dump不出来第一步检查这个目录是否存在、以及目录权限。4.3 dump出来怎么看dump出来的文件一般有两种格式纯二进制raw文件和带头部的dump文件。raw文件可以用Python的numpy加载也可以直接用高通的QPST工具离线解析。我习惯的做法是先用Python脚本解析raw文件看文件名里的信息一个常见的dump文件名格式大概是IMG_20250101_120000_[PIPELINE_ID]_[NODE_ID]_[FRAME_ID].raw文件名携带的信息包括时间、pipeline id、node id、frame id这正好能和log里的时间戳对应上。解析raw文件时如果尺寸不对可以先查看文件大小通过像素格式和分辨率反推是否正确。例如一个1920x1080的NV12图每帧大小应该约等于192010803/2。对于3A统计dump出来的文件一般是txt或bin里面记录的是图像各个区域的亮度均值、对焦评价值等。分析这类文件时可以按frame id逐条绘制曲线这样能很直观看到AEC收敛过程有没有来回震荡。5. 离线Log合成脚本让用户态与内核态同屏5.1 为什么要离线合成Log抓是抓到了但分析仍然费劲。UMD的logcat是threadtime格式每一行都带进程号、线程号、时间戳内核的dmesg则是另一个时间体系两个文件的时间戳参照不一样直接对比基本没法用。我遇到过不止一次用户态log显示某个request已经返回了内核侧同一时间的log却显示硬件还没有启动查了一圈发现两个时间轴差了十几秒。把两侧日志合到一起本质上做一件事把两边的日志时间统一到一个坐标系里。时间对齐完成后所有日志都按顺序排列可以直接从日志流上还原一帧从HAL下发到ISP出图的完整链路。5.2 脚本设计思路与代码我写过一个Python脚本思路不复杂读取UMD日志文件解析每行开头的日期时间logcat默认“MM-DD HH:mm:ss.mmm”threadtime格式里还带进程号。读取KMD日志文件解析dmesg时间一般是“秒.毫秒”或者带时间戳需要结合开机时间换算。人工选择一个同步锚点比如系统开机时刻或UMD和KMD共同打印的一条进程启动日志。统一成绝对时间戳后排序输出。锚点的选择最关键。如果平台能在启动时同步记录开机时刻那直接用systime换算dmesg里的时间加开机时刻offset即可。如果拿不到准确开机时间就找一条两侧都存在的日志例如“camx_initialize”或“CamxCreate”日志把它们视作同一时刻再反推偏移量。下面是脚本的核心示例供参考#!/usr/bin/env python3 # -*- coding: utf-8 -*- Offline Log Merger for Camx UMD/KMD logs 用法: python3 log_merger.py --umd umd.log --kmd kmd.log --boot-time 2025-01-01 12:00:00.000 import argparse import re from datetime import datetime, timedelta UMD_TIME_RE re.compile(r^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})) KMD_TIME_RE re.compile(r^\[\s*(\d\.\d)\]) def parse_umd_time(line, year): m UMD_TIME_RE.match(line) if not m: return None, line dt datetime.strptime(f{year}-{m.group(1)}, %Y-%m-%d %H:%M:%S.%f) return dt, line[m.end():].strip() def parse_kmd_time(line, boot_time): m KMD_TIME_RE.match(line) if not m: return None, line seconds float(m.group(1)) dt boot_time timedelta(secondsseconds) return dt, line[m.end():].strip() def main(): ap argparse.ArgumentParser() ap.add_argument(--umd, requiredTrue) ap.add_argument(--kmd, requiredTrue) ap.add_argument(--boot-time, requiredTrue) ap.add_argument(--year, default2025) args ap.parse_args() boot_time datetime.strptime(args.boot_time, %Y-%m-%d %H:%M:%S.%f) entries [] with open(args.umd, r, errorsignore) as f: for line in f: dt, content parse_umd_time(line, args.year) if dt: entries.append((dt, UMD, content)) with open(args.kmd, r, errorsignore) as f: for line in f: dt, content parse_kmd_time(line, boot_time) if dt: entries.append((dt, KMD, content)) entries.sort(keylambda x: x[0]) fmt %Y-%m-%d %H:%M:%S.%f with open(merged_log.txt, w) as out: for dt, tag, content in entries: out.write(f{dt.strftime(fmt)[:-3]} [{tag}] {content}\n) print(f合并完成共 {len(entries)} 条日志输出到 merged_log.txt) if __name__ __main__: main()脚本本身不复杂但胜在实用。实际使用时UMD日志的年份如果跨年要去日志里确认或者从文件修改时间推断KMD日志如果平台没有提供开机时刻可以在脚本里加一个“手动对齐时间戳”参数用两侧都有的一条日志来锚定。5.3 实际使用效果与局限用这个脚本处理完日志之后整个日志流里UMD和KMD是交错的。比如一条3840x2160的抓拍请求日志顺序会是这样10:00:01.123 [UMD] Camx::RequestProcessor: New request 42 received 10:00:01.124 [UMD] Camx::HWL: Submitting request 42 to KMD, pipeline0 10:00:01.125 [KMD] cam_req_mgr: apply_request req_id42 10:00:01.130 [KMD] cam_isp: hw acquire req_id42, slot3 10:00:01.142 [KMD] cam_isp: notify_sof req_id42 10:00:01.150 [UMD] Camx::StatsProcessor: SOF received req_id42这样一眼就能看出request在哪个环节停留了多长时间UMD有没有及时收到KMD的sof事件。局限也很明显如果两侧时间戳对不齐合并后顺序就会错乱所以第一步还是要核对锚点。另外脚本只是文本处理不会区分进程线程如果同时打开多个camera场景建议先在UMD侧标记pipeline id再进脚本合并。6. 常见问题与排查技巧实录6.1 常见问题速查表现象可能原因处理建议UMD Log只有少量打印overrides文件路径不对或属性未加载确认平台读取路径重启camera providerKMD动态调试无输出debug分区未挂载或权限不足先mount debugfs检查节点访问权限图像dump没有文件生成dump目录不存在或dumpserver无写权限手动创建目录并chmod 777dump文件生成了但无法解析dump格式不是默认格式对照文件名和文件大小反推像素格式日志时间戳对不上锚点选错用启动时刻或特定日志对齐打开大量日志后卡顿日志量过大拖慢帧率分级开启只保留目标模块detail日志系统重启后log丢失日志没有落盘或落盘分区清理提前设置pstore或设置日志循环缓冲区6.2 我的几点避坑心得第一个心得很实际改完camxoverridesettings后一定要确认改的文件被正确加载。不要把时间浪费在猜配置上用一个特殊字符串比如“DUMP_TEST_ON”写进去再搜log里有没有出现有就是加载了。第二个心得抓KMD日志时尽量同时抓一份time stamp的锚点。可以在开camera前执行一次“date”把这个输出也存下来后面换算dmesg时间就方便很多。如果不做等日志抓完想对时间轴时就晚了。第三个心得图像dump尽量从raw开始。有时候效果问题绕来绕去看中间yuv会觉得无从下手但如果直接看raw没问题起码能把sensor摘出去如果raw本身有问题那就直接查sensor端出图和I2C配置问题范围一下子就缩小了。第四个心得高通平台不同子系统对日志处理的细节虽然有差异但整体思路是通用的找开关、开日志、抓现场、对时间轴。把这一套标准化之后无论是给产线复现问题还是远程让现场工程师抓log沟通成本都会小很多。最后再分享一个细节楼道里经常看到同事调试时开着一堆终端窗口刷日志其实真正有效的调试时间往往只集中在复现的那两分钟里。把Log开关做成一套标准操作文档抓log流程做成脚本每次复现前只需要一条命令启动抓取复现后一条命令停止抓取并把日志打包出来。这套流程一旦跑顺调试效率能提升一大截这也是我不厌其烦写这篇文章的原因。
返回列表