oland显卡HDMI热插拔问题分析

您所在的位置:网站首页 小米8分辨率是2k吗 oland显卡HDMI热插拔问题分析

oland显卡HDMI热插拔问题分析

2023-11-26 19:01| 来源: 网络整理| 查看: 265

......

HDMI-2disconnected( normalleftinvertedrightxaxisyaxis)

xrandr会打印出来所有的显示器信息的全部信息,包括主屏位置,显示模式(复制或者扩展),支持的分辨率等等。或者是可以看/sys/class/drm/card0-HDMI-A-1/status 。

$ cat /sys/ class/ drm/ card0- HDMI- A-1/ status

connected

$

这两个状态有一点区别是:xrandr是调用drm提供的接口读取显示器状态,而sys下的状态是drm每次更新状态之后填进去的。

因此在这个问题的上下文中,拔出hdmi线,在sys下看到状态是connected,但是执行xrandr之后,状态就更新为disconnect。不难看出,第一次驱动判断hdmi状态出错了,第二次判断是对的。接下来要分析的事,hdmi热插拔之后驱动做了什么事,具体定位是哪里判断出错了。

HDMI原理介绍

名词解释

DDC(Display Data Channel):DDC是显示器与电脑主机进行通信的一个总线标准,他的基本功能就是将显示器的基本信息发送给主机。例如:可显示频率范围、生产厂商、生产日期、产品序列号、产品型号、标准显示模式和参数、亮度、对比度、色温参数等等。

EDID(Extended Display Identification Data Standard):是显示器通过DDC传输给电脑主机的标准数据信息格式。每次启动在drm debug信息或者X的启动日志中都能看到对应显示器的EDID信息。

HPD(Hot Plug Detection):热插拔探测,为热插拔设计的。

TMDS:最小传输差分信号传输技术。

CEC(Consumer Electronics Control):消费电子控制通道,电子设备可以借助cec信号控制hdmi接口上的连接的装置,比如单键播放,系统待机等。

HDMI定义

从上图可以看出来HDMI接口包括:3个TMDS数据通道、1个TMDS时钟通道、CEC控制信号,DDC信号,+5v电源输出和HPD信号。TMDS是用来传输HDMI数据的,CEC是用了该控制试听设备的,和本文关系不大暂不介绍。DDC信号是显示器与主机电脑进行统信的一个总线,基本功能是将显示器的基本信息发送给主机(如EDID信息);HPD信号是显示器向主机发送的检测信号,用来检测显示器连接或断开。当HDMI主机检测到HPD引脚大于2v表示显示器与主机之间连接,当HDMI主机检测到HPD引脚小于0.8v表示显示器与主机之间断开了。当计算机通过HDMI接口与计算机相连时,主机通过+5v电源输出给显示器的DDC存储器供电,确保及时显示器不开机,计算机主机也能通过HDMI接口读到显示器的EDID数据。

主机设备上电后会检测HPD是都被上拉到2v以上,接着主机设备已经通过+5v电源输出给EDID ROM供电。通过DDC读取到显示器的EDID,解析分辨率。检测TMDS信号是都被拉上来,如果是,准备输出TMDS信号。

主机设备检检测到HPD小于0.8v,停止输出TMDS信号。

内核radeon驱动代码分析

通过前面原理介绍发现radeon驱动HDMI热插拔分为3部分:HPD中断触发,DDC读取EDID,最后是HDMI接口detect。

HPD中断触发

HPD电平变化之后触发中断,cpu探测到中断上来,经过解析、分发、映射等等操作,最终调到驱动的中断处理函数来。radeon驱动的中断处理函数入口是radeon_driver_irq_handler_kms函数:

irqreturn_tradeon_driver_irq_handler_kms( intirq, void*arg)

{

structdrm_device* dev= ( structdrm_device*) arg;

structradeon_device* rdev= dev-> dev_private;

irqreturn_tret;

ret = radeon_irq_process(rdev);

if(ret == IRQ_HANDLED)

pm_runtime_mark_last_busy(dev->dev);

returnret;

}

radeon_irq_process是定义好的一个宏,通过钩子函数,分别调用到si、cik或者是evergreen中真正的中断处理函数中来,这几个处理都比较类似,以si_irq_process为例。radedon中断是共享中断,诸如显示、uvd硬解、gui_idle等等事件都是通过这个中断触发的。处理函数去radeon读取wptr寄存器的值,根据寄存器内容来判断是哪类事件触发中断。这是内核对寄存器内容的定义:

* EachIVringentryis128 bits:

* [7:0]-interruptsourceid

* [31:8]-reserved

* [59:32]-interruptsourcedata

* [63:60]-reserved

* [71:64]-RINGID

* [79:72]-VMID

* [127:80]-reserved

下面是radeon驱动根据寄存器内容判断触发中断事件类型的代码段:

restart_ih:

rptr = rdev->ih.rptr;

DRM_DEBUG( "si_irq_process start: rptr %d, wptr %dn", rptr, wptr);

