drm_wait_netbsd.h revision 1.14.10.1 1 /* $NetBSD: drm_wait_netbsd.h,v 1.14.10.1 2019/04/23 10:16:52 martin 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/errno.h>
43 #include <linux/mutex.h>
44 #include <linux/spinlock.h>
45
46 typedef kcondvar_t drm_waitqueue_t;
47
48 #define DRM_HZ hz /* XXX Hurk... */
49
50 #define DRM_UDELAY DELAY
51
52 static inline void
53 DRM_INIT_WAITQUEUE(drm_waitqueue_t *q, const char *name)
54 {
55 cv_init(q, name);
56 }
57
58 static inline void
59 DRM_DESTROY_WAITQUEUE(drm_waitqueue_t *q)
60 {
61 cv_destroy(q);
62 }
63
64 static inline bool
65 DRM_WAITERS_P(drm_waitqueue_t *q, struct mutex *interlock)
66 {
67 KASSERT(mutex_is_locked(interlock));
68 return cv_has_waiters(q);
69 }
70
71 static inline void
72 DRM_WAKEUP_ONE(drm_waitqueue_t *q, struct mutex *interlock)
73 {
74 KASSERT(mutex_is_locked(interlock));
75 cv_signal(q);
76 }
77
78 static inline void
79 DRM_WAKEUP_ALL(drm_waitqueue_t *q, struct mutex *interlock)
80 {
81 KASSERT(mutex_is_locked(interlock));
82 cv_broadcast(q);
83 }
84
85 static inline bool
86 DRM_SPIN_WAITERS_P(drm_waitqueue_t *q, spinlock_t *interlock)
87 {
88 KASSERT(spin_is_locked(interlock));
89 return cv_has_waiters(q);
90 }
91
92 static inline void
93 DRM_SPIN_WAKEUP_ONE(drm_waitqueue_t *q, spinlock_t *interlock)
94 {
95 KASSERT(spin_is_locked(interlock));
96 cv_signal(q);
97 }
98
99 static inline void
100 DRM_SPIN_WAKEUP_ALL(drm_waitqueue_t *q, spinlock_t *interlock)
101 {
102 KASSERT(spin_is_locked(interlock));
103 cv_broadcast(q);
104 }
105
106 /*
107 * DRM_SPIN_WAIT_ON is a replacement for the legacy DRM_WAIT_ON
108 * portability macro. It requires a spin interlock, which may require
109 * changes to the surrounding code so that the waits actually are
110 * interlocked by a spin lock. It also polls the condition at every
111 * tick, which masks missing wakeups. Since DRM_WAIT_ON is going away,
112 * in favour of Linux's native wait_event* API, waits in new code
113 * should be written to use the DRM_*WAIT*_UNTIL macros below.
114 *
115 * Like the legacy DRM_WAIT_ON, DRM_SPIN_WAIT_ON returns
116 *
117 * . -EBUSY if timed out (yes, -EBUSY, not -ETIMEDOUT or -EWOULDBLOCK),
118 * . -EINTR/-ERESTARTSYS if interrupted by a signal, or
119 * . 0 if the condition was true before or just after the timeout.
120 *
121 * Note that cv_timedwait* return -EWOULDBLOCK, not -EBUSY, on timeout.
122 *
123 * Note that ERESTARTSYS is actually ELAST+1 and only used in Linux
124 * code and must be converted for use in NetBSD code (user or kernel.)
125 */
126
127 #define DRM_SPIN_WAIT_ON(RET, Q, INTERLOCK, TICKS, CONDITION) do \
128 { \
129 unsigned _dswo_ticks = (TICKS); \
130 unsigned _dswo_start, _dswo_end; \
131 \
132 KASSERT(spin_is_locked((INTERLOCK))); \
133 KASSERT(!cpu_intr_p()); \
134 KASSERT(!cpu_softintr_p()); \
135 KASSERT(!cold); \
136 \
137 for (;;) { \
138 if (CONDITION) { \
139 (RET) = 0; \
140 break; \
141 } \
142 if (_dswo_ticks == 0) { \
143 (RET) = -EBUSY; /* Match Linux... */ \
144 break; \
145 } \
146 _dswo_start = hardclock_ticks; \
147 /* XXX errno NetBSD->Linux */ \
148 (RET) = -cv_timedwait_sig((Q), &(INTERLOCK)->sl_lock, 1); \
149 _dswo_end = hardclock_ticks; \
150 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 signal,
186 * -EBUSY on timeout, and zero on success; and cv_*wait*, which return
187 * -EINTR/-ERESTARTSYS on signal, -EWOULDBLOCK on timeout, and zero on
188 * 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 = hardclock_ticks; \
246 /* XXX errno NetBSD->Linux */ \
247 (RET) = -WAIT((Q), &(INTERLOCK)->mtx_lock, \
248 MIN(_dtwu_ticks, INT_MAX/2)); \
249 _dtwu_end = hardclock_ticks; \
250 if ((_dtwu_end - _dtwu_start) < _dtwu_ticks) \
251 _dtwu_ticks -= _dtwu_end - _dtwu_start; \
252 else \
253 _dtwu_ticks = 0; \
254 if (RET) { \
255 if ((RET) == -ERESTART) \
256 (RET) = -ERESTARTSYS; \
257 if ((RET) == -EWOULDBLOCK) \
258 (RET) = (CONDITION) ? 1 : 0; \
259 break; \
260 } \
261 } \
262 } while (0)
263
264 #define DRM_TIMED_WAIT_NOINTR_UNTIL(RET, Q, I, T, C) \
265 _DRM_TIMED_WAIT_UNTIL(RET, cv_timedwait, Q, I, T, C)
266
267 #define DRM_TIMED_WAIT_UNTIL(RET, Q, I, T, C) \
268 _DRM_TIMED_WAIT_UNTIL(RET, cv_timedwait_sig, Q, I, T, C)
269
270 /*
271 * XXX Can't assert sleepable here because we hold a spin lock. At
272 * least we can assert that we're not in (soft) interrupt context, and
273 * hope that nobody tries to use these with a sometimes quickly
274 * satisfied condition while holding a different spin lock.
275 */
276
277 #define _DRM_SPIN_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, CONDITION) do \
278 { \
279 KASSERT(spin_is_locked((INTERLOCK))); \
280 KASSERT(!cpu_intr_p()); \
281 KASSERT(!cpu_softintr_p()); \
282 KASSERT(!cold); \
283 (RET) = 0; \
284 while (!(CONDITION)) { \
285 /* XXX errno NetBSD->Linux */ \
286 (RET) = -WAIT((Q), &(INTERLOCK)->sl_lock); \
287 if ((RET) == -ERESTART) \
288 (RET) = -ERESTARTSYS; \
289 if (RET) \
290 break; \
291 } \
292 } while (0)
293
294 #define DRM_SPIN_WAIT_NOINTR_UNTIL(RET, Q, I, C) \
295 _DRM_SPIN_WAIT_UNTIL(RET, cv_wait_nointr, Q, I, C)
296
297 #define DRM_SPIN_WAIT_UNTIL(RET, Q, I, C) \
298 _DRM_SPIN_WAIT_UNTIL(RET, cv_wait_sig, Q, I, C)
299
300 #define _DRM_SPIN_TIMED_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, TICKS, CONDITION) \
301 do \
302 { \
303 unsigned _dstwu_ticks = (TICKS); \
304 unsigned _dstwu_start, _dstwu_end; \
305 \
306 KASSERT(spin_is_locked((INTERLOCK))); \
307 KASSERT(!cpu_intr_p()); \
308 KASSERT(!cpu_softintr_p()); \
309 KASSERT(!cold); \
310 \
311 for (;;) { \
312 if (CONDITION) { \
313 (RET) = MAX(_dstwu_ticks, 1); \
314 break; \
315 } \
316 if (_dstwu_ticks == 0) { \
317 (RET) = 0; \
318 break; \
319 } \
320 _dstwu_start = hardclock_ticks; \
321 /* XXX errno NetBSD->Linux */ \
322 (RET) = -WAIT((Q), &(INTERLOCK)->sl_lock, \
323 MIN(_dstwu_ticks, INT_MAX/2)); \
324 _dstwu_end = hardclock_ticks; \
325 if ((_dstwu_end - _dstwu_start) < _dstwu_ticks) \
326 _dstwu_ticks -= _dstwu_end - _dstwu_start; \
327 else \
328 _dstwu_ticks = 0; \
329 if (RET) { \
330 if ((RET) == -ERESTART) \
331 (RET) = -ERESTARTSYS; \
332 if ((RET) == -EWOULDBLOCK) \
333 (RET) = (CONDITION) ? 1 : 0; \
334 break; \
335 } \
336 } \
337 } while (0)
338
339 #define DRM_SPIN_TIMED_WAIT_NOINTR_UNTIL(RET, Q, I, T, C) \
340 _DRM_SPIN_TIMED_WAIT_UNTIL(RET, cv_timedwait, Q, I, T, C)
341
342 #define DRM_SPIN_TIMED_WAIT_UNTIL(RET, Q, I, T, C) \
343 _DRM_SPIN_TIMED_WAIT_UNTIL(RET, cv_timedwait_sig, Q, I, T, C)
344
345 #endif /* _DRM_DRM_WAIT_NETBSD_H_ */
346