ARTICLE DETAIL

资讯详情

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

RK3588 HDMI IN热插拔失效排查:从硬件信号到Android上层全链路分析

RK3588 HDMI IN热插拔失效排查:从硬件信号到Android上层全链路分析 做RK3588方案的兄弟应该都遇到过这么一幕板子烧好固件HDMI IN口接上机顶盒或者电脑第一次插上去画面正常开心不到三秒把线拔掉再插回去采集画面直接消失dmesg里干干净净好像这根线从来不存在一样。重启设备才恢复但下一次拔插又复现。这个问题的本质不是某个API没用对也不是APP层漏写了回调而是HDMI IN热插拔这条链路在RK3588平台上实在太长了。硬件信号、内核中断、V4L2子系统、HAL层、Android Framework、APP UI六个环节任何一环断了现象都一样——插拔失效。这篇东西我从硬件信号讲到Android上层把每一层该查什么、怎么验证、常见坑是什么都梳理一遍。给正在做RK3588 Android HDMI IN采集成品方案的工程师留个参考。1. RK3588的HDMI IN热插拔难点到底在哪先搞清楚一个容易被忽略的事实HDMI IN和HDMI OUT的热插拔完全是两套逻辑。很多人用HDMI OUT的经验去套HDMI IN结果被坑得莫名其妙。1.1 HDMI IN与HDMI OUT的热插拔是两套完全不同的逻辑HDMI OUT是Source端SoC主动往外输出信号。热插拔检测靠的是HPDHot Plug Detect引脚外部显示器或者电视通过HPD脚告诉SoC“我在这可以输出”。Android这边DisplayFramework本身就有一套完整的Hotplug事件处理从DRM/KMS到SurfaceFlinger全是现成的所以HDMI OUT热插拔基本是开箱即用的。RK3588的HDMI IN就不一样了。它内部是HDMI RX控制器本质上是一个Sink端接收器工作方式和摄像头输入更像。外部的Source设备比如机顶盒、PC检测到我们这边提供了5V和正确的EDID之后才开始往HDMI总线上送TMDS信号。RK3588的HDMI RX控制器把TMDS信号解析成视频流再通过内部的ISP/CIF通路送到采集设备节点。这套链路决定了两件事。第一热插拔的“源头”在硬件引脚上。我们的板卡需要把外部HDMI的HPD信号和5V检测信号接进SoC的中断控制器或者GPIO内核驱动才能感知到“线插上/拔掉了”。这不像USB那样有完整的Hub枚举机制全靠引脚电平变化。第二拔插之后不只是要“知道插上了”还要让Source端重新握手。Source端和Sink端之间要通过DDC通道I2C读EDIDEDID读不到或者读晚了Source端就不会输出信号表现出来就是“明明检测到插入了但就是没有画面”。1.2 一条插拔消息要穿过六层才算生效在RK3588的Android方案里一次完整的热插拔事件要经历这么一条链路硬件引脚电平变化 → SoC中断控制器 → 内核hdmirx驱动 → 内核事件上报uevent或input子系统 → HAL层监听并回调 → Framework层HdmiControlService维护状态 → APP收到广播或回调刷新UI。任何一层出问题上层表现完全一样。而且麻烦的是上层出了问题你往底层查底层又没问题底层出问题上层又看似正常。没有任何一个节点能一眼看出“这条链路卡在哪了”。后面我会专门讲每一层怎么验证这里先记住一句话排查热插拔问题必须一层一层的确认跳过任何一层都会浪费时间。1.3 RK3588方案里常见的三处设计伏笔第一个伏笔是HPD引脚没接。有些精简版开发板原理图里HDMI IN座子的HPD脚直接悬空或者拉死压根没进SoC。软件层面再努力也没用因为没有电平变化来触发中断。第二个伏笔是DDC通道被其他外设占用。HDMI IN的DDC走的是I2C有些板子把它和触摸屏I2C复用两边驱动同时初始化地址冲突EDID读不稳定。第三个伏笔是驱动里的电源管理。拔线之后hdmirx驱动如果进入runtime suspend把时钟关了再次插入时中断虽然触发了但驱动没有做时钟恢复导致采集链路起不来。这三个伏笔会在后面详细展开。2. 先搞定硬件信号链路5V、HPD、DDC一个都不能少做软件的人往往习惯性跳过硬件直接看代码但HDMI IN热插拔恰恰是必须先过硬件这一关。原因很简单软件能处理的前提是“有信号变化”而信号变化全靠硬件设计。2.1 拿到原理图第一件事确认HPD到了哪个引脚HDMI座子的19脚是HPDSource端用它来检测Sink端是否存在。RK3588的HDMI RX一般是把HPD信号接到SoC的一组GPIO上通过中断触发通知驱动。拿到原理图先找HDMI IN部分的HPD网络标号确认它连到了RK3588的哪个Ball。常见的有两种接法直接接到GPIO或者接到HDMI RX控制器的专用HPD输入脚。不管是哪种都要记录下这个引脚的GPIO组和Bank号后面配dts要用。如果原理图上HPD叫个什么HDMI_HPD_IN之类的名字但搜遍全板找不到它连接到SoC哪里那就麻烦了说明硬件上可能是悬空的。这时拿万用表量一下座子19脚对地电压如果一直是0V八成就是没接。我自己就遇到过一块板子HPD经过一个0欧电阻再接SoC贴片的时候电阻没贴导致一直检测不到插入。示波器一量Source端输出5V但我们这边HPD纹丝不动最后补焊电阻解决。所以先看一眼BOM确认这种串联电阻和滤波电容都在。2.2 5V检测与HPD的电源域细节除了HPD还要关注HDMI座子的18脚这个是Source端提供的5V电源。RK3588的hdmirx驱动里有时候不只是检测HPD也会检测5V信号。有些板卡设计里5V除了给座子供电还会通过分压电路接到SoC的GPIO用来做“外部设备是否存在”的第二路确认。这里要注意电源域问题。如果5V分压脚接的GPIO域是1.8V供电而分压电路按3.3V设计那么高电平时GPIO可能读到错误的电平或者长期过压损伤引脚。遇到热插拔状态不稳定的时候查一查这个分压电阻比例对不对。DDC通道也很关键。HDMI的DDC就是I2C协议地址是0x50用来读EDID。RK3588的hdmirx内部会通过这条I2C总线去读Source端的EDID。原理图上的SCL/SDA有没有配上拉电阻、上拉到哪个电压域直接影响EDID读取稳定性。很多“插上之后半天才出画面”的问题都是DDC上拉电阻阻值不对导致信号边沿太缓。2.3 硬件侧快速验证万用表和示波器怎么看在动软件之前先用万用表确认几个关键电平能省下大量排查时间。未插入时HPD应该是低电平或者高阻插入后Source端检测到我们提供的5V注意这个5V也是Source端输出的HPD会变成高电平或者由我们的SoC驱动拉高。具体看板卡设计有的是Source输出HPD高电平给SoC有的是SoC检测到5V后自己拉高HPD。量为5V脚插入后有5V拔掉后为0V。量为DDC上拉SCL/SDA空闲状态下应该有上拉的3.3V或5V电平短路到地就说明座子有问题。示波器主要看插拔瞬间的电平变化沿。有些板子因为滤波电容太大电平上升沿要几十毫秒甚至几百毫秒导致SoC中断控制器识别不到有效边沿或者触发多次抖动。看到这种情况要么改硬件电容要么在软件里做去抖处理后面会讲。硬件确认没问题之后再进入内核和dts阶段。3. 内核驱动与设备树让热插拔事件从引脚变成中断硬件信号没问题接下来就是把“电平变化”变成“内核事件”。这一步的落脚点就是dts配置和hdmirx驱动的中断处理。3.1 找到hdmirx设备节点并核对关键属性RK3588的内核SDK里HDMI IN对应的设备树节点一般叫hdmirx_ctrlercompatible为rockchip,rk3588-hdmirx。在SDK的dts文件里搜索hdmirx就能找到。一个典型节点的关键属性大概长这样hdmirx_ctrler: hdmirx_ctrler { compatible rockchip,rk3588-hdmirx; reg 0x0 0x19000000 0x0 0x10000; interrupts GIC_SPI 214 IRQ_TYPE_LEVEL_HIGH; clocks cru CLK_HDMIRX_REF, cru CLK_HDMIRX; clock-names ref, pclk; pinctrl-names default; pinctrl-0 hdmirx_hpd hdmirx_ddc; status okay; };注意几点。第一interrupts里的中断号要跟芯片手册对得上。RK3588不同的批次、不同的封装Ball中断号可能不一样。不要照抄别人板子的dts先查自己板子的SoC手册确认。第二clocks必须使能之前遇到过一个案例是hdmirx的pclk被某个驱动关掉了导致中断能触发但寄存器读出来全是0。第三pinctrl里要包含HPD引脚和DDC引脚的复用配置HPD要配成GPIO中断模式DDC要配成I2C功能如果复用错了两个功能都会异常。3.2 确认中断注册与pinctrl复用打开驱动源码一般情况下hdmirx驱动在probe阶段会通过platform_get_irq获取中断号然后调用request_irq或devm_request_threaded_irq注册中断处理函数。HPD变化时中断处理函数中会检测HPD电平然后调用完整的插拔处理流程。中断触发的模式也得看硬件。如果HPD直接连到SoC的GPIO中断控制器通常配置成IRQ_TYPE_LEVEL_HIGH或者IRQ_TYPE_EDGE_RISING/FALLING。如果你的板子HPD信号有反相电路触发条件就要反着配。我之前在某个项目里HPD经过一个三极管反相后再进SoCdts里配的上升沿触发结果每次拔线才触发中断插线反而安静折腾好久才发现硬件是有反相的。还有一种情况是HPD引脚同时被复用成了其他外设的引脚。当pinctrl没有正确配置或者系统里其他驱动把这个GPIO申请走了hdmirx就收不到中断。排查方法是在sysfs里看当前引脚的复用状态cat /sys/kernel/debug/gpio看HPD对应GPIO的状态是”gpio”还是”alt function”。如果显示被其他驱动占用那就得改pinctrl配置或者让那个驱动让出引脚。3.3 内核日志与/proc/interrupts实测手法驱动是否收到中断最快的方法看两个地方。第一个是/proc/interrupts。连续执行cat命令同时拔插HDMI线观察hdmirx对应中断行的计数有没有增长。如果有增长说明硬件信号已经到中断控制器了。如果没有增长问题在硬件引脚、pinctrl或者dts中断配置。第二个是内核日志。Rockchip的hdmirx驱动在插拔事件里通常有对应的日志输出可以用下面的命令盯dmesg -w | grep -i hdmirx正常拔插会看到类似“hdmirx: plug in”、“hdmirx: plug out”或者“rk_hdmirx: EVENT”这样的信息。如果驱动对插拔事件已经做出反应但上层没感知到就要接着看HAL层和Framework层。如果插拔了却一行日志都没有说明中断没有进来问题定位在硬件或dts不需要再往上看了。内核里还有一个比较容易忽略的节点Rockchip的V4L2媒体拓扑里hdmirx子设备会注册在media controller里。如果media链路配置错误比如pad没连接上也会导致有中断但采集不到数据。排查方法media-ctl -p -d /dev/media0看hdmirx的输出pad有没有连接到后续cif/isp的输入pad。有些SDK版本需要手动配置一条链路否则数据流跑不通。4. 从内核到Android上层插拔事件是怎么传上去的内核驱动收到中断、处理了插拔事件这只是第一步。Android上层还等着一则“插拔通知”这条通知靠uevent和HAL回调来传递。4.1 uevent与内核input事件是主要上报通道Rockchip的hdmirx驱动把热插拔状态交给用户空间通常有两条路。一条是uevent。驱动通过kobject_uevent_env向用户空间发送环境变量HAL层监听NETLINK_KOBJECT_UEVENT的消息。常见的事件变量包括ACTIONCHANGE DRIVERrockchip-hdmirx HDMI_STATE1HAL收到这些event后再往上通知Framework。另一条是input子系统。有些SDK版本会把hdmirx的插拔状态作为input事件上报用input_report_switchswitch类型是SW_LID之类。用户空间通过读取input设备节点的事件来获取状态变化。到底走哪条路取决于你手上的SDK版本。所以排查时先在自己代码里搜索HDMI_STATE或者“hdmi_state”看看驱动和HAL层对应的是哪种协议。有的方案两者都有有一路处理就够但也会有开发者两路事件都监听结果收到重复状态导致上层UI闪烁。4.2 Rockchip HAL层与HdmiControlService的监听逻辑在Rockchip的Android SDK里HDMI IN的HAL代码通常位于hardware/rockchip/hdmirx或者external目录下以native服务或HAL库的形式存在。它会打开一个uevent socket在事件循环里解析插拔事件然后回调到Android Framework的HdmiControlService。Framework这边的关键类是HdmiControlService它内部维护一个HDMI_IN的状态机。当HAL上报insert状态时Framework构建一个HdmiDeviceInfo对象然后通过回调通知系统UI和已注册的APP。当上报remove状态时Framework清理设备信息并发送拔出广播。开发者需要特别注意一个点HAL层的uevent socket只在HdmiControlService初始化时创建一次如果HAL或Framework发生异常socket可能悄悄关闭之后就再也不会收到热插拔事件。遇到“拔插一次之后再也不报”的问题除了驱动层面还要检查HAL的socket有没有死掉。可以在Native层加日志或者用lsof查看socket句柄是否还存在。另外部分RK方案的HDMI IN状态还会通过v4l2的事件机制上报也就是V4L2_EVENT_SOURCE_CHANGE。V4L2应用层可以通过select/poll监听设备节点在POLLPRI事件到来时调用VIDIOC_DQEVENT获取事件信息。如果你的方案是走V4L2事件这条路那么在应用层就不能只做普通的read采集必须把事件等待逻辑写对。4.3 应用层拿到热插拔状态后的正确姿势应用层拿到热插拔状态变更通常有两种方式。第一种是监听Android系统广播比如ACTION_HDMI_PLUGGED和Intent.ACTION_HDMI_PLUGGED在onReceive里刷新UI。这种方式最简单但前提是Framework已经把状态上报出来了。如果Framework没上报APP再怎么做都是白搭。第二种是直接通过Binder接口查询HAL状态或者通过V4L2 ioctl查询当前信号状态。这种方式更贴近底层适合做深度定制的场景。比如有些应用需要知道“当前HDMI IN输入的分辨率是多少”单纯收到插拔广播还不够还要调用查询接口拿timing信息。我的建议是应用层不要自己做轮询不要每隔几百毫秒去查一下HDMI状态。轮询一方面浪费资源更容易产生UI闪烁另一方面如果Framework层的状态机和查询接口没有同步好轮询拿到的状态和实际事件上报的状态会不一致。更稳的做法是收到插拔事件之后异步去查询一次详细状态然后刷新UI。这里有一个调试验证的小技巧在Framework已经正常上报的状态下用adb shell内置的dumpsys就可以直接看到当前HDMI IN状态adb shell dumpsys hdmi_control确认里面有没有HDMI IN的设备信息。如果dumpsys有信息说明Framework状态正常问题在APP。如果dumpsys为空说明问题在HAL或更底层。5. 实测排查四类热插拔问题的完整定位过程这一章不聊抽象原理直接按现象拆排查路径。每一类都是我实际遇到过并且最终解决了的问题排查链路可以复现。5.1 插上信号源毫无反应从哪里开始查这是最基础也最让人抓狂的问题。插入HDMI线后采集画面没有任何反应dmesg也看不到插拔日志。排查顺序是硬件电平 → 中断计数 → 驱动事件 → 上层广播。先拿万用表量HPD和5V电压。插入时5V应该有HPD按硬件设计有对应电平。如果5V没有怀疑HDMI座子接触不良或者Source端没输出。如果HPD电平不对看看HPD有没有进SoC有没有反相。这一步确定了再执行cat /proc/interrupts | grep -i hdmirx连续拔插几次观察计数。无变化就是中断没触发检查dts中断配置和pinctrl。有变化就执行dmesg | grep -i hdmirx有日志但上层不更新就用前面提到的dumpsys hdmi_control检查Framework。没有日志看驱动中断处理函数里是不是被early return挡掉了比如HPD状态确认逻辑和硬件电平不匹配。常见坑就是驱动里写死了“HPD高电平有效”而你的板子硬件是低电平有效readl一个寄存器栏。5.2 第一次能出图拔掉再插就永远识别不了这类问题在RK3588上非常典型也是热插拔最让人头疼的case。第一次插入能正常出图拔掉再插就没反应必须重启或重载驱动。先把卸载和加载驱动试一遍看能不能恢复。能恢复说明驱动的状态机在“拔出”之后进入了错误分支。需要重点检查两个地方。第一个是驱动在STREAMOFF或拔出事件中有没有关闭某个时钟下一次插入时没有重新使能。常见的是hdmirx的PHY时钟拔掉后驱动为了省电调了clk_disable_unused但插入事件到达时触发路径没有对这个时钟做prepare_enable。解决方法是在插入事件处理里显式开启所有需要的时钟。第二个是media pipeline的link状态没有复位。V4L2的media controller在stream off之后有些link会变成disconnected状态重新插入时驱动没有重建link。这时候在应用层重新调用VIDIOC_STREAMON之前先检查一下media-ctl的link配置在代码里加上重新建立link的逻辑。还有一个比较隐蔽的情况HAL层的st会话没有释放。第一次start之后HAL内部维护了一个采集线程和buffer队列拔出事件触发时线程可能挂在某个阻塞调用里没有退出。第二次插入HAL尝试重新start但线程资源没释放导致start失败。这种问题从dmesg看内核是正常的只有HAL层的logcat会露出端倪logcat -s | grep -i hdmi看到类似“start stream failed”或者“device busy”的日志就往HAL的资源释放方向查。5.3 拔插后花屏、黑屏、分辨率错乱的深层原因如果拔插之后能检测到事件但画面花屏或者分辨率识别错误问题多半出在信号时序协商上。HDMI Source端在检测到Sink存在后会重新协商分辨率。这依赖EDID。如果我们的EDID内容过时了Source端使用了无法正常解析的时序就会出现花屏。排查方法是在驱动里打开EDID dumpcat /sys/kernel/debug/dri/0/summary或者直接用I2C工具读取DDC上0x50地址的内容跟标准EDID对比。最常出问题的是4K分辨率下的像素时钟频率有些低成本HDMI线缆在长距离传输时信号质量不行导致高带宽时序不稳定。此时可以尝试在EDID里屏蔽4K 60Hz的时序强制Source端降级到1080p看问题是否消失。分辨率错乱还有一个原因是录像/显示的宽高比配置没有跟随时序更新。V4L2分辨率变化后上层必须重新查询当前的时序参数并重新协商buffer格式否则用旧的宽高比去采集新的时序数据就会错位花屏。这个需要在HAL层处理格式变更事件而不能只在启动时设置一次。5.4 从EDID异常引出的兼容性问题EDID是HDMI IN功能里最容易出兼容性问题的环节。同一个板子接A品牌的机顶盒没问题接B品牌的游戏机就黑屏接PC显卡能出图接MacBook就闪烁。最常见的根因是EDID里声明的颜色深度和色彩空间与实际RX控制器支持的不匹配。RK3588 hdmirx支持的色彩空间通常是RGB和YCbCr444/420但如果你在EDID里声明了YCbCr444支持而实际链路协商失败Source端就会输出导致解码异常的信号。稳妥起见在EDID里只声明RGB和YCbCr420关掉YCbCr444兼容性会好很多。另一个是EDID的DDC通信时序问题。HDMI DDC的通信速率标准是100kHz主控那边如果I2C时钟配得太快源设备会读EDID失败。有些Source端设备对EDID读取失败的容忍度极低读一次失败就不再尝试表现为“插上无反应”。在驱动里可以加EDID重试机制for (retry 0; retry 3; retry) { if (rk_hdmi_read_edid(...) 0) break; msleep(50); }别小看这50ms的重试间隔实测能把兼容性错误率降低一大截。6. 源码里值得改的优化点与工程建议热插拔问题修到能出图并不代表结束。量产项目的稳定性还取决于几个容易被忽略的细节。6.1 插拔去抖别让毛刺事件把状态机带偏HDMI座子插入瞬间金属触点会经历物理弹跳信号在几十毫秒内会多次跳变。如果每个跳变都触发一次中断驱动就会收到一连串插拔事件状态机可能反复切换最终停在错误状态。硬件上可以加RC滤波但软件去抖更灵活。在中断处理函数里用一个timer来延迟确认状态中断到达后停止当前timer重新启动一个20ms~50ms的单次timertimer到期后再读取HPD电平确认真实状态。这种做法可以大幅减少毛刺带来的误判。static void hdmirx_hpd_timer_handler(struct timer_list *t) { // 读取HPD电平确认是插入还是拔出 if (hdmirx_read_hpd()) { schedule_work(hdmirx-plug_in_work); } else { schedule_work(hdmirx-plug_out_work); } }6.2 热插拔联调的固化顺序做过多块RK3588方案之后我把热插拔联调固化成了下面这套顺序推荐你也按这个来能省去重复排查的时间确认HPD和5V电平在插拔瞬间有明确跳变这一步用万用表/示波器5分钟。确认/proc/interrupts中断计数随插拔增长这一步30秒。确认dmesg里驱动有插拔日志输出这一步1分钟。确认HAL层收到事件看logcat或加调试打印。确认Framework dumpsys信息更新执行dumpsys hdmi_control。确认APP回调触发UI刷新。哪一步断了就修哪一步千万不要在步骤2没通过的情况下去查APP逻辑。6.3 兼容性测试清单与ROI取舍量产前HDMI IN热插拔至少要覆盖这些测试目标信号源类型PC显卡、电视机顶盒、游戏主机、笔记本HDMI输出。分辨率覆盖720p 60Hz、1080p 60Hz、4K 30Hz、4K 60Hz。拔插时序通电前插好再开机、开机后插入、持续频繁拔插100次、拔出后立刻插入。异常场景Source端断电后拔线、Source端切换分辨率时拔线、HDMI线虚接。最后还是要说回一个踩过多次坑的建议如果条件允许在硬件设计阶段就把HPD和5V检测都引到SoC的独立GPIO上做成双路检测。软件侧可以做一个“HPD为高但5V为低”的异常状态判断有效规避插头未完全插入时导致的信号抖动。工作多年回头看热插拔问题修到最后拼的往往不是代码能力而是对整个链路每一层行为的熟悉程度。先把链路摸清楚问题就解决了一半。
返回列表