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