drm/nouveau/tmr: avoid processing completed alarms when adding a new one
[firefly-linux-kernel-4.4.55.git] / drivers / gpu / drm / nouveau / nvkm / subdev / timer / base.c
index d4dae1f12d622b03cb2af61d2dcb966fed28f7fb..74e40f1d8f52bb55bd6b697e97c87df20c475cd0 100644 (file)
@@ -65,24 +65,37 @@ nvkm_timer_alarm(struct nvkm_timer *tmr, u32 nsec, struct nvkm_alarm *alarm)
        struct nvkm_alarm *list;
        unsigned long flags;
 
-       alarm->timestamp = nvkm_timer_read(tmr) + nsec;
-
-       /* append new alarm to list, in soonest-alarm-first order */
+       /* Remove alarm from pending list.
+        *
+        * This both protects against the corruption of the list,
+        * and implements alarm rescheduling/cancellation.
+        */
        spin_lock_irqsave(&tmr->lock, flags);
-       if (!nsec) {
-               if (!list_empty(&alarm->head))
-                       list_del(&alarm->head);
-       } else {
+       list_del_init(&alarm->head);
+
+       if (nsec) {
+               /* Insert into pending list, ordered earliest to latest. */
+               alarm->timestamp = nvkm_timer_read(tmr) + nsec;
                list_for_each_entry(list, &tmr->alarms, head) {
                        if (list->timestamp > alarm->timestamp)
                                break;
                }
+
                list_add_tail(&alarm->head, &list->head);
+
+               /* Update HW if this is now the earliest alarm. */
+               list = list_first_entry(&tmr->alarms, typeof(*list), head);
+               if (list == alarm) {
+                       tmr->func->alarm_init(tmr, alarm->timestamp);
+                       /* This shouldn't happen if callers aren't stupid.
+                        *
+                        * Worst case scenario is that it'll take roughly
+                        * 4 seconds for the next alarm to trigger.
+                        */
+                       WARN_ON(alarm->timestamp <= nvkm_timer_read(tmr));
+               }
        }
        spin_unlock_irqrestore(&tmr->lock, flags);
-
-       /* process pending alarms */
-       nvkm_timer_alarm_trigger(tmr);
 }
 
 void