1 1.2 riastrad /* $NetBSD: drm_lock.c,v 1.3 2021/12/18 23:44:57 riastradh Exp $ */ 2 1.2 riastrad 3 1.3 riastrad /* 4 1.1 riastrad * \file drm_lock.c 5 1.1 riastrad * IOCTLs for locking 6 1.1 riastrad * 7 1.1 riastrad * \author Rickard E. (Rik) Faith <faith (at) valinux.com> 8 1.1 riastrad * \author Gareth Hughes <gareth (at) valinux.com> 9 1.1 riastrad */ 10 1.1 riastrad 11 1.1 riastrad /* 12 1.1 riastrad * Created: Tue Feb 2 08:37:54 1999 by faith (at) valinux.com 13 1.1 riastrad * 14 1.1 riastrad * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. 15 1.1 riastrad * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. 16 1.1 riastrad * All Rights Reserved. 17 1.1 riastrad * 18 1.1 riastrad * Permission is hereby granted, free of charge, to any person obtaining a 19 1.1 riastrad * copy of this software and associated documentation files (the "Software"), 20 1.1 riastrad * to deal in the Software without restriction, including without limitation 21 1.1 riastrad * the rights to use, copy, modify, merge, publish, distribute, sublicense, 22 1.1 riastrad * and/or sell copies of the Software, and to permit persons to whom the 23 1.1 riastrad * Software is furnished to do so, subject to the following conditions: 24 1.1 riastrad * 25 1.1 riastrad * The above copyright notice and this permission notice (including the next 26 1.1 riastrad * paragraph) shall be included in all copies or substantial portions of the 27 1.1 riastrad * Software. 28 1.1 riastrad * 29 1.1 riastrad * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 1.1 riastrad * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 1.1 riastrad * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 32 1.1 riastrad * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 33 1.1 riastrad * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 34 1.1 riastrad * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 35 1.1 riastrad * OTHER DEALINGS IN THE SOFTWARE. 36 1.1 riastrad */ 37 1.1 riastrad 38 1.2 riastrad #include <sys/cdefs.h> 39 1.2 riastrad __KERNEL_RCSID(0, "$NetBSD: drm_lock.c,v 1.3 2021/12/18 23:44:57 riastradh Exp $"); 40 1.2 riastrad 41 1.1 riastrad #include <linux/export.h> 42 1.3 riastrad #include <linux/sched/signal.h> 43 1.3 riastrad 44 1.3 riastrad #include <drm/drm.h> 45 1.3 riastrad #include <drm/drm_drv.h> 46 1.3 riastrad #include <drm/drm_file.h> 47 1.3 riastrad #include <drm/drm_print.h> 48 1.3 riastrad 49 1.3 riastrad #include "drm_internal.h" 50 1.2 riastrad #include "drm_legacy.h" 51 1.1 riastrad 52 1.1 riastrad static int drm_lock_take(struct drm_lock_data *lock_data, unsigned int context); 53 1.1 riastrad 54 1.1 riastrad /** 55 1.3 riastrad * Take the heavyweight lock. 56 1.3 riastrad * 57 1.3 riastrad * \param lock lock pointer. 58 1.3 riastrad * \param context locking context. 59 1.3 riastrad * \return one if the lock is held, or zero otherwise. 60 1.3 riastrad * 61 1.3 riastrad * Attempt to mark the lock as held by the given context, via the \p cmpxchg instruction. 62 1.3 riastrad */ 63 1.3 riastrad static 64 1.3 riastrad int drm_lock_take(struct drm_lock_data *lock_data, 65 1.3 riastrad unsigned int context) 66 1.3 riastrad { 67 1.3 riastrad unsigned int old, new, prev; 68 1.3 riastrad volatile unsigned int *lock = &lock_data->hw_lock->lock; 69 1.3 riastrad 70 1.3 riastrad spin_lock_bh(&lock_data->spinlock); 71 1.3 riastrad do { 72 1.3 riastrad old = *lock; 73 1.3 riastrad if (old & _DRM_LOCK_HELD) 74 1.3 riastrad new = old | _DRM_LOCK_CONT; 75 1.3 riastrad else { 76 1.3 riastrad new = context | _DRM_LOCK_HELD | 77 1.3 riastrad ((lock_data->user_waiters + lock_data->kernel_waiters > 1) ? 78 1.3 riastrad _DRM_LOCK_CONT : 0); 79 1.3 riastrad } 80 1.3 riastrad prev = cmpxchg(lock, old, new); 81 1.3 riastrad } while (prev != old); 82 1.3 riastrad spin_unlock_bh(&lock_data->spinlock); 83 1.3 riastrad 84 1.3 riastrad if (_DRM_LOCKING_CONTEXT(old) == context) { 85 1.3 riastrad if (old & _DRM_LOCK_HELD) { 86 1.3 riastrad if (context != DRM_KERNEL_CONTEXT) { 87 1.3 riastrad DRM_ERROR("%d holds heavyweight lock\n", 88 1.3 riastrad context); 89 1.3 riastrad } 90 1.3 riastrad return 0; 91 1.3 riastrad } 92 1.3 riastrad } 93 1.3 riastrad 94 1.3 riastrad if ((_DRM_LOCKING_CONTEXT(new)) == context && (new & _DRM_LOCK_HELD)) { 95 1.3 riastrad /* Have lock */ 96 1.3 riastrad return 1; 97 1.3 riastrad } 98 1.3 riastrad return 0; 99 1.3 riastrad } 100 1.3 riastrad 101 1.3 riastrad /** 102 1.3 riastrad * This takes a lock forcibly and hands it to context. Should ONLY be used 103 1.3 riastrad * inside *_unlock to give lock to kernel before calling *_dma_schedule. 104 1.3 riastrad * 105 1.3 riastrad * \param dev DRM device. 106 1.3 riastrad * \param lock lock pointer. 107 1.3 riastrad * \param context locking context. 108 1.3 riastrad * \return always one. 109 1.3 riastrad * 110 1.3 riastrad * Resets the lock file pointer. 111 1.3 riastrad * Marks the lock as held by the given context, via the \p cmpxchg instruction. 112 1.3 riastrad */ 113 1.3 riastrad static int drm_lock_transfer(struct drm_lock_data *lock_data, 114 1.3 riastrad unsigned int context) 115 1.3 riastrad { 116 1.3 riastrad unsigned int old, new, prev; 117 1.3 riastrad volatile unsigned int *lock = &lock_data->hw_lock->lock; 118 1.3 riastrad 119 1.3 riastrad lock_data->file_priv = NULL; 120 1.3 riastrad do { 121 1.3 riastrad old = *lock; 122 1.3 riastrad new = context | _DRM_LOCK_HELD; 123 1.3 riastrad prev = cmpxchg(lock, old, new); 124 1.3 riastrad } while (prev != old); 125 1.3 riastrad return 1; 126 1.3 riastrad } 127 1.3 riastrad 128 1.3 riastrad static int drm_legacy_lock_free(struct drm_lock_data *lock_data, 129 1.3 riastrad unsigned int context) 130 1.3 riastrad { 131 1.3 riastrad unsigned int old, new, prev; 132 1.3 riastrad volatile unsigned int *lock = &lock_data->hw_lock->lock; 133 1.3 riastrad 134 1.3 riastrad spin_lock_bh(&lock_data->spinlock); 135 1.3 riastrad if (lock_data->kernel_waiters != 0) { 136 1.3 riastrad drm_lock_transfer(lock_data, 0); 137 1.3 riastrad lock_data->idle_has_lock = 1; 138 1.3 riastrad spin_unlock_bh(&lock_data->spinlock); 139 1.3 riastrad return 1; 140 1.3 riastrad } 141 1.3 riastrad spin_unlock_bh(&lock_data->spinlock); 142 1.3 riastrad 143 1.3 riastrad do { 144 1.3 riastrad old = *lock; 145 1.3 riastrad new = _DRM_LOCKING_CONTEXT(old); 146 1.3 riastrad prev = cmpxchg(lock, old, new); 147 1.3 riastrad } while (prev != old); 148 1.3 riastrad 149 1.3 riastrad if (_DRM_LOCK_IS_HELD(old) && _DRM_LOCKING_CONTEXT(old) != context) { 150 1.3 riastrad DRM_ERROR("%d freed heavyweight lock held by %d\n", 151 1.3 riastrad context, _DRM_LOCKING_CONTEXT(old)); 152 1.3 riastrad return 1; 153 1.3 riastrad } 154 1.3 riastrad wake_up_interruptible(&lock_data->lock_queue); 155 1.3 riastrad return 0; 156 1.3 riastrad } 157 1.3 riastrad 158 1.3 riastrad /** 159 1.1 riastrad * Lock ioctl. 160 1.1 riastrad * 161 1.1 riastrad * \param inode device inode. 162 1.1 riastrad * \param file_priv DRM file private. 163 1.1 riastrad * \param cmd command. 164 1.1 riastrad * \param arg user argument, pointing to a drm_lock structure. 165 1.1 riastrad * \return zero on success or negative number on failure. 166 1.1 riastrad * 167 1.1 riastrad * Add the current task to the lock wait queue, and attempt to take to lock. 168 1.1 riastrad */ 169 1.2 riastrad int drm_legacy_lock(struct drm_device *dev, void *data, 170 1.2 riastrad struct drm_file *file_priv) 171 1.1 riastrad { 172 1.1 riastrad DECLARE_WAITQUEUE(entry, current); 173 1.1 riastrad struct drm_lock *lock = data; 174 1.1 riastrad struct drm_master *master = file_priv->master; 175 1.1 riastrad int ret = 0; 176 1.1 riastrad 177 1.3 riastrad if (!drm_core_check_feature(dev, DRIVER_LEGACY)) 178 1.3 riastrad return -EOPNOTSUPP; 179 1.2 riastrad 180 1.1 riastrad ++file_priv->lock_count; 181 1.1 riastrad 182 1.1 riastrad if (lock->context == DRM_KERNEL_CONTEXT) { 183 1.1 riastrad DRM_ERROR("Process %d using kernel context %d\n", 184 1.1 riastrad task_pid_nr(current), lock->context); 185 1.1 riastrad return -EINVAL; 186 1.1 riastrad } 187 1.1 riastrad 188 1.1 riastrad DRM_DEBUG("%d (pid %d) requests lock (0x%08x), flags = 0x%08x\n", 189 1.1 riastrad lock->context, task_pid_nr(current), 190 1.3 riastrad master->lock.hw_lock ? master->lock.hw_lock->lock : -1, 191 1.3 riastrad lock->flags); 192 1.1 riastrad 193 1.1 riastrad add_wait_queue(&master->lock.lock_queue, &entry); 194 1.1 riastrad spin_lock_bh(&master->lock.spinlock); 195 1.1 riastrad master->lock.user_waiters++; 196 1.1 riastrad spin_unlock_bh(&master->lock.spinlock); 197 1.1 riastrad 198 1.1 riastrad for (;;) { 199 1.1 riastrad __set_current_state(TASK_INTERRUPTIBLE); 200 1.1 riastrad if (!master->lock.hw_lock) { 201 1.1 riastrad /* Device has been unregistered */ 202 1.1 riastrad send_sig(SIGTERM, current, 0); 203 1.1 riastrad ret = -EINTR; 204 1.1 riastrad break; 205 1.1 riastrad } 206 1.1 riastrad if (drm_lock_take(&master->lock, lock->context)) { 207 1.1 riastrad master->lock.file_priv = file_priv; 208 1.1 riastrad master->lock.lock_time = jiffies; 209 1.1 riastrad break; /* Got lock */ 210 1.1 riastrad } 211 1.1 riastrad 212 1.1 riastrad /* Contention */ 213 1.1 riastrad mutex_unlock(&drm_global_mutex); 214 1.1 riastrad schedule(); 215 1.1 riastrad mutex_lock(&drm_global_mutex); 216 1.1 riastrad if (signal_pending(current)) { 217 1.1 riastrad ret = -EINTR; 218 1.1 riastrad break; 219 1.1 riastrad } 220 1.1 riastrad } 221 1.1 riastrad spin_lock_bh(&master->lock.spinlock); 222 1.1 riastrad master->lock.user_waiters--; 223 1.1 riastrad spin_unlock_bh(&master->lock.spinlock); 224 1.1 riastrad __set_current_state(TASK_RUNNING); 225 1.1 riastrad remove_wait_queue(&master->lock.lock_queue, &entry); 226 1.1 riastrad 227 1.1 riastrad DRM_DEBUG("%d %s\n", lock->context, 228 1.1 riastrad ret ? "interrupted" : "has lock"); 229 1.1 riastrad if (ret) return ret; 230 1.1 riastrad 231 1.1 riastrad /* don't set the block all signals on the master process for now 232 1.1 riastrad * really probably not the correct answer but lets us debug xkb 233 1.1 riastrad * xserver for now */ 234 1.3 riastrad if (!drm_is_current_master(file_priv)) { 235 1.1 riastrad dev->sigdata.context = lock->context; 236 1.1 riastrad dev->sigdata.lock = master->lock.hw_lock; 237 1.1 riastrad } 238 1.1 riastrad 239 1.1 riastrad if (dev->driver->dma_quiescent && (lock->flags & _DRM_LOCK_QUIESCENT)) 240 1.1 riastrad { 241 1.1 riastrad if (dev->driver->dma_quiescent(dev)) { 242 1.1 riastrad DRM_DEBUG("%d waiting for DMA quiescent\n", 243 1.1 riastrad lock->context); 244 1.1 riastrad return -EBUSY; 245 1.1 riastrad } 246 1.1 riastrad } 247 1.1 riastrad 248 1.1 riastrad return 0; 249 1.1 riastrad } 250 1.1 riastrad 251 1.1 riastrad /** 252 1.1 riastrad * Unlock ioctl. 253 1.1 riastrad * 254 1.1 riastrad * \param inode device inode. 255 1.1 riastrad * \param file_priv DRM file private. 256 1.1 riastrad * \param cmd command. 257 1.1 riastrad * \param arg user argument, pointing to a drm_lock structure. 258 1.1 riastrad * \return zero on success or negative number on failure. 259 1.1 riastrad * 260 1.1 riastrad * Transfer and free the lock. 261 1.1 riastrad */ 262 1.2 riastrad int drm_legacy_unlock(struct drm_device *dev, void *data, struct drm_file *file_priv) 263 1.1 riastrad { 264 1.1 riastrad struct drm_lock *lock = data; 265 1.1 riastrad struct drm_master *master = file_priv->master; 266 1.1 riastrad 267 1.3 riastrad if (!drm_core_check_feature(dev, DRIVER_LEGACY)) 268 1.3 riastrad return -EOPNOTSUPP; 269 1.2 riastrad 270 1.1 riastrad if (lock->context == DRM_KERNEL_CONTEXT) { 271 1.1 riastrad DRM_ERROR("Process %d using kernel context %d\n", 272 1.1 riastrad task_pid_nr(current), lock->context); 273 1.1 riastrad return -EINVAL; 274 1.1 riastrad } 275 1.1 riastrad 276 1.2 riastrad if (drm_legacy_lock_free(&master->lock, lock->context)) { 277 1.1 riastrad /* FIXME: Should really bail out here. */ 278 1.1 riastrad } 279 1.1 riastrad 280 1.1 riastrad return 0; 281 1.1 riastrad } 282 1.1 riastrad 283 1.1 riastrad /** 284 1.1 riastrad * This function returns immediately and takes the hw lock 285 1.1 riastrad * with the kernel context if it is free, otherwise it gets the highest priority when and if 286 1.1 riastrad * it is eventually released. 287 1.1 riastrad * 288 1.1 riastrad * This guarantees that the kernel will _eventually_ have the lock _unless_ it is held 289 1.1 riastrad * by a blocked process. (In the latter case an explicit wait for the hardware lock would cause 290 1.1 riastrad * a deadlock, which is why the "idlelock" was invented). 291 1.1 riastrad * 292 1.1 riastrad * This should be sufficient to wait for GPU idle without 293 1.1 riastrad * having to worry about starvation. 294 1.1 riastrad */ 295 1.1 riastrad 296 1.2 riastrad void drm_legacy_idlelock_take(struct drm_lock_data *lock_data) 297 1.1 riastrad { 298 1.1 riastrad int ret; 299 1.1 riastrad 300 1.1 riastrad spin_lock_bh(&lock_data->spinlock); 301 1.1 riastrad lock_data->kernel_waiters++; 302 1.1 riastrad if (!lock_data->idle_has_lock) { 303 1.1 riastrad 304 1.1 riastrad spin_unlock_bh(&lock_data->spinlock); 305 1.1 riastrad ret = drm_lock_take(lock_data, DRM_KERNEL_CONTEXT); 306 1.1 riastrad spin_lock_bh(&lock_data->spinlock); 307 1.1 riastrad 308 1.1 riastrad if (ret == 1) 309 1.1 riastrad lock_data->idle_has_lock = 1; 310 1.1 riastrad } 311 1.1 riastrad spin_unlock_bh(&lock_data->spinlock); 312 1.1 riastrad } 313 1.2 riastrad EXPORT_SYMBOL(drm_legacy_idlelock_take); 314 1.1 riastrad 315 1.2 riastrad void drm_legacy_idlelock_release(struct drm_lock_data *lock_data) 316 1.1 riastrad { 317 1.1 riastrad unsigned int old, prev; 318 1.1 riastrad volatile unsigned int *lock = &lock_data->hw_lock->lock; 319 1.1 riastrad 320 1.1 riastrad spin_lock_bh(&lock_data->spinlock); 321 1.1 riastrad if (--lock_data->kernel_waiters == 0) { 322 1.1 riastrad if (lock_data->idle_has_lock) { 323 1.1 riastrad do { 324 1.1 riastrad old = *lock; 325 1.1 riastrad prev = cmpxchg(lock, old, DRM_KERNEL_CONTEXT); 326 1.1 riastrad } while (prev != old); 327 1.1 riastrad wake_up_interruptible(&lock_data->lock_queue); 328 1.1 riastrad lock_data->idle_has_lock = 0; 329 1.1 riastrad } 330 1.1 riastrad } 331 1.1 riastrad spin_unlock_bh(&lock_data->spinlock); 332 1.1 riastrad } 333 1.2 riastrad EXPORT_SYMBOL(drm_legacy_idlelock_release); 334 1.1 riastrad 335 1.3 riastrad static int drm_legacy_i_have_hw_lock(struct drm_device *dev, 336 1.3 riastrad struct drm_file *file_priv) 337 1.1 riastrad { 338 1.1 riastrad struct drm_master *master = file_priv->master; 339 1.1 riastrad return (file_priv->lock_count && master->lock.hw_lock && 340 1.1 riastrad _DRM_LOCK_IS_HELD(master->lock.hw_lock->lock) && 341 1.1 riastrad master->lock.file_priv == file_priv); 342 1.1 riastrad } 343 1.3 riastrad 344 1.3 riastrad void drm_legacy_lock_release(struct drm_device *dev, struct file *filp) 345 1.3 riastrad { 346 1.3 riastrad struct drm_file *file_priv = filp->private_data; 347 1.3 riastrad 348 1.3 riastrad /* if the master has gone away we can't do anything with the lock */ 349 1.3 riastrad if (!dev->master) 350 1.3 riastrad return; 351 1.3 riastrad 352 1.3 riastrad if (drm_legacy_i_have_hw_lock(dev, file_priv)) { 353 1.3 riastrad DRM_DEBUG("File %p released, freeing lock for context %d\n", 354 1.3 riastrad filp, _DRM_LOCKING_CONTEXT(file_priv->master->lock.hw_lock->lock)); 355 1.3 riastrad drm_legacy_lock_free(&file_priv->master->lock, 356 1.3 riastrad _DRM_LOCKING_CONTEXT(file_priv->master->lock.hw_lock->lock)); 357 1.3 riastrad } 358 1.3 riastrad } 359 1.3 riastrad 360 1.3 riastrad void drm_legacy_lock_master_cleanup(struct drm_device *dev, struct drm_master *master) 361 1.3 riastrad { 362 1.3 riastrad if (!drm_core_check_feature(dev, DRIVER_LEGACY)) 363 1.3 riastrad return; 364 1.3 riastrad 365 1.3 riastrad /* 366 1.3 riastrad * Since the master is disappearing, so is the 367 1.3 riastrad * possibility to lock. 368 1.3 riastrad */ 369 1.3 riastrad mutex_lock(&dev->struct_mutex); 370 1.3 riastrad if (master->lock.hw_lock) { 371 1.3 riastrad if (dev->sigdata.lock == master->lock.hw_lock) 372 1.3 riastrad dev->sigdata.lock = NULL; 373 1.3 riastrad master->lock.hw_lock = NULL; 374 1.3 riastrad master->lock.file_priv = NULL; 375 1.3 riastrad wake_up_interruptible_all(&master->lock.lock_queue); 376 1.3 riastrad } 377 1.3 riastrad mutex_unlock(&dev->struct_mutex); 378 1.3 riastrad } 379