diff options
author | Ben Skeggs <bskeggs@redhat.com> | 2017-05-11 03:19:48 -0400 |
---|---|---|
committer | Ben Skeggs <bskeggs@redhat.com> | 2017-05-11 18:32:57 -0400 |
commit | 1b0f84380b10ee97f7d2dd191294de9017e94d1d (patch) | |
tree | 0507437bc996484f39ef8290358ec60da67a49fd | |
parent | 3733bd8b407211739e72d051e5f30ad82a52c4bc (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.c | 26 |
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); |