From 2b8341b3f917c108b47f6a8a771a40d226c57883 Mon Sep 17 00:00:00 2001 From: Mario Kleiner Date: Fri, 19 Feb 2016 02:06:38 +0100 Subject: [PATCH 1/9] drm/radeon: Don't hang in radeon_flip_work_func on disabled crtc. (v2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a regression introduced in Linux 4.4. Limit the amount of time radeon_flip_work_func can delay programming a page flip, by both limiting the maximum amount of time per wait cycle and the maximum number of wait cycles. Continue the flip if the limit is exceeded, even if that may result in a visual or timing glitch. This is to prevent a hang of page flips, as reported in fdo bug #93746: Disconnecting a DisplayPort display in parallel to a kms pageflip getting queued can cause the following hang of page flips and thereby an unusable desktop: 1. kms pageflip ioctl() queues pageflip -> queues execution of radeon_flip_work_func. 2. Hotunplug of display causes the driver to DPMS OFF the unplugged display. Display engine shuts down, scanout no longer moves, but stays at its resting position at start line of vblank. 3. radeon_flip_work_func executes while crtc is off, and due to the non-moving scanout position, the new flip delay code introduced into Linux 4.4 by commit 5b5561b3660d ("drm/radeon: Fixup hw vblank counter/ts..") enters an infinite wait loop. 4. After reconnecting the display, the pageflip continues to hang in 3. and the display doesn't update its view of the desktop. This patch fixes the Linux 4.4 regression from fdo bug #93746 v2: Skip wait immediately if !radeon_crtc->enabled, as suggested by Michel. Reported-by: Bernd Steinhauser Signed-off-by: Mario Kleiner Tested-by: Bernd Steinhauser Cc: # 4.4+ Cc: Michel Dänzer Cc: Alex Deucher Reviewed-by: Michel Dänzer Signed-off-by: Alex Deucher --- drivers/gpu/drm/radeon/radeon_display.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c index 298ea1c453c3..2b9ba03a7c1a 100644 --- a/drivers/gpu/drm/radeon/radeon_display.c +++ b/drivers/gpu/drm/radeon/radeon_display.c @@ -403,7 +403,8 @@ static void radeon_flip_work_func(struct work_struct *__work) struct drm_crtc *crtc = &radeon_crtc->base; unsigned long flags; int r; - int vpos, hpos, stat, min_udelay; + int vpos, hpos, stat, min_udelay = 0; + unsigned repcnt = 4; struct drm_vblank_crtc *vblank = &crtc->dev->vblank[work->crtc_id]; down_read(&rdev->exclusive_lock); @@ -454,7 +455,7 @@ static void radeon_flip_work_func(struct work_struct *__work) * In practice this won't execute very often unless on very fast * machines because the time window for this to happen is very small. */ - for (;;) { + while (radeon_crtc->enabled && repcnt--) { /* GET_DISTANCE_TO_VBLANKSTART returns distance to real vblank * start in hpos, and to the "fudged earlier" vblank start in * vpos. @@ -472,10 +473,22 @@ static void radeon_flip_work_func(struct work_struct *__work) /* Sleep at least until estimated real start of hw vblank */ spin_unlock_irqrestore(&crtc->dev->event_lock, flags); min_udelay = (-hpos + 1) * max(vblank->linedur_ns / 1000, 5); + if (min_udelay > vblank->framedur_ns / 2000) { + /* Don't wait ridiculously long - something is wrong */ + repcnt = 0; + break; + } usleep_range(min_udelay, 2 * min_udelay); spin_lock_irqsave(&crtc->dev->event_lock, flags); }; + if (!repcnt) + DRM_DEBUG_DRIVER("Delay problem on crtc %d: min_udelay %d, " + "framedur %d, linedur %d, stat %d, vpos %d, " + "hpos %d\n", work->crtc_id, min_udelay, + vblank->framedur_ns / 1000, + vblank->linedur_ns / 1000, stat, vpos, hpos); + /* do the flip (mmio) */ radeon_page_flip(rdev, radeon_crtc->crtc_id, work->base); From e1d09dc0ccc6c91e3916476f636edb76da1f65bb Mon Sep 17 00:00:00 2001 From: Mario Kleiner Date: Fri, 19 Feb 2016 02:06:39 +0100 Subject: [PATCH 2/9] drm/amdgpu: Don't hang in amdgpu_flip_work_func on disabled crtc. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a regression introduced in Linux 4.4. This is a port of the same fix for radeon-kms in the patch "drm/radeon: Don't hang in radeon_flip_work_func on disabled crtc. (v2)" Limit the amount of time amdgpu_flip_work_func can delay programming a page flip, by both limiting the maximum amount of time per wait cycle and the maximum number of wait cycles. Continue the flip if the limit is exceeded, even if that may result in a visual or timing glitch. This is to prevent a hang of page flips, as reported in fdo bug #93746: Disconnecting a DisplayPort display in parallel to a kms pageflip getting queued can cause the following hang of page flips and thereby an unusable desktop: 1. kms pageflip ioctl() queues pageflip -> queues execution of amdgpu_flip_work_func. 2. Hotunplug of display causes the driver to DPMS OFF the unplugged display. Display engine shuts down, scanout no longer moves, but stays at its resting position at start line of vblank. 3. amdgpu_flip_work_func executes while crtc is off, and due to the non-moving scanout position, the new flip delay code introduced into Linux 4.4 by commit 8e36f9d33c13 ("drm/amdgpu: Fixup hw vblank counter/ts..") enters an infinite wait loop. 4. After reconnecting the display, the pageflip continues to hang in 3. and the display doesn't update its view of the desktop. This patch fixes the Linux 4.4 regression from fdo bug #93746 Reported-by: Bernd Steinhauser Signed-off-by: Mario Kleiner Cc: # 4.4+ Cc: Michel Dänzer Cc: Alex Deucher Reviewed-by: Michel Dänzer Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_display.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c index acd066d0a805..8297bc319369 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_display.c @@ -72,8 +72,8 @@ static void amdgpu_flip_work_func(struct work_struct *__work) struct drm_crtc *crtc = &amdgpuCrtc->base; unsigned long flags; - unsigned i; - int vpos, hpos, stat, min_udelay; + unsigned i, repcnt = 4; + int vpos, hpos, stat, min_udelay = 0; struct drm_vblank_crtc *vblank = &crtc->dev->vblank[work->crtc_id]; amdgpu_flip_wait_fence(adev, &work->excl); @@ -96,7 +96,7 @@ static void amdgpu_flip_work_func(struct work_struct *__work) * In practice this won't execute very often unless on very fast * machines because the time window for this to happen is very small. */ - for (;;) { + while (amdgpuCrtc->enabled && repcnt--) { /* GET_DISTANCE_TO_VBLANKSTART returns distance to real vblank * start in hpos, and to the "fudged earlier" vblank start in * vpos. @@ -114,10 +114,22 @@ static void amdgpu_flip_work_func(struct work_struct *__work) /* Sleep at least until estimated real start of hw vblank */ spin_unlock_irqrestore(&crtc->dev->event_lock, flags); min_udelay = (-hpos + 1) * max(vblank->linedur_ns / 1000, 5); + if (min_udelay > vblank->framedur_ns / 2000) { + /* Don't wait ridiculously long - something is wrong */ + repcnt = 0; + break; + } usleep_range(min_udelay, 2 * min_udelay); spin_lock_irqsave(&crtc->dev->event_lock, flags); }; + if (!repcnt) + DRM_DEBUG_DRIVER("Delay problem on crtc %d: min_udelay %d, " + "framedur %d, linedur %d, stat %d, vpos %d, " + "hpos %d\n", work->crtc_id, min_udelay, + vblank->framedur_ns / 1000, + vblank->linedur_ns / 1000, stat, vpos, hpos); + /* do the flip (mmio) */ adev->mode_info.funcs->page_flip(adev, work->crtc_id, work->base); /* set the flip status */ From 04ab3b7622a618ea081d17427c15a292710b40cb Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Thu, 18 Feb 2016 11:10:09 -0500 Subject: [PATCH 3/9] drm/amdgpu/gfx8: fix priv reg interrupt enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looks like a copy/paste typo. Reviewed-by: Christian König Noticed-by: David Panariti Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c index 8f8ec37ecd88..1c40bd90afbb 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v8_0.c @@ -4995,7 +4995,7 @@ static int gfx_v8_0_set_priv_reg_fault_state(struct amdgpu_device *adev, case AMDGPU_IRQ_STATE_ENABLE: cp_int_cntl = RREG32(mmCP_INT_CNTL_RING0); cp_int_cntl = REG_SET_FIELD(cp_int_cntl, CP_INT_CNTL_RING0, - PRIV_REG_INT_ENABLE, 0); + PRIV_REG_INT_ENABLE, 1); WREG32(mmCP_INT_CNTL_RING0, cp_int_cntl); break; default: From 10f950f640e689336af0a0d587a4b3c26a73cd44 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 19 Feb 2016 15:18:45 -0500 Subject: [PATCH 4/9] drm/amdgpu: fix locking in force performance level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looks like a copy paste typo when we added powerplay support. Reviewed-by: Christian König Acked-by: Harry Wentland Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c index 7d8d84eaea4a..5ed5598997e9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c @@ -157,6 +157,7 @@ static ssize_t amdgpu_set_dpm_forced_performance_level(struct device *dev, mutex_lock(&adev->pm.mutex); if (adev->pm.dpm.thermal_active) { count = -EINVAL; + mutex_unlock(&adev->pm.mutex); goto fail; } ret = amdgpu_dpm_force_performance_level(adev, level); @@ -167,8 +168,6 @@ static ssize_t amdgpu_set_dpm_forced_performance_level(struct device *dev, mutex_unlock(&adev->pm.mutex); } fail: - mutex_unlock(&adev->pm.mutex); - return count; } From 0c67df48597f0e60eaf7f187ee38f6ccce56561e Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 19 Feb 2016 15:30:15 -0500 Subject: [PATCH 5/9] drm/amdgpu/pm: add some checks for PX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I.e., doesn't make sense to change power states or check the temperature when the asic is powered off. Reviewed-by: Christian König Acked-by: Harry Wentland Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c index 5ed5598997e9..8de765dee3e7 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c @@ -113,6 +113,10 @@ static ssize_t amdgpu_get_dpm_forced_performance_level(struct device *dev, struct drm_device *ddev = dev_get_drvdata(dev); struct amdgpu_device *adev = ddev->dev_private; + if ((adev->flags & AMD_IS_PX) && + (ddev->switch_power_state != DRM_SWITCH_POWER_ON)) + return snprintf(buf, PAGE_SIZE, "off\n"); + if (adev->pp_enabled) { enum amd_dpm_forced_level level; @@ -140,6 +144,11 @@ static ssize_t amdgpu_set_dpm_forced_performance_level(struct device *dev, enum amdgpu_dpm_forced_level level; int ret = 0; + /* Can't force performance level when the card is off */ + if ((adev->flags & AMD_IS_PX) && + (ddev->switch_power_state != DRM_SWITCH_POWER_ON)) + return -EINVAL; + if (strncmp("low", buf, strlen("low")) == 0) { level = AMDGPU_DPM_FORCED_LEVEL_LOW; } else if (strncmp("high", buf, strlen("high")) == 0) { @@ -181,8 +190,14 @@ static ssize_t amdgpu_hwmon_show_temp(struct device *dev, char *buf) { struct amdgpu_device *adev = dev_get_drvdata(dev); + struct drm_device *ddev = adev->ddev; int temp; + /* Can't get temperature when the card is off */ + if ((adev->flags & AMD_IS_PX) && + (ddev->switch_power_state != DRM_SWITCH_POWER_ON)) + return -EINVAL; + if (!adev->pp_enabled && !adev->pm.funcs->get_temperature) temp = 0; else @@ -846,12 +861,16 @@ static int amdgpu_debugfs_pm_info(struct seq_file *m, void *data) struct drm_info_node *node = (struct drm_info_node *) m->private; struct drm_device *dev = node->minor->dev; struct amdgpu_device *adev = dev->dev_private; + struct drm_device *ddev = adev->ddev; if (!adev->pm.dpm_enabled) { seq_printf(m, "dpm not enabled\n"); return 0; } - if (adev->pp_enabled) { + if ((adev->flags & AMD_IS_PX) && + (ddev->switch_power_state != DRM_SWITCH_POWER_ON)) { + seq_printf(m, "PX asic powered off\n"); + } else if (adev->pp_enabled) { amdgpu_dpm_debugfs_print_current_performance_level(adev, m); } else { mutex_lock(&adev->pm.mutex); From 8e7cedc6f7fe762ffe6e348502be34b11fa79298 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 19 Feb 2016 17:55:31 -0500 Subject: [PATCH 6/9] drm/amdgpu/pm: adjust display configuration after powerstate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set_power_state defaults to no displays, so we need to update the display configuration after setting up the powerstate on the first call. In most cases this is not an issue since ends up getting called multiple times at any given modeset and the proper order is achieved in the display changed handling at the top of the function. Reviewed-by: Christian König Acked-by: Jordan Lazare Signed-off-by: Alex Deucher Cc: stable@vger.kernel.org --- drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c index 8de765dee3e7..66855b62a603 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_pm.c @@ -648,8 +648,6 @@ static void amdgpu_dpm_change_power_state_locked(struct amdgpu_device *adev) /* update display watermarks based on new power state */ amdgpu_display_bandwidth_update(adev); - /* update displays */ - amdgpu_dpm_display_configuration_changed(adev); adev->pm.dpm.current_active_crtcs = adev->pm.dpm.new_active_crtcs; adev->pm.dpm.current_active_crtc_count = adev->pm.dpm.new_active_crtc_count; @@ -669,6 +667,9 @@ static void amdgpu_dpm_change_power_state_locked(struct amdgpu_device *adev) amdgpu_dpm_post_set_power_state(adev); + /* update displays */ + amdgpu_dpm_display_configuration_changed(adev); + if (adev->pm.funcs->force_performance_level) { if (adev->pm.dpm.thermal_active) { enum amdgpu_dpm_forced_level level = adev->pm.dpm.forced_level; From 39d4275058baf53e89203407bf3841ff2c74fa32 Mon Sep 17 00:00:00 2001 From: Alex Deucher Date: Fri, 19 Feb 2016 18:05:10 -0500 Subject: [PATCH 7/9] drm/radeon/pm: adjust display configuration after powerstate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set_power_state defaults to no displays, so we need to update the display configuration after setting up the powerstate on the first call. In most cases this is not an issue since ends up getting called multiple times at any given modeset and the proper order is achieved in the display changed handling at the top of the function. Reviewed-by: Christian König Acked-by: Jordan Lazare Signed-off-by: Alex Deucher Cc: stable@vger.kernel.org --- drivers/gpu/drm/radeon/radeon_pm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/radeon/radeon_pm.c b/drivers/gpu/drm/radeon/radeon_pm.c index 248c5a9fb0b6..ca3be90a3bb4 100644 --- a/drivers/gpu/drm/radeon/radeon_pm.c +++ b/drivers/gpu/drm/radeon/radeon_pm.c @@ -1079,8 +1079,6 @@ static void radeon_dpm_change_power_state_locked(struct radeon_device *rdev) /* update display watermarks based on new power state */ radeon_bandwidth_update(rdev); - /* update displays */ - radeon_dpm_display_configuration_changed(rdev); rdev->pm.dpm.current_active_crtcs = rdev->pm.dpm.new_active_crtcs; rdev->pm.dpm.current_active_crtc_count = rdev->pm.dpm.new_active_crtc_count; @@ -1101,6 +1099,9 @@ static void radeon_dpm_change_power_state_locked(struct radeon_device *rdev) radeon_dpm_post_set_power_state(rdev); + /* update displays */ + radeon_dpm_display_configuration_changed(rdev); + if (rdev->asic->dpm.force_performance_level) { if (rdev->pm.dpm.thermal_active) { enum radeon_dpm_forced_level level = rdev->pm.dpm.forced_level; From 827108d08070c7515c14eb47e5fa03f59cfe38cf Mon Sep 17 00:00:00 2001 From: Bradley Pankow Date: Mon, 22 Feb 2016 20:11:47 -0500 Subject: [PATCH 8/9] amdgpu: fix NULL pointer dereference at tonga_check_states_equal The event_data passed from pem_fini was not cleared upon initialization. This caused NULL checks to pass and cast_const_phw_tonga_power_state to attempt to dereference an invalid pointer. Clear the event_data in pem_init and pem_fini before calling pem_handle_event. Reviewed-by: Rex Zhu Signed-off-by: Bradley Pankow Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/powerplay/eventmgr/eventmgr.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/amd/powerplay/eventmgr/eventmgr.c b/drivers/gpu/drm/amd/powerplay/eventmgr/eventmgr.c index 52a3efc97f05..46410e3c7349 100644 --- a/drivers/gpu/drm/amd/powerplay/eventmgr/eventmgr.c +++ b/drivers/gpu/drm/amd/powerplay/eventmgr/eventmgr.c @@ -31,7 +31,7 @@ static int pem_init(struct pp_eventmgr *eventmgr) { int result = 0; - struct pem_event_data event_data; + struct pem_event_data event_data = { {0} }; /* Initialize PowerPlay feature info */ pem_init_feature_info(eventmgr); @@ -52,7 +52,7 @@ static int pem_init(struct pp_eventmgr *eventmgr) static void pem_fini(struct pp_eventmgr *eventmgr) { - struct pem_event_data event_data; + struct pem_event_data event_data = { {0} }; pem_uninit_featureInfo(eventmgr); pem_unregister_interrupts(eventmgr); From 6378076bcfdcd1d4f8d726d08d3fa044736873eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Fri, 19 Feb 2016 10:03:03 +0100 Subject: [PATCH 9/9] drm/amdgpu: disable direct VM updates when vm_debug is set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That should make user space bugs more obvious. Signed-off-by: Christian König Reviewed-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c index 7380f782cd14..d20c2a8929cb 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_gem.c @@ -596,7 +596,8 @@ int amdgpu_gem_va_ioctl(struct drm_device *dev, void *data, break; } ttm_eu_backoff_reservation(&ticket, &list); - if (!r && !(args->flags & AMDGPU_VM_DELAY_UPDATE)) + if (!r && !(args->flags & AMDGPU_VM_DELAY_UPDATE) && + !amdgpu_vm_debug) amdgpu_gem_va_update_vm(adev, bo_va, args->operation); drm_gem_object_unreference_unlocked(gobj);