基于DRM框架的HDMI热插拔流程分析

1. DRM介绍

DRM 全称是Direct Rendering Manager,进行显示输出管理、buffer 分配、帧缓冲。对应userspace 库
为libdrm,,libdrm 库提供了一系列友好的控制封装,使用户可以方便的进行显示的控制和buffer 申请。

GDK8使用的是瑞芯微的RK3328芯片,而瑞芯微的显示框架有两大模块,分别是DRM与FB,其中FB框架是对应3.x内核的,而DRM框架是对应4.x内核,因此使用4.19.161内核的GDK8就是采用基于DRM显示框架的HDMI。

DRM的设备节点为"/dev/dri/cardX", X 为0-15 的数值,默认使用的是/dev/dri/card0

CRTC:显示控制器,在rockchip 平台是SOC 内部VOP(部分文档也称为LCDC)模块的抽象;
Plane:图层,在rockchip 平台是SOC 内部VOP(LCDC)模块win 图层的抽象;
Encoder:输出转换器,指RGB、LVDS、DSI、eDP、HDMI、CVBS、VGA 等显示接口;
Connector:连接器,指encoder 和panel 之间交互的接口部分;
Bridge:桥接设备,一般用于注册encoder 后面另外再接的转换芯片,如DSI2HDMI 转换芯片。
Panel:泛指屏,各种LCD、HDMI 等显示设备的抽象;
GEM:buffer 管理和分配,类似android 下的ion。

2. 热插拔介绍

热插拔是指在不关闭系统的前提下,插拔外部设备而不影响系统的正常使用。这个功能对需要许多外设都是非常重要的,毕竟你不可能希望拔出HDMI后,只能关机插入HDMI,再重新上电后,才能让HDMI再次工作。

DRM驱动处理热插拔的过程比较复杂,下面会对一些主要的函数设置断点,并分析其的作用。

3. HDMI热插拔流程分析

3.1 更新物理层状态

当内核检测到HDMI热插拔事件后,首先会通过dw_hdmi_phy_update_hpd函数更改hdmi的phy状态。

void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,bool force, bool disabled, bool rxsense)
{u8 old_mask = hdmi->phy_mask;if (force || disabled || !rxsense)hdmi->phy_mask |= HDMI_PHY_RX_SENSE;elsehdmi->phy_mask &= ~HDMI_PHY_RX_SENSE;if (old_mask != hdmi->phy_mask)hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
}
EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd);

在用户空间中,可以通过dw-hdmi目录下的虚文件,查看HDMI的状态信息。

cat /sys/kernel/debug/dw-hdmi/status
PHY: enabled                    Mode: HDMI
Pixel Clk: 297000000Hz          TMDS Clk: 297000000Hz
Color Format: YUV444            Color Depth: 8 bit
Colorimetry: ITU.BT709          EOTF: OffMode:当前的输出模式
Pixel Clk:当前输出的像素时钟
TMDS Clk:当前输出的HDMI符号率
Color Format:当前输出的颜色格式
Color Depth:当前输出的颜色深度
Colorimery:当前输出的颜色标准
EOTF:HDR信息

通过栈回溯可以看到,在内核检测到HDMI插拔时,内核会检测到中断事件,并创建一个新的线程来处理该事件,这个时候内核会调用dw_hdmi_irq,在dw_hdmi_irq内会去更新hdmi的phy和rxsense的状态。

kn# Child-SP          RetAddr           Call Site
00 ffffff80`0a9b3d20 ffffff80`087f17d8 lk!dw_hdmi_phy_update_hpd [drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @ 1730]
01 ffffff80`0a9b3d20 ffffff80`087f1a1c lk!dw_hdmi_setup_rx_sense+0x78 [drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @ 3178]
02 ffffff80`0a9b3d20 ffffff80`08129238 lk!dw_hdmi_irq+0x22c [drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @ 3219]
03 ffffff80`0a9b3d20 ffffff80`08129618 lk!irq_thread_fn+0x28 [kernel/irq/manage.c @ 1010]
04 ffffff80`0a9b3d20 ffffff80`080e168c lk!irq_thread+0x118 [kernel/irq/manage.c @ 1091]
05 ffffff80`0a9b3d20 ffffff80`08085dd0 lk!kthread+0x12c [kernel/kthread.c @ 260]
06 ffffff80`0a9b3d20 00000008`00000008 lk!ret_from_fork+0x10 [arch/arm64/kernel/entry.S @ 1104]

