drm_wait_netbsd.h revision 1.15.6.1 1 /* $NetBSD: drm_wait_netbsd.h,v 1.15.6.1 2020/02/29 20:20:17 ad 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
45 typedef kcondvar_t drm_waitqueue_t;
46
47 #define DRM_UDELAY DELAY
48
49 static inline void
50 DRM_INIT_WAITQUEUE(drm_waitqueue_t *q, const char *name)
51 {
52 cv_init(q, name);
53 }
54
55 static inline void
56 DRM_DESTROY_WAITQUEUE(drm_waitqueue_t *q)
57 {
58 cv_destroy(q);
59 }
60
61 static inline bool
62 DRM_WAITERS_P(drm_waitqueue_t *q, struct mutex *interlock)
63 {
64 KASSERT(mutex_is_locked(interlock));
65 return cv_has_waiters(q);
66 }
67
68 static inline void
69 DRM_WAKEUP_ONE(drm_waitqueue_t *q, struct mutex *interlock)
70 {
71 KASSERT(mutex_is_locked(interlock));
72 cv_signal(q);
73 }
74
75 static inline void
76 DRM_WAKEUP_ALL(drm_waitqueue_t *q, struct mutex *interlock)
77 {
78 KASSERT(mutex_is_locked(interlock));
79 cv_broadcast(q);
80 }
81
82 static inline bool
83 DRM_SPIN_WAITERS_P(drm_waitqueue_t *q, spinlock_t *interlock)
84 {
85 KASSERT(spin_is_locked(interlock));
86 return cv_has_waiters(q);
87 }
88
89 static inline void
90 DRM_SPIN_WAKEUP_ONE(drm_waitqueue_t *q, spinlock_t *interlock)
91 {
92 KASSERT(spin_is_locked(interlock));
93 cv_signal(q);
94 }
95
96 static inline void
97 DRM_SPIN_WAKEUP_ALL(drm_waitqueue_t *q, spinlock_t *interlock)
98 {
99 KASSERT(spin_is_locked(interlock));
100 cv_broadcast(q);
101 }
102
103 /*
104 * DRM_SPIN_WAIT_ON is a replacement for the legacy DRM_WAIT_ON
105 * portability macro. It requires a spin interlock, which may require
106 * changes to the surrounding code so that the waits actually are
107 * interlocked by a spin lock. It also polls the condition at every
108 * tick, which masks missing wakeups. Since DRM_WAIT_ON is going away,
109 * in favour of Linux's native wait_event* API, waits in new code
110 * should be written to use the DRM_*WAIT*_UNTIL macros below.
111 *
112 * Like the legacy DRM_WAIT_ON, DRM_SPIN_WAIT_ON returns
113 *
114 * . -EBUSY if timed out (yes, -EBUSY, not -ETIMEDOUT or -EWOULDBLOCK),
115 * . -EINTR/-ERESTARTSYS if interrupted by a signal, or
116 * . 0 if the condition was true before or just after the timeout.
117 *
118 * Note that cv_timedwait* return -EWOULDBLOCK, not -EBUSY, on timeout.
119 *
120 * Note that ERESTARTSYS is actually ELAST+1 and only used in Linux
121 * code and must be converted for use in NetBSD code (user or kernel.)
122 */
123
124 #define DRM_SPIN_WAIT_ON(RET, Q, INTERLOCK, TICKS, CONDITION) do \
125 { \
126 unsigned _dswo_ticks = (TICKS); \
127 unsigned _dswo_start, _dswo_end; \
128 \
129 KASSERT(spin_is_locked((INTERLOCK))); \
130 KASSERT(!cpu_intr_p()); \
131 KASSERT(!cpu_softintr_p()); \
132 KASSERT(!cold); \
133 \
134 for (;;) { \
135 if (CONDITION) { \
136 (RET) = 0; \
137 break; \
138 } \
139 if (_dswo_ticks == 0) { \
140 (RET) = -EBUSY; /* Match Linux... */ \
141 break; \
142 } \
143 _dswo_start = hardclock_ticks; \
144 /* XXX errno NetBSD->Linux */ \
145 (RET) = -cv_timedwait_sig((Q), &(INTERLOCK)->sl_lock, 1); \
146 _dswo_end = hardclock_ticks; \
147 if (_dswo_end - _dswo_start < _dswo_ticks) \
148 _dswo_ticks -= _dswo_end - _dswo_start; \
149 else \
150 _dswo_ticks = 0; \
151 if (RET) { \
152 if ((RET) == -ERESTART) \
153 (RET) = -ERESTARTSYS; \
154 if ((RET) == -EWOULDBLOCK) \
155 /* Waited only one tick. */ \
156 continue; \
157 break; \
158 } \
159 } \
160 } while (0)
161
162 /*
163 * The DRM_*WAIT*_UNTIL macros are replacements for the Linux
164 * wait_event* macros. Like DRM_SPIN_WAIT_ON, they add an interlock,
165 * and so may require some changes to the surrounding code. They have
166 * a different return value convention from DRM_SPIN_WAIT_ON and a
167 * different return value convention from cv_*wait*.
168 *
169 * The untimed DRM_*WAIT*_UNTIL macros return
170 *
171 * . -EINTR/-ERESTARTSYS if interrupted by a signal, or
172 * . zero if the condition evaluated
173 *
174 * The timed DRM_*TIMED_WAIT*_UNTIL macros return
175 *
176 * . -EINTR/-ERESTARTSYS if interrupted by a signal,
177 * . 0 if the condition was false after the timeout,
178 * . 1 if the condition was true just after the timeout, or
179 * . the number of ticks remaining if the condition was true before the
180 * timeout.
181 *
182 * Contrast DRM_SPIN_WAIT_ON which returns -EINTR/-ERESTARTSYS on signal,
183 * -EBUSY on timeout, and zero on success; and cv_*wait*, which return
184 * -EINTR/-ERESTARTSYS on signal, -EWOULDBLOCK on timeout, and zero on
185 * success.
186 *
187 * XXX In retrospect, giving the timed and untimed macros a different
188 * return convention from one another to match Linux may have been a
189 * bad idea. All of this inconsistent timeout return convention logic
190 * has been a consistent source of bugs.
191 *
192 * Note that ERESTARTSYS is actually ELAST+1 and only used in Linux
193 * code and must be converted for use in NetBSD code (user or kernel.)
194 */
195
196 #define _DRM_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, CONDITION) do \
197 { \
198 KASSERT(mutex_is_locked((INTERLOCK))); \
199 ASSERT_SLEEPABLE(); \
200 KASSERT(!cold); \
201 for (;;) { \
202 if (CONDITION) { \
203 (RET) = 0; \
204 break; \
205 } \
206 /* XXX errno NetBSD->Linux */ \
207 (RET) = -WAIT((Q), &(INTERLOCK)->mtx_lock); \
208 if (RET) { \
209 if ((RET) == -ERESTART) \
210 (RET) = -ERESTARTSYS; \
211 break; \
212 } \
213 } \
214 } while (0)
215
216 #define cv_wait_nointr(Q, I) (cv_wait((Q), (I)), 0)
217
218 #define DRM_WAIT_NOINTR_UNTIL(RET, Q, I, C) \
219 _DRM_WAIT_UNTIL(RET, cv_wait_nointr, Q, I, C)
220
221 #define DRM_WAIT_UNTIL(RET, Q, I, C) \
222 _DRM_WAIT_UNTIL(RET, cv_wait_sig, Q, I, C)
223
224 #define _DRM_TIMED_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, TICKS, CONDITION) do \
225 { \
226 unsigned _dtwu_ticks = (TICKS); \
227 unsigned _dtwu_start, _dtwu_end; \
228 \
229 KASSERT(mutex_is_locked((INTERLOCK))); \
230 ASSERT_SLEEPABLE(); \
231 KASSERT(!cold); \
232 \
233 for (;;) { \
234 if (CONDITION) { \
235 (RET) = MAX(_dtwu_ticks, 1); \
236 break; \
237 } \
238 if (_dtwu_ticks == 0) { \
239 (RET) = 0; \
240 break; \
241 } \
242 _dtwu_start = hardclock_ticks; \
243 /* XXX errno NetBSD->Linux */ \
244 (RET) = -WAIT((Q), &(INTERLOCK)->mtx_lock, \
245 MIN(_dtwu_ticks, INT_MAX/2)); \
246 _dtwu_end = hardclock_ticks; \
247 if ((_dtwu_end - _dtwu_start) < _dtwu_ticks) \
248 _dtwu_ticks -= _dtwu_end - _dtwu_start; \
249 else \
250 _dtwu_ticks = 0; \
251 if (RET) { \
252 if ((RET) == -ERESTART) \
253 (RET) = -ERESTARTSYS; \
254 if ((RET) == -EWOULDBLOCK) \
255 (RET) = (CONDITION) ? 1 : 0; \
256 break; \
257 } \
258 } \
259 } while (0)
260
261 #define DRM_TIMED_WAIT_NOINTR_UNTIL(RET, Q, I, T, C) \
262 _DRM_TIMED_WAIT_UNTIL(RET, cv_timedwait, Q, I, T, C)
263
264 #define DRM_TIMED_WAIT_UNTIL(RET, Q, I, T, C) \
265 _DRM_TIMED_WAIT_UNTIL(RET, cv_timedwait_sig, Q, I, T, C)
266
267 /*
268 * XXX Can't assert sleepable here because we hold a spin lock. At
269 * least we can assert that we're not in (soft) interrupt context, and
270 * hope that nobody tries to use these with a sometimes quickly
271 * satisfied condition while holding a different spin lock.
272 */
273
274 #define _DRM_SPIN_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, CONDITION) do \
275 { \
276 KASSERT(spin_is_locked((INTERLOCK))); \
277 KASSERT(!cpu_intr_p()); \
278 KASSERT(!cpu_softintr_p()); \
279 KASSERT(!cold); \
280 (RET) = 0; \
281 while (!(CONDITION)) { \
282 /* XXX errno NetBSD->Linux */ \
283 (RET) = -WAIT((Q), &(INTERLOCK)->sl_lock); \
284 if ((RET) == -ERESTART) \
285 (RET) = -ERESTARTSYS; \
286 if (RET) \
287 break; \
288 } \
289 } while (0)
290
291 #define DRM_SPIN_WAIT_NOINTR_UNTIL(RET, Q, I, C) \
292 _DRM_SPIN_WAIT_UNTIL(RET, cv_wait_nointr, Q, I, C)
293
294 #define DRM_SPIN_WAIT_UNTIL(RET, Q, I, C) \
295 _DRM_SPIN_WAIT_UNTIL(RET, cv_wait_sig, Q, I, C)
296
297 #define _DRM_SPIN_TIMED_WAIT_UNTIL(RET, WAIT, Q, INTERLOCK, TICKS, CONDITION) \
298 do \
299 { \
300 unsigned _dstwu_ticks = (TICKS); \
301 unsigned _dstwu_start, _dstwu_end; \
302 \
303 KASSERT(spin_is_locked((INTERLOCK))); \
304 KASSERT(!cpu_intr_p()); \
305 KASSERT(!cpu_softintr_p()); \
306 KASSERT(!cold); \
307 \
308 for (;;) { \
309 if (CONDITION) { \
310 (RET) = MAX(_dstwu_ticks, 1); \
311 break; \
312 } \
313 if (_dstwu_ticks == 0) { \
314 (RET) = 0; \
315 break; \
316 } \
317 _dstwu_start = hardclock_ticks; \
318 /* XXX errno NetBSD->Linux */ \
319 (RET) = -WAIT((Q), &(INTERLOCK)->sl_lock, \
320 MIN(_dstwu_ticks, INT_MAX/2)); \
321 _dstwu_end = hardclock_ticks; \
322 if ((_dstwu_end - _dstwu_start) < _dstwu_ticks) \
323 _dstwu_ticks -= _dstwu_end - _dstwu_start; \
324 else \
325 _dstwu_ticks = 0; \
326 if (RET) { \
327 if ((RET) == -ERESTART) \
328 (RET) = -ERESTARTSYS; \
329 if ((RET) == -EWOULDBLOCK) \
330 (RET) = (CONDITION) ? 1 : 0; \
331 break; \
332 } \
333 } \
334 } while (0)
335
336 #define DRM_SPIN_TIMED_WAIT_NOINTR_UNTIL(RET, Q, I, T, C) \
337 _DRM_SPIN_TIMED_WAIT_UNTIL(RET, cv_timedwait, Q, I, T, C)
338
339 #define DRM_SPIN_TIMED_WAIT_UNTIL(RET, Q, I, T, C) \
340 _DRM_SPIN_TIMED_WAIT_UNTIL(RET, cv_timedwait_sig, Q, I, T, C)
341
342 #endif /* _DRM_DRM_WAIT_NETBSD_H_ */
343