diff mbox

FBC patchset

Message ID aefc95$nsdpu@orsmga001.jf.intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Chris Wilson July 8, 2011, 7:43 p.m. UTC
Oh dear, it was all looking too good. Works fine with just one output.
Runs into a nasty race if you add a second and start unplugging things...

The issue is that when unplugging an output, userspace sees this and
appropriately reconfigures from an extended desktop to just using, for the
sake of argument, the LVDS. As the desktop is changing size this requires
a new framebuffer and so we begin to teardown the two crtcs and replace
with just the one with the new fb. The first thing we do is disable the
crtc connected to the unplugged display. This triggers an
intel_update_fbc() which spots that it can now enable FBC on the current
single crtc + old fb and promptly dispatches a delayed task to do so.

The mode reconfiguration continues apace and we disable the other crtc and
start to replace the plane's FB. This involves a couple of
wait-for-vblanks, and so is quite slow. During this time we avoid taking
the struct_mutex. Meanwhile, the delayed task kicks off on a second CPU
grabs the struct_mutex and reconfigures the FBC registers. Apparently
enabling FBC on a dead plane is an undefined operation. Hilarity ensues,
followed by a swift reboot.

Bumping to 250ms sufficiently delays the task to miss the race, but we
can not foretell just how long any given crtc modeset will take. So what
we need is to take the mode_config.lock in order to prevent concurrent
access to the FBC registers and also to prevent the fb from disappearing
beneath us:

---
 drivers/gpu/drm/i915/intel_display.c |    8 +++++++-
 1 files changed, 7 insertions(+), 1 deletions(-)

Comments

Keith Packard July 8, 2011, 8 p.m. UTC | #1
On Fri, 08 Jul 2011 20:43:47 +0100, Chris Wilson <chris@chris-wilson.co.uk> wrote:

> Bumping to 250ms sufficiently delays the task to miss the race, but we
> can not foretell just how long any given crtc modeset will take. So what
> we need is to take the mode_config.lock in order to prevent concurrent
> access to the FBC registers and also to prevent the fb from disappearing
> beneath us:

Sounds like we also need to push out any FBC reconfiguration anytime
modesetting occurs to ensure that we've waited past a vblank interval?
Chris Wilson July 8, 2011, 8:19 p.m. UTC | #2
On Fri, 08 Jul 2011 13:00:09 -0700, Keith Packard <keithp@keithp.com> wrote:
> On Fri, 08 Jul 2011 20:43:47 +0100, Chris Wilson <chris@chris-wilson.co.uk> wrote:
> 
> > Bumping to 250ms sufficiently delays the task to miss the race, but we
> > can not foretell just how long any given crtc modeset will take. So what
> > we need is to take the mode_config.lock in order to prevent concurrent
> > access to the FBC registers and also to prevent the fb from disappearing
> > beneath us:
> 
> Sounds like we also need to push out any FBC reconfiguration anytime
> modesetting occurs to ensure that we've waited past a vblank interval?

During intel_crtc_mode_set() and friends we only call into
intel_update_fbc() which just clears the FBC enabled bit and schedules a
delayed task to do the rest, if required. Fixing the mode_config locking
is sufficient to ensure that by the time the delayed task is run, the
modeset has finished, the pipe is running and at least 50ms, in the
original or 250ms in the update, has past since the final call to
intel_enable_fbc() which is at the end of *_crtc_enable().

If we are going from 2 crtcs to 1 with no swapping of the framebuffer,
then the single crtc that we want to enable FBC on, remains running for
the whole duration and the actual enabling is just deferred by 250ms.

If we are just moving the fb y-offset (e.g. panning the display), then the
pipe will remain running but we disable FBC and wait 250ms before
re-enabling.

So I think it just reduced into being an incorrect locking issue, but
we're still making the assumption that it is safe to touch the FBC just
because X ms have passed. :|
-Chris
Keith Packard July 8, 2011, 9:28 p.m. UTC | #3
On Fri, 08 Jul 2011 21:19:29 +0100, Chris Wilson <chris@chris-wilson.co.uk> wrote:
> On Fri, 08 Jul 2011 13:00:09 -0700, Keith Packard <keithp@keithp.com> wrote:
> > On Fri, 08 Jul 2011 20:43:47 +0100, Chris Wilson <chris@chris-wilson.co.uk> wrote:
> > 
> > > Bumping to 250ms sufficiently delays the task to miss the race, but we
> > > can not foretell just how long any given crtc modeset will take. So what
> > > we need is to take the mode_config.lock in order to prevent concurrent
> > > access to the FBC registers and also to prevent the fb from disappearing
> > > beneath us:
> > 
> > Sounds like we also need to push out any FBC reconfiguration anytime
> > modesetting occurs to ensure that we've waited past a vblank interval?
> 
> During intel_crtc_mode_set() and friends we only call into
> intel_update_fbc() which just clears the FBC enabled bit and schedules a
> delayed task to do the rest, if required.