3.2 检测连接器状态

drm驱动通过drm_helper_hpd_irq_event函数检测每一个connector的状态。
通过下面的栈回溯可以看到当HDMI插入后,内核同样会检测到中断事件,并创建一个新的线程来处理该事件,在更新完phy和rxsense的状态后,由于是热插拔事件,所以会调用repo_hpd_event函数(hpd:hot plug detect)。

kn# Child-SP          RetAddr           Call Site
00 ffffff80`0abd3d60 ffffff80`087f241c lk!drm_helper_hpd_irq_event [drivers/gpu/drm/drm_probe_helper.c @ 773]
01 ffffff80`0abd3d60 ffffff80`080db1b8 lk!repo_hpd_event+0x8c [drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @ 383]
02 ffffff80`0abd3d60 ffffff80`080db47c lk!process_one_work+0x1a0 [./arch/arm64/include/asm/jump_label.h @ 31]
03 ffffff80`0abd3d60 ffffff80`080e168c lk!worker_thread+0x4c [./include/linux/compiler.h @ 193]
04 ffffff80`0abd3d60 ffffff80`08085dd0 lk!kthread+0x12c [kernel/kthread.c @ 260]
05 ffffff80`0abd3d60 00000008`00000008 lk!ret_from_fork+0x10 [arch/arm64/kernel/entry.S @ 1104]

repo_hpd_event函数中,首先会根据phy的状态设置去设置rxsense(rxsense的状态会改变多次),之后如果发现设备存在,就会调用drm_helper_hpd_irq_event

static void repo_hpd_event(struct work_struct *p_work)
{struct dw_hdmi *hdmi = container_of(p_work, struct dw_hdmi, work.work);u8 phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0);mutex_lock(&hdmi->mutex);if (!(phy_stat & HDMI_PHY_RX_SENSE))hdmi->rxsense = false;if (phy_stat & HDMI_PHY_HPD)hdmi->rxsense = true;mutex_unlock(&hdmi->mutex);if (hdmi->bridge.dev) {bool change;change = drm_helper_hpd_irq_event(hdmi->bridge.dev);#ifdef CONFIG_CEC_NOTIFIERif (change)cec_notifier_repo_cec_hpd(hdmi->cec_notifier,hdmi->hpd_state,ktime_get());
#endif}
}

drm_helper_hpd_irq_event函数主要做两个事情,分别是修改connector的状态和决定是否通知用户空间执行对应的操作。