/* Order reading of wptr vs. reading of IH ring data */

rmb;

/* display interrupts */

si_irq_ack(rdev);

while(rptr != wptr) {

/* wptr/rptr are in bytes! */

ring_index = rptr / 4;

src_id = le32_to_cpu(rdev->ih.ring[ring_index]) & 0xff;

src_data = le32_to_cpu(rdev->ih.ring[ring_index + 1]) & 0xfffffff;

ring_id = le32_to_cpu(rdev->ih.ring[ring_index + 2]) & 0xff;

switch(src_id) {

case1: /* D1 vblank/vline */

....

case8: /* D1 page flip */

.....

case42: /* HPD hotplug */

......

在本文的上下文场景下,寄存器判断得出中断源是HPD,执行下面的操作:

case 42: /* HPD hotplug */

if(src_data msi_enabled = 0;

if(radeon_msi_ok(rdev)) {

int ret = pci_enable_msi(rdev->pdev);

if(!ret) {

rdev->msi_enabled = 1;

dev_info(rdev->dev, "radeon: using MSI.n");

}

}

INIT_DELAYED_WORK(&rdev->hotplug_work, radeon_hotplug_work_func);

INIT_WORK(&rdev->dp_work, radeon_dp_work_func);

INIT_WORK(&rdev->audio_work, r600_audio_update_hdmi);

.....

}

从中断初始化中看,任务在这时候已经被插入在工作队列里,等到真的事件上来时候才会被调用。 hotplug_work -> radeon_hotplug_work_func ,最终到 drm_helper_hpd_irq_event 中。

booldrm_helper_hpd_irq_event(struct drm_device *dev){

structdrm_connector* connector;

structdrm_connector_list_iterconn_iter;

enumdrm_connector_status old_status;

boolchanged = false;

if(!dev->mode_config.poll_enabled)

returnfalse;

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);

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);

returnchanged;

}

循环遍历每一个显示器,标记当前的设备连接状态,调用drm_helper_probe_detect得到当前状态,判断当前状态和刚刚标记的状态,如果相同则什么都不执行直接退出。如果不同,说明状态发生了变化,调用drm_kms_helper_hotplug_event重新设置当前的显示信号。

通过DDC获取EDID

显卡驱动都需要读取显示器的EDID,通过解析EDID回去显示器支持的分辨率,频率等等。radeon驱动也是读取EDID帮助判断显示器连接状态。

bool radeon_ddc_probe(struct radeon_connector *radeon_connector, bool use_aux)

{

if(radeon_connector->router.ddc_valid)

radeon_router_select_ddc_port(radeon_connector);

if(use_aux) {

ret = i2c_transfer(&radeon_connector->ddc_bus->aux.ddc, msgs, 2);

} else{

ret = i2c_transfer(&radeon_connector->ddc_bus->adapter, msgs, 2);

}

if(ret != 2)

/* Couldn't find an accessible DDC on this connector */

returnfalse;

if(drm_edid_header_is_valid(buf) < 6) {

/* Couldn't find an accessible EDID on this

* connector */

returnfalse;

}

drm_edid_header_is_valid(buf));

returntrue;

}

i2c读取显示器EDID,判断i2c读取结果和EDID合法性,都为成功的情况下,认为显示器状态正常。否则,返回失败。虽然原理中讲hpd读到电压在0.8v

HDMI接口detect

根据DDC返回值,detect函数根据显卡芯片类型、DDC类型等等因素设置显示器连接状态。

staticenum drm_connector_status

radeon_dvi_detect(struct drm_connector *connector, bool force)

{

if(radeon_connector->ddc_bus)

dret = radeon_ddc_probe(radeon_connector, false);

if(dret) {

radeon_connector->detected_by_load = false;

radeon_connector_free_edid(connector);

radeon_connector_get_edid(connector);

if(!radeon_connector->edid) {

if((rdev->family == CHIP_RS690 || rdev->family == CHIP_RS740) &&

radeon_connector->base.null_edid_counter) {

ret = connector_status_disconnected;

radeon_connector->ddc_bus = NULL;

} else{

ret = connector_status_connected;

broken_edid = true; /* defer use_digital to later */

}

} else{

radeon_connector->use_digital =

!!(radeon_connector->edid->input & DRM_EDID_INPUT_DIGITAL);

if((!radeon_connector->use_digital) && radeon_connector->shared_ddc) {

radeon_connector_free_edid(connector);

ret = connector_status_disconnected;

} else{

ret = connector_status_connected;

}

}

}

}

总结

HDMI拔出正常逻辑应该是:HPD探测到电压变化触发中断,接下来DDC读取显示器EDID返回失败,最终到dvi_detect函数中,通过DDC返回的失败,设置显示器连接状态为disconnected。

在这个问题中,HDMI拔出,HPD探测到电压变化中断触发,DDC读取显示器EDID返回成功,detect函数设置显示器连接状态是connected。那真正出错的位置是DDC不应该读取到EDID。返回搜狐,查看更多



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3