DRM crtc Timeout 流程分析

本文深入探讨了DRM CRTC超时问题,包括vblank超时和flip done超时的分析,详细解析了等待vblanks和flip完成的函数,并概述了从推图流程到硬件完成的整个过程,旨在理解显示错误背后的原因。
摘要由CSDN通过智能技术生成

DRM crtc Timeout 流程分析

引言:相信在处理原厂显示问题时都会遇到crtc timeout 的问题,可能是hw_done / vblank / flip done 超时,软硬件工作异常导致这些标志位没有按时完成,导致显示错误。

本章主要从crtc timeout 的问题来分析drm送图流程和其中的关键点。drm有十几万行代码,都看完并铭记于心不太现实,我们所需要了解的是一些关键函数和关键喉咙。我们以timeout 的问题引入来分析过程和代码。

timeout :超时,我们显示过程是对时间敏感的,如果超时可能会引起卡顿,撕裂,黑屏等问题。

从以下几个方面来分析timeout:

  1. 首先要了解有哪些timeout
  2. vblank timeout,vblank 分析
  3. flip done timeout,framebuffer flip done,fb fence分析

打印timeout log的函数有哪些

drm_atomic_helper_wait_for_vblanks msecs_to_jiffies(500));
等待标志位vblank_count:

    for_each_old_crtc_in_state(old_state, crtc, old_crtc_state, i) {                                                                                                                 
        if (!(crtc_mask & drm_crtc_mask(crtc)))                                                                                                                                      
            continue;                                                                                                                                                                
                                                                                                                                                                      
        ret = wait_event_timeout(dev->vblank[i].queue,                                                                                                                               
                old_state->crtcs[i].last_vblank_count !=                                                                                                                             
                    drm_crtc_vblank_count(crtc),                                                                                                                                     
                msecs_to_jiffies(500));                                                                                                                                                                                                                                                                                                                                  
        WARN(!ret, "[CRTC:%d:%s] vblank wait timed out\n",                                                                                                                           
             crtc->base.id, crtc->name);                                                                                                                                                                                                                                                                                                                                 
        drm_crtc_vblank_put(crtc);                                                                                                                                                   
    } 

意义:只是为了判断是否按时来中断了,然后上报,对于驱动没有太多价值。因为我们使用的是nonblock commit。主要还是看flip done。
/*
Drivers using the nonblocking commit tracking support initialized by calling
drm_atomic_helper_setup_commit() should look at
drm_atomic_helper_wait_for_flip_done() as an alternative.
*/

drm_atomic_helper_commit_cleanup_done 10 * HZ);
等待标志位flip_done:

   ret = wait_for_completion_timeout(&commit->flip_done,                                                                                                                        
                      10 * HZ);                                                                                                                                                  
    if (ret == 0) {                                                                                                                                                              
        DRM_ERROR("[CRTC:%d:%s] flip_done timed out\n",                                                                                                                          
              crtc->base.id, crtc->name);                                                                                                                                        
        sdrv_crtc_recovery(crtc);                                                                                                                                                
    }               

意义: 硬件buffer flip 完成,昭示着前置buffer我们已经用完了,和buffer fence 配合起来,release fence。
注:/在释放引用之前,必须等待vblank事件发出完成的信号,因为vblank工作没有自己的引用。 /,也就是说要先完成vblank 然后再完成flip done。

下面这三个芯驰驱动正常送图时没有用到。
drm_atomic_helper_wait_for_flip_done 10 * HZ); ----
stall_checks 10HZ);
drm_atomic_helper_wait_for_dependencies 10
HZ);

Vblank timeout 分析 (vblank 分析)

drm_atomic_helper_wait_for_vblanks 500ms
commit atomic update to hardware 把atomic update给硬件 drm_atomic_helper_commit_tail
drm_atomic_helper_commit_planes 中commit 更新DP 寄存器
drm_atomic_helper_commit_modeset_enables 确认是否需要修改mode ,开关那些,如果需要则调用enable
drm_atomic_helper_commit_hw_done complete_all(&commit->hw_done); 完成hw_done。等这个hw_done是在commit tail之前。这里主要是更新寄存器,一般不会timeout。
drm_atomic_helper_wait_for_vblanks 等待vblanks计数是否增加。超时时间500ms

