diff options
author | Rob Clark <robdclark@chromium.org> | 2019-11-04 12:37:36 -0500 |
---|---|---|
committer | Sean Paul <seanpaul@chromium.org> | 2019-11-06 13:00:21 -0500 |
commit | 86de88cfeb7cf33c7bbd18360e041c7d4e651bba (patch) | |
tree | e68b05d9f67df3c30ca92c041ba7cdea649bbab6 | |
parent | b330f3972f4f2a829d41fb9e9b552bec7d73a840 (diff) |
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
-rw-r--r-- | drivers/gpu/drm/drm_atomic_helper.c | 15 | ||||
-rw-r--r-- | drivers/gpu/drm/drm_self_refresh_helper.c | 18 | ||||
-rw-r--r-- | include/drm/drm_self_refresh_helper.h | 3 |
3 files changed, 27 insertions, 9 deletions
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 3ef2ac52ce94..2dd2cd87cdbb 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c | |||
@@ -1581,8 +1581,11 @@ static void commit_tail(struct drm_atomic_state *old_state) | |||
1581 | { | 1581 | { |
1582 | struct drm_device *dev = old_state->dev; | 1582 | struct drm_device *dev = old_state->dev; |
1583 | const struct drm_mode_config_helper_funcs *funcs; | 1583 | const struct drm_mode_config_helper_funcs *funcs; |
1584 | struct drm_crtc_state *new_crtc_state; | ||
1585 | struct drm_crtc *crtc; | ||
1584 | ktime_t start; | 1586 | ktime_t start; |
1585 | s64 commit_time_ms; | 1587 | s64 commit_time_ms; |
1588 | unsigned int i, new_self_refresh_mask = 0; | ||
1586 | 1589 | ||
1587 | funcs = dev->mode_config.helper_private; | 1590 | funcs = dev->mode_config.helper_private; |
1588 | 1591 | ||
@@ -1602,6 +1605,15 @@ static void commit_tail(struct drm_atomic_state *old_state) | |||
1602 | 1605 | ||
1603 | drm_atomic_helper_wait_for_dependencies(old_state); | 1606 | drm_atomic_helper_wait_for_dependencies(old_state); |
1604 | 1607 | ||
1608 | /* | ||
1609 | * We cannot safely access new_crtc_state after | ||
1610 | * drm_atomic_helper_commit_hw_done() so figure out which crtc's have | ||
1611 | * self-refresh active beforehand: | ||
1612 | */ | ||
1613 | for_each_new_crtc_in_state(old_state, crtc, new_crtc_state, i) | ||
1614 | if (new_crtc_state->self_refresh_active) | ||
1615 | new_self_refresh_mask |= BIT(i); | ||
1616 | |||
1605 | if (funcs && funcs->atomic_commit_tail) | 1617 | if (funcs && funcs->atomic_commit_tail) |
1606 | funcs->atomic_commit_tail(old_state); | 1618 | funcs->atomic_commit_tail(old_state); |
1607 | else | 1619 | else |
@@ -1610,7 +1622,8 @@ static void commit_tail(struct drm_atomic_state *old_state) | |||
1610 | commit_time_ms = ktime_ms_delta(ktime_get(), start); | 1622 | commit_time_ms = ktime_ms_delta(ktime_get(), start); |
1611 | if (commit_time_ms > 0) | 1623 | if (commit_time_ms > 0) |
1612 | drm_self_refresh_helper_update_avg_times(old_state, | 1624 | drm_self_refresh_helper_update_avg_times(old_state, |
1613 | (unsigned long)commit_time_ms); | 1625 | (unsigned long)commit_time_ms, |
1626 | new_self_refresh_mask); | ||
1614 | 1627 | ||
1615 | drm_atomic_helper_commit_cleanup_done(old_state); | 1628 | drm_atomic_helper_commit_cleanup_done(old_state); |
1616 | 1629 | ||
diff --git a/drivers/gpu/drm/drm_self_refresh_helper.c b/drivers/gpu/drm/drm_self_refresh_helper.c index 68f4765a5896..dd33fec5aabd 100644 --- a/drivers/gpu/drm/drm_self_refresh_helper.c +++ b/drivers/gpu/drm/drm_self_refresh_helper.c | |||
@@ -133,29 +133,33 @@ out_drop_locks: | |||
133 | * drm_self_refresh_helper_update_avg_times - Updates a crtc's SR time averages | 133 | * drm_self_refresh_helper_update_avg_times - Updates a crtc's SR time averages |
134 | * @state: the state which has just been applied to hardware | 134 | * @state: the state which has just been applied to hardware |
135 | * @commit_time_ms: the amount of time in ms that this commit took to complete | 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 | * Called after &drm_mode_config_funcs.atomic_commit_tail, this function will | 139 | * Called after &drm_mode_config_funcs.atomic_commit_tail, this function will |
138 | * update the average entry/exit self refresh times on self refresh transitions. | 140 | * update the average entry/exit self refresh times on self refresh transitions. |
139 | * These averages will be used when calculating how long to delay before | 141 | * These averages will be used when calculating how long to delay before |
140 | * entering self refresh mode after activity. | 142 | * entering self refresh mode after activity. |
141 | */ | 143 | */ |
142 | void drm_self_refresh_helper_update_avg_times(struct drm_atomic_state *state, | 144 | void |
143 | unsigned int commit_time_ms) | 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 | struct drm_crtc *crtc; | 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 | int i; | 151 | int i; |
148 | 152 | ||
149 | for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, | 153 | for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) { |
150 | new_crtc_state, i) { | 154 | bool new_self_refresh_active = new_self_refresh_mask & BIT(i); |
151 | struct drm_self_refresh_data *sr_data = crtc->self_refresh_data; | 155 | struct drm_self_refresh_data *sr_data = crtc->self_refresh_data; |
152 | struct ewma_psr_time *time; | 156 | struct ewma_psr_time *time; |
153 | 157 | ||
154 | if (old_crtc_state->self_refresh_active == | 158 | if (old_crtc_state->self_refresh_active == |
155 | new_crtc_state->self_refresh_active) | 159 | new_self_refresh_active) |
156 | continue; | 160 | continue; |
157 | 161 | ||
158 | if (new_crtc_state->self_refresh_active) | 162 | if (new_self_refresh_active) |
159 | time = &sr_data->entry_avg_ms; | 163 | time = &sr_data->entry_avg_ms; |
160 | else | 164 | else |
161 | time = &sr_data->exit_avg_ms; | 165 | time = &sr_data->exit_avg_ms; |
diff --git a/include/drm/drm_self_refresh_helper.h b/include/drm/drm_self_refresh_helper.h index 5b79d253fb46..520235c20708 100644 --- a/include/drm/drm_self_refresh_helper.h +++ b/include/drm/drm_self_refresh_helper.h | |||
@@ -13,7 +13,8 @@ struct drm_crtc; | |||
13 | 13 | ||
14 | void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state); | 14 | void drm_self_refresh_helper_alter_state(struct drm_atomic_state *state); |
15 | void drm_self_refresh_helper_update_avg_times(struct drm_atomic_state *state, | 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 | int drm_self_refresh_helper_init(struct drm_crtc *crtc); | 19 | int drm_self_refresh_helper_init(struct drm_crtc *crtc); |
19 | void drm_self_refresh_helper_cleanup(struct drm_crtc *crtc); | 20 | void drm_self_refresh_helper_cleanup(struct drm_crtc *crtc); |