1 /* $NetBSD: drm_wait_netbsd.h,v 1.19 2021/12/19 12:41:15 riastradh Exp $ */
2
3 /*-
4 * Copyright (c) 2013 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Taylor R. Campbell.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #ifndef _DRM_DRM_WAIT_NETBSD_H_
33 #define _DRM_DRM_WAIT_NETBSD_H_
34
35 #include <sys/param.h>
36 #include <sys/condvar.h>
37 #include <sys/cpu.h> /* cpu_intr_p */
38 #include <sys/kernel.h>
39 #include <sys/mutex.h>
40 #include <sys/systm.h>
41
42 #include <linux/mutex.h>
43 #include <linux/spinlock.h>
44 #include <linux/sched.h>
45
46 typedef kcondvar_t drm_waitqueue_t;
47
48 #define DRM_UDELAY DELAY
49
50 static inline void
DRM_INIT_WAITQUEUE(drm_waitqueue_t * q,const char * name)51 DRM_INIT_WAITQUEUE(drm_waitqueue_t *q, const char *name)
52 {
53 cv_init(q, name);
54 }
55
56 static inline void
DRM_DESTROY_WAITQUEUE(drm_waitqueue_t * q)57 DRM_DESTROY_WAITQUEUE(drm_waitqueue_t *q)
58 {
59 cv_destroy(q);
60 }
61
62 static inline bool
DRM_WAITERS_P(drm_waitqueue_t * q,struct mutex * interlock)63 DRM_WAITERS_P(drm_waitqueue_t *q, struct mutex *interlock)
64 {
65 KASSERT(mutex_is_locked(interlock));
66 return cv_has_waiters(q);
67 }
68
69 static inline void
DRM_WAKEUP_ONE(drm_waitqueue_t * q,struct mutex * interlock)70 DRM_WAKEUP_ONE(drm_waitqueue_t *q, struct mutex *interlock)
71 {
72 KASSERT(mutex_is_locked(interlock));
73 cv_signal(q);
74 }
75
76 static inline void
DRM_WAKEUP_ALL(drm_waitqueue_t * q,struct mutex * interlock)77 DRM_WAKEUP_ALL(drm_waitqueue_t *q, struct mutex *interlock)
78 {
79 KASSERT(mutex_is_locked(interlock));
80 cv_broadcast(q);
81 }
82
83 static inline bool
DRM_SPIN_WAITERS_P(drm_waitqueue_t * q,spinlock_t * interlock)84 DRM_SPIN_WAITERS_P(drm_waitqueue_t *q, spinlock_t *interlock)
85 {
86 KASSERT(spin_is_locked(interlock));
87 return cv_has_waiters(q);
88 }
89
90 static inline void
DRM_SPIN_WAKEUP_ONE(drm_waitqueue_t * q,spinlock_t * interlock)91 DRM_SPIN_WAKEUP_ONE(drm_waitqueue_t *q, spinlock_t *interlock)
92 {
93 KASSERT(spin_is_locked(interlock));
94 cv_signal(q);
95 }
96
97 static inline void
DRM_SPIN_WAKEUP_ALL(drm_waitqueue_t * q,spinlock_t * interlock)98 DRM_SPIN_WAKEUP_ALL(drm_waitqueue_t *q, spinlock_t *interlock)
99 {
100 KASSERT(spin_is_locked(interlock));
101 cv_broadcast(q);
102 }
103
104 /*
105 * DRM_SPIN_WAIT_ON is a replacement for the legacy DRM_WAIT_ON
106 * portability macro. It requires a spin interlock, which may require
107 * changes to the surrounding code so that the waits actually are
108 * interlocked by a spin lock. It also polls the condition at every
109 * tick, which masks missing wakeups. Since DRM_WAIT_ON is going away,
110 * in favour of Linux's native wait_event* API, waits in new code
111 * should be written to use the DRM_*WAIT*_UNTIL macros below.
112 *
113 * Like the legacy DRM_WAIT_ON, DRM_SPIN_WAIT_ON returns
114 *
115 * . -EBUSY if timed out (yes, -EBUSY, not -ETIMEDOUT or -EWOULDBLOCK),
116 * . -EINTR/-ERESTARTSYS if interrupted by a signal, or
117 * . 0 if the condition was true before or just after the timeout.
118 *
119 * Note that cv_timedwait* return EWOULDBLOCK, not EBUSY, on timeout.
120 *
121 * Note that ERESTARTSYS is actually ELAST+1 and only used in Linux
122 * code and must be converted for use in NetBSD code (user or kernel.)
123 */
124
125 #define DRM_SPIN_WAIT_ON(RET, Q, INTERLOCK, TICKS, CONDITION) do \
126 { \
127 unsigned _dswo_ticks = (TICKS); \
128 unsigned _dswo_start, _dswo_end; \
129 \
130 KASSERT(spin_is_locked((INTERLOCK))); \
131 KASSERT(!cpu_intr_p()); \
132 KASSERT(!cpu_softintr_p()); \
133 KASSERT(!cold); \
134 \
135 for (;;) { \
136 if (CONDITION) { \
137 (RET) = 0; \
138 break; \
139 } \
140 if (_dswo_ticks == 0) { \
141 (RET) = -EBUSY; /* Match Linux... */ \
142 break; \
143 } \
144 _dswo_start = getticks(); \
145 /* XXX errno NetBSD->Linux */ \
146 (RET) = -cv_timedwait_sig((Q), &(INTERLOCK)->sl_lock, 1); \
147 _dswo_end = getticks(); \
148 if (_dswo_ticks == MAX_SCHEDULE_TIMEOUT) \
149 /* nothing, never time out */; \
150 else if (_dswo_end - _dswo_start < _dswo_ticks) \
151 _dswo_ticks -= _dswo_end - _dswo_start; \
152 else \
153 _dswo_ticks = 0; \
154 if (RET) { \
155 if ((RET) == -ERESTART) \
156 (RET) = -ERESTARTSYS; \
157 if ((RET) == -EWOULDBLOCK) \
158 /* Waited only one tick. */ \
159 continue; \
160 break; \
161 } \
162 } \
163 } while (0)
164
165 /*
166 * The DRM_*WAIT*_UNTIL macros are replacements for the Linux
167 * wait_event* macros. Like DRM_SPIN_WAIT_ON, they add an interlock,
168 * and so may require some changes to the surrounding code. They have
169 * a different return value convention from DRM_SPIN_WAIT_ON and a
170 * different return value convention from cv_*wait*.
171 *
172 * The untimed DRM_*WAIT*_UNTIL macros return
173 *
174 * . -EINTR/-ERESTARTSYS if interrupted by a signal, or
175 * . zero if the condition evaluated
176 *
177 * The timed DRM_*TIMED_WAIT*_UNTIL macros return
178 *
179 * . -EINTR/-ERESTARTSYS if interrupted by a signal,
180 * . 0 if the condition was false after the timeout,
181 * . 1 if the condition was true just after the timeout, or
182 * . the number of ticks remaining if the condition was true before the
183 * timeout.
184 *
185 * Contrast DRM_SPIN_WAIT_ON which returns -EINTR/-ERESTARTSYS on
186 * signal, -EBUSY on timeout, and zero on success; and cv_*wait*, which
187 * return EINTR/ERESTARTSYS on signal, EWOULDBLOCK on timeout, and zero
188 * on success.
189 *
190 * XXX In retrospect, giving the timed and untimed macros a different
191 * return convention from one another to match Linux may have been a
192 * bad idea. All of this inconsistent timeout return convention logic
193 * has been a consistent source of bugs.
194 *
195 * Note that ERESTARTSYS is actually ELAST+1 and only used in Linux
196 * code and must be converted for use in NetBSD code (user or kernel.)
197 */
198
199 #define _DRM_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, CONDITION) do \
200 { \
201 KASSERT(mutex_is_locked((INTERLOCK))); \
202 ASSERT_SLEEPABLE(); \
203 KASSERT(!cold); \
204 for (;;) { \
205 if (CONDITION) { \
206 (RET) = 0; \
207 break; \
208 } \
209 /* XXX errno NetBSD->Linux */ \
210 (RET) = -WAIT((Q), &(INTERLOCK)->mtx_lock); \
211 if (RET) { \
212 if ((RET) == -ERESTART) \
213 (RET) = -ERESTARTSYS; \
214 break; \
215 } \
216 } \
217 } while (0)
218
219 #define cv_wait_nointr(Q, I) (cv_wait((Q), (I)), 0)
220
221 #define DRM_WAIT_NOINTR_UNTIL(RET, Q, I, C) \
222 _DRM_WAIT_UNTIL(RET, cv_wait_nointr, Q, I, C)
223
224 #define DRM_WAIT_UNTIL(RET, Q, I, C) \
225 _DRM_WAIT_UNTIL(RET, cv_wait_sig, Q, I, C)
226
227 #define _DRM_TIMED_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, TICKS, CONDITION) do \
228 { \
229 unsigned _dtwu_ticks = (TICKS); \
230 unsigned _dtwu_start, _dtwu_end; \
231 \
232 KASSERT(mutex_is_locked((INTERLOCK))); \
233 ASSERT_SLEEPABLE(); \
234 KASSERT(!cold); \
235 \
236 for (;;) { \
237 if (CONDITION) { \
238 (RET) = MAX(_dtwu_ticks, 1); \
239 break; \
240 } \
241 if (_dtwu_ticks == 0) { \
242 (RET) = 0; \
243 break; \
244 } \
245 _dtwu_start = getticks(); \
246 /* XXX errno NetBSD->Linux */ \
247 (RET) = -WAIT((Q), &(INTERLOCK)->mtx_lock, \
248 MIN(_dtwu_ticks, INT_MAX/2)); \
249 _dtwu_end = getticks(); \
250 if (_dtwu_ticks == MAX_SCHEDULE_TIMEOUT) \
251 /* nothing, never time out */; \
252 else if ((_dtwu_end - _dtwu_start) < _dtwu_ticks) \
253 _dtwu_ticks -= _dtwu_end - _dtwu_start; \
254 else \
255 _dtwu_ticks = 0; \
256 if (RET) { \
257 if ((RET) == -ERESTART) \
258 (RET) = -ERESTARTSYS; \
259 if ((RET) == -EWOULDBLOCK) \
260 (RET) = (CONDITION) ? 1 : 0; \
261 break; \
262 } \
263 } \
264 } while (0)
265
266 #define DRM_TIMED_WAIT_NOINTR_UNTIL(RET, Q, I, T, C) \
267 _DRM_TIMED_WAIT_UNTIL(RET, cv_timedwait, Q, I, T, C)
268
269 #define DRM_TIMED_WAIT_UNTIL(RET, Q, I, T, C) \
270 _DRM_TIMED_WAIT_UNTIL(RET, cv_timedwait_sig, Q, I, T, C)
271
272 /*
273 * XXX Can't assert sleepable here because we hold a spin lock. At
274 * least we can assert that we're not in (soft) interrupt context, and
275 * hope that nobody tries to use these with a sometimes quickly
276 * satisfied condition while holding a different spin lock.
277 */
278
279 #define _DRM_SPIN_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, CONDITION) do \
280 { \
281 KASSERT(spin_is_locked((INTERLOCK))); \
282 KASSERT(!cpu_intr_p()); \
283 KASSERT(!cpu_softintr_p()); \
284 KASSERT(!cold); \
285 (RET) = 0; \
286 while (!(CONDITION)) { \
287 /* XXX errno NetBSD->Linux */ \
288 (RET) = -WAIT((Q), &(INTERLOCK)->sl_lock); \
289 if ((RET) == -ERESTART) \
290 (RET) = -ERESTARTSYS; \
291 if (RET) \
292 break; \
293 } \
294 } while (0)
295
296 #define DRM_SPIN_WAIT_NOINTR_UNTIL(RET, Q, I, C) \
297 _DRM_SPIN_WAIT_UNTIL(RET, cv_wait_nointr, Q, I, C)
298
299 #define DRM_SPIN_WAIT_UNTIL(RET, Q, I, C) \
300 _DRM_SPIN_WAIT_UNTIL(RET, cv_wait_sig, Q, I, C)
301
302 #define _DRM_SPIN_TIMED_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, TICKS, CONDITION) \
303 do \
304 { \
305 unsigned _dstwu_ticks = (TICKS); \
306 unsigned _dstwu_start, _dstwu_end; \
307 \
308 KASSERT(spin_is_locked((INTERLOCK))); \
309 KASSERT(!cpu_intr_p()); \
310 KASSERT(!cpu_softintr_p()); \
311 KASSERT(!cold); \
312 \
313 for (;;) { \
314 if (CONDITION) { \
315 (RET) = MAX(_dstwu_ticks, 1); \
316 break; \
317 } \
318 if (_dstwu_ticks == 0) { \
319 (RET) = 0; \
320 break; \
321 } \
322 _dstwu_start = getticks(); \
323 /* XXX errno NetBSD->Linux */ \
324 (RET) = -WAIT((Q), &(INTERLOCK)->sl_lock, \
325 MIN(_dstwu_ticks, INT_MAX/2)); \
326 _dstwu_end = getticks(); \
327 if (_dstwu_ticks == MAX_SCHEDULE_TIMEOUT) \
328 /* nothing, never time out */; \
329 else if ((_dstwu_end - _dstwu_start) < _dstwu_ticks) \
330 _dstwu_ticks -= _dstwu_end - _dstwu_start; \
331 else \
332 _dstwu_ticks = 0; \
333 if (RET) { \
334 if ((RET) == -ERESTART) \
335 (RET) = -ERESTARTSYS; \
336 if ((RET) == -EWOULDBLOCK) \
337 (RET) = (CONDITION) ? 1 : 0; \
338 break; \
339 } \
340 } \
341 } while (0)
342
343 #define DRM_SPIN_TIMED_WAIT_NOINTR_UNTIL(RET, Q, I, T, C) \
344 _DRM_SPIN_TIMED_WAIT_UNTIL(RET, cv_timedwait, Q, I, T, C)
345
346 #define DRM_SPIN_TIMED_WAIT_UNTIL(RET, Q, I, T, C) \
347 _DRM_SPIN_TIMED_WAIT_UNTIL(RET, cv_timedwait_sig, Q, I, T, C)
348
349 #endif /* _DRM_DRM_WAIT_NETBSD_H_ */
350