ARTICLE DETAIL

资讯详情

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

RK3576内核与设备树排障实战:HDMI热插拔无声等典型问题解析

RK3576内核与设备树排障实战:HDMI热插拔无声等典型问题解析 1. 从一次HDMI插拔引发的无声故障说起RK3576这颗芯片最近在工控和边缘计算圈子里热度不低8核CPU加6TOPS NPU的配置跑Ubuntu和Android14都挺顺。但我在实际项目里遇到最多的问题不是性能不够而是内核和设备树层面的配置冲突——这类问题往往表现为功能时好时坏插上某个外设就挂了排查起来特别费劲。就拿一个真实案例来说客户反馈RK3576板子跑Android14插上HDMI线之后媒体声音就没了。拔掉HDMI声音恢复插上又没声音。这个现象听起来像是音频路由问题但根子其实在设备树里——HDMI的I2S音频通道和板载Codec的I2S通道在设备树中共享了同一个I2S控制器内核在HDMI热插拔时重新枚举了音频设备把默认输出路由到了HDMI但HDMI音频时钟配置又不对导致整个音频子系统进入异常状态。这类问题在Rockchip平台上非常典型。RK3576、RK3568、RV1106这些芯片的Linux SDK虽然都基于Rockchip BSP但每颗芯片的时钟树、电源域、引脚复用都有差异设备树的写法不能直接照搬。这篇内容就是把我这几年在Rockchip平台上踩过的内核与设备树排障经验系统化整理出来重点讲排查思路和定位方法而不是给一堆结论让你背。如果你正在用RK3576做产品开发或者从RK3568迁移到RK3576又或者用RV1106做视觉方案下面这些内容应该能帮你省下不少调试时间。我会从内核启动日志分析、设备树覆盖机制、时钟与电源域冲突、外设热插拔异常这几个维度展开每个部分都配上实际案例和排查命令。2. 内核启动日志里那些容易被忽略的关键行2.1 从串口日志第一屏开始建立基线很多人拿到板子第一件事就是看能不能进系统能进就认为没问题。但Rockchip平台的内核启动日志里藏着大量设备树解析和驱动probe的信息这些信息在系统跑起来之后不会主动告诉你。我的习惯是每次修改设备树或内核配置后先完整抓一份串口启动日志用minicom或picocom保存到文件然后跟上一版做diff。具体操作上串口参数是1500000 8N1RK3576的debug串口默认是ttyFIQ0。抓日志的时候注意把内核log level调到7# 在内核cmdline里加 loglevel7 ignore_loglevel这样能确保dev_dbg级别的信息也打出来。抓下来的日志重点看几个地方OF:开头的设备树解析信息、rockchip-开头的平台驱动probe信息、clk:和regulator:的注册信息。特别是OF: graph: no port node found这类警告往往意味着设备树里某个节点的连接关系没写对。我见过一个案例RK3576的MIPI DSI屏幕在系统启动后能显示但背光亮度调节无效。查启动日志发现pwm-backlight驱动probe成功了但rockchip-pwm控制器在设备树里被配置成了disabled状态背光驱动用的是软件PWM模拟。这就是典型的功能看起来正常但实际走错了路径不看启动日志根本发现不了。2.2 用dmesg时间戳定位probe顺序问题内核启动日志的时间戳是排查probe顺序问题的利器。Rockchip平台很多外设之间存在依赖关系比如PHY驱动必须在MAC驱动之前probe完成否则网络会时通时断。RK3576的GMAC和YT8521 PHY就是典型例子。如果PHY设备树配置里reset-gpios的引脚被其他驱动先占用了PHY就会probe失败但MAC驱动可能仍然会注册成功表现为ifconfig能看到网卡但ping不通。这时候用dmesg | grep -E yt8521|stmmac|mdio看时间戳顺序。正常情况下应该是mdio总线先注册然后yt8521probe最后stmmac绑定PHY。如果顺序乱了就要检查设备树里节点的status和依赖关系。提示RK3576的GMAC时钟有多个来源设备树里assigned-clocks和assigned-clock-rates如果配错PHY会link不上但驱动不报错。用cat /sys/kernel/debug/clk/clk_summary | grep gmac确认实际时钟频率。2.3 内核缓冲与动态调试的配合使用dmesg默认缓冲区大小是CONFIG_LOG_BUF_SHIFT决定的通常是128KB。对于Rockchip这种外设多的平台启动日志很容易超过这个大小前面的信息会被冲掉。我一般会在内核配置里把它调到CONFIG_LOG_BUF_SHIFT18256KB或者更激进一点用201MB。另外dynamic_debug在排查驱动问题时特别好用。比如怀疑某个I2C设备通信异常可以这样开调试# 开启i2c核心的动态调试 echo file drivers/i2c/* p /sys/kernel/debug/dynamic_debug/control # 开启具体设备驱动的调试 echo file drivers/media/i2c/* p /sys/kernel/debug/dynamic_debug/control这样不用重新编译内核就能看到详细的I2C传输信息。RK3576的I2C控制器有FIFO模式如果设备树里fifo-mode没配或者配错会出现传输看起来成功但数据不对的情况动态调试能直接打出每次传输的字节数。3. 设备树覆盖机制与Rockchip特有写法3.1 为什么直接改rk3576-evb.dts是个坏习惯Rockchip SDK的设备树组织方式是rk3576.dtsiSoC级rk3576-evb.dts板级。很多人图省事直接改板级dts结果SDK升级时冲突一大堆。正确的做法是用设备树覆盖Device Tree Overlay把板子特有的改动放在独立的*.dtso文件里。RK3576的U-Boot支持在启动时加载overlay具体是在extlinux.conf或boot.scr里指定fdtoverlays /overlays/rk3576-myboard.dtso这样SoC级的改动跟着SDK走板级改动独立维护。我经手的一个项目从RK3568迁移到RK3576因为一直用overlaySoC相关的改动只花了半天就完成了剩下的时间都在调板级外设。3.2rockchip,grf和rockchip,pmu的引用陷阱Rockchip设备树里大量使用rockchip,grfGeneral Register File和rockchip,pmuPower Management Unit来配置引脚复用和电源域。这两个节点的phandle在rk3576.dtsi里定义但不同SDK版本的phandle编号可能不同。我遇到过一个问题从RK3568的dts直接复制了一段rockchip,grf grf的配置到RK3576编译没问题但运行时引脚复用完全不对。原因是RK3576的GRF节点分成了多个grf、grf_ioc、grf_usb等必须引用正确的那个。排查方法是# 查看设备树中实际解析的grf节点 ls /proc/device-tree/ | grep grf # 查看引脚复用实际寄存器值 cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pinsRK3576的引脚复用寄存器分布在多个GRF区域设备树里写错phandle不会报错但硬件行为完全不对。这个坑我在三个项目里都见过每次都是因为直接复制了其他芯片的配置。3.3 设备树配置的验证清单改完设备树之后别急着编译烧录。我总结了一个验证清单按顺序过一遍能过滤掉80%的低级错误检查项命令/方法常见问题语法检查dtc -I dts -O dtb -o /dev/null xxx.dts括号不匹配、属性名拼写错误phandle引用grep -o [a-z_]* xxx.dts | sort -u引用了不存在的label引脚冲突cat /sys/kernel/debug/pinctrl/pinmux-pins同一引脚被两个节点引用时钟配置cat /sys/kernel/debug/clk/clk_summaryassigned-clock-rates配错电源域cat /sys/kernel/debug/regulator/regulator_summary电源域未使能或电压不对中断配置cat /proc/interrupts中断号冲突或触发方式错误这个清单看着简单但每次改完设备树花五分钟过一遍比烧录后发现功能不对再回头查要快得多。特别是引脚冲突RK3576的引脚功能多一个引脚可能同时被UART、I2C、PWM引用设备树编译不报错但运行时只有一个能生效。4. 时钟树与电源域的联动排查4.1 RK3576时钟树的层级关系RK3576的时钟树比RK3568复杂不少特别是新增了多个PLL和分频器。设备树里配时钟的时候assigned-clocks和assigned-clock-rates必须成对出现而且父时钟必须先使能。举个例子RK3576的I2S时钟路径是PLL_AUDIO - clk_i2s0_frac - clk_i2s0。如果设备树里只配了clk_i2s0的rate没配clk_i2s0_frac内核会尝试自动计算分频比但算出来的频率可能不是标准音频采样率的整数倍导致播放有杂音。正确的写法是i2s0_8ch { assigned-clocks cru CLK_I2S0_8CH_TX_SRC, cru CLK_I2S0_8CH_TX; assigned-clock-rates 12288000, 12288000; status okay; };12288000是48kHz采样率的标准时钟这样配出来音质才干净。我见过有人配成12000000结果播放48kHz音频时每秒钟会有微小的时间偏差听久了能感觉到节奏不对。4.2 电源域未使能导致的玄学故障Rockchip平台的电源域Power Domain管理很细RK3576有十几个PD。设备树里每个外设节点都要正确引用power-domains否则会出现驱动probe成功但读写寄存器全返回0的情况。排查电源域问题最直接的方法是看regulator_summarycat /sys/kernel/debug/regulator/regulator_summary输出里会列出每个电源域的使能状态、电压、电流。如果某个外设的电源域显示disabled但驱动已经probe那基本可以确定是设备树里power-domains引用错了。我遇到过一个典型案例RK3576的GPU在跑OpenCL测试时随机挂死。查regulator_summary发现GPU的电源域在负载高时会被自动关闭原因是设备树里power-domains只引用了pd_gpu没引用pd_gpu_core。RK3576的GPU有两个电源域必须都引用才能保证高负载下不掉电。4.3 时钟与电源域的调试命令组合排查这类问题我习惯用一组命令组合拳# 1. 查看时钟树 cat /sys/kernel/debug/clk/clk_summary | grep -E gpu|i2s|gmac # 2. 查看电源域状态 cat /sys/kernel/debug/pm_genpd/pm_genpd_summary # 3. 查看设备probe状态 ls /sys/bus/platform/drivers/ | while read d; do ls /sys/bus/platform/drivers/$d/ 2/dev/null | grep -v ^$d$ | \ while read dev; do if [ -L /sys/bus/platform/drivers/$d/$dev ]; then echo $d - $dev fi done done # 4. 查看regulator cat /sys/kernel/debug/regulator/regulator_summary这四条命令跑一遍基本能定位到是时钟没使能、电源域没开、还是驱动没probe。RK3576的pm_genpd_summary输出特别有用它会显示每个电源域的active和on状态以及引用计数。如果某个域的引用计数是0但设备在用那就是设备树里漏配了power-domains。5. 外设热插拔异常的排查链路5.1 HDMI热插拔导致音频丢失的完整分析回到开头那个HDMI插拔导致音频丢失的问题。排查过程分四步第一步确认音频路由变化。用tinymix查看音频通路tinymix # 插HDMI前 # Primary Playback - I2S0 # 插HDMI后 # Primary Playback - HDMI第二步检查HDMI音频时钟。HDMI的音频时钟来自clk_hdmi_audio如果这个时钟没使能或者频率不对HDMI音频设备会注册失败但Android的音频策略仍然会把默认输出切到HDMI导致切过去但没声音。cat /sys/kernel/debug/clk/clk_summary | grep hdmi第三步查设备树里HDMI音频节点的连接关系。RK3576的HDMI音频通过I2S5连接到SoC设备树里hdmi_sound节点必须正确引用i2s5_8chhdmi_sound: hdmi-sound { compatible simple-audio-card; simple-audio-card,format i2s; simple-audio-card,mclk-fs 128; simple-audio-card,cpu { sound-dai i2s5_8ch; }; simple-audio-card,codec { sound-dai hdmi; }; };如果mclk-fs配错HDMI音频会有杂音或者完全没声。128是HDMI音频的标准MCLK/FS比。第四步修改音频策略。在Android的audio_policy_configuration.xml里把HDMI音频的优先级调低或者配置成不自动切换。这样插HDMI时不会强制切换默认输出。5.2 USB设备热插拔的电源域问题RK3576的USB3.0和USB2.0控制器共享部分电源域。如果设备树里USB节点的power-domains配置不完整会出现插U盘能识别但读写大文件时掉线的情况。排查方法# 监控USB电源域状态 watch -n 0.5 cat /sys/kernel/debug/pm_genpd/pm_genpd_summary | grep usb # 查看USB控制器时钟 cat /sys/kernel/debug/clk/clk_summary | grep usb # 查看USB PHY状态 cat /sys/kernel/debug/phy/rockchip-usb2phy/statusRK3576的USB2.0 PHY有独立的power-domains设备树里usb2phy节点必须引用pd_usb2phy。如果漏了U盘在低功耗状态下会掉线。5.3 热插拔事件的设备树中断配置很多外设的热插拔检测依赖GPIO中断。RK3576的GPIO中断控制器支持双边沿触发但设备树里interrupts属性必须配成IRQ_TYPE_EDGE_BOTH否则只能检测插入不能检测拔出。gpio3 { usb_hub_reset: usb-hub-reset { rockchip,pins 3 RK_PC4 RK_FUNC_GPIO pcfg_pull_up; }; }; usb_hub: usb-hub { compatible usb-hub; reset-gpios gpio3 RK_PC4 GPIO_ACTIVE_LOW; interrupt-parent gpio3; interrupts RK_PC4 IRQ_TYPE_EDGE_BOTH; };注意RK3576的GPIO中断号计算方式和RK3568不同interrupts属性里用的是GPIO bank内的相对编号不是全局中断号。写错了不会报错但中断永远不触发。6. 从RK3568迁移到RK3576的设备树适配要点6.1 引脚编号与GRF引用的差异RK3568和RK3576的引脚编号规则基本一致但GRF引用差异很大。RK3568的引脚复用基本都在一个GRF里RK3576分成了grf、grf_ioc、grf_usb、grf_audio等多个区域。迁移时最容易出错的是I2C和UART的引脚配置。RK3568的写法i2c1 { pinctrl-0 i2c1m0_xfer; };RK3576上如果I2C1的引脚在grf_ioc区域同样的写法编译能过但引脚复用不生效。必须确认引脚所在的GRF区域用对应的pinctrl节点。6.2 时钟ID和复位ID的变化RK3576的时钟ID和复位ID跟RK3568完全不同设备树里clocks和resets属性引用的宏定义必须用RK3576的dt-bindings头文件。直接复制RK3568的配置会编译报错但如果宏名字碰巧一样但值不同就会出大问题。我的做法是迁移时先把所有clocks和resets引用列出来逐个对照RK3576的rk3576-cru.h确认。这个工作看着繁琐但比烧录后一个个功能试要快得多。6.3 迁移后的功能验证顺序迁移完成后按这个顺序验证功能能最快定位问题串口确保debug串口正常否则后面没法看日志存储eMMC和SD卡能挂载确保系统能启动网络GMAC能link上PHY驱动正常USBUSB2.0和USB3.0都能识别设备显示HDMI和MIPI DSI能出图音频板载Codec和HDMI音频都能出声外设I2C、SPI、PWM等按项目需求逐个验证每验证一个功能抓一份dmesg和clk_summary存档。后面出问题时跟正常状态的存档对比能快速定位差异。7. 几个让我印象深刻的排障案例7.1 RV1106的I2C传感器间歇性通信失败RV1106做视觉方案时接了一个I2C温度传感器表现是每跑几个小时就通信失败一次重启后恢复。查dmesg发现i2c-rk3x控制器报了timeout错误。排查发现RV1106的I2C控制器时钟在低功耗模式下会被自动降频但设备树里clock-frequency配的是400000降频后实际只有100000传感器响应变慢导致超时。解决方法是在设备树里给I2C控制器加rockchip,grf引用确保时钟不被自动降频i2c3 { clock-frequency 400000; rockchip,grf grf; status okay; };这个问题的隐蔽性在于它不是每次都失败而是负载低的时候才触发用常规测试方法很难复现。7.2 RK3576的Ubuntu系统ADB连接不稳定RK3576跑Ubuntu时ADB连接时断时续。查USB控制器日志发现dwc3驱动在runtime suspend和resume之间频繁切换。原因是设备树里USB3.0控制器的power-domains配了但rockchip,usb3-phy的引用不对。修正后的配置usbdrd3_0 { power-domains power RK3576_PD_USB3; status okay; }; usbdrd_dwc3_0 { dr_mode otg; phys usb3phy_grf 0, usb2phy_grf 0; phy-names usb3-phy, usb2-phy; status okay; };关键是phys属性里同时引用了USB3 PHY和USB2 PHY只引用一个会导致ADB在USB2和USB3模式切换时断连。7.3 内核裁剪后设备树节点被误删为了减小内核体积有人会裁剪内核配置。但裁剪内核配置不会自动删除设备树节点如果设备树里引用了被裁剪掉的驱动内核启动时会报OF: device node xxx has no driver但系统仍然能启动只是功能缺失。我见过一个案例裁剪掉了CONFIG_PWM_ROCKCHIP但设备树里背光节点仍然引用pwm结果背光完全不亮而且没有任何错误提示。排查方法是# 查看设备树中所有statusokay但无驱动的节点 for node in /proc/device-tree/*; do if [ -f $node/status ] [ $(cat $node/status) okay ]; then if [ ! -d /sys/bus/platform/drivers/$(basename $node) ]; then echo No driver: $(basename $node) fi fi done这个脚本能快速找出设备树里使能了但没有对应驱动的节点裁剪内核后跑一遍特别有用。8. 建立自己的排障工具箱8.1 常用调试命令速查在Rockchip平台上排障下面这些命令我几乎每天都会用到# 设备树解析结果 ls /proc/device-tree/ cat /proc/device-tree/model # 时钟树 cat /sys/kernel/debug/clk/clk_summary # 电源域 cat /sys/kernel/debug/pm_genpd/pm_genpd_summary # 引脚复用 cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins # GPIO状态 cat /sys/kernel/debug/gpio # Regulator cat /sys/kernel/debug/regulator/regulator_summary # 中断 cat /proc/interrupts # 设备probe状态 ls /sys/bus/platform/drivers/*/ # 动态调试 echo file drivers/*/* p /sys/kernel/debug/dynamic_debug/control把这些命令做成alias或者脚本排障时能省不少时间。8.2 设备树diff工具的使用改设备树最怕改出问题不知道改了什么。我的做法是每次修改前先备份修改后用dtc反编译成dts再diff# 从运行中的系统导出设备树 dtc -I fs -O dts /proc/device-tree -o current.dts # 跟源码里的dts对比 diff -u rk3576-evb.dts current.dts这样能看到内核实际解析的设备树跟源码的差异包括U-Boot传进来的overlay改动。RK3576的U-Boot可能会在启动时修改设备树比如根据硬件版本调整内存大小这些改动在源码里看不到但会影响内核行为。8.3 内核日志的持久化保存串口日志在系统启动后会被dmesg覆盖但dmesg缓冲区有限。我的做法是在/etc/rc.local里加一行把启动日志保存到文件dmesg /var/log/boot.log然后在/etc/logrotate.d/里配置轮转保留最近10次启动的日志。这样出问题时能回溯到之前正常状态的日志做对比。对于RK3576这种支持pstore的平台还可以配置ramoops把内核崩溃日志保存到保留内存里重启后从/sys/fs/pstore/读取。配置方法是在设备树里加ramoops: ramoops110000 { compatible ramoops; reg 0x0 0x110000 0x0 0xf0000; record-size 0x20000; console-size 0x80000; ftrace-size 0x20000; pmsg-size 0x20000; };这样即使内核panic了重启后也能看到崩溃前的日志对排查偶发性崩溃特别有用。9. 一些零散但实用的经验设备树里status属性的默认值在不同SDK版本里可能不同。RK3576的SDK里rk3576.dtsi中大部分节点默认是disabled需要在板级dts里显式okay。但从RK3568迁移过来的代码可能假设默认是okay导致功能不生效。每次迁移后先全局搜一遍status属性确认所有需要的外设都显式使能了。内核命令行参数rockchip,uboot-logo-on控制U-Boot是否显示logo。如果设备树里配了但U-Boot没编译对应支持logo不会显示但也不会报错。排查显示问题时先确认这个参数。RK3576的rkisp和rkcif驱动对设备树里ports节点的连接关系要求很严格。如果endpoint的remote-endpoint引用错了驱动probe会成功但取流失败。用media-ctl -p查看media拓扑确认所有link都正确建立。I2C设备的reg地址在设备树里是7位地址不是8位。从datasheet复制地址时注意区分。RK3576的I2C控制器支持10位地址但设备树里要显式配i2c-ten-bit-addr。最后说一个关于内核版本选择的经验。RK3576的Linux SDK默认用6.1内核但有些新外设驱动只在6.6内核里有。如果项目需要用到这些外设要么等SDK更新要么自己backport驱动。Backport的时候注意file_operations结构体的变化6.6内核里read和write的签名跟6.1不同直接复制会编译报错。这些经验都是一个个项目踩出来的每一条背后都有至少半天的调试时间。Rockchip平台的文档不算少但很多细节只有实际调试过才知道。希望这些内容能帮你在RK3576或者其他Rockchip平台上少走点弯路。
返回列表