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
This commit is contained in:
Родитель
3733bd8b40
Коммит
1b0f84380b
|
@ -36,23 +36,29 @@ nvkm_timer_alarm_trigger(struct nvkm_timer *tmr)
|
|||
unsigned long flags;
|
||||
LIST_HEAD(exec);
|
||||
|
||||
/* move any due alarms off the pending list */
|
||||
/* Process pending alarms. */
|
||||
spin_lock_irqsave(&tmr->lock, flags);
|
||||
list_for_each_entry_safe(alarm, atemp, &tmr->alarms, head) {
|
||||
if (alarm->timestamp <= nvkm_timer_read(tmr))
|
||||
list_move_tail(&alarm->head, &exec);
|
||||
/* Have we hit the earliest alarm that hasn't gone off? */
|
||||
if (alarm->timestamp > nvkm_timer_read(tmr)) {
|
||||
/* Schedule it. If we didn't race, we're done. */
|
||||
tmr->func->alarm_init(tmr, alarm->timestamp);
|
||||
if (alarm->timestamp > nvkm_timer_read(tmr))
|
||||
break;
|
||||
}
|
||||
|
||||
/* Move to completed list. We'll drop the lock before
|
||||
* executing the callback so it can reschedule itself.
|
||||
*/
|
||||
list_move_tail(&alarm->head, &exec);
|
||||
}
|
||||
|
||||
/* reschedule interrupt for next alarm time */
|
||||
if (!list_empty(&tmr->alarms)) {
|
||||
alarm = list_first_entry(&tmr->alarms, typeof(*alarm), head);
|
||||
tmr->func->alarm_init(tmr, alarm->timestamp);
|
||||
} else {
|
||||
/* Shut down interrupt if no more pending alarms. */
|
||||
if (list_empty(&tmr->alarms))
|
||||
tmr->func->alarm_fini(tmr);
|
||||
}
|
||||
spin_unlock_irqrestore(&tmr->lock, flags);
|
||||
|
||||
/* execute any pending alarm handlers */
|
||||
/* Execute completed callbacks. */
|
||||
list_for_each_entry_safe(alarm, atemp, &exec, head) {
|
||||
list_del_init(&alarm->head);
|
||||
alarm->func(alarm);
|
||||
|
|
Загрузка…
Ссылка в новой задаче