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:
parent
3733bd8b40
commit
1b0f84380b
|
@ -36,23 +36,29 @@ nvkm_timer_alarm_trigger(struct nvkm_timer *tmr)
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
LIST_HEAD(exec);
|
LIST_HEAD(exec);
|
||||||
|
|
||||||
/* move any due alarms off the pending list */
|
/* Process pending alarms. */
|
||||||
spin_lock_irqsave(&tmr->lock, flags);
|
spin_lock_irqsave(&tmr->lock, flags);
|
||||||
list_for_each_entry_safe(alarm, atemp, &tmr->alarms, head) {
|
list_for_each_entry_safe(alarm, atemp, &tmr->alarms, head) {
|
||||||
if (alarm->timestamp <= nvkm_timer_read(tmr))
|
/* Have we hit the earliest alarm that hasn't gone off? */
|
||||||
list_move_tail(&alarm->head, &exec);
|
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 */
|
/* Shut down interrupt if no more pending alarms. */
|
||||||
if (!list_empty(&tmr->alarms)) {
|
if (list_empty(&tmr->alarms))
|
||||||
alarm = list_first_entry(&tmr->alarms, typeof(*alarm), head);
|
|
||||||
tmr->func->alarm_init(tmr, alarm->timestamp);
|
|
||||||
} else {
|
|
||||||
tmr->func->alarm_fini(tmr);
|
tmr->func->alarm_fini(tmr);
|
||||||
}
|
|
||||||
spin_unlock_irqrestore(&tmr->lock, flags);
|
spin_unlock_irqrestore(&tmr->lock, flags);
|
||||||
|
|
||||||
/* execute any pending alarm handlers */
|
/* Execute completed callbacks. */
|
||||||
list_for_each_entry_safe(alarm, atemp, &exec, head) {
|
list_for_each_entry_safe(alarm, atemp, &exec, head) {
|
||||||
list_del_init(&alarm->head);
|
list_del_init(&alarm->head);
|
||||||
alarm->func(alarm);
|
alarm->func(alarm);
|
||||||
|
|
Loading…
Reference in a new issue