oland显卡HDMI热插拔问题分析 |
您所在的位置:网站首页 › 小米8分辨率是2k吗 › oland显卡HDMI热插拔问题分析 |
...... 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 |