bool drm_helper_hpd_irq_event(struct drm_device *dev)
{struct drm_connector *connector;struct drm_connector_list_iter conn_iter;enum drm_connector_status old_status;bool changed = false;if (!dev->mode_config.poll_enabled)return false;mutex_lock(&dev->mode_config.mutex);drm_connector_list_iter_begin(dev, &conn_iter);drm_for_each_connector_iter(connector, &conn_iter) {/* Only handle HPD capable connectors. */if (!(connector->polled & DRM_CONNECTOR_POLL_HPD))continue;old_status = connector->status;connector->status = drm_helper_probe_detect(connector, NULL, false);DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n",connector->base.id,connector->name,drm_get_connector_status_name(old_status),drm_get_connector_status_name(connector->status));if (old_status != connector->status)changed = true;}drm_connector_list_iter_end(&conn_iter);mutex_unlock(&dev->mode_config.mutex);if (changed)drm_kms_helper_hotplug_event(dev);return changed;
}

3.3 触发KMS事件

如果发现状态已经改变drm_helper_hpd_irq_event就会调用drm_kms_helper_hotplug_event触发KMS(kernel mode setting)事件。
其中drm_sysfs_hotplug_event通过kobject_uevent_env函数发送uevent给用户空间的udev进程;而drm_client_dev_hotplug会通知客户端发生了热插拔事件。

void drm_kms_helper_hotplug_event(struct drm_device *dev)
{/* send a uevent + call fbdev */drm_sysfs_hotplug_event(dev);if (dev->mode_config.funcs->output_poll_changed)dev->mode_config.funcs->output_poll_changed(dev);drm_client_dev_hotplug(dev);
}

3.4 转移阵地->用户空间

在Ubuntu的用户空间内有两大程序为DRM驱动服务,分别是负责管理设备的udevd守护进程和图形化程序Xorg。

3.4.1 设备管理工具-udevd

在Linux中,设备的底层支持是内核处理的,但是它们的相关事件是在用户空间中通过udevd进程进行管理的,在上面的drm_kms_helper_hotplug_event内也可以看到,内核会通过kobject_uevent_envudevd发生uevent事件,获取uevent的事件后,udevd会去通过用户空间的脚本文件执行相应的操作。

139 ?        00:00:00 systemd-udevd

udevd管理的规则文件通常放在/etc/udev/rules.d//usr/lib/udev/rules.d目录下,并且以.rules为文件的后缀名。

file /etc/udev/rules.d/60-drm.rules
/etc/udev/rules.d/60-drm.rules: ASCII text
cat /etc/udev/rules.d/60-drm.rules
SUBSYSTEM=="drm", ACTION=="change", ENV{HOTPLUG}=="1", RUN+="/usr/local/bin/drm-hotplug.sh"

通过上面的60-drm.rules文件可以知道,热插拔事件发生后,会去运行drm-hotplug.sh脚本文件,下面脚本会通过xrandr改变屏幕的显示状态。

cat /usr/local/bin/drm-hotplug.sh
#!/bin/sh -x# Try to figure out XAUTHORITY and DISPLAY
for pid in $(pgrep X 2>/dev/null || ls /proc|grep -ow "[0-9]*"|sort -rn); doPROC_DIR=/proc/$pid# Filter out non-X processesreadlink $PROC_DIR/exe|grep -qwE "X$|Xorg$" || continue# Parse auth file and display from cmd argsexport XAUTHORITY=$(cat $PROC_DIR/cmdline|tr '\0' '\n'| \grep -w "\-auth" -A 1|tail -1)export DISPLAY=$(cat $PROC_DIR/cmdline|tr '\0' '\n'| \grep -w "^:.*" || echo ":0")echo Found auth: $XAUTHORITY for dpy: $DISPLAYbreak
doneexport DISPLAY=${DISPLAY:-:0}# Find an authorized user
unset USER
for user in root $(users);dosudo -u $user xdpyinfo &>/dev/null && \{ USER=$user; break; }
done
[ $USER ] || exit 0# Find disconnected monitors
MONITORS=$(sudo -u $user xrandr|grep -w disconnected|cut -d' ' -f1)# Make sure every disconnected monitors been disabled especially DP-1.
for monitor in $MONITORS;dosudo -u $user xrandr --output $monitor --off
done# Find connected monitors
MONITORS=$(sudo -u $user xrandr|grep -w connected|cut -d' ' -f1)# Make sure every connected monitors been enabled with a valid mode.
for monitor in $MONITORS;do# Unlike the drm driver, X11 modesetting drv uses HDMI for HDMI-ACRTC=$(echo $monitor|sed "s/HDMI\(-[^B]\)/HDMI-A\1/")SYS="/sys/class/drm/card*-$CRTC/"# Already got a valid modegrep -w "$(cat $SYS/mode)" $SYS/modes && continue# Ether disabled or wrongly configuredsudo -u $user xrandr --output $monitor --auto
doneexit 0

3.4.2 图形化程序-Xorg

drm_client_dev_hotplug通过客户端Xorg热插拔事件发生后,Xorg会更新显示信息(比如合成图层,充填缓冲区),并通过libdrm库中的drmIoctl,帮助drm驱动协助处理显示信息。
首先Xorg会通过RROutputSetCrtcs获取显示设备的信息。

Bool
RROutputSetCrtcs(RROutputPtr output, RRCrtcPtr * crtcs, int numCrtcs)
{RRCrtcPtr *newCrtcs;int i;if (numCrtcs == output->numCrtcs) {for (i = 0; i < numCrtcs; i++)if (output->crtcs[i] != crtcs[i])break;if (i == numCrtcs)return TRUE;}if (numCrtcs) {newCrtcs = xallocarray(numCrtcs, sizeof(RRCrtcPtr));if (!newCrtcs)return FALSE;}elsenewCrtcs = NULL;free(output->crtcs);memcpy(newCrtcs, crtcs, numCrtcs * sizeof(RRCrtcPtr));output->crtcs = newCrtcs;output->numCrtcs = numCrtcs;RROutputChanged(output, TRUE);return TRUE;
}

之后会通过RRCrtcSet函数决定是否需要显示图形化界面。

Bool
RRCrtcSet(RRCrtcPtr crtc,RRModePtr mode,int x,int y, Rotation rotation, int numOutputs, RROutputPtr * outputs)
{ScreenPtr pScreen = crtc->pScreen;Bool ret = FALSE;Bool recompute = TRUE;Bool crtcChanged;int  o;rrScrPriv(pScreen);crtcChanged = FALSE;for (o = 0; o < numOutputs; o++) {if (outputs[o] && outputs[o]->crtc != crtc) {crtcChanged = TRUE;break;}}/* See if nothing changed */if (crtc->mode == mode &&crtc->x == x &&crtc->y == y &&crtc->rotation == rotation &&crtc->numOutputs == numOutputs &&!memcmp(crtc->outputs, outputs, numOutputs * sizeof(RROutputPtr)) &&!RRCrtcPendingProperties(crtc) && !RRCrtcPendingTransform(crtc) &&!crtcChanged) {recompute = FALSE;ret = TRUE;}else {if (pScreen->isGPU) {ScreenPtr master = pScreen->current_master;int width = 0, height = 0;if (mode) {width = mode->mode.width;height = mode->mode.height;}ret = rrCheckPixmapBounding(master, crtc,rotation, x, y, width, height);if (!ret)return FALSE;if (pScreen->current_master) {Bool sync = rrGetPixmapSharingSyncProp(numOutputs, outputs);ret = rrSetupPixmapSharing(crtc, width, height,x, y, rotation, sync,numOutputs, outputs);}}
#if RANDR_12_INTERFACEif (pScrPriv->rrCrtcSet) {ret = (*pScrPriv->rrCrtcSet) (pScreen, crtc, mode, x, y,rotation, numOutputs, outputs);}else
#endif{#if RANDR_10_INTERFACEif (pScrPriv->rrSetConfig) {RRScreenSize size;RRScreenRate rate;if (!mode) {RRCrtcNotify(crtc, NULL, x, y, rotation, NULL, 0, NULL);ret = TRUE;}else {size.width = mode->mode.width;size.height = mode->mode.height;if (outputs[0]->mmWidth && outputs[0]->mmHeight) {size.mmWidth = outputs[0]->mmWidth;size.mmHeight = outputs[0]->mmHeight;}else {size.mmWidth = pScreen->mmWidth;size.mmHeight = pScreen->mmHeight;}size.nRates = 1;rate.rate = RRVerticalRefresh(&mode->mode);size.pRates = &rate;ret =(*pScrPriv->rrSetConfig) (pScreen, rotation, rate.rate,&size);/** Old 1.0 interface tied screen size to mode size*/if (ret) {RRCrtcNotify(crtc, mode, x, y, rotation, NULL, 1,outputs);RRScreenSizeNotify(pScreen);}}}
#endif}if (ret) {RRTellChanged(pScreen);for (o = 0; o < numOutputs; o++)RRPostPendingProperties(outputs[o]);}}if (recompute)RRComputeContiguity(pScreen);return ret;
}

实验环境

硬件设备:GDK8 + 挥码枪
软件:Nano Code

GDK8(GEDU Development Kit 8)是格蠹科技针对ARMv8平台研发的开发和调试平台,预装了GNU的开发工具链,可以在ARM系统本机开发各种ARM应用软件和驱动程序, 彻底改变了传统的交叉编译方式,大大提高了开发和调试效率。 GDK8可以与格蠹科技的Nano Debugger(NDB)一起工作,使用WinDBG的各种调试命令来调试Linux程序,将Linux平台的调试技术推上一个新的台阶, 是学习和研究ARMv8架构、LINUX操作系统和嵌入式软件技术的有力助手。

挥码枪(Nano Target Probe,NTP)是基于ARM CoreSight技术的硬件调试器, 其核心功能是通过CoreSight协议访问目标系统,实现系统调试和调优等功能。

挥码枪上手指南
Nano Code下载链接

基于DRM框架的HDMI热插拔流程分析相关推荐

  1. 3588 Rockchip_基于 DRM 框架的 HDMI 开发指南

    ROCKCHIP_基于 DRM 框架的 HDMI 开发指南-1 1 简介 OVERVIEW-1 2 变量定义 VARIABLE DEFINITION -1 3 HDMI/DP 相关配置 HDMI/DP ...

  2. 基于TI C2540的OSAL UART流程分析

    一.前言 上一篇讲述了OSAL中的按键处理流程,本节再来分析一下OSAL中的UART是如何实现收发的. 在OSAL中串行通信包括USB.SPI.UART三种方式,它们被抽象出共同的NPI接口层,下面以 ...

  3. 【原创】基于SSM框架的家装网站分析与设计

    好不容易买了一个房子,还得装修,怎么装修呢? 找工人.买材料.找设计师,东奔西跑太烦了,为了响应这方面的需求,于是家装网站诞生了,目前做的比较好的装修网站,有土巴兔,我们在设计时具备一定的参考价值,那 ...

  4. Apache Mahout基于商品的协同过滤算法流程分析

    最近使用mahout的itemBase协同过滤算法,研究了下他的源码,记录如下,以备后忘-- 其算法实现大致分四个主要的部分: 1.将输入数据转化成矩阵 2.计算相似性 3.还是转化数据格式,为计算预 ...

  5. Linux内核4.14版本——drm框架分析(1)——drm简介

    目录 1. DRM简介(Direct Rendering Manager) 1.1 DRM发展历史 1.2 DRM架构对比FB架构优势 1.3 DRM图形显示框架 1.4 DRM图形显示框架涉及元素 ...

  6. 转:Android之 MTP框架和流程分析

    2019独角兽企业重金招聘Python工程师标准>>> 转载:http://www.cnblogs.com/skywang12345/p/3474206.html 概要 本文的目的是 ...

  7. mtp java_Android之 MTP框架和流程分析

    概要 本文的目的是介绍Android系统中MTP的一些相关知识.主要的内容包括: 第1部分 MTP简介 对Mtp协议进行简单的介绍. 第2部分 MTP框架 介绍Android系统下MTP的框架. 第3 ...

  8. 【Android 插件化】Hook 插件化框架 ( 从 Hook 应用角度分析 Activity 启动流程 一 | Activity 进程相关源码 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  9. 基于Android13的系统启动流程分析(三)之FirstStageMain阶段

    Android13系统启动阶段大致分为FirstStageMain阶段和SecondStageMain,此章主要讲FirstStageMain阶段 (若分析有误敬请指教) 本章讲解的方向和你将收获的知 ...

  10. 你想要的系列:网络请求框架OkHttp3全解系列 - (二)OkHttp的工作流程分析

    Okhttp系列文章: 你想要的系列:网络请求框架OkHttp3全解系列 - (一)OkHttp的基本使用 你想要的系列:网络请求框架OkHttp3全解系列 - (二)OkHttp的工作流程分析 你想 ...

最新文章

  1. Markdown编辑器使用
  2. ubuntu14.04下出现libmysqlclient.so.20找不到问题
  3. java List的用法
  4. DHCP服务和NIS服务
  5. Spring Cloud Feign 使用Apache的HTTP Client替换Feign原生httpclient
  6. 树莓派 触摸屏_如何用树莓派搭建一个颗粒物(PM2.5)传感器
  7. 吴恩达机器学习练习4:神经网络学习(损失函数和正则化)
  8. laravel application 容器app
  9. Android* 4.1.1 (Jelly Bean) x86 模拟器系统映像
  10. Leetcode 17 - Letter Combinations of a Phone Number
  11. eclipse插件下载速度慢,可以这样解决
  12. Protected Process Light(PPL)微软系统进程保护机制
  13. 关于酷狗直播伴侣无法识别虚拟摄像头问题----提供摄像头识别套装、支持远程技术协助(更新2020-11-23)
  14. HEVC帧内预测参考相邻帧代码解析
  15. Linux 并发与竞争
  16. 无人驾驶、自动驾驶MDC、车联网技术报告
  17. 使用map_server保存并使用已有地图
  18. cesium粒子系统介绍
  19. php简易日历,PHP简单日历实例
  20. 西门子1200连接安川伺服的心得

热门文章

  1. 高德地图API开发应用-----地图显示+定位+marker
  2. 关于邮件签名证书的常见问题
  3. 让GitHub沸腾的阿里内部神作Java并发原理JDK源码手册,现已开源
  4. aardio - 【开源软件】aardio文件加解密
  5. STM32 ADC多通道采样声音传感器和环境光传感器
  6. Maven server structure problem
  7. 互联网开放医疗之中医
  8. PAT 乙级 1072 开学寄语 (20分)
  9. Linux HA Cluster的实例演示(2)
  10. RFID电子标签打印机如何维护