ret = wait_event_timeout(dev->vblank[i].queue,                                                                                                                               
        old_state->crtcs[i].last_vblank_count !=                                                                                                                             
            drm_crtc_vblank_count(crtc),                                                                                                                                     
        msecs_to_jiffies(500));  

唤醒wait_for_vblanks :

sdrv_irq_handler
	->kunlun_crtc_handle_vblank if(val & SDRV_TCON_EOF_MASK) 
		-> drm_crtc_handle_vblank
			->drm_handle_vblank(crtc->dev, drm_crtc_index(crtc));
				->drm_update_vblank_count(dev, pipe, true);
				diff = in_vblank_irq ? 1 : 0;
					->    store_vblank(dev, pipe, diff, &t_vblank, cur_vblank);
						vblank->count += vblank_count_inc;

调用栈:

[   13.857509] CPU: 1 PID: 1414 Comm: kworker/u12:4 Tainted: G           O    4.14.61-03877-g0360838-dirty #5
[   13.857513] Hardware name: Semidrive kunlun x9 MS Board (DT)
[   13.857520] Workqueue: events_unbound commit_work
[   13.857522] Call trace:
[   13.857528] [<ffff00000808a3cc>] dump_backtrace+0x0/0x3c0
[   13.857532] [<ffff00000808a7a0>] show_stack+0x14/0x1c
[   13.857535] [<ffff000008cdddbc>] dump_stack+0xc4/0xfc
[   13.857538] [<ffff00000867c9ec>] drm_atomic_helper_wait_for_vblanks+0x188/0x1e8
[   13.857541] [<ffff00000867cbac>] drm_atomic_helper_commit_tail+0xa8/0x120
[   13.857544] [<ffff00000867db68>] commit_tail+0x4c/0x84
[   13.857547] [<ffff00000867d808>] commit_work+0x10/0x18
[   13.857551] [<ffff0000080f6a30>] process_one_work+0x1f8/0x454
[   13.857554] [<ffff0000080f6f74>] worker_thread+0x2e8/0x44c
[   13.857557] [<ffff0000080fbd3c>] kthread+0x154/0x18c
[   13.857560] [<ffff000008084d68>] ret_from_fork+0x10/0x18

函数分析:
工作队列commit_work。

drm_atomic_helper_commit 
INIT_WORK(&state->commit_work, commit_work);

调用栈:

[   49.411368] [drm] >>> nonblock = 1
[   49.411386] CPU: 1 PID: 2967 Comm: HwBinder:2893_2 Tainted: G           O    4.14.61-03877-g0360838-dirty #7
[   49.411392] Hardware name: Semidrive kunlun x9 MS Board (DT)
[   49.411397] Call trace:
[   49.411415] [<ffff00000808a3cc>] dump_backtrace+0x0/0x3c0
[   49.411428] [<ffff00000808a7a0>] show_stack+0x14/0x1c
[   49.411440] [<ffff000008cdddbc>] dump_stack+0xc4/0xfc
[   49.411453] [<ffff00000867d0f0>] drm_atomic_helper_commit+0x34/0x378
[   49.411466] [<ffff00000869f294>] drm_mode_atomic_ioctl+0x748/0xc18
[   49.411477] [<ffff00000868aed4>] drm_ioctl+0x23c/0x388
[   49.411489] [<ffff0000082955c0>] do_vfs_ioctl+0x554/0x810
[   49.411499] [<ffff0000082959e8>] SyS_ioctl+0x88/0x94
[   49.411508] Exception stack(0xffff0000113dbec0 to 0xffff0000113dc000)
[   49.411521] bec0: 0000000000000005 00000000c03864bc 0000e36de5259fc0 0000e36def8838d0
[   49.411533] bee0: 0000e36def883900 0000e36def87e150 0000000f00000061 0000000000000000
[   49.411544] bf00: 000000000000001d 0000e36de5259f48 0000e36de5259f10 0000e36de5259f48
[   49.411556] bf20: 0000e36de5259f90 0000000000000002 0000e36de525a408 0000e36de525a400
[   49.411568] bf40: 0000e36decb71280 0000e36df04e6308 0000e36de4b6c000 0000e36de525c020
[   49.411579] bf60: 00000000c03864bc 0000000000000005 0000000000000600 0000e36def899360
[   49.411591] bf80: 0000e36def8994c0 0000e36def80a660 0000e36def883840 0000e36de525c020
[   49.411603] bfa0: 0000e36def805978 0000e36de5259f80 0000e36df04e6390 0000e36de5259e90
[   49.411614] bfc0: 0000e36df0528888 00000000a0000000 0000000000000005 000000000000001d
[   49.411625] bfe0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
[   49.411636] [<ffff000008083ac0>] el0_svc_naked+0x34/0x38

