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); |
