Overview
一篇关于垂直同步 V-Sync 解释的文章:https://daily.elepover.com/2021/03/27/vsync/index.html
当 GPU 渲染的速度 > 显示器刷新的速度时,GPU 在显示器还来不及完成渲染完一帧时,就切换了 framebuffer,会导致出现撕裂现象。
帧率大于显示器刷新率时,启用垂直同步。
帧率小于显示器刷新率时,禁用垂直同步。
为了支持 vblank,底层 drvier 需要调用 drm_vblank_init() 初始化,另外需要实现 drm_crtc_funcs.enable_vblank 和 drm_crtc_funcs.disable_vblank 两个回调函数,并且在 vblank 中断中调用 drm_crtc_handle_vblank()。
数据结构
每个 crtc 对应一个 struct drm_vblank_crtc 结构体。
struct drm_vblank_crtc {
struct drm_device *dev;
wait_queue_head_t queue;
struct timer_list disable_timer;
seqlock_t seqlock;
atomic64_t count;
ktime_t time;
atomic_t refcount;
u32 last;
u32 max_vblank_count;
unsigned int inmodeset;
unsigned int pipe;
int framedur_ns;
int linedur_ns;
struct drm_display_mode hwmode;
bool enabled;
struct kthread_worker *worker;
struct list_head pending_work;
wait_queue_head_t work_wait_queue;
};
pipe
: 表示第几个 crtc,在 drm_vblank_init 中初始化。
refcount
: vblank interrupt user/waiter 数量。
max_vblank_count
: vblank register 最大的范围,如果不为 0 表示支持硬件 vblank count 计数,底层 driver 初始化该数值,并且 drm_crtc_funcs.get_vblank_counter 必须提供。
inmodeset
: 表示是否在 modeset 过程中,1 vblank is disabled, 0 vblank is enabled.
struct drm_pending_vblank_event 保存在 crtc_state->event 中
struct drm_pending_vblank_event {
struct drm_pending_event base;
unsigned int pipe;
u64 sequence;
union {
struct drm_event base;
struct drm_event_vblank vbl;
struct drm_event_crtc_sequence seq;
} event;
};
pipe: 属于哪一个 crtc id.
sequence: 硬件 vblank 应该在该数量 trigger.
struct drm_pending_event {
struct completion *completion;
void (*completion_release)(struct completion *completion);
struct drm_event *event;
struct dma_fence *fence;
struct drm_file *file_priv;
struct list_head link;
struct list_head pending_link;
};
function flow
init 函数中首先调用 drm_vblank_init():
int drm_vblank_init(struct drm_device *dev, unsigned int num_crtcs)
{
int ret;
unsigned int i;
dev->vblank = drmm_kcalloc(dev, num_crtcs, sizeof(*dev->vblank), GFP_KERNEL);
dev->num_crtcs = num_crtcs;
for (i = 0; i < num_crtcs; i++) {
struct drm_vblank_crtc *vblank = &dev->vblank[i];
vblank->dev = dev;
vblank->pipe = i;
init_waitqueue_head(&vblank->queue);
timer_setup(&vblank->disable_timer, vblank_disable_fn, 0);
ret = drmm_add_action_or_reset(dev, drm_vblank_init_release,
vblank);
ret = drm_vblank_worker_init(vblank);
}
return 0;
}
接着做 reset 的动作:
drm_mode_config_reset();
crtc->funcs->reset(); // drm_atomic_helper_crtc_reset()
drm_crtc_vblank_reset();
void drm_crtc_vblank_reset(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
unsigned int pipe = drm_crtc_index(crtc);
struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
spin_lock_irq(&dev->vbl_lock);
if (!vblank->inmodeset) {
atomic_inc(&vblank->refcount);
vblank->inmodeset = 1;
}
spin_unlock_irq(&dev->vbl_lock);
}
在 userspace 进行 atomic commit 之后,会先创建 vblank event.
struct drm_pending_vblank_event -> struct drm_pending_event -> struct drm_event
preapre_signaling();
create_vblank_event();
struct drm_pending_vblank_event *e = NULL;
e = kzalloc(sizeof *e, GFP_KERNEL);
e->event.base.type = DRM_EVENT_FLIP_COMPLETE;
e->event.base.length = sizeof(e->event);
e->event.vbl.crtc_id = crtc->base.id;
e->event.vbl.user_data = user_data;
crtc_state->event = e;
drm_event_reserve_init(dev, file_priv, &e->base, &e->event.base);
p->event = e; // drm_pending_event->event = e
list_add(&p->pending_link, &file_priv->pending_event_list); // 把 drm_pending_event 挂入链表
p->file_priv = file_priv;
drm_atomic_helper_setup_commit();
new_crtc_state->event->base.completion = &commit->flip_done;
new_crtc_state->event->base.completion_release = release_crtc_commit;
接着进入 crtc_funcs->atomic_disable() 回调,先调用 drm_crtc_vblank_off() 关掉 vblank 中断。
再调用 drm_crtc_vblank_on() 打开中断。
void drm_crtc_vblank_on(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
unsigned int pipe = drm_crtc_index(crtc);
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
drm_reset_vblank_timestamp(dev, pipe);
if (atomic_read(&vblank->refcount) != 0 || drm_vblank_offdelay == 0)
drm_WARN_ON(dev, drm_vblank_enable(dev, pipe));
}
static int drm_vblank_enable(struct drm_device *dev, unsigned int pipe)
{
struct drm_vblank_crtc *vblank = drm_vblank_crtc(dev, pipe);
int ret = 0;
if (!vblank->enabled) {
ret = __enable_vblank(dev, pipe); // crtc->funcs->enable_vblank
if (ret) {
atomic_dec(&vblank->refcount);
} else {
drm_update_vblank_count(dev, pipe, 0);
WRITE_ONCE(vblank->enabled, true);
}
}
return ret;
}
进入 driver 中断,在中断中调用 drm_crtc_handle_vblank(), 这个函数的作用有唤醒 commit 过程后面的 drm_atomic_helper_wait_for_vblanks() 阻塞等待 vblank 的函数,并且向 userspace 发送通过 drm_crtc_arm_vblank_event() 添加到 vblank_event_list 中的 pending event。
drm_crtc_handle_vblank();
wake_up(&vblank->queue); // 唤醒 drm_atomic_helper_wait_for_vblanks()
// 对通过 drm_crtc_arm_vblank_event() 保存到 vblank_event_list 的 pending event 处理
drm_handle_vblank_events(dev, pipe);
还需要调用 drm_crtc_send_vblank_event() 向 userspace 发送 event 事件。
void drm_crtc_send_vblank_event(struct drm_crtc *crtc,
struct drm_pending_vblank_event *e)
{
struct drm_device *dev = crtc->dev;
u64 seq;
unsigned int pipe = drm_crtc_index(crtc);
ktime_t now;
if (drm_dev_has_vblank(dev)) {
seq = drm_vblank_count_and_time(dev, pipe, &now);
}
e->pipe = pipe;
send_vblank_event(dev, e, seq, now);
}
send_vblank_event();
drm_send_event_timestamp_locked();
drm_send_event_helper();
if (e->completion) {
// 这边是 drm_atomic_helper_setup_commit() 中设定的
// new_crtc_state->event->base.completion = &commit->flip_done;
complete_all(e->completion);
e->completion_release(e->completion);
e->completion = NULL;
}
在完成 send_vblank_event() 之后,唤醒 drm_atomic_helper_wait_for_vblanks() ,结束当前的 atomic commit.
注意到各类驱动处理 vblank event 有两种方式:
第一种一般在 crtc_funcs->atomic_flush 中调用 drm_crtc_arm_vblank_event() 将 drm event 放入 pending_event_list 中,等到进入 isr 的时候,只需调用 drm_crtc_handle_vblank(), 会把 drm event 发送给 userspace。
第二种在 isr 中调用 drm_crtc_handle_vblank() + drm_crtc_send_vblank_event(),这时候 pending_event_list 为空因为之前没有调用过 drm_crtc_arm_vblank_event(), 所以需要手动调用 drm_crtc_send_vblank_event() 来发送 drm event 给 userspace。
drm_crtc_arm_vblank_event() 函数注释中写到,需要在整个 atomic commit sequence 完成后才能打开 vblank 中断,否则会出现竞态同步问题。