drm/nouveau/tmr: handle races with hw when updating the next alarm time
authorBen Skeggs <bskeggs@redhat.com>
Thu, 11 May 2017 07:19:48 +0000 (17:19 +1000)
committerBen Skeggs <bskeggs@redhat.com>
Thu, 11 May 2017 22:32:57 +0000 (08:32 +1000)
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
drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c

index 07dc82bfe346029aab3827f8d435769b433d1ced..4e696627fdcdf3f47eb9de7e34fdb01b1ff722c2 100644 (file)
@@ -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);