|
@@ -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);
|
|
@@ -65,24 +71,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
|