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