4.9-stable review patch.  If anyone has any objections, please let me know.

------------------

From: Ben Skeggs <bske...@redhat.com>

commit 1b0f84380b10ee97f7d2dd191294de9017e94d1d upstream.

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 <bske...@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>

---
 drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c |   26 ++++++++++++++---------
 1 file changed, 16 insertions(+), 10 deletions(-)

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


Reply via email to