flip done timeout分析

drm_atomic_helper_commit_cleanup_done 10S

drm_atomic_helper_setup_commit
	->new_crtc_state->event->base.completion = &commit->flip_done;
	struct drm_crtc_state new_crtc_state
		->struct drm_pending_vblank_event event
			->struct drm_pending_event base
				->struct completion *completion;

commit atomic update to hardware 把atomic update给硬件 drm_atomic_helper_commit_tail
drm_atomic_helper_commit_planes 中commit 更新DP 寄存器
drm_atomic_helper_commit_modeset_enables 确认是否需要修改mode ,开关那些,如果需要则调用enable
drm_atomic_helper_commit_hw_done complete_all(&commit->hw_done); 完成hw_done。等这个hw_done是在commit tail之前。这里主要是更新寄存器,一般不会timeout。
drm_atomic_helper_wait_for_vblanks 等待vblanks计数是否增加。超时时间500ms
drm_atomic_helper_cleanup_planes 清plane resource after commit 和 cleanup_fb(sdrv 驱动没有实现cleanup_fb)。
drm_atomic_helper_commit_cleanup_done
complete_all(&commit->cleanup_done);
wait_for_completion_timeout(&commit->flip_done, 10*HZ)

唤醒commit->flip_done:

xxxx_irq_handler
->kunlun_crtc_handle_vblank if(val & SDRV_TCON_EOF_MASK) 
queue_work(system_highpri_wq, &kcrtc->vsync_work);   INIT_WORK(&kcrtc->vsync_work, sdrv_wait_updatedone_work);
	->sdrv_wait_updatedone_work
		->ret = dpc->ops->update_done(dpc); (check flc done)
			->if (!ret) { drm_crtc_send_vblank_event(crtc, kcrtc->event);
				->send_vblank_event(dev, e, seq, &now);
					->drm_send_event_locked(dev, &e->base);
					complete_all(e->completion);  (new_crtc_state->event-					>base.completion = &commit->flip_done;) flip done 完成。

调用栈:

[   14.034644] CPU: 0 PID: 46 Comm: kworker/u12:1 Tainted: G           O    4.14.61-03877-g0360838-dirty #5
[   14.034647] Hardware name: Semidrive kunlun x9 MS Board (DT)
[   14.034651] Workqueue: events_unbound commit_work
[   14.034653] Call trace:
[   14.034657] [<ffff00000808a3cc>] dump_backtrace+0x0/0x3c0
[   14.034660] [<ffff00000808a7a0>] show_stack+0x14/0x1c
[   14.034663] [<ffff000008cdddbc>] dump_stack+0xc4/0xfc
[   14.034666] [<ffff00000867de28>] drm_atomic_helper_commit_cleanup_done+0xb4/0x134
[   14.034669] [<ffff00000867db70>] commit_tail+0x54/0x84
[   14.034671] [<ffff00000867d808>] commit_work+0x10/0x18
[   14.034675] [<ffff0000080f6a30>] process_one_work+0x1f8/0x454
[   14.034678] [<ffff0000080f6f74>] worker_thread+0x2e8/0x44c
[   14.034681] [<ffff0000080fbd3c>] kthread+0x154/0x18c
[   14.034684] [<ffff000008084d68>] ret_from_fork+0x10/0x18

推图流程分析:

hwc送图:

android::DrmDisplayCompositor::CommitFrame in drmdisplaycompositor.cpp
uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_ATOMIC_NONBLOCK;
libdrm 送图:
int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req, uint32_t flags,

atomic.flags = flags;
atomic.objs_ptr = VOID2U64(objs_ptr);
atomic.count_props_ptr = VOID2U64(count_props_ptr);
atomic.props_ptr = VOID2U64(props_ptr);
atomic.prop_values_ptr = VOID2U64(prop_values_ptr);
atomic.user_data = VOID2U64(user_data);

libdrm

xf86drmMode.c1451 ret = DRM_IOCTL(fd, DRM_IOCTL_MODE_ATOMIC, &atomic); in drmModeAtomicCommit()

drm kms

调用到kernel
DRM_IOCTL_DEF(DRM_IOCTL_MODE_ATOMIC, drm_mode_atomic_ioctl, DRM_MASTER|DRM_CONTROL_ALLOW|DRM_UNLOCKED),

int drm_mode_atomic_ioctl(struct drm_device *dev,
void *data, struct drm_file *file_priv)
drm_mode_atomic_ioctl函数中干的事儿:
检查传入的flag是否合理
state = drm_atomic_state_alloc(dev);
把user space 配置的KMS属性放到state中。如果是plane 的属性的话,标记plane。并把之前的fb放到old_fb
plane = obj_to_plane(obj);
plane_mask |= (1 << drm_plane_index(plane));
plane->old_fb = plane->fb;
准备fence,crtc_state->event->base.fence = fence;
ret = prepare_crtc_signaling(dev, state, arg, file_priv, &fence_state,
&num_fences);
crtc_state->event->base.fence = fence;
传入的flag是DRM_MODE_ATOMIC_NONBLOCK,调用ret = drm_atomic_nonblocking_commit(state);
drm_atomic_clean_old_fb。把最新的state的fb放到plane的fb,然后把old_fb put掉。
struct drm_framebuffer *new_fb = plane->state->fb;
if (new_fb)
drm_framebuffer_get(new_fb);
plane->fb = new_fb;
plane->crtc = plane->state->crtc;

        if (plane->old_fb)
            drm_framebuffer_put(plane->old_fb);
    }
    plane->old_fb = NULL;

