Line data Source code
1 : // SPDX-License-Identifier: MIT
2 :
3 : #include <uapi/linux/sched/types.h>
4 :
5 : #include <drm/drm_print.h>
6 : #include <drm/drm_vblank.h>
7 : #include <drm/drm_vblank_work.h>
8 : #include <drm/drm_crtc.h>
9 :
10 : #include "drm_internal.h"
11 :
12 : /**
13 : * DOC: vblank works
14 : *
15 : * Many DRM drivers need to program hardware in a time-sensitive manner, many
16 : * times with a deadline of starting and finishing within a certain region of
17 : * the scanout. Most of the time the safest way to accomplish this is to
18 : * simply do said time-sensitive programming in the driver's IRQ handler,
19 : * which allows drivers to avoid being preempted during these critical
20 : * regions. Or even better, the hardware may even handle applying such
21 : * time-critical programming independently of the CPU.
22 : *
23 : * While there's a decent amount of hardware that's designed so that the CPU
24 : * doesn't need to be concerned with extremely time-sensitive programming,
25 : * there's a few situations where it can't be helped. Some unforgiving
26 : * hardware may require that certain time-sensitive programming be handled
27 : * completely by the CPU, and said programming may even take too long to
28 : * handle in an IRQ handler. Another such situation would be where the driver
29 : * needs to perform a task that needs to complete within a specific scanout
30 : * period, but might possibly block and thus cannot be handled in an IRQ
31 : * context. Both of these situations can't be solved perfectly in Linux since
32 : * we're not a realtime kernel, and thus the scheduler may cause us to miss
33 : * our deadline if it decides to preempt us. But for some drivers, it's good
34 : * enough if we can lower our chance of being preempted to an absolute
35 : * minimum.
36 : *
37 : * This is where &drm_vblank_work comes in. &drm_vblank_work provides a simple
38 : * generic delayed work implementation which delays work execution until a
39 : * particular vblank has passed, and then executes the work at realtime
40 : * priority. This provides the best possible chance at performing
41 : * time-sensitive hardware programming on time, even when the system is under
42 : * heavy load. &drm_vblank_work also supports rescheduling, so that self
43 : * re-arming work items can be easily implemented.
44 : */
45 :
46 0 : void drm_handle_vblank_works(struct drm_vblank_crtc *vblank)
47 : {
48 : struct drm_vblank_work *work, *next;
49 0 : u64 count = atomic64_read(&vblank->count);
50 0 : bool wake = false;
51 :
52 : assert_spin_locked(&vblank->dev->event_lock);
53 :
54 0 : list_for_each_entry_safe(work, next, &vblank->pending_work, node) {
55 0 : if (!drm_vblank_passed(count, work->count))
56 0 : continue;
57 :
58 0 : list_del_init(&work->node);
59 0 : drm_vblank_put(vblank->dev, vblank->pipe);
60 0 : kthread_queue_work(vblank->worker, &work->base);
61 0 : wake = true;
62 : }
63 0 : if (wake)
64 0 : wake_up_all(&vblank->work_wait_queue);
65 0 : }
66 :
67 : /* Handle cancelling any pending vblank work items and drop respective vblank
68 : * references in response to vblank interrupts being disabled.
69 : */
70 0 : void drm_vblank_cancel_pending_works(struct drm_vblank_crtc *vblank)
71 : {
72 : struct drm_vblank_work *work, *next;
73 :
74 : assert_spin_locked(&vblank->dev->event_lock);
75 :
76 0 : list_for_each_entry_safe(work, next, &vblank->pending_work, node) {
77 0 : list_del_init(&work->node);
78 0 : drm_vblank_put(vblank->dev, vblank->pipe);
79 : }
80 :
81 0 : wake_up_all(&vblank->work_wait_queue);
82 0 : }
83 :
84 : /**
85 : * drm_vblank_work_schedule - schedule a vblank work
86 : * @work: vblank work to schedule
87 : * @count: target vblank count
88 : * @nextonmiss: defer until the next vblank if target vblank was missed
89 : *
90 : * Schedule @work for execution once the crtc vblank count reaches @count.
91 : *
92 : * If the crtc vblank count has already reached @count and @nextonmiss is
93 : * %false the work starts to execute immediately.
94 : *
95 : * If the crtc vblank count has already reached @count and @nextonmiss is
96 : * %true the work is deferred until the next vblank (as if @count has been
97 : * specified as crtc vblank count + 1).
98 : *
99 : * If @work is already scheduled, this function will reschedule said work
100 : * using the new @count. This can be used for self-rearming work items.
101 : *
102 : * Returns:
103 : * %1 if @work was successfully (re)scheduled, %0 if it was either already
104 : * scheduled or cancelled, or a negative error code on failure.
105 : */
106 0 : int drm_vblank_work_schedule(struct drm_vblank_work *work,
107 : u64 count, bool nextonmiss)
108 : {
109 0 : struct drm_vblank_crtc *vblank = work->vblank;
110 0 : struct drm_device *dev = vblank->dev;
111 : u64 cur_vbl;
112 : unsigned long irqflags;
113 0 : bool passed, inmodeset, rescheduling = false, wake = false;
114 0 : int ret = 0;
115 :
116 0 : spin_lock_irqsave(&dev->event_lock, irqflags);
117 0 : if (work->cancelling)
118 : goto out;
119 :
120 0 : spin_lock(&dev->vbl_lock);
121 0 : inmodeset = vblank->inmodeset;
122 0 : spin_unlock(&dev->vbl_lock);
123 0 : if (inmodeset)
124 : goto out;
125 :
126 0 : if (list_empty(&work->node)) {
127 0 : ret = drm_vblank_get(dev, vblank->pipe);
128 0 : if (ret < 0)
129 : goto out;
130 0 : } else if (work->count == count) {
131 : /* Already scheduled w/ same vbl count */
132 : goto out;
133 : } else {
134 : rescheduling = true;
135 : }
136 :
137 0 : work->count = count;
138 0 : cur_vbl = drm_vblank_count(dev, vblank->pipe);
139 0 : passed = drm_vblank_passed(cur_vbl, count);
140 0 : if (passed)
141 0 : drm_dbg_core(dev,
142 : "crtc %d vblank %llu already passed (current %llu)\n",
143 : vblank->pipe, count, cur_vbl);
144 :
145 0 : if (!nextonmiss && passed) {
146 0 : drm_vblank_put(dev, vblank->pipe);
147 0 : ret = kthread_queue_work(vblank->worker, &work->base);
148 :
149 0 : if (rescheduling) {
150 0 : list_del_init(&work->node);
151 0 : wake = true;
152 : }
153 : } else {
154 0 : if (!rescheduling)
155 0 : list_add_tail(&work->node, &vblank->pending_work);
156 : ret = true;
157 : }
158 :
159 : out:
160 0 : spin_unlock_irqrestore(&dev->event_lock, irqflags);
161 0 : if (wake)
162 0 : wake_up_all(&vblank->work_wait_queue);
163 0 : return ret;
164 : }
165 : EXPORT_SYMBOL(drm_vblank_work_schedule);
166 :
167 : /**
168 : * drm_vblank_work_cancel_sync - cancel a vblank work and wait for it to
169 : * finish executing
170 : * @work: vblank work to cancel
171 : *
172 : * Cancel an already scheduled vblank work and wait for its
173 : * execution to finish.
174 : *
175 : * On return, @work is guaranteed to no longer be scheduled or running, even
176 : * if it's self-arming.
177 : *
178 : * Returns:
179 : * %True if the work was cancelled before it started to execute, %false
180 : * otherwise.
181 : */
182 0 : bool drm_vblank_work_cancel_sync(struct drm_vblank_work *work)
183 : {
184 0 : struct drm_vblank_crtc *vblank = work->vblank;
185 0 : struct drm_device *dev = vblank->dev;
186 0 : bool ret = false;
187 :
188 0 : spin_lock_irq(&dev->event_lock);
189 0 : if (!list_empty(&work->node)) {
190 0 : list_del_init(&work->node);
191 0 : drm_vblank_put(vblank->dev, vblank->pipe);
192 0 : ret = true;
193 : }
194 :
195 0 : work->cancelling++;
196 0 : spin_unlock_irq(&dev->event_lock);
197 :
198 0 : wake_up_all(&vblank->work_wait_queue);
199 :
200 0 : if (kthread_cancel_work_sync(&work->base))
201 0 : ret = true;
202 :
203 0 : spin_lock_irq(&dev->event_lock);
204 0 : work->cancelling--;
205 0 : spin_unlock_irq(&dev->event_lock);
206 :
207 0 : return ret;
208 : }
209 : EXPORT_SYMBOL(drm_vblank_work_cancel_sync);
210 :
211 : /**
212 : * drm_vblank_work_flush - wait for a scheduled vblank work to finish
213 : * executing
214 : * @work: vblank work to flush
215 : *
216 : * Wait until @work has finished executing once.
217 : */
218 0 : void drm_vblank_work_flush(struct drm_vblank_work *work)
219 : {
220 0 : struct drm_vblank_crtc *vblank = work->vblank;
221 0 : struct drm_device *dev = vblank->dev;
222 :
223 0 : spin_lock_irq(&dev->event_lock);
224 0 : wait_event_lock_irq(vblank->work_wait_queue, list_empty(&work->node),
225 : dev->event_lock);
226 0 : spin_unlock_irq(&dev->event_lock);
227 :
228 0 : kthread_flush_work(&work->base);
229 0 : }
230 : EXPORT_SYMBOL(drm_vblank_work_flush);
231 :
232 : /**
233 : * drm_vblank_work_init - initialize a vblank work item
234 : * @work: vblank work item
235 : * @crtc: CRTC whose vblank will trigger the work execution
236 : * @func: work function to be executed
237 : *
238 : * Initialize a vblank work item for a specific crtc.
239 : */
240 0 : void drm_vblank_work_init(struct drm_vblank_work *work, struct drm_crtc *crtc,
241 : void (*func)(struct kthread_work *work))
242 : {
243 0 : kthread_init_work(&work->base, func);
244 0 : INIT_LIST_HEAD(&work->node);
245 0 : work->vblank = &crtc->dev->vblank[drm_crtc_index(crtc)];
246 0 : }
247 : EXPORT_SYMBOL(drm_vblank_work_init);
248 :
249 0 : int drm_vblank_worker_init(struct drm_vblank_crtc *vblank)
250 : {
251 : struct kthread_worker *worker;
252 :
253 0 : INIT_LIST_HEAD(&vblank->pending_work);
254 0 : init_waitqueue_head(&vblank->work_wait_queue);
255 0 : worker = kthread_create_worker(0, "card%d-crtc%d",
256 0 : vblank->dev->primary->index,
257 : vblank->pipe);
258 0 : if (IS_ERR(worker))
259 0 : return PTR_ERR(worker);
260 :
261 0 : vblank->worker = worker;
262 :
263 0 : sched_set_fifo(worker->task);
264 0 : return 0;
265 : }
|