Does a further call into intel_crtc_mode_set push out the delayed task
so that it now occurs at least 50ms after the later call? That seems
useful, although it might not be necessary.

> If we are going from 2 crtcs to 1 with no swapping of the framebuffer,
> then the single crtc that we want to enable FBC on, remains running for
> the whole duration and the actual enabling is just deferred by 250ms.

Which is fine; mode setting doesn't happen often.

> If we are just moving the fb y-offset (e.g. panning the display), then the
> pipe will remain running but we disable FBC and wait 250ms before
> re-enabling.

That also seems fine; we certainly don't want to spend any time
optimizing for this case.

> So I think it just reduced into being an incorrect locking issue, but
> we're still making the assumption that it is safe to touch the FBC just
> because X ms have passed. :|

We could, of course, make sure that a certain number of vblank intervals
have passed instead of using a fixed timeout. Or we could compute the
vblank interval from the mode and then just make sure the timeout used
is sufficient. Or we could just use 100ms and assume that no-one will
ever set a mode of less than 10Hz.
Chris Wilson July 8, 2011, 10:08 p.m. UTC | #4
On Fri, 08 Jul 2011 14:28:35 -0700, Keith Packard <keithp@keithp.com> wrote:
> On Fri, 08 Jul 2011 21:19:29 +0100, Chris Wilson <chris@chris-wilson.co.uk> wrote:
> > On Fri, 08 Jul 2011 13:00:09 -0700, Keith Packard <keithp@keithp.com> wrote:
> > > On Fri, 08 Jul 2011 20:43:47 +0100, Chris Wilson <chris@chris-wilson.co.uk> wrote:
> > During intel_crtc_mode_set() and friends we only call into
> > intel_update_fbc() which just clears the FBC enabled bit and schedules a
> > delayed task to do the rest, if required.
> 
> Does a further call into intel_crtc_mode_set push out the delayed task
> so that it now occurs at least 50ms after the later call? That seems
> useful, although it might not be necessary.

Yes, each call to intel_enable_fbc() resets the timer on the task,
delaying it further. The original purpose of this was to make sure that the
task did not kick off whilst the application was still page-flipping, and
it works equally well in the case of multiple calls during modeset or
very quick changes.

> We could, of course, make sure that a certain number of vblank intervals
> have passed instead of using a fixed timeout. Or we could compute the
> vblank interval from the mode and then just make sure the timeout used
> is sufficient. Or we could just use 100ms and assume that no-one will
> ever set a mode of less than 10Hz.

We already do make the assumption that 20Hz is the minimum refresh anybody
is likely to set, as a fallback for vblank timeouts. Maybe it is time we
formalized that assumption, #define VBLANK_TIMEOUT_MS 50, or
static inline int intel_get_vblank_timeout(dev, pipe) { return 50; }?
-Chris
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index e375500..bec9e1d 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -1603,6 +1603,11 @@  static void intel_fbc_work_fn(struct work_struct *__work)
 	struct drm_device *dev = work->crtc->dev;
 	struct drm_i915_private *dev_priv = dev->dev_private;
 
+	if (!mutex_trylock(&dev->mode_config.mutex)) {
+		schedule_delayed_work(&work->work, msecs_to_jiffies(100));
+		return;
+	}
+
 	mutex_lock(&dev->struct_mutex);
 	if (work == dev_priv->fbc_work) {
 		/* Double check that we haven't switched fb without cancelling
@@ -1620,6 +1625,7 @@  static void intel_fbc_work_fn(struct work_struct *__work)
 		dev_priv->fbc_work = NULL;
 	}
 	mutex_unlock(&dev->struct_mutex);
+	mutex_unlock(&dev->mode_config.mutex);
 
 	kfree(work);
 }
@@ -1684,7 +1690,7 @@  static void intel_enable_fbc(struct drm_crtc *crtc, unsigned long interval)
 	 * and indeed performing the enable as a co-routine and not
 	 * waiting synchronously upon the vblank.
 	 */
-	schedule_delayed_work(&work->work, msecs_to_jiffies(50));
+	schedule_delayed_work(&work->work, msecs_to_jiffies(250));
 }
 
 void intel_disable_fbc(struct drm_device *dev)