본문 바로가기
Linux/DRM

DRM flip-work

by Jayden77 2022. 1. 24.

DRM flip-wrok 는  kernel-3.12 부터 추가 되었다. 

flip-work는 drm framebuffer 객체가 시스템에서 해제되는 것을 지연 시키기위해서 사용한다.

자세한 내용은 아래 struct drm_framebuffer 에 대한 주석 을 참고

commit cabaafc78935521c5abc7ec72278dbaa5400c995
Author: Rob Clark <robdclark@gmail.com>
Date:   Wed Aug 7 14:41:54 2013 -0400

    drm: add flip-work helper

    A small helper to queue up work to do, from workqueue context, after a
    flip.  Typically useful to defer unreffing buffers that may be read by
    the display controller until vblank.

    v1: original
    v2: wire up docbook + couple docbook fixes

    Signed-off-by: Rob Clark <robdclark@gmail.com>
    Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
    Signed-off-by: Dave Airlie <airlied@redhat.com>

 

drm_flip_work_init

1) queued와 commited 리스트를 초기화 한다.

2) spinlock을 초기화 한다.

3) work->worker에 flip_worker를 등록한다.

void drm_flip_work_init(struct drm_flip_work *work,
                const char *name, drm_flip_func_t func)
{
        work->name = name;
        INIT_LIST_HEAD(&work->queued);
        INIT_LIST_HEAD(&work->commited);
        spin_lock_init(&work->lock);
        work->func = func;

        INIT_WORK(&work->worker, flip_worker);
}
EXPORT_SYMBOL(drm_flip_work_init);

 

drm_flip_work_commit 

1) commited  리스트에 queue 리스트를 추가한다.

2) queued 리스트를 초기화 한다.

3) worker를 실행한다. *) flip_worker가 실행된다.

void drm_flip_work_commit(struct drm_flip_work *work,
                struct workqueue_struct *wq)
{
        unsigned long flags;

        spin_lock_irqsave(&work->lock, flags);
        list_splice_tail(&work->queued, &work->commited);
        INIT_LIST_HEAD(&work->queued);
        spin_unlock_irqrestore(&work->lock, flags);
        queue_work(wq, &work->worker);
}
EXPORT_SYMBOL(drm_flip_work_commit);

 

drm_flip_work_queue

1) drm_flip_task를 drm_flip_work_allocate_task를 호출해서 할당한다. 

2) task 할당이 성공하면 drm_flip_work_queue_task를 호출해서 task를 queued리스트 에 추가 한다. 

3) task 할당이 실패 하면 work의 func를 바로 실행한다. 

void drm_flip_work_queue(struct drm_flip_work *work, void *val)
{
        struct drm_flip_task *task;

        task = drm_flip_work_allocate_task(val,
                                drm_can_sleep() ? GFP_KERNEL : GFP_ATOMIC);
        if (task) {
                drm_flip_work_queue_task(work, task);
        } else {
                DRM_ERROR("%s could not allocate task!\n", work->name);
                work->func(work, val);
        }
}
EXPORT_SYMBOL(drm_flip_work_queue);

 

보통 crtc를 초기화 할때 drm_flip_work_init를 호출하고 crtc를 종료할때 drm_flip_work_cleanup 를 호출 한다. 

 

Rockchip  flip-work 동작

1) crtc의 atomic_flush에서 drm_framebuffer_get을 호출해서 fb에 대한 참조 계수를 증가 시킨다.

   drm_crtc_vblank_get를 호출 해서 vblank 입터럽트 활성화를 보증 한다. 

2) crtc에서 vblank인터럽트가 발생하면 flip_worker가 동작하면서 사용이 완료된 fb의 참조 계수를 감소 시킨다. 

 

Rockchip 코드 분석

1) vop_create_crtc 

crtc를 생성하면서 drm_flip_work_init를 호출 한다.

*) rockchip은 work의 func을 vop_fb_unref_worker로 설정한다. 

static int vop_create_crtc(struct vop *vop)
{
	...

	drm_flip_work_init(&vop->fb_unref_work, "fb_unref",
		vop_fb_unref_worker);

	init_completion(&vop->dsp_hold_completion);
	init_completion(&vop->line_flag_completion);
	crtc->port = port;

	return 0;
	...
}

2) vop_destroy_crtc

crtc를 삭제 하면서 drm_flip_work_cleanup을 호출한다.

static void vop_destroy_crtc(struct vop *vop)
{
	...
	drm_crtc_cleanup(crtc);
	drm_flip_work_cleanup(&vop->fb_unref_work);
}

 

3) vop_fb_unref_worker

drm_crtc_vblank_put과 drm_framebuffer_put을 호출한다. 

static void vop_fb_unref_worker(struct drm_flip_work *work, void *val)
{
        struct vop *vop = container_of(work, struct vop, fb_unref_work);
        struct drm_framebuffer *fb = val;

        drm_crtc_vblank_put(&vop->crtc);
        drm_framebuffer_put(fb);
}

*) vop_fb_unref_worker에서 drm_crtc_vblank_put과 drm_framebuffer_put을 호출한다. 

 

4) vop_handler_vblank

drm_flip_work_commitvop_handler_vblank 핸들러에서 VOP_PENDING_FB_UNREF가 참인 경우 호출 된다. 

static void vop_handle_vblank(struct vop *vop)
{
	...
    
	spin_unlock_irqrestore(&drm->event_lock, flags);
	
    if (test_and_clear_bit(VOP_PENDING_FB_UNREF, &vop->pending))
    	drm_flip_work_commit(&vop->fb_unref_work, system_unbound_wq);
}

 

5) vop_crtc_atomic_flush 

drm_flip_work_queuevop_crtc_atomic_flush 에서 호출된다.

static void vop_crtc_atomic_flush(struct drm_crtc *crtc,
                                  struct drm_crtc_state *old_crtc_state)
{
        struct drm_atomic_state *old_state = old_crtc_state->state;
        struct drm_plane_state *old_plane_state, *new_plane_state;
        struct vop *vop = to_vop(crtc);
        struct drm_plane *plane;
        int i;

        if (WARN_ON(!vop->is_enabled))
                return;

        spin_lock(&vop->reg_lock);

        vop_cfg_done(vop);

        spin_unlock(&vop->reg_lock);

        /*
         * There is a (rather unlikely) possiblity that a vblank interrupt
         * fired before we set the cfg_done bit. To avoid spuriously
         * signalling flip completion we need to wait for it to finish.
         */
        vop_wait_for_irq_handler(vop);

        spin_lock_irq(&crtc->dev->event_lock);
        if (crtc->state->event) {
                WARN_ON(drm_crtc_vblank_get(crtc) != 0);
                WARN_ON(vop->event);

                vop->event = crtc->state->event;
                crtc->state->event = NULL;
        }
        spin_unlock_irq(&crtc->dev->event_lock);

        for_each_oldnew_plane_in_state(old_state, plane, old_plane_state,
                                       new_plane_state, i) {
                if (!old_plane_state->fb)
                        continue;

                if (old_plane_state->fb == new_plane_state->fb)
                        continue;

                drm_framebuffer_get(old_plane_state->fb);
                drm_flip_work_queue(&vop->fb_unref_work, old_plane_state->fb);
                set_bit(VOP_PENDING_FB_UNREF, &vop->pending);
                WARN_ON(drm_crtc_vblank_get(crtc) != 0);
        }
}

 

참고 사항

1) struct drm_framebuffer 에 대한 주석

더보기

Note that the fb is refcounted for the benefit of driver internals, for example some hw, disabling a CRTC/plane is asynchronous, andscanout does not actually complete until the next vblank.  So some cleanup (like releasing the reference(s) on the backing GEM bo(s)) should be deferred.  In cases like this, the driver would like to hold a ref to the fb even though it has already been removed from userspace perspective. See drm_framebuffer_get() and
drm_framebuffer_put(). The refcount is stored inside the mode object @base.

fb는 드라이버 내부 이점을 위해서 참조계수를 한다. 예를 들어 일부 H/W의 경우 CRTC와 plane의 비활성화가 비동기 적이고 scanout이 실제로 다음번 vblank 까지 완료되지 않을수있다. 따라서 일부 정리 단계(예 GEM bo(s)에 대한 참조 반환)는 지연되어서 처리되어야한다. 이 경우 드라이버는 사용자 공간 관점에서 이미 제거되었다고 하더라도 fb에 대한 참조를 유지 하려고 한다. drm_framebuffer_get과 drm_framebuffer_put을 참조

 

2) drm_framebuffer_get

/**
 * drm_framebuffer_get - acquire a framebuffer reference
 * @fb: DRM framebuffer
 *
 * This function increments the framebuffer's reference count.
 */
static inline void drm_framebuffer_get(struct drm_framebuffer *fb)
{
        drm_mode_object_get(&fb->base);
}

 

3) drm_framebuffer_put

/**
 * drm_framebuffer_put - release a framebuffer reference
 * @fb: DRM framebuffer
 *
 * This function decrements the framebuffer's reference count and frees the
 * framebuffer if the reference count drops to zero.
 */
static inline void drm_framebuffer_put(struct drm_framebuffer *fb)
{
        drm_mode_object_put(&fb->base);
}

 

4) struct drm_crtc_state 에 대한 주석

더보기

Note that if the driver supports vblank signalling and timestamping the vblank counters and timestamps must agree with the ones returned from page flip events.
With the current vblank helper infrastructure this can be achieved by holding a vblank reference while the page flip is pending, acquired through drm_crtc_vblank_get() and released with drm_crtc_vblank_put(). 
Drivers are free to implement their own vblank counter and timestamp tracking though, e.g. if they have accurate timestamp registers in hardware.

For hardware which supports some means to synchronize vblank interrupt delivery with committing display state there's also drm_crtc_arm_vblank_event(). See the documentation of that function for a detailed discussion of the constraints it needs to be used safely.

If the device can't notify of flip completion in a race-free way at all, then the event should be armed just after the page flip is committed. In the worst case the driver will send the event to userspace one frame too late. This doesn't allow for a real atomic update, but it should avoid tearing.

 

동작 흐름

1) A의 atomic_flush에서 old state의 fb와 new state의  fb가 동일하므로 추가적인 동작은 하지 않는다.

2) vblank 인터럽트에서 flip woker관련 동작은 하지 않는다.

    > user space관점에서 Buffer A는 삭제될수 있다. 

3) atomic_update에 의해서 new state의 fb가 변경된다.
    > display controller는 Buffer A를 출력 하고있다.   

4) B의 atomic_flush에서 old state와 new state가 일치 하지 않으므로 flip worker에 old state의 fb를 등록한다. 

    > display controller는 Buffer A를 출력 하고있다.

5) 이후 vblank 인터럽트 발생시 old state의 fb를 해제 한다. 

    > 이시점에서 display controller는 Buffer B를 출력 하고있다.

 

 

'Linux > DRM' 카테고리의 다른 글

drm_atomic_helper_commit_planes  (0) 2021.11.08
drm_connector_helper_funcs 의 get_modes 다중 호출 처리  (0) 2020.12.08