Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drm/atomic: fix self-refresh helpers crtc state dereference

drm_self_refresh_helper_update_avg_times() was incorrectly accessing the
new incoming state after drm_atomic_helper_commit_hw_done(). But this
state might have already been superceeded by an !nonblock atomic update
resulting in dereferencing an already free'd crtc_state.

TODO I *think* this will more or less do the right thing.. althought I'm
not 100% sure if, for example, we enter psr in a nonblock commit, and
then leave psr in a !nonblock commit that overtakes the completion of
the nonblock commit. Not sure if this sort of scenario can happen in
practice. But not crashing is better than crashing, so I guess we
should either take this patch or rever the self-refresh helpers until
Sean can figure out a better solution.

Fixes: d4da4e33341c ("drm: Measure Self Refresh Entry/Exit times to avoid thrashing")
Cc: Sean Paul <seanpaul@chromium.org>
Signed-off-by: Rob Clark <robdclark@chromium.org>
[seanpaul fixed up some checkpatch warns]
Signed-off-by: Sean Paul <seanpaul@chromium.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20191104173737.142558-1-robdclark@gmail.com

authored by

Rob Clark and committed by
Sean Paul
86de88cf b330f397

+27 -9
+14 -1
drivers/gpu/drm/drm_atomic_helper.c
··· 1581 1581 { 1582 1582 struct drm_device *dev = old_state->dev; 1583 1583 const struct drm_mode_config_helper_funcs *funcs; 1584 + struct drm_crtc_state *new_crtc_state; 1585 + struct drm_crtc *crtc; 1584 1586 ktime_t start; 1585 1587 s64 commit_time_ms; 1588 + unsigned int i, new_self_refresh_mask = 0; 1586 1589 1587 1590 funcs = dev->mode_config.helper_private; 1588 1591 ··· 1605 1602 1606 1603 drm_atomic_helper_wait_for_dependencies(old_state); 1607 1604 1605 + /* 1606 + * We cannot safely access new_crtc_state after 1607 + * drm_atomic_helper_commit_hw_done() so figure out which crtc's have 1608 + * self-refresh active beforehand: 1609 + */ 1610 + for_each_new_crtc_in_state(old_state, crtc, new_crtc_state, i) 1611 + if (new_crtc_state->self_refresh_active) 1612 + new_self_refresh_mask |= BIT(i); 1613 + 1608 1614 if (funcs && funcs->atomic_commit_tail) 1609 1615 funcs->atomic_commit_tail(old_state); 1610 1616 else ··· 1622 1610 commit_time_ms = ktime_ms_delta(ktime_get(), start); 1623 1611 if (commit_time_ms > 0) 1624 1612 drm_self_refresh_helper_update_avg_times(old_state, 1625 - (unsigned long)commit_time_ms); 1613 + (unsigned long)commit_time_ms, 1614 + new_self_refresh_mask); 1626 1615 1627 1616 drm_atomic_helper_commit_cleanup_done(old_state); 1628 1617
+11 -7
drivers/gpu/drm/drm_self_refresh_helper.c
··· 133 133 * drm_self_refresh_helper_update_avg_times - Updates a crtc's SR time averages 134 134 * @state: the state which has just been applied to hardware 135 135 * @commit_time_ms: the amount of time in ms that this commit took to complete 136 + * @new_self_refresh_mask: bitmask of crtc's that have self_refresh_active in 137 + * new state 136 138 * 137 139 * Called after &drm_mode_config_funcs.atomic_commit_tail, this function will 138 140 * update the average entry/exit self refresh times on self refresh transitions. 139 141 * These averages will be used when calculating how long to delay before 140 142 * entering self refresh mode after activity. 141 143 */ 142 - void drm_self_refresh_helper_update_avg_times(struct drm_atomic_state *state, 143 - unsigned int commit_time_ms) 144 + void 145 + drm_self_refresh_helper_update_avg_times(struct drm_atomic_state *state, 146 + unsigned int commit_time_ms, 147 + unsigned int new_self_refresh_mask) 144 148 { 145 149 struct drm_crtc *crtc; 146 - struct drm_crtc_state *old_crtc_state, *new_crtc_state; 150 + struct drm_crtc_state *old_crtc_state; 147 151 int i; 148 152 149 - for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, 150 - new_crtc_state, i) { 153 + for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) { 154 + bool new_self_refresh_active = new_self_refresh_mask & BIT(i); 151 155 struct drm_self_refresh_data *sr_data = crtc->self_refresh_data; 152 156 struct ewma_psr_time *time; 153 157 154 158 if (old_crtc_state->self_refresh_active == 155 - new_crtc_state->self_refresh_active) 159 + new_self_refresh_active) 156 160 continue; 157 161 158 - if (new_crtc_state->self_refresh_active) 162 + if (new_self_refresh_active) 159 163 time = &sr_data->entry_avg_ms; 160 164 else 161 165 time = &sr_data->exit_avg_ms;
+2 -1
include/drm/drm_self_refresh_helper.h
··· 13 13 14 14 void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state); 15 15 void drm_self_refresh_helper_update_avg_times(struct drm_atomic_state *state, 16 - unsigned int commit_time_ms); 16 + unsigned int commit_time_ms, 17 + unsigned int new_self_refresh_mask); 17 18 18 19 int drm_self_refresh_helper_init(struct drm_crtc *crtc); 19 20 void drm_self_refresh_helper_cleanup(struct drm_crtc *crtc);