一、vblank使能与去使能流程
本文的分析的代码基于linux内核5.4.191。
当应用程序调用命令为DRM_IOCTL_MODE_ATOMIC的ioctl时,陷入内核后会调用函数drm_mode_atomic_ioctl,有如下调用流程,应用程序调用ioctl时会传入类型为struct drm_mode_atomic的参数,该结构体有一个成员flags,以下流程以flags设置了DRM_MODE_ATOMIC_NONBLOCK为例:
应用程序进程 kworker线程 driver irq handler
drm_mode_atomic_ioctl
->drm_atomic_nonblocking_commit
->drm_atomic_helper_commit
->queue_work
commit_work
->commit_tail
->drm_atomic_helper_commit_tail_rpm
->drm_atomic_helper_commit_planes
->xxx_crtc_atomic_flush driver回调函数
->drm_atomic_helper_wait_for_vblanks
->drm_crtc_vblank_get
->drm_vblank_get
->drm_crtc_vblank_count(crtc);
->wait_event_timeout 超时时间100ms
睡眠,并挂到队列vblank->queue上
xxx_crtc_irq_handler
->drm_handle_vblank
->drm_update_vblank_count
->wake_up(&vblank->queue);
->drm_crtc_vblank_put ->drm_handle_vblank_events
->drm_vblank_put ->drm_vblank_put
->vblank_disable_fn/mod_timer ->send_vblank_event
->drm_crtc_send_vblank_event
以下分析中,黄色字体为应用程序流程,蓝色字体为kworker线程流程, 绿色字体为DC irq handler流程。
1、应用程序调用queue_work后将工作加入工作队列,工作的函数为commit_work,随后应用程序继续运行不需要等待。
2、kworker线程执行应用程序加入work队列的work的函数commit_work。
3、kworker线程会调用driver的回调函数xxx_crtc_atomic_flush,该回调函数主要是设置寄存器显示图像。
4、kworker线程调用函数drm_atomic_helper_wait_for_vblanks:
- 函数drm_crtc_vblank_get将vblank的refcount即引用计数自增加1。如果引用计数自增后等于1,最终会调用DC driver注册的回调函数xxx_crtc_enable_vblank(类型为struct drm_crtc_funcs的成员enable_vblank指向该函数),该回调函数会使能DC crtc的中断;如果引用计数自增后不为1,则不做处理。
- 函数drm_crtc_vblank_count获取vblank的count值,并保存该值。
- 函数wait_event_timeout将kworker自身设置为TASK_UNINTERRUPTIBLE,并挂到vblank的queue队列上,直到超过100ms或上面保存的vblank的count值不等于新获取的vblank的count值。
5、在DC设置为每秒60帧的情况下,每16.7ms会触发一次中断, 所以100ms是远大于DC中断间隔时间的。DC中断触发后,中断处理程序调用函数drm_handle_vblank。
6、drm_handle_vblank会进行如下处理:
- 函数drm_update_vblank_count会自增vblank的count值,自增的值一般是1。
- 函数wake_up唤醒vblank的queue上的任务。此时kworker线程被唤醒,发现保存的vblank的count和新获取的vblank的count不等,则继续执行。此时kworker线程可以与DC IRQ handler并行执行。
- 函数drm_handle_vblank_events将dev的链表vblank_event_list上的所有event发送出去。在该驱动handler中,该链表为空,所以不会执行drm_vblank_put和send_vblank_event。
- 如果条件dev->vblank_disable_immediate && drm_vblank_offdelay > 0 && !atomic_read(&vblank->refcount)为真,则会调用函数vblank_disable_fn去使能(diable)vblank,关闭DC中断。在上面代码流程图中省略了。因为DC driver并未设置dev->vblank_disable_immediate为true。
- 函数drm_crtc_send_vblank_event将vblank event发送出去。
7、kworker线程调用函数drm_crtc_vblank_put,最终调用函数drm_vblank_put,该函数对vblank的引用计数refcount自减1,如果自减后不为0,则不处理;否则进行如下处理:
- 如果drm_vblank_offdelay为0,则不处理。设置该全局变量默认为5000ms。
- 如果drm_vblank_offdelay小于0,调用函数vblank_disable_fn去使能(diable)vblank。
- 如果drm_vblank_offdelay大于0且dev->vblank_disable_immediate为false,则调用函数mod_timer修改vblank的disable_timer的超时时间为当前时间加上drm_vblank_offdelay(默认为5000ms),其中disbale_timer在vblank初始化中创建,timer超时回调函数为vblank_disable_fn。
所以从上面流程分析可知,只要应用程序调用命令为DRM_IOCTL_MODE_ATOMIC的ioctl的时间不超过5000ms,则不会disable vblank,因为应用程序下一次发起DRM_IOCTL_MODE_ATOMIC ioctl时,kworker线程又会调用drm_vblank_get和drm_vblank_put,而drm_vblank_put会调用mod_timer,mod_timer等价于del_timer(timer); timer->expires = expires; add_timer(timer);,即先将timer从超时队列中删除,然后修改超时时间,最后再加入超时队列等待超时。
如果结束应用程序,从内核日志可以发现5000ms后,vblank被disable了,DC不再触发中断。
注:假设下一次发起DRM_IOCTL_MODE_ATOMIC的ioctl时,调用drm_vblank_get后(vblank 的refcount自增变为1,但是因为vblank的enabled还未设置为false,所以不会enable vblank),但是还未调用drm_vblank_put时,disable_timer超时并调用函数vblank_disable_fn,因为函数vblank_disable_fn会判断vblank的refcount,只有该值为0才会diable vblank,而此时为1所以不会disable vblank。同时drm_vblank_get和vblank_disable_fn都会有自旋锁dev->vbl_lock保护(spin_lock_irqsave),所以不会存在并发问题。
二、driver发送event方式对vlbank refcount的影响
driver有如下两种方式发送vblank event:
- driver回调函数xxx_crtc_atomic_flush将vblank event(函数参数类型为struct drm_crtc的成员state->event)保存下来。DC driver irq handler调用函数drm_crtc_send_vblank_event将保存的vblank event发送出去。上面的流程分析就是这种情况。
- driver回调函数xxx_crtc_atomic_flush先调用函数drm_crtc_vblank_get,然后调用函数drm_crtc_arm_vblank_event将vblank event插入链表dev->vblank_event_list,DC driver irq handler调用函数drm_crtc_handle_vblank,函数drm_crtc_handle_vblank最终对链表dev->vblank_event_list中的每个vblank event调用一次drm_vblank_put和send_vblank_event将vblank event发送出去。
因为第二种方式调用drm_crtc_handle_vblank会对每个vblank event调用一次drm_vblank_put,所以其他厂商的驱动在调用函数drm_crtc_arm_vblank_event前也会调用一次drm_crtc_vblank_get。这样也能保证在vblank event发送出去之前不会disable vblank。