diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c | 59 |
1 files changed, 39 insertions, 20 deletions
diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c b/drivers/gpu/drm/nouveau/nvkm/subdev/timer/base.c index 07dc82bfe346..f2a86eae0a0d 100644 --- 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_timer *tmr) | |||
36 | unsigned long flags; | 36 | unsigned long flags; |
37 | LIST_HEAD(exec); | 37 | LIST_HEAD(exec); |
38 | 38 | ||
39 | /* move any due alarms off the pending list */ | 39 | /* Process pending alarms. */ |
40 | spin_lock_irqsave(&tmr->lock, flags); | 40 | spin_lock_irqsave(&tmr->lock, flags); |
41 | list_for_each_entry_safe(alarm, atemp, &tmr->alarms, head) { | 41 | list_for_each_entry_safe(alarm, atemp, &tmr->alarms, head) { |
42 | if (alarm->timestamp <= nvkm_timer_read(tmr)) | 42 | /* Have we hit the earliest alarm that hasn't gone off? */ |
43 | list_move_tail(&alarm->head, &exec); | 43 | if (alarm->timestamp > nvkm_timer_read(tmr)) { |
44 | /* Schedule it. If we didn't race, we're done. */ | ||
45 | tmr->func->alarm_init(tmr, alarm->timestamp); | ||
46 | if (alarm->timestamp > nvkm_timer_read(tmr)) | ||
47 | break; | ||
48 | } | ||
49 | |||
50 | /* Move to completed list. We'll drop the lock before | ||
51 | * executing the callback so it can reschedule itself. | ||
52 | */ | ||
53 | list_move_tail(&alarm->head, &exec); | ||
44 | } | 54 | } |
45 | 55 | ||
46 | /* reschedule interrupt for next alarm time */ | 56 | /* Shut down interrupt if no more pending alarms. */ |
47 | if (!list_empty(&tmr->alarms)) { | 57 | if (list_empty(&tmr->alarms)) |
48 | alarm = list_first_entry(&tmr->alarms, typeof(*alarm), head); | ||
49 | tmr->func->alarm_init(tmr, alarm->timestamp); | ||
50 | } else { | ||
51 | tmr->func->alarm_fini(tmr); | 58 | tmr->func->alarm_fini(tmr); |
52 | } | ||
53 | spin_unlock_irqrestore(&tmr->lock, flags); | 59 | spin_unlock_irqrestore(&tmr->lock, flags); |
54 | 60 | ||
55 | /* execute any pending alarm handlers */ | 61 | /* Execute completed callbacks. */ |
56 | list_for_each_entry_safe(alarm, atemp, &exec, head) { | 62 | list_for_each_entry_safe(alarm, atemp, &exec, head) { |
57 | list_del_init(&alarm->head); | 63 | list_del_init(&alarm->head); |
58 | alarm->func(alarm); | 64 | alarm->func(alarm); |
@@ -65,24 +71,37 @@ nvkm_timer_alarm(struct nvkm_timer *tmr, u32 nsec, struct nvkm_alarm *alarm) | |||
65 | struct nvkm_alarm *list; | 71 | struct nvkm_alarm *list; |
66 | unsigned long flags; | 72 | unsigned long flags; |
67 | 73 | ||
68 | alarm->timestamp = nvkm_timer_read(tmr) + nsec; | 74 | /* Remove alarm from pending list. |
69 | 75 | * | |
70 | /* append new alarm to list, in soonest-alarm-first order */ | 76 | * This both protects against the corruption of the list, |
77 | * and implements alarm rescheduling/cancellation. | ||
78 | */ | ||
71 | spin_lock_irqsave(&tmr->lock, flags); | 79 | spin_lock_irqsave(&tmr->lock, flags); |
72 | if (!nsec) { | 80 | list_del_init(&alarm->head); |
73 | if (!list_empty(&alarm->head)) | 81 | |
74 | list_del(&alarm->head); | 82 | if (nsec) { |
75 | } else { | 83 | /* Insert into pending list, ordered earliest to latest. */ |
84 | alarm->timestamp = nvkm_timer_read(tmr) + nsec; | ||
76 | list_for_each_entry(list, &tmr->alarms, head) { | 85 | list_for_each_entry(list, &tmr->alarms, head) { |
77 | if (list->timestamp > alarm->timestamp) | 86 | if (list->timestamp > alarm->timestamp) |
78 | break; | 87 | break; |
79 | } | 88 | } |
89 | |||
80 | list_add_tail(&alarm->head, &list->head); | 90 | list_add_tail(&alarm->head, &list->head); |
91 | |||
92 | /* Update HW if this is now the earliest alarm. */ | ||
93 | list = list_first_entry(&tmr->alarms, typeof(*list), head); | ||
94 | if (list == alarm) { | ||
95 | tmr->func->alarm_init(tmr, alarm->timestamp); | ||
96 | /* This shouldn't happen if callers aren't stupid. | ||
97 | * | ||
98 | * Worst case scenario is that it'll take roughly | ||
99 | * 4 seconds for the next alarm to trigger. | ||
100 | */ | ||
101 | WARN_ON(alarm->timestamp <= nvkm_timer_read(tmr)); | ||
102 | } | ||
81 | } | 103 | } |
82 | spin_unlock_irqrestore(&tmr->lock, flags); | 104 | spin_unlock_irqrestore(&tmr->lock, flags); |
83 | |||
84 | /* process pending alarms */ | ||
85 | nvkm_timer_alarm_trigger(tmr); | ||
86 | } | 105 | } |
87 | 106 | ||
88 | void | 107 | void |