| 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 | |