complete_crtc_signaling(dev, state, fence_state, num_fences, !ret); 清除fence
释放state的引用
drop掉所有的锁然后清掉context

drm_atomic_nonblocking_commit 分析:(drm_atomic_commit block 接口,函数内容是完全一致的,只是nonblock 标志位不同)
check state的合法性和能否被处理。可以的话就送硬件处理,否则的话返回。
return config->funcs->atomic_commit(state->dev, state, true); 调用到helper drm_atomic_helper_commit函数。这里来配置推图和相关timed out时间。
int drm_atomic_helper_commit(struct drm_device *dev,
struct drm_atomic_state *state,
bool nonblock)

drm_atomic_helper_commit分析:(nonblock 和 block 都会走这个函数只是nonblock 标志位不同)
drm_atomic_helper_setup_commit 安装commit
INIT_WORK(&state->commit_work, commit_work); 初始化commit work。
drm_atomic_helper_prepare_planes prepare plane resources before commit commit前准备plane 资源
如果是block则等待plane state fence隐藏,这里涉及到dma fence dma_fence_wait 这部分后续单独分析。
drm_atomic_helper_swap_state state 新旧交换。

  1. ret = wait_for_completion_interruptible(&commit->hw_done); 设置等待硬件完成。
  2. 设置connector、crtc、plane、private obj当前state 放到old state 中,然后设置new state 为null。这里没有看到哪里用到了new state,有看到plane在reset的时候用到,但是没有找到调用者。后续stack查一下。
    有了这个序列,它完全适合plane准备/清理序列:
    a.调用drm_atomic_helper_prepare_planes(),获取已准备好的原子状态。
    b.执行其他可能失败的步骤。
    c.使用此函数将暂存状态放入当前状态指针中。
    d.实际提交硬件状态。
    e.使用@state调用drm_atomic_helper_cleanup_planes(),因为步骤3包含旧的状态。还要执行该状态所需的任何其他清理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值