1280183Sdumbbell/** 2280183Sdumbbell * \file drm_lock.c 3280183Sdumbbell * IOCTLs for locking 4280183Sdumbbell * 5280183Sdumbbell * \author Rickard E. (Rik) Faith <faith@valinux.com> 6280183Sdumbbell * \author Gareth Hughes <gareth@valinux.com> 7280183Sdumbbell */ 8280183Sdumbbell 9280183Sdumbbell/* 10280183Sdumbbell * Created: Tue Feb 2 08:37:54 1999 by faith@valinux.com 11280183Sdumbbell * 12235783Skib * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas. 13235783Skib * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California. 14235783Skib * All Rights Reserved. 15235783Skib * 16235783Skib * Permission is hereby granted, free of charge, to any person obtaining a 17235783Skib * copy of this software and associated documentation files (the "Software"), 18235783Skib * to deal in the Software without restriction, including without limitation 19235783Skib * the rights to use, copy, modify, merge, publish, distribute, sublicense, 20235783Skib * and/or sell copies of the Software, and to permit persons to whom the 21235783Skib * Software is furnished to do so, subject to the following conditions: 22235783Skib * 23235783Skib * The above copyright notice and this permission notice (including the next 24235783Skib * paragraph) shall be included in all copies or substantial portions of the 25235783Skib * Software. 26235783Skib * 27235783Skib * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28235783Skib * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29235783Skib * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 30235783Skib * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 31235783Skib * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 32235783Skib * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33235783Skib * OTHER DEALINGS IN THE SOFTWARE. 34235783Skib */ 35235783Skib 36235783Skib#include <sys/cdefs.h> 37235783Skib__FBSDID("$FreeBSD: releng/11.0/sys/dev/drm2/drm_lock.c 288112 2015-09-22 15:32:27Z bz $"); 38235783Skib 39280183Sdumbbell#include <dev/drm2/drmP.h> 40280183Sdumbbell 41288112Sbz#if defined(__linux__) 42280183Sdumbbellstatic int drm_notifier(void *priv); 43288112Sbz#endif 44280183Sdumbbell 45280183Sdumbbellstatic int drm_lock_take(struct drm_lock_data *lock_data, unsigned int context); 46280183Sdumbbell 47280183Sdumbbell/** 48280183Sdumbbell * Lock ioctl. 49235783Skib * 50280183Sdumbbell * \param inode device inode. 51280183Sdumbbell * \param file_priv DRM file private. 52280183Sdumbbell * \param cmd command. 53280183Sdumbbell * \param arg user argument, pointing to a drm_lock structure. 54280183Sdumbbell * \return zero on success or negative number on failure. 55235783Skib * 56280183Sdumbbell * Add the current task to the lock wait queue, and attempt to take to lock. 57235783Skib */ 58235783Skibint drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv) 59235783Skib{ 60235783Skib struct drm_lock *lock = data; 61280183Sdumbbell struct drm_master *master = file_priv->master; 62235783Skib int ret = 0; 63235783Skib 64280183Sdumbbell ++file_priv->lock_count; 65280183Sdumbbell 66235783Skib if (lock->context == DRM_KERNEL_CONTEXT) { 67235783Skib DRM_ERROR("Process %d using kernel context %d\n", 68280183Sdumbbell DRM_CURRENTPID, lock->context); 69280183Sdumbbell return -EINVAL; 70235783Skib } 71235783Skib 72235783Skib DRM_DEBUG("%d (pid %d) requests lock (0x%08x), flags = 0x%08x\n", 73280183Sdumbbell lock->context, DRM_CURRENTPID, 74280183Sdumbbell master->lock.hw_lock->lock, lock->flags); 75235783Skib 76280183Sdumbbell mtx_lock(&master->lock.spinlock); 77280183Sdumbbell master->lock.user_waiters++; 78280183Sdumbbell mtx_unlock(&master->lock.spinlock); 79235783Skib 80235783Skib for (;;) { 81280183Sdumbbell#if defined(__linux__) 82280183Sdumbbell if (!master->lock.hw_lock) { 83280183Sdumbbell /* Device has been unregistered */ 84280183Sdumbbell send_sig(SIGTERM, current, 0); 85280183Sdumbbell ret = -EINTR; 86280183Sdumbbell break; 87280183Sdumbbell } 88280183Sdumbbell#endif 89280183Sdumbbell if (drm_lock_take(&master->lock, lock->context)) { 90280183Sdumbbell master->lock.file_priv = file_priv; 91280183Sdumbbell master->lock.lock_time = jiffies; 92235783Skib atomic_inc(&dev->counts[_DRM_STAT_LOCKS]); 93280183Sdumbbell break; /* Got lock */ 94235783Skib } 95235783Skib 96235783Skib /* Contention */ 97280183Sdumbbell DRM_UNLOCK_ASSERT(dev); 98280183Sdumbbell ret = -sx_sleep(&master->lock.lock_queue, &drm_global_mutex, 99235783Skib PCATCH, "drmlk2", 0); 100280183Sdumbbell if (ret == -ERESTART) 101280183Sdumbbell ret = -ERESTARTSYS; 102235783Skib if (ret != 0) 103235783Skib break; 104235783Skib } 105280183Sdumbbell mtx_lock(&master->lock.spinlock); 106280183Sdumbbell master->lock.user_waiters--; 107280183Sdumbbell mtx_unlock(&master->lock.spinlock); 108235783Skib 109280183Sdumbbell DRM_DEBUG("%d %s\n", lock->context, 110280183Sdumbbell ret ? "interrupted" : "has lock"); 111280183Sdumbbell if (ret) return ret; 112235783Skib 113280183Sdumbbell#if defined(__linux__) 114280183Sdumbbell /* don't set the block all signals on the master process for now 115280183Sdumbbell * really probably not the correct answer but lets us debug xkb 116280183Sdumbbell * xserver for now */ 117280183Sdumbbell if (!file_priv->is_master) { 118280183Sdumbbell sigemptyset(&dev->sigmask); 119280183Sdumbbell sigaddset(&dev->sigmask, SIGSTOP); 120280183Sdumbbell sigaddset(&dev->sigmask, SIGTSTP); 121280183Sdumbbell sigaddset(&dev->sigmask, SIGTTIN); 122280183Sdumbbell sigaddset(&dev->sigmask, SIGTTOU); 123280183Sdumbbell dev->sigdata.context = lock->context; 124280183Sdumbbell dev->sigdata.lock = master->lock.hw_lock; 125280183Sdumbbell block_all_signals(drm_notifier, &dev->sigdata, &dev->sigmask); 126280183Sdumbbell } 127280183Sdumbbell#endif 128235783Skib 129280183Sdumbbell if (dev->driver->dma_quiescent && (lock->flags & _DRM_LOCK_QUIESCENT)) 130280183Sdumbbell { 131280183Sdumbbell if (dev->driver->dma_quiescent(dev)) { 132280183Sdumbbell DRM_DEBUG("%d waiting for DMA quiescent\n", 133280183Sdumbbell lock->context); 134280183Sdumbbell return -EBUSY; 135280183Sdumbbell } 136280183Sdumbbell } 137235783Skib 138235783Skib return 0; 139235783Skib} 140235783Skib 141280183Sdumbbell/** 142280183Sdumbbell * Unlock ioctl. 143280183Sdumbbell * 144280183Sdumbbell * \param inode device inode. 145280183Sdumbbell * \param file_priv DRM file private. 146280183Sdumbbell * \param cmd command. 147280183Sdumbbell * \param arg user argument, pointing to a drm_lock structure. 148280183Sdumbbell * \return zero on success or negative number on failure. 149280183Sdumbbell * 150280183Sdumbbell * Transfer and free the lock. 151280183Sdumbbell */ 152235783Skibint drm_unlock(struct drm_device *dev, void *data, struct drm_file *file_priv) 153235783Skib{ 154235783Skib struct drm_lock *lock = data; 155280183Sdumbbell struct drm_master *master = file_priv->master; 156235783Skib 157235783Skib if (lock->context == DRM_KERNEL_CONTEXT) { 158235783Skib DRM_ERROR("Process %d using kernel context %d\n", 159280183Sdumbbell DRM_CURRENTPID, lock->context); 160280183Sdumbbell return -EINVAL; 161235783Skib } 162235783Skib 163235783Skib atomic_inc(&dev->counts[_DRM_STAT_UNLOCKS]); 164235783Skib 165280183Sdumbbell if (drm_lock_free(&master->lock, lock->context)) { 166280183Sdumbbell /* FIXME: Should really bail out here. */ 167235783Skib } 168235783Skib 169280183Sdumbbell#if defined(__linux__) 170280183Sdumbbell unblock_all_signals(); 171280183Sdumbbell#endif 172235783Skib return 0; 173235783Skib} 174235783Skib 175280183Sdumbbell/** 176280183Sdumbbell * Take the heavyweight lock. 177280183Sdumbbell * 178280183Sdumbbell * \param lock lock pointer. 179280183Sdumbbell * \param context locking context. 180280183Sdumbbell * \return one if the lock is held, or zero otherwise. 181280183Sdumbbell * 182280183Sdumbbell * Attempt to mark the lock as held by the given context, via the \p cmpxchg instruction. 183280183Sdumbbell */ 184280183Sdumbbellstatic 185280183Sdumbbellint drm_lock_take(struct drm_lock_data *lock_data, 186280183Sdumbbell unsigned int context) 187235783Skib{ 188280183Sdumbbell unsigned int old, new, prev; 189235783Skib volatile unsigned int *lock = &lock_data->hw_lock->lock; 190235783Skib 191280183Sdumbbell mtx_lock(&lock_data->spinlock); 192235783Skib do { 193235783Skib old = *lock; 194235783Skib if (old & _DRM_LOCK_HELD) 195235783Skib new = old | _DRM_LOCK_CONT; 196280183Sdumbbell else { 197280183Sdumbbell new = context | _DRM_LOCK_HELD | 198280183Sdumbbell ((lock_data->user_waiters + lock_data->kernel_waiters > 1) ? 199280183Sdumbbell _DRM_LOCK_CONT : 0); 200280183Sdumbbell } 201280183Sdumbbell prev = cmpxchg(lock, old, new); 202280183Sdumbbell } while (prev != old); 203280183Sdumbbell mtx_unlock(&lock_data->spinlock); 204235783Skib 205235783Skib if (_DRM_LOCKING_CONTEXT(old) == context) { 206235783Skib if (old & _DRM_LOCK_HELD) { 207235783Skib if (context != DRM_KERNEL_CONTEXT) { 208235783Skib DRM_ERROR("%d holds heavyweight lock\n", 209280183Sdumbbell context); 210235783Skib } 211235783Skib return 0; 212235783Skib } 213235783Skib } 214280183Sdumbbell 215280183Sdumbbell if ((_DRM_LOCKING_CONTEXT(new)) == context && (new & _DRM_LOCK_HELD)) { 216235783Skib /* Have lock */ 217235783Skib return 1; 218235783Skib } 219235783Skib return 0; 220235783Skib} 221235783Skib 222280183Sdumbbell/** 223280183Sdumbbell * This takes a lock forcibly and hands it to context. Should ONLY be used 224280183Sdumbbell * inside *_unlock to give lock to kernel before calling *_dma_schedule. 225280183Sdumbbell * 226280183Sdumbbell * \param dev DRM device. 227280183Sdumbbell * \param lock lock pointer. 228280183Sdumbbell * \param context locking context. 229280183Sdumbbell * \return always one. 230280183Sdumbbell * 231280183Sdumbbell * Resets the lock file pointer. 232280183Sdumbbell * Marks the lock as held by the given context, via the \p cmpxchg instruction. 233280183Sdumbbell */ 234280183Sdumbbellstatic int drm_lock_transfer(struct drm_lock_data *lock_data, 235280183Sdumbbell unsigned int context) 236235783Skib{ 237280183Sdumbbell unsigned int old, new, prev; 238235783Skib volatile unsigned int *lock = &lock_data->hw_lock->lock; 239235783Skib 240235783Skib lock_data->file_priv = NULL; 241235783Skib do { 242235783Skib old = *lock; 243235783Skib new = context | _DRM_LOCK_HELD; 244280183Sdumbbell prev = cmpxchg(lock, old, new); 245280183Sdumbbell } while (prev != old); 246235783Skib return 1; 247235783Skib} 248235783Skib 249280183Sdumbbell/** 250280183Sdumbbell * Free lock. 251280183Sdumbbell * 252280183Sdumbbell * \param dev DRM device. 253280183Sdumbbell * \param lock lock. 254280183Sdumbbell * \param context context. 255280183Sdumbbell * 256280183Sdumbbell * Resets the lock file pointer. 257280183Sdumbbell * Marks the lock as not held, via the \p cmpxchg instruction. Wakes any task 258280183Sdumbbell * waiting on the lock queue. 259280183Sdumbbell */ 260235783Skibint drm_lock_free(struct drm_lock_data *lock_data, unsigned int context) 261235783Skib{ 262280183Sdumbbell unsigned int old, new, prev; 263235783Skib volatile unsigned int *lock = &lock_data->hw_lock->lock; 264235783Skib 265280183Sdumbbell mtx_lock(&lock_data->spinlock); 266280183Sdumbbell if (lock_data->kernel_waiters != 0) { 267280183Sdumbbell drm_lock_transfer(lock_data, 0); 268280183Sdumbbell lock_data->idle_has_lock = 1; 269280183Sdumbbell mtx_unlock(&lock_data->spinlock); 270280183Sdumbbell return 1; 271280183Sdumbbell } 272280183Sdumbbell mtx_unlock(&lock_data->spinlock); 273280183Sdumbbell 274235783Skib do { 275235783Skib old = *lock; 276280183Sdumbbell new = _DRM_LOCKING_CONTEXT(old); 277280183Sdumbbell prev = cmpxchg(lock, old, new); 278280183Sdumbbell } while (prev != old); 279235783Skib 280235783Skib if (_DRM_LOCK_IS_HELD(old) && _DRM_LOCKING_CONTEXT(old) != context) { 281235783Skib DRM_ERROR("%d freed heavyweight lock held by %d\n", 282280183Sdumbbell context, _DRM_LOCKING_CONTEXT(old)); 283235783Skib return 1; 284235783Skib } 285280183Sdumbbell wake_up_interruptible(&lock_data->lock_queue); 286235783Skib return 0; 287235783Skib} 288280183Sdumbbell 289288112Sbz#if defined(__linux__) 290280183Sdumbbell/** 291280183Sdumbbell * If we get here, it means that the process has called DRM_IOCTL_LOCK 292280183Sdumbbell * without calling DRM_IOCTL_UNLOCK. 293280183Sdumbbell * 294280183Sdumbbell * If the lock is not held, then let the signal proceed as usual. If the lock 295280183Sdumbbell * is held, then set the contended flag and keep the signal blocked. 296280183Sdumbbell * 297280183Sdumbbell * \param priv pointer to a drm_sigdata structure. 298280183Sdumbbell * \return one if the signal should be delivered normally, or zero if the 299280183Sdumbbell * signal should be blocked. 300280183Sdumbbell */ 301280183Sdumbbellstatic int drm_notifier(void *priv) 302280183Sdumbbell{ 303280183Sdumbbell struct drm_sigdata *s = (struct drm_sigdata *) priv; 304280183Sdumbbell unsigned int old, new, prev; 305280183Sdumbbell 306280183Sdumbbell /* Allow signal delivery if lock isn't held */ 307280183Sdumbbell if (!s->lock || !_DRM_LOCK_IS_HELD(s->lock->lock) 308280183Sdumbbell || _DRM_LOCKING_CONTEXT(s->lock->lock) != s->context) 309280183Sdumbbell return 1; 310280183Sdumbbell 311280183Sdumbbell /* Otherwise, set flag to force call to 312280183Sdumbbell drmUnlock */ 313280183Sdumbbell do { 314280183Sdumbbell old = s->lock->lock; 315280183Sdumbbell new = old | _DRM_LOCK_CONT; 316280183Sdumbbell prev = cmpxchg(&s->lock->lock, old, new); 317280183Sdumbbell } while (prev != old); 318280183Sdumbbell return 0; 319280183Sdumbbell} 320288112Sbz#endif 321280183Sdumbbell 322280183Sdumbbell/** 323280183Sdumbbell * This function returns immediately and takes the hw lock 324280183Sdumbbell * with the kernel context if it is free, otherwise it gets the highest priority when and if 325280183Sdumbbell * it is eventually released. 326280183Sdumbbell * 327280183Sdumbbell * This guarantees that the kernel will _eventually_ have the lock _unless_ it is held 328280183Sdumbbell * by a blocked process. (In the latter case an explicit wait for the hardware lock would cause 329280183Sdumbbell * a deadlock, which is why the "idlelock" was invented). 330280183Sdumbbell * 331280183Sdumbbell * This should be sufficient to wait for GPU idle without 332280183Sdumbbell * having to worry about starvation. 333280183Sdumbbell */ 334280183Sdumbbell 335280183Sdumbbellvoid drm_idlelock_take(struct drm_lock_data *lock_data) 336280183Sdumbbell{ 337280183Sdumbbell int ret; 338280183Sdumbbell 339280183Sdumbbell mtx_lock(&lock_data->spinlock); 340280183Sdumbbell lock_data->kernel_waiters++; 341280183Sdumbbell if (!lock_data->idle_has_lock) { 342280183Sdumbbell 343280183Sdumbbell mtx_unlock(&lock_data->spinlock); 344280183Sdumbbell ret = drm_lock_take(lock_data, DRM_KERNEL_CONTEXT); 345280183Sdumbbell mtx_lock(&lock_data->spinlock); 346280183Sdumbbell 347280183Sdumbbell if (ret == 1) 348280183Sdumbbell lock_data->idle_has_lock = 1; 349280183Sdumbbell } 350280183Sdumbbell mtx_unlock(&lock_data->spinlock); 351280183Sdumbbell} 352280183SdumbbellEXPORT_SYMBOL(drm_idlelock_take); 353280183Sdumbbell 354280183Sdumbbellvoid drm_idlelock_release(struct drm_lock_data *lock_data) 355280183Sdumbbell{ 356280183Sdumbbell unsigned int old, prev; 357280183Sdumbbell volatile unsigned int *lock = &lock_data->hw_lock->lock; 358280183Sdumbbell 359280183Sdumbbell mtx_lock(&lock_data->spinlock); 360280183Sdumbbell if (--lock_data->kernel_waiters == 0) { 361280183Sdumbbell if (lock_data->idle_has_lock) { 362280183Sdumbbell do { 363280183Sdumbbell old = *lock; 364280183Sdumbbell prev = cmpxchg(lock, old, DRM_KERNEL_CONTEXT); 365280183Sdumbbell } while (prev != old); 366280183Sdumbbell wake_up_interruptible(&lock_data->lock_queue); 367280183Sdumbbell lock_data->idle_has_lock = 0; 368280183Sdumbbell } 369280183Sdumbbell } 370280183Sdumbbell mtx_unlock(&lock_data->spinlock); 371280183Sdumbbell} 372280183SdumbbellEXPORT_SYMBOL(drm_idlelock_release); 373280183Sdumbbell 374280183Sdumbbellint drm_i_have_hw_lock(struct drm_device *dev, struct drm_file *file_priv) 375280183Sdumbbell{ 376280183Sdumbbell struct drm_master *master = file_priv->master; 377280183Sdumbbell return (file_priv->lock_count && master->lock.hw_lock && 378280183Sdumbbell _DRM_LOCK_IS_HELD(master->lock.hw_lock->lock) && 379280183Sdumbbell master->lock.file_priv == file_priv); 380280183Sdumbbell} 381