1 /* $NetBSD: drm_wait_netbsd.h,v 1.17 2020/07/03 16:23:02 maxv 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 = getticks(); \ 144 /* XXX errno NetBSD->Linux */ \ 145 (RET) = -cv_timedwait_sig((Q), &(INTERLOCK)->sl_lock, 1); \ 146 _dswo_end = getticks(); \ 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 = getticks(); \ 243 /* XXX errno NetBSD->Linux */ \ 244 (RET) = -WAIT((Q), &(INTERLOCK)->mtx_lock, \ 245 MIN(_dtwu_ticks, INT_MAX/2)); \ 246 _dtwu_end = getticks(); \ 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 = getticks(); \ 318 /* XXX errno NetBSD->Linux */ \ 319 (RET) = -WAIT((Q), &(INTERLOCK)->sl_lock, \ 320 MIN(_dstwu_ticks, INT_MAX/2)); \ 321 _dstwu_end = getticks(); \ 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