drm/i915: Disable C3 when enabling vblank interrupts on i945gm
authorVille Syrjälä <ville.syrjala@linux.intel.com>
Fri, 22 Mar 2019 18:08:03 +0000 (20:08 +0200)
committerVille Syrjälä <ville.syrjala@linux.intel.com>
Mon, 25 Mar 2019 06:38:20 +0000 (08:38 +0200)
The AGPBUSY thing doesn't work on i945gm anymore. This means
the gmch is incapable of waking the CPU from C3 when an interrupt
is generated. The interrupts just get postponed indefinitely until
something wakes up the CPU. This is rather annoying for vblank
interrupts as we are unable to maintain a steady framerate
unless the machine is sufficiently loaded to stay out of C3.

To combat this let's use pm_qos to prevent C3 whenever vblank
interrupts are enabled. To maintain reasonable amount of powersaving
we will attempt to limit this to C3 only while leaving C1 and C2
enabled.

v2: Use READ_ONCE() (Chris)

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=30364
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20190322180804.3300-1-ville.syrjala@linux.intel.com
Reviewed-by: Chris Wilson <chris@chris-wilson.co.uk>
drivers/gpu/drm/i915/i915_drv.h
drivers/gpu/drm/i915/i915_irq.c

index 9c846e7..11803d4 100644 (file)
@@ -2042,6 +2042,14 @@ struct drm_i915_private {
                struct i915_vma *scratch;
        } gt;
 
+       /* For i945gm vblank irq vs. C3 workaround */
+       struct {
+               struct work_struct work;
+               struct pm_qos_request pm_qos;
+               u8 c3_disable_latency;
+               u8 enabled;
+       } i945gm_vblank;
+
        /* perform PHY state sanity checks? */
        bool chv_phy_assert[2];
 
index 2f78829..fcbe173 100644 (file)
@@ -30,6 +30,7 @@
 
 #include <linux/sysrq.h>
 #include <linux/slab.h>
+#include <linux/cpuidle.h>
 #include <linux/circ_buf.h>
 #include <drm/drm_irq.h>
 #include <drm/drm_drv.h>
@@ -3131,6 +3132,16 @@ static int i8xx_enable_vblank(struct drm_device *dev, unsigned int pipe)
        return 0;
 }
 
+static int i945gm_enable_vblank(struct drm_device *dev, unsigned int pipe)
+{
+       struct drm_i915_private *dev_priv = to_i915(dev);
+
+       if (dev_priv->i945gm_vblank.enabled++ == 0)
+               schedule_work(&dev_priv->i945gm_vblank.work);
+
+       return i8xx_enable_vblank(dev, pipe);
+}
+
 static int i965_enable_vblank(struct drm_device *dev, unsigned int pipe)
 {
        struct drm_i915_private *dev_priv = to_i915(dev);
@@ -3195,6 +3206,16 @@ static void i8xx_disable_vblank(struct drm_device *dev, unsigned int pipe)
        spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags);
 }
 
+static void i945gm_disable_vblank(struct drm_device *dev, unsigned int pipe)
+{
+       struct drm_i915_private *dev_priv = to_i915(dev);
+
+       i8xx_disable_vblank(dev, pipe);
+
+       if (--dev_priv->i945gm_vblank.enabled == 0)
+               schedule_work(&dev_priv->i945gm_vblank.work);
+}
+
 static void i965_disable_vblank(struct drm_device *dev, unsigned int pipe)
 {
        struct drm_i915_private *dev_priv = to_i915(dev);
@@ -3228,6 +3249,60 @@ static void gen8_disable_vblank(struct drm_device *dev, unsigned int pipe)
        spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags);
 }
 
+static void i945gm_vblank_work_func(struct work_struct *work)
+{
+       struct drm_i915_private *dev_priv =
+               container_of(work, struct drm_i915_private, i945gm_vblank.work);
+
+       /*
+        * Vblank interrupts fail to wake up the device from C3,
+        * hence we want to prevent C3 usage while vblank interrupts
+        * are enabled.
+        */
+       pm_qos_update_request(&dev_priv->i945gm_vblank.pm_qos,
+                             READ_ONCE(dev_priv->i945gm_vblank.enabled) ?
+                             dev_priv->i945gm_vblank.c3_disable_latency :
+                             PM_QOS_DEFAULT_VALUE);
+}
+
+static int cstate_disable_latency(const char *name)
+{
+       const struct cpuidle_driver *drv;
+       int i;
+
+       drv = cpuidle_get_driver();
+       if (!drv)
+               return 0;
+
+       for (i = 0; i < drv->state_count; i++) {
+               const struct cpuidle_state *state = &drv->states[i];
+
+               if (!strcmp(state->name, name))
+                       return state->exit_latency ?
+                               state->exit_latency - 1 : 0;
+       }
+
+       return 0;
+}
+
+static void i945gm_vblank_work_init(struct drm_i915_private *dev_priv)
+{
+       INIT_WORK(&dev_priv->i945gm_vblank.work,
+                 i945gm_vblank_work_func);
+
+       dev_priv->i945gm_vblank.c3_disable_latency =
+               cstate_disable_latency("C3");
+       pm_qos_add_request(&dev_priv->i945gm_vblank.pm_qos,
+                          PM_QOS_CPU_DMA_LATENCY,
+                          PM_QOS_DEFAULT_VALUE);
+}
+
+static void i945gm_vblank_work_fini(struct drm_i915_private *dev_priv)
+{
+       cancel_work_sync(&dev_priv->i945gm_vblank.work);
+       pm_qos_remove_request(&dev_priv->i945gm_vblank.pm_qos);
+}
+
 static void ibx_irq_reset(struct drm_i915_private *dev_priv)
 {
        if (HAS_PCH_NOP(dev_priv))
@@ -4525,6 +4600,9 @@ void intel_irq_init(struct drm_i915_private *dev_priv)
        struct intel_rps *rps = &dev_priv->gt_pm.rps;
        int i;
 
+       if (IS_I945GM(dev_priv))
+               i945gm_vblank_work_init(dev_priv);
+
        intel_hpd_init_work(dev_priv);
 
        INIT_WORK(&rps->work, gen6_pm_rps_work);
@@ -4647,6 +4725,13 @@ void intel_irq_init(struct drm_i915_private *dev_priv)
                        dev->driver->irq_uninstall = i8xx_irq_reset;
                        dev->driver->enable_vblank = i8xx_enable_vblank;
                        dev->driver->disable_vblank = i8xx_disable_vblank;
+               } else if (IS_I945GM(dev_priv)) {
+                       dev->driver->irq_preinstall = i915_irq_reset;
+                       dev->driver->irq_postinstall = i915_irq_postinstall;
+                       dev->driver->irq_uninstall = i915_irq_reset;
+                       dev->driver->irq_handler = i915_irq_handler;
+                       dev->driver->enable_vblank = i945gm_enable_vblank;
+                       dev->driver->disable_vblank = i945gm_disable_vblank;
                } else if (IS_GEN(dev_priv, 3)) {
                        dev->driver->irq_preinstall = i915_irq_reset;
                        dev->driver->irq_postinstall = i915_irq_postinstall;
@@ -4677,6 +4762,9 @@ void intel_irq_fini(struct drm_i915_private *i915)
 {
        int i;
 
+       if (IS_I945GM(i915))
+               i945gm_vblank_work_fini(i915);
+
        for (i = 0; i < MAX_L3_SLICES; ++i)
                kfree(i915->l3_parity.remap_info[i]);
 }