Message ID | 1469544617-20003-2-git-send-email-cpaul@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Op 26-07-16 om 16:50 schreef Lyude: > Since the watermark calculations for Skylake are still broken, we're apt > to hitting underruns very easily under multi-monitor configurations. > While it would be lovely if this was fixed, it's not. Another problem > that's been coming from this however, is the mysterious issue of > underruns causing full system hangs. An easy way to reproduce this with > a skylake system: > > - Get a laptop with a skylake GPU, and hook up two external monitors to > it > - Move the cursor from the built-in LCD to one of the external displays > as quickly as you can > - You'll get a few pipe underruns, and eventually the entire system will > just freeze. > > After doing a lot of investigation and reading through the bspec, I > found the existence of the SAGV, which is responsible for adjusting the > system agent voltage and clock frequencies depending on how much power > we need. According to the bspec: > > "The display engine access to system memory is blocked during the > adjustment time. SAGV defaults to enabled. Software must use the > GT-driver pcode mailbox to disable SAGV when the display engine is not > able to tolerate the blocking time." > > The rest of the bspec goes on to explain that software can simply leave > the SAGV enabled, and disable it when we use interlaced pipes/have more > then one pipe active. > > Sure enough, with this patchset the system hangs resulting from pipe > underruns on Skylake have completely vanished on my T460s. Additionally, > the bspec mentions turning off the SAGV with more then one pipe enabled > as a workaround for display underruns. While this patch doesn't entirely > fix that, it looks like it does improve the situation a little bit so > it's likely this is going to be required to make watermarks on Skylake > fully functional. > > Changes since v3: > - Use time_before() to compare timeout to jiffies > Changes since v2: > - Really apply minor style nitpicks to patch this time > Changes since v1: > - Added comments about this probably being one of the requirements to > fixing Skylake's watermark issues > - Minor style nitpicks from Matt Roper > - Disable these functions on Broxton, since it doesn't have an SAGV > > Reviewed-by: Matt Roper <matthew.d.roper@intel.com> > Signed-off-by: Lyude <cpaul@redhat.com> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> > Cc: stable@vger.kernel.org > --- > drivers/gpu/drm/i915/i915_drv.h | 2 + > drivers/gpu/drm/i915/i915_reg.h | 5 ++ > drivers/gpu/drm/i915/intel_pm.c | 110 ++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 117 insertions(+) > > diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h > index 9f655e2..1f6fe8c 100644 > --- a/drivers/gpu/drm/i915/i915_drv.h > +++ b/drivers/gpu/drm/i915/i915_drv.h > @@ -1962,6 +1962,8 @@ struct drm_i915_private { > struct i915_suspend_saved_registers regfile; > struct vlv_s0ix_state vlv_s0ix_state; > > + bool skl_sagv_enabled; > + > struct { > /* > * Raw watermark latency values: > diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h > index 9397dde..89d146f 100644 > --- a/drivers/gpu/drm/i915/i915_reg.h > +++ b/drivers/gpu/drm/i915/i915_reg.h > @@ -7166,6 +7166,11 @@ enum { > #define HSW_PCODE_DE_WRITE_FREQ_REQ 0x17 > #define DISPLAY_IPS_CONTROL 0x19 > #define HSW_PCODE_DYNAMIC_DUTY_CYCLE_CONTROL 0x1A > +#define GEN9_PCODE_SAGV_CONTROL 0x21 > +#define GEN9_SAGV_DISABLE 0x0 > +#define GEN9_SAGV_LOW_FREQ 0x1 > +#define GEN9_SAGV_HIGH_FREQ 0x2 > +#define GEN9_SAGV_DYNAMIC_FREQ 0x3 > #define GEN6_PCODE_DATA _MMIO(0x138128) > #define GEN6_PCODE_FREQ_IA_RATIO_SHIFT 8 > #define GEN6_PCODE_FREQ_RING_RATIO_SHIFT 16 > diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c > index 64d628c..066fe6b 100644 > --- a/drivers/gpu/drm/i915/intel_pm.c > +++ b/drivers/gpu/drm/i915/intel_pm.c > @@ -2876,6 +2876,109 @@ skl_wm_plane_id(const struct intel_plane *plane) > } > > static void > +skl_sagv_get_hw_state(struct drm_i915_private *dev_priv) > +{ > + u32 temp; > + int ret; > + > + if (IS_BROXTON(dev_priv)) > + return; > + > + mutex_lock(&dev_priv->rps.hw_lock); > + ret = sandybridge_pcode_read(dev_priv, GEN9_PCODE_SAGV_CONTROL, &temp); > + mutex_unlock(&dev_priv->rps.hw_lock); > + > + if (!ret) { > + dev_priv->skl_sagv_enabled = !!(temp & GEN9_SAGV_DYNAMIC_FREQ); > + } else { > + /* > + * If for some reason we can't access the SAGV state, follow > + * the bspec and assume it's enabled > + */ > + DRM_ERROR("Failed to get SAGV state, assuming enabled\n"); > + dev_priv->skl_sagv_enabled = true; > + } > +} > + > +/* > + * SAGV dynamically adjusts the system agent voltage and clock frequencies > + * depending on power and performance requirements. The display engine access > + * to system memory is blocked during the adjustment time. Having this enabled > + * in multi-pipe configurations can cause issues (such as underruns causing > + * full system hangs), and the bspec also suggests that software disable it > + * when more then one pipe is enabled. > + */ > +static int > +skl_enable_sagv(struct drm_i915_private *dev_priv) > +{ > + int ret; > + > + if (IS_BROXTON(dev_priv)) > + return 0; > + if (dev_priv->skl_sagv_enabled) > + return 0; > + > + mutex_lock(&dev_priv->rps.hw_lock); > + DRM_DEBUG_KMS("Enabling the SAGV\n"); > + > + ret = sandybridge_pcode_write(dev_priv, GEN9_PCODE_SAGV_CONTROL, > + GEN9_SAGV_DYNAMIC_FREQ); > + if (!ret) > + dev_priv->skl_sagv_enabled = true; > + else > + DRM_ERROR("Failed to enable the SAGV\n"); > + > + /* We don't need to wait for SAGV when enabling */ > + mutex_unlock(&dev_priv->rps.hw_lock); > + return ret; > +} > + > +static int > +skl_disable_sagv(struct drm_i915_private *dev_priv) > +{ > + int ret = 0; > + unsigned long timeout; > + u32 temp; > + > + if (IS_BROXTON(dev_priv)) > + return 0; > + if (!dev_priv->skl_sagv_enabled) > + return 0; > + > + mutex_lock(&dev_priv->rps.hw_lock); > + DRM_DEBUG_KMS("Disabling the SAGV\n"); > + > + /* bspec says to keep retrying for at least 1 ms */ > + timeout = jiffies + msecs_to_jiffies(1); > + do { > + ret = sandybridge_pcode_write(dev_priv, GEN9_PCODE_SAGV_CONTROL, > + GEN9_SAGV_DISABLE); > + if (ret) { > + DRM_ERROR("Failed to disable the SAGV\n"); > + goto out; > + } > + > + ret = sandybridge_pcode_read(dev_priv, GEN9_PCODE_SAGV_CONTROL, > + &temp); > + if (ret) { > + DRM_ERROR("Failed to check the status of the SAGV\n"); > + goto out; > + } > + } while (!(temp & 0x1) && time_before(jiffies, timeout)); > + > + if (temp & 0x1) { > + dev_priv->skl_sagv_enabled = false; > + } else { > + ret = -1; > + DRM_ERROR("Request to disable SAGV timed out\n"); > + } > + > +out: > + mutex_unlock(&dev_priv->rps.hw_lock); > + return ret; > +} > + > +static void > skl_ddb_get_pipe_allocation_limits(struct drm_device *dev, > const struct intel_crtc_state *cstate, > struct skl_ddb_entry *alloc, /* out */ > @@ -3686,6 +3789,11 @@ static void skl_write_wm_values(struct drm_i915_private *dev_priv, > struct drm_device *dev = &dev_priv->drm; > struct intel_crtc *crtc; > > + if (dev_priv->active_crtcs == 1) > + skl_enable_sagv(dev_priv); > + else > + skl_disable_sagv(dev_priv); active_crtcs is a bitmask, so unless you only want to enable it for PIPE_A, you should use: if (intel_state->modeset && hweight32(intel_state->active_crtcs) == 1) And probably put it somewhere where you have access to the atomic state. ~Maarten
On Tue, Jul 26, 2016 at 04:58:58PM +0200, Maarten Lankhorst wrote: > Op 26-07-16 om 16:50 schreef Lyude: > > Since the watermark calculations for Skylake are still broken, we're apt > > to hitting underruns very easily under multi-monitor configurations. > > While it would be lovely if this was fixed, it's not. Another problem > > that's been coming from this however, is the mysterious issue of > > underruns causing full system hangs. An easy way to reproduce this with > > a skylake system: > > > > - Get a laptop with a skylake GPU, and hook up two external monitors to > > it > > - Move the cursor from the built-in LCD to one of the external displays > > as quickly as you can > > - You'll get a few pipe underruns, and eventually the entire system will > > just freeze. > > > > After doing a lot of investigation and reading through the bspec, I > > found the existence of the SAGV, which is responsible for adjusting the > > system agent voltage and clock frequencies depending on how much power > > we need. According to the bspec: > > > > "The display engine access to system memory is blocked during the > > adjustment time. SAGV defaults to enabled. Software must use the > > GT-driver pcode mailbox to disable SAGV when the display engine is not > > able to tolerate the blocking time." > > > > The rest of the bspec goes on to explain that software can simply leave > > the SAGV enabled, and disable it when we use interlaced pipes/have more > > then one pipe active. > > > > Sure enough, with this patchset the system hangs resulting from pipe > > underruns on Skylake have completely vanished on my T460s. Additionally, > > the bspec mentions turning off the SAGV with more then one pipe enabled > > as a workaround for display underruns. While this patch doesn't entirely > > fix that, it looks like it does improve the situation a little bit so > > it's likely this is going to be required to make watermarks on Skylake > > fully functional. > > > > Changes since v3: > > - Use time_before() to compare timeout to jiffies > > Changes since v2: > > - Really apply minor style nitpicks to patch this time > > Changes since v1: > > - Added comments about this probably being one of the requirements to > > fixing Skylake's watermark issues > > - Minor style nitpicks from Matt Roper > > - Disable these functions on Broxton, since it doesn't have an SAGV > > > > Reviewed-by: Matt Roper <matthew.d.roper@intel.com> > > Signed-off-by: Lyude <cpaul@redhat.com> > > Cc: Daniel Vetter <daniel.vetter@ffwll.ch> > > Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> > > Cc: stable@vger.kernel.org > > --- > > drivers/gpu/drm/i915/i915_drv.h | 2 + > > drivers/gpu/drm/i915/i915_reg.h | 5 ++ > > drivers/gpu/drm/i915/intel_pm.c | 110 ++++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 117 insertions(+) > > > > diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h > > index 9f655e2..1f6fe8c 100644 > > --- a/drivers/gpu/drm/i915/i915_drv.h > > +++ b/drivers/gpu/drm/i915/i915_drv.h > > @@ -1962,6 +1962,8 @@ struct drm_i915_private { > > struct i915_suspend_saved_registers regfile; > > struct vlv_s0ix_state vlv_s0ix_state; > > > > + bool skl_sagv_enabled; > > + > > struct { > > /* > > * Raw watermark latency values: > > diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h > > index 9397dde..89d146f 100644 > > --- a/drivers/gpu/drm/i915/i915_reg.h > > +++ b/drivers/gpu/drm/i915/i915_reg.h > > @@ -7166,6 +7166,11 @@ enum { > > #define HSW_PCODE_DE_WRITE_FREQ_REQ 0x17 > > #define DISPLAY_IPS_CONTROL 0x19 > > #define HSW_PCODE_DYNAMIC_DUTY_CYCLE_CONTROL 0x1A > > +#define GEN9_PCODE_SAGV_CONTROL 0x21 > > +#define GEN9_SAGV_DISABLE 0x0 > > +#define GEN9_SAGV_LOW_FREQ 0x1 > > +#define GEN9_SAGV_HIGH_FREQ 0x2 > > +#define GEN9_SAGV_DYNAMIC_FREQ 0x3 > > #define GEN6_PCODE_DATA _MMIO(0x138128) > > #define GEN6_PCODE_FREQ_IA_RATIO_SHIFT 8 > > #define GEN6_PCODE_FREQ_RING_RATIO_SHIFT 16 > > diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c > > index 64d628c..066fe6b 100644 > > --- a/drivers/gpu/drm/i915/intel_pm.c > > +++ b/drivers/gpu/drm/i915/intel_pm.c > > @@ -2876,6 +2876,109 @@ skl_wm_plane_id(const struct intel_plane *plane) > > } > > > > static void > > +skl_sagv_get_hw_state(struct drm_i915_private *dev_priv) > > +{ > > + u32 temp; > > + int ret; > > + > > + if (IS_BROXTON(dev_priv)) > > + return; > > + > > + mutex_lock(&dev_priv->rps.hw_lock); > > + ret = sandybridge_pcode_read(dev_priv, GEN9_PCODE_SAGV_CONTROL, &temp); > > + mutex_unlock(&dev_priv->rps.hw_lock); > > + > > + if (!ret) { > > + dev_priv->skl_sagv_enabled = !!(temp & GEN9_SAGV_DYNAMIC_FREQ); > > + } else { > > + /* > > + * If for some reason we can't access the SAGV state, follow > > + * the bspec and assume it's enabled > > + */ > > + DRM_ERROR("Failed to get SAGV state, assuming enabled\n"); > > + dev_priv->skl_sagv_enabled = true; > > + } > > +} > > + > > +/* > > + * SAGV dynamically adjusts the system agent voltage and clock frequencies > > + * depending on power and performance requirements. The display engine access > > + * to system memory is blocked during the adjustment time. Having this enabled > > + * in multi-pipe configurations can cause issues (such as underruns causing > > + * full system hangs), and the bspec also suggests that software disable it > > + * when more then one pipe is enabled. > > + */ > > +static int > > +skl_enable_sagv(struct drm_i915_private *dev_priv) > > +{ > > + int ret; > > + > > + if (IS_BROXTON(dev_priv)) > > + return 0; > > + if (dev_priv->skl_sagv_enabled) > > + return 0; > > + > > + mutex_lock(&dev_priv->rps.hw_lock); > > + DRM_DEBUG_KMS("Enabling the SAGV\n"); > > + > > + ret = sandybridge_pcode_write(dev_priv, GEN9_PCODE_SAGV_CONTROL, > > + GEN9_SAGV_DYNAMIC_FREQ); > > + if (!ret) > > + dev_priv->skl_sagv_enabled = true; > > + else > > + DRM_ERROR("Failed to enable the SAGV\n"); > > + > > + /* We don't need to wait for SAGV when enabling */ > > + mutex_unlock(&dev_priv->rps.hw_lock); > > + return ret; > > +} > > + > > +static int > > +skl_disable_sagv(struct drm_i915_private *dev_priv) > > +{ > > + int ret = 0; > > + unsigned long timeout; > > + u32 temp; > > + > > + if (IS_BROXTON(dev_priv)) > > + return 0; > > + if (!dev_priv->skl_sagv_enabled) > > + return 0; > > + > > + mutex_lock(&dev_priv->rps.hw_lock); > > + DRM_DEBUG_KMS("Disabling the SAGV\n"); > > + > > + /* bspec says to keep retrying for at least 1 ms */ > > + timeout = jiffies + msecs_to_jiffies(1); > > + do { > > + ret = sandybridge_pcode_write(dev_priv, GEN9_PCODE_SAGV_CONTROL, > > + GEN9_SAGV_DISABLE); > > + if (ret) { > > + DRM_ERROR("Failed to disable the SAGV\n"); > > + goto out; > > + } > > + > > + ret = sandybridge_pcode_read(dev_priv, GEN9_PCODE_SAGV_CONTROL, > > + &temp); > > + if (ret) { > > + DRM_ERROR("Failed to check the status of the SAGV\n"); > > + goto out; > > + } > > + } while (!(temp & 0x1) && time_before(jiffies, timeout)); > > + > > + if (temp & 0x1) { > > + dev_priv->skl_sagv_enabled = false; > > + } else { > > + ret = -1; > > + DRM_ERROR("Request to disable SAGV timed out\n"); > > + } > > + > > +out: > > + mutex_unlock(&dev_priv->rps.hw_lock); > > + return ret; > > +} > > + > > +static void > > skl_ddb_get_pipe_allocation_limits(struct drm_device *dev, > > const struct intel_crtc_state *cstate, > > struct skl_ddb_entry *alloc, /* out */ > > @@ -3686,6 +3789,11 @@ static void skl_write_wm_values(struct drm_i915_private *dev_priv, > > struct drm_device *dev = &dev_priv->drm; > > struct intel_crtc *crtc; > > > > + if (dev_priv->active_crtcs == 1) > > + skl_enable_sagv(dev_priv); > > + else > > + skl_disable_sagv(dev_priv); > active_crtcs is a bitmask, so unless you only want to enable it for PIPE_A, > you should use: if (intel_state->modeset && hweight32(intel_state->active_crtcs) == 1) is_power_of_2() is a bit cheaper. Oh and we want this this thing enabled w/ 0 active pipes too.
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index 9f655e2..1f6fe8c 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -1962,6 +1962,8 @@ struct drm_i915_private { struct i915_suspend_saved_registers regfile; struct vlv_s0ix_state vlv_s0ix_state; + bool skl_sagv_enabled; + struct { /* * Raw watermark latency values: diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 9397dde..89d146f 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -7166,6 +7166,11 @@ enum { #define HSW_PCODE_DE_WRITE_FREQ_REQ 0x17 #define DISPLAY_IPS_CONTROL 0x19 #define HSW_PCODE_DYNAMIC_DUTY_CYCLE_CONTROL 0x1A +#define GEN9_PCODE_SAGV_CONTROL 0x21 +#define GEN9_SAGV_DISABLE 0x0 +#define GEN9_SAGV_LOW_FREQ 0x1 +#define GEN9_SAGV_HIGH_FREQ 0x2 +#define GEN9_SAGV_DYNAMIC_FREQ 0x3 #define GEN6_PCODE_DATA _MMIO(0x138128) #define GEN6_PCODE_FREQ_IA_RATIO_SHIFT 8 #define GEN6_PCODE_FREQ_RING_RATIO_SHIFT 16 diff --git a/drivers/gpu/drm/i915/intel_pm.c b/drivers/gpu/drm/i915/intel_pm.c index 64d628c..066fe6b 100644 --- a/drivers/gpu/drm/i915/intel_pm.c +++ b/drivers/gpu/drm/i915/intel_pm.c @@ -2876,6 +2876,109 @@ skl_wm_plane_id(const struct intel_plane *plane) } static void +skl_sagv_get_hw_state(struct drm_i915_private *dev_priv) +{ + u32 temp; + int ret; + + if (IS_BROXTON(dev_priv)) + return; + + mutex_lock(&dev_priv->rps.hw_lock); + ret = sandybridge_pcode_read(dev_priv, GEN9_PCODE_SAGV_CONTROL, &temp); + mutex_unlock(&dev_priv->rps.hw_lock); + + if (!ret) { + dev_priv->skl_sagv_enabled = !!(temp & GEN9_SAGV_DYNAMIC_FREQ); + } else { + /* + * If for some reason we can't access the SAGV state, follow + * the bspec and assume it's enabled + */ + DRM_ERROR("Failed to get SAGV state, assuming enabled\n"); + dev_priv->skl_sagv_enabled = true; + } +} + +/* + * SAGV dynamically adjusts the system agent voltage and clock frequencies + * depending on power and performance requirements. The display engine access + * to system memory is blocked during the adjustment time. Having this enabled + * in multi-pipe configurations can cause issues (such as underruns causing + * full system hangs), and the bspec also suggests that software disable it + * when more then one pipe is enabled. + */ +static int +skl_enable_sagv(struct drm_i915_private *dev_priv) +{ + int ret; + + if (IS_BROXTON(dev_priv)) + return 0; + if (dev_priv->skl_sagv_enabled) + return 0; + + mutex_lock(&dev_priv->rps.hw_lock); + DRM_DEBUG_KMS("Enabling the SAGV\n"); + + ret = sandybridge_pcode_write(dev_priv, GEN9_PCODE_SAGV_CONTROL, + GEN9_SAGV_DYNAMIC_FREQ); + if (!ret) + dev_priv->skl_sagv_enabled = true; + else + DRM_ERROR("Failed to enable the SAGV\n"); + + /* We don't need to wait for SAGV when enabling */ + mutex_unlock(&dev_priv->rps.hw_lock); + return ret; +} + +static int +skl_disable_sagv(struct drm_i915_private *dev_priv) +{ + int ret = 0; + unsigned long timeout; + u32 temp; + + if (IS_BROXTON(dev_priv)) + return 0; + if (!dev_priv->skl_sagv_enabled) + return 0; + + mutex_lock(&dev_priv->rps.hw_lock); + DRM_DEBUG_KMS("Disabling the SAGV\n"); + + /* bspec says to keep retrying for at least 1 ms */ + timeout = jiffies + msecs_to_jiffies(1); + do { + ret = sandybridge_pcode_write(dev_priv, GEN9_PCODE_SAGV_CONTROL, + GEN9_SAGV_DISABLE); + if (ret) { + DRM_ERROR("Failed to disable the SAGV\n"); + goto out; + } + + ret = sandybridge_pcode_read(dev_priv, GEN9_PCODE_SAGV_CONTROL, + &temp); + if (ret) { + DRM_ERROR("Failed to check the status of the SAGV\n"); + goto out; + } + } while (!(temp & 0x1) && time_before(jiffies, timeout)); + + if (temp & 0x1) { + dev_priv->skl_sagv_enabled = false; + } else { + ret = -1; + DRM_ERROR("Request to disable SAGV timed out\n"); + } + +out: + mutex_unlock(&dev_priv->rps.hw_lock); + return ret; +} + +static void skl_ddb_get_pipe_allocation_limits(struct drm_device *dev, const struct intel_crtc_state *cstate, struct skl_ddb_entry *alloc, /* out */ @@ -3686,6 +3789,11 @@ static void skl_write_wm_values(struct drm_i915_private *dev_priv, struct drm_device *dev = &dev_priv->drm; struct intel_crtc *crtc; + if (dev_priv->active_crtcs == 1) + skl_enable_sagv(dev_priv); + else + skl_disable_sagv(dev_priv); + for_each_intel_crtc(dev, crtc) { int i, level, max_level = ilk_wm_max_level(dev); enum pipe pipe = crtc->pipe; @@ -4228,6 +4336,8 @@ void skl_wm_get_hw_state(struct drm_device *dev) /* Easy/common case; just sanitize DDB now if everything off */ memset(ddb, 0, sizeof(*ddb)); } + + skl_sagv_get_hw_state(dev_priv); } static void ilk_pipe_wm_get_hw_state(struct drm_crtc *crtc)