aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBen Skeggs <bskeggs@redhat.com>2017-05-11 03:19:48 -0400
committerBen Skeggs <bskeggs@redhat.com>2017-05-11 18:32:57 -0400
commit1b0f84380b10ee97f7d2dd191294de9017e94d1d (patch)
tree0507437bc996484f39ef8290358ec60da67a49fd
parent3733bd8b407211739e72d051e5f30ad82a52c4bc (diff)
drm/nouveau/tmr: handle races with hw when updating the next alarm time
If the time to the next alarm is short enough, we could race with HW and end up with an ~4 second delay until it triggers. Fix this by checking again after we update HW. Signed-off-by: Ben Skeggs <bskeggs@redhat.com> Cc: stable@vger.kernel.org
-rw-r--r--drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c26
1 files changed, 16 insertions, 10 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
index 07dc82bfe346..4e696627fdcd 100644
--- a/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
+++ b/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c
@@ -36,23 +36,29 @@ nvkm_timer_alarm_trigger(struct nvkm_timer *tmr)
36 unsigned long flags; 36 unsigned long flags;
37 LIST_HEAD(exec); 37 LIST_HEAD(exec);
38 38
39 /* move any due alarms off the pending list */ 39 /* Process pending alarms. */
40 spin_lock_irqsave(&tmr->lock, flags); 40 spin_lock_irqsave(&tmr->lock, flags);
41 list_for_each_entry_safe(alarm, atemp, &tmr->alarms, head) { 41 list_for_each_entry_safe(alarm, atemp, &tmr->alarms, head) {
42 if (alarm->timestamp <= nvkm_timer_read(tmr)) 42 /* Have we hit the earliest alarm that hasn't gone off? */
43 list_move_tail(&alarm->head, &exec); 43 if (alarm->timestamp > nvkm_timer_read(tmr)) {
44 /* Schedule it. If we didn't race, we're done. */
45 tmr->func->alarm_init(tmr, alarm->timestamp);
46 if (alarm->timestamp > nvkm_timer_read(tmr))
47 break;
48 }
49
50 /* Move to completed list. We'll drop the lock before
51 * executing the callback so it can reschedule itself.
52 */
53 list_move_tail(&alarm->head, &exec);
44 } 54 }
45 55
46 /* reschedule interrupt for next alarm time */ 56 /* Shut down interrupt if no more pending alarms. */
47 if (!list_empty(&tmr->alarms)) { 57 if (list_empty(&tmr->alarms))
48 alarm = list_first_entry(&tmr->alarms, typeof(*alarm), head);
49 tmr->func->alarm_init(tmr, alarm->timestamp);
50 } else {
51 tmr->func->alarm_fini(tmr); 58 tmr->func->alarm_fini(tmr);
52 }
53 spin_unlock_irqrestore(&tmr->lock, flags); 59 spin_unlock_irqrestore(&tmr->lock, flags);
54 60
55 /* execute any pending alarm handlers */ 61 /* Execute completed callbacks. */
56 list_for_each_entry_safe(alarm, atemp, &exec, head) { 62 list_for_each_entry_safe(alarm, atemp, &exec, head) {
57 list_del_init(&alarm->head); 63 list_del_init(&alarm->head);
58 alarm->func(alarm); 64 alarm->func(alarm);