上一篇了解了下驱动如何上报给vsyncworker vsync;《DRM驱动之drm_vblank》
接下来聊一下另一种与vsync相关的刷图方式。
drm刷图的接口比较多,比如drmModeSetCrtc,drmModeSetPlane,drmModeAtomicCommit等;
最近在看drm中驱动的flip done事件,想到有个接口drmModePageFlip是用到事件回调的形式刷新。
防止刷新撕裂的防止有很多种,比如阻塞,非阻塞(需要用fence来同步),还有就是可以用drmModePageFlip和drmHandleEvent来防止撕裂。
今天我们来通过这个接口,看看drm驱动是如何用事件来防止撕裂的。(主要看事件处理流程,图像的刷新简单过一下)
应用程序
void page_flip_handler(...)
{
drmModePageFlip(fd, crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, data);
...
}
int main(void)
{
drmEventContext ev = {};
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = page_flip_handler;
...
drmModePageFlip(fd, crtc_id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, data);
while (1) {
drmHandleEvent(&ev);
}
}
drm_public int drmHandleEvent(int fd, drmEventContextPtr evctx)
{
char buffer[1024];
int len, i;
struct drm_event *e;
struct drm_event_vblank *vblank;
struct drm_event_crtc_sequence *seq;
void *user_data;
len = read(fd, buffer, sizeof buffer);
if (len == 0)
return 0;
if (len < (int)sizeof *e)
return -1;
i = 0;
while (i < len) {
e = (struct drm_event *)(buffer + i);
switch (e->type) {
case DRM_EVENT_VBLANK:
... ...
break;
case DRM_EVENT_FLIP_COMPLETE:
vblank = (struct drm_event_vblank *) e;
user_data = U642VOID (vblank->user_data);
evctx->page_flip_handler(fd,
vblank->sequence,
vblank->tv_sec,
vblank->tv_usec,
user_data);
break;
case DRM_EVENT_CRTC_SEQUENCE:
... ...
break;
default:
break;
}
i += e->length;
}
return 0;
}
为了便于分析,我把所有的实现都贴在上面了,没有单拎出来。
drm驱动流程
我们先来看第一个drmModePageFlip内核具体做了什么
drmModePageFlip会推一张图,也会创建drm_pending_vblank_event。
这里commit调用的是drm_atomic_nonblocking_commit,此函数会创建workqueue进行刷图处理,因此ioctl不会等待hw配置完成。
应用就可以接着执行下面的内容
while (1) {
drmHandleEvent(&ev);
}
drmHandleEvent中read(fd, buffer, sizeof buffer);
从drm中读出event事件,当前如果没有事件进程就会休眠。
第一次read肯定是没有事件的,file_priv->event_list是空的。事件是哪里来的呢?我们只要找到谁向file_priv->event_list放事件就可以找到是事件是哪里来的了。
事件发送
我已经找到了,还是看下rockchip实现的中断服务程序
static irqreturn_t vop_isr(int irq, void *data)
{
... ...
if (active_irqs & FS_INTR) {
drm_crtc_handle_vblank(crtc);
vop_handle_vblank(vop);
active_irqs &= ~FS_INTR;
ret = IRQ_HANDLED;
}
... ...
}
上一篇我们讲过这里会更新vblank count,还有vop_handle_vblank函数,我们来看下
static void vop_handle_vblank(struct vop *vop)
{
... ...
spin_lock(&drm->event_lock);
if (vop->event) {
drm_crtc_send_vblank_event(crtc, vop->event);
drm_crtc_vblank_put(crtc);
vop->event = NULL;
}
... ...
}
如果vop里的drm_pending_vblank_event不为空,就会执行drm_crtc_send_vblank_event
分析下drm_crtc_send_vblank_event。
drm_crtc_send_vblank_event里的东西不多,其中有两点比较有意思的地方:
- e->file_priv为空的话,直接释放e;
- 就是我们要找的,把drm_pending_event添加到file_priv->event_list, 并唤醒event_wait;
不知道大家注意没,e->file_priv是在这里赋值的,也就是说不调用drm_mode_page_flip_ioctl,drm_pending_event有可能永远不会放到e->file_priv(没有追,只是猜测)
总结:
app使用drmModePageFlip刷新图像,drm driver会创建一个drm_pending_vblank_event,并且调用drm_atomic_nonblocking_commit把修改配置到hw;
app使用drmHandleEvent一直读取事件,读不到会休眠。display模块vsync中断到来之后调用中断处理函数发送drm_crtc_send_vblank_event,将drm_pending_event添加到file_priv->event_list, 并唤醒event_wait。
app使用drmHandleEvent读到事件,判断事件类型,调用event中的page_flip_handler回调,在page_flip_handler刷新下一帧图像。