diff options
author | Ben Widawsky <ben@bwidawsk.net> | 2011-04-25 11:25:20 -0700 |
---|---|---|
committer | Keith Packard <keithp@keithp.com> | 2011-05-10 13:56:47 -0700 |
commit | 4912d04193733a825216b926ffd290fada88ab07 (patch) | |
tree | 28f299240c04955f9cca7a7fad794ece744da4bb /drivers/gpu/drm/i915/i915_irq.c | |
parent | d1ebd816e6d7967c764f0cfa7d718f7c5cc7a8e4 (diff) |
drm/i915: move gen6 rps handling to workqueue
The render P-state handling code requires reading from a GT register.
This means that FORCEWAKE must be written to, a resource which is shared
and should be protected by struct_mutex. Hence we can not manipulate
that register from within the interrupt handling and so must delegate
the task to a workqueue.
Signed-off-by: Ben Widawsky <ben@bwidawsk.net>
Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Signed-off-by: Keith Packard <keithp@keithp.com>
Diffstat (limited to 'drivers/gpu/drm/i915/i915_irq.c')
-rw-r--r-- | drivers/gpu/drm/i915/i915_irq.c | 49 |
1 files changed, 41 insertions, 8 deletions
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index edd208e4730..5be2aa0f54a 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -367,22 +367,30 @@ static void notify_ring(struct drm_device *dev, jiffies + msecs_to_jiffies(DRM_I915_HANGCHECK_PERIOD)); } -static void gen6_pm_irq_handler(struct drm_device *dev) +static void gen6_pm_rps_work(struct work_struct *work) { - drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private; + drm_i915_private_t *dev_priv = container_of(work, drm_i915_private_t, + rps_work); u8 new_delay = dev_priv->cur_delay; - u32 pm_iir; + u32 pm_iir, pm_imr; + + spin_lock_irq(&dev_priv->rps_lock); + pm_iir = dev_priv->pm_iir; + dev_priv->pm_iir = 0; + pm_imr = I915_READ(GEN6_PMIMR); + spin_unlock_irq(&dev_priv->rps_lock); - pm_iir = I915_READ(GEN6_PMIIR); if (!pm_iir) return; + mutex_lock(&dev_priv->dev->struct_mutex); if (pm_iir & GEN6_PM_RP_UP_THRESHOLD) { if (dev_priv->cur_delay != dev_priv->max_delay) new_delay = dev_priv->cur_delay + 1; if (new_delay > dev_priv->max_delay) new_delay = dev_priv->max_delay; } else if (pm_iir & (GEN6_PM_RP_DOWN_THRESHOLD | GEN6_PM_RP_DOWN_TIMEOUT)) { + gen6_gt_force_wake_get(dev_priv); if (dev_priv->cur_delay != dev_priv->min_delay) new_delay = dev_priv->cur_delay - 1; if (new_delay < dev_priv->min_delay) { @@ -396,12 +404,19 @@ static void gen6_pm_irq_handler(struct drm_device *dev) I915_WRITE(GEN6_RP_INTERRUPT_LIMITS, I915_READ(GEN6_RP_INTERRUPT_LIMITS) & ~0x3f0000); } + gen6_gt_force_wake_put(dev_priv); } - gen6_set_rps(dev, new_delay); + gen6_set_rps(dev_priv->dev, new_delay); dev_priv->cur_delay = new_delay; - I915_WRITE(GEN6_PMIIR, pm_iir); + /* + * rps_lock not held here because clearing is non-destructive. There is + * an *extremely* unlikely race with gen6_rps_enable() that is prevented + * by holding struct_mutex for the duration of the write. + */ + I915_WRITE(GEN6_PMIMR, pm_imr & ~pm_iir); + mutex_unlock(&dev_priv->dev->struct_mutex); } static void pch_irq_handler(struct drm_device *dev) @@ -525,13 +540,30 @@ static irqreturn_t ironlake_irq_handler(struct drm_device *dev) i915_handle_rps_change(dev); } - if (IS_GEN6(dev)) - gen6_pm_irq_handler(dev); + if (IS_GEN6(dev) && pm_iir & GEN6_PM_DEFERRED_EVENTS) { + /* + * IIR bits should never already be set because IMR should + * prevent an interrupt from being shown in IIR. The warning + * displays a case where we've unsafely cleared + * dev_priv->pm_iir. Although missing an interrupt of the same + * type is not a problem, it displays a problem in the logic. + * + * The mask bit in IMR is cleared by rps_work. + */ + unsigned long flags; + spin_lock_irqsave(&dev_priv->rps_lock, flags); + WARN(dev_priv->pm_iir & pm_iir, "Missed a PM interrupt\n"); + I915_WRITE(GEN6_PMIMR, pm_iir); + dev_priv->pm_iir |= pm_iir; + spin_unlock_irqrestore(&dev_priv->rps_lock, flags); + queue_work(dev_priv->wq, &dev_priv->rps_work); + } /* should clear PCH hotplug event before clear CPU irq */ I915_WRITE(SDEIIR, pch_iir); I915_WRITE(GTIIR, gt_iir); I915_WRITE(DEIIR, de_iir); + I915_WRITE(GEN6_PMIIR, pm_iir); done: I915_WRITE(DEIER, de_ier); @@ -1658,6 +1690,7 @@ void i915_driver_irq_preinstall(struct drm_device * dev) INIT_WORK(&dev_priv->hotplug_work, i915_hotplug_work_func); INIT_WORK(&dev_priv->error_work, i915_error_work_func); + INIT_WORK(&dev_priv->rps_work, gen6_pm_rps_work); if (HAS_PCH_SPLIT(dev)) { ironlake_irq_preinstall(dev); |