Commit 230ca8fb authored by Laurent Pinchart's avatar Laurent Pinchart Committed by Greg Kroah-Hartman

drm: rcar-du: Fix race condition when disabling planes at CRTC stop

commit 641307df upstream.

When stopping the CRTC the driver must disable all planes and wait for
the change to take effect at the next vblank. Merely calling
drm_crtc_wait_one_vblank() is not enough, as the function doesn't
include any mechanism to handle the race with vblank interrupts.

Replace the drm_crtc_wait_one_vblank() call with a manual mechanism that
handles the vblank interrupt race.
Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Reviewed-by: default avatarKieran Bingham <kieran.bingham+renesas@ideasonboard.com>
Signed-off-by: default avatarthongsyho <thong.ho.px@rvc.renesas.com>
Signed-off-by: default avatarNhan Nguyen <nhan.nguyen.yb@renesas.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 758e22ac
......@@ -392,6 +392,31 @@ static void rcar_du_crtc_start(struct rcar_du_crtc *rcrtc)
rcrtc->started = true;
}
static void rcar_du_crtc_disable_planes(struct rcar_du_crtc *rcrtc)
{
struct rcar_du_device *rcdu = rcrtc->group->dev;
struct drm_crtc *crtc = &rcrtc->crtc;
u32 status;
/* Make sure vblank interrupts are enabled. */
drm_crtc_vblank_get(crtc);
/*
* Disable planes and calculate how many vertical blanking interrupts we
* have to wait for. If a vertical blanking interrupt has been triggered
* but not processed yet, we don't know whether it occurred before or
* after the planes got disabled. We thus have to wait for two vblank
* interrupts in that case.
*/
spin_lock_irq(&rcrtc->vblank_lock);
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0);
status = rcar_du_crtc_read(rcrtc, DSSR);
rcrtc->vblank_count = status & DSSR_VBK ? 2 : 1;
spin_unlock_irq(&rcrtc->vblank_lock);
if (!wait_event_timeout(rcrtc->vblank_wait, rcrtc->vblank_count == 0,
msecs_to_jiffies(100)))
dev_warn(rcdu->dev, "vertical blanking timeout\n");
drm_crtc_vblank_put(crtc);
}
static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
{
struct drm_crtc *crtc = &rcrtc->crtc;
......@@ -400,17 +425,16 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
return;
/* Disable all planes and wait for the change to take effect. This is
* required as the DSnPR registers are updated on vblank, and no vblank
* will occur once the CRTC is stopped. Disabling planes when starting
* the CRTC thus wouldn't be enough as it would start scanning out
* immediately from old frame buffers until the next vblank.
* required as the plane enable registers are updated on vblank, and no
* vblank will occur once the CRTC is stopped. Disabling planes when
* starting the CRTC thus wouldn't be enough as it would start scanning
* out immediately from old frame buffers until the next vblank.
*
* This increases the CRTC stop delay, especially when multiple CRTCs
* are stopped in one operation as we now wait for one vblank per CRTC.
* Whether this can be improved needs to be researched.
*/
rcar_du_group_write(rcrtc->group, rcrtc->index % 2 ? DS2PR : DS1PR, 0);
drm_crtc_wait_one_vblank(crtc);
rcar_du_crtc_disable_planes(rcrtc);
/* Disable vertical blanking interrupt reporting. We first need to wait
* for page flip completion before stopping the CRTC as userspace
......@@ -548,9 +572,24 @@ static irqreturn_t rcar_du_crtc_irq(int irq, void *arg)
irqreturn_t ret = IRQ_NONE;
u32 status;
spin_lock(&rcrtc->vblank_lock);
status = rcar_du_crtc_read(rcrtc, DSSR);
rcar_du_crtc_write(rcrtc, DSRCR, status & DSRCR_MASK);
if (status & DSSR_VBK) {
/*
* Wake up the vblank wait if the counter reaches 0. This must
* be protected by the vblank_lock to avoid races in
* rcar_du_crtc_disable_planes().
*/
if (rcrtc->vblank_count) {
if (--rcrtc->vblank_count == 0)
wake_up(&rcrtc->vblank_wait);
}
}
spin_unlock(&rcrtc->vblank_lock);
if (status & DSSR_VBK) {
drm_crtc_handle_vblank(&rcrtc->crtc);
rcar_du_crtc_finish_page_flip(rcrtc);
......@@ -606,6 +645,8 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index)
}
init_waitqueue_head(&rcrtc->flip_wait);
init_waitqueue_head(&rcrtc->vblank_wait);
spin_lock_init(&rcrtc->vblank_lock);
rcrtc->group = rgrp;
rcrtc->mmio_offset = mmio_offsets[index];
......
......@@ -15,6 +15,7 @@
#define __RCAR_DU_CRTC_H__
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <drm/drmP.h>
......@@ -33,6 +34,9 @@ struct rcar_du_vsp;
* @started: whether the CRTC has been started and is running
* @event: event to post when the pending page flip completes
* @flip_wait: wait queue used to signal page flip completion
* @vblank_lock: protects vblank_wait and vblank_count
* @vblank_wait: wait queue used to signal vertical blanking
* @vblank_count: number of vertical blanking interrupts to wait for
* @outputs: bitmask of the outputs (enum rcar_du_output) driven by this CRTC
* @group: CRTC group this CRTC belongs to
*/
......@@ -48,6 +52,10 @@ struct rcar_du_crtc {
struct drm_pending_vblank_event *event;
wait_queue_head_t flip_wait;
spinlock_t vblank_lock;
wait_queue_head_t vblank_wait;
unsigned int vblank_count;
unsigned int outputs;
struct rcar_du_group *group;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment