From 3b19b50cb5cd31e60eb03e99dd1109b6d0f5b8a3 Mon Sep 17 00:00:00 2001 From: Thomas Hellstrom Date: Sun, 21 Oct 2007 12:20:56 +0200 Subject: [PATCH] Remove the need for the hardware lock in the buffer manager. Add interface entry cleaning a memory type without touching NO_EVICT buffers. --- libdrm/xf86drm.c | 7 +- libdrm/xf86mm.h | 4 +- linux-core/Makefile.kernel | 2 +- linux-core/drm_bo.c | 11 ++- linux-core/drm_bo_lock.c | 178 +++++++++++++++++++++++++++++++++++++++++++++ linux-core/drm_compat.c | 3 + linux-core/drm_vm.c | 11 ++- shared-core/drm.h | 5 +- 8 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 linux-core/drm_bo_lock.c diff --git a/libdrm/xf86drm.c b/libdrm/xf86drm.c index b61c2250..ee0043cb 100644 --- a/libdrm/xf86drm.c +++ b/libdrm/xf86drm.c @@ -2820,14 +2820,15 @@ int drmMMTakedown(int fd, unsigned memType) * the buffer manager is NOT locked. */ -int drmMMLock(int fd, unsigned memType, int lockBM) +int drmMMLock(int fd, unsigned memType, int lockBM, int ignoreNoEvict) { struct drm_mm_type_arg arg; int ret; memset(&arg, 0, sizeof(arg)); arg.mem_type = memType; - arg.lock_unlock_bm = lock_bm; + arg.lock_flags |= (lockBM) ? DRM_BO_LOCK_UNLOCK_BM : 0; + arg.lock_flags |= (ignoreNoEvict) = DRM_BO_LOCK_IGNORE_NO_EVICT; do{ ret = ioctl(fd, DRM_IOCTL_MM_LOCK, &arg); @@ -2844,7 +2845,7 @@ int drmMMUnlock(int fd, unsigned memType, int unlockBM) memset(&arg, 0, sizeof(arg)); arg.mem_type = memType; - arg.lock_unlock_bm = unlockBM; + arg.lock_flags |= (unlockBM) ? DRM_BO_LOCK_UNLOCK_BM : 0; do{ ret = ioctl(fd, DRM_IOCTL_MM_UNLOCK, &arg); diff --git a/libdrm/xf86mm.h b/libdrm/xf86mm.h index f8ec1d75..0516bd32 100644 --- a/libdrm/xf86mm.h +++ b/libdrm/xf86mm.h @@ -172,8 +172,8 @@ extern int drmBOWaitIdle(int fd, drmBO *buf, unsigned hint); extern int drmMMInit(int fd, unsigned long pOffset, unsigned long pSize, unsigned memType); extern int drmMMTakedown(int fd, unsigned memType); -extern int drmMMLock(int fd, unsigned memType); -extern int drmMMUnlock(int fd, unsigned memType); +extern int drmMMLock(int fd, unsigned memType, int lockBM, int ignoreNoEvict); +extern int drmMMUnlock(int fd, unsigned memType, int unlockBM); #endif diff --git a/linux-core/Makefile.kernel b/linux-core/Makefile.kernel index 86b225f3..79136431 100644 --- a/linux-core/Makefile.kernel +++ b/linux-core/Makefile.kernel @@ -13,7 +13,7 @@ drm-objs := drm_auth.o drm_bufs.o drm_context.o drm_dma.o drm_drawable.o \ drm_sysfs.o drm_pci.o drm_agpsupport.o drm_scatter.o \ drm_memory_debug.o ati_pcigart.o drm_sman.o \ drm_hashtab.o drm_mm.o drm_object.o drm_compat.o \ - drm_fence.o drm_ttm.o drm_bo.o drm_bo_move.o drm_bo_lock2.o + drm_fence.o drm_ttm.o drm_bo.o drm_bo_move.o drm_bo_lock.o tdfx-objs := tdfx_drv.o r128-objs := r128_drv.o r128_cce.o r128_state.o r128_irq.o mga-objs := mga_drv.o mga_dma.o mga_state.o mga_warp.o mga_irq.o diff --git a/linux-core/drm_bo.c b/linux-core/drm_bo.c index a2a0291d..e6eb6320 100644 --- a/linux-core/drm_bo.c +++ b/linux-core/drm_bo.c @@ -2255,11 +2255,11 @@ int drm_mm_init_ioctl(struct drm_device *dev, void *data, struct drm_file *file_ return -EINVAL; } - ret = -EINVAL; ret = drm_bo_write_lock(&bm->bm_lock, file_priv); if (ret) return ret; + ret = -EINVAL; if (arg->magic != DRM_BO_INIT_MAGIC) { DRM_ERROR("You are using an old libdrm that is not compatible with\n" "\tthe kernel DRM module. Please upgrade your libdrm.\n"); @@ -2353,7 +2353,12 @@ int drm_mm_lock_ioctl(struct drm_device *dev, void *data, struct drm_file *file_ return -EINVAL; } - if (arg->lock_unlock_bm) { + if (arg->lock_flags & DRM_BO_LOCK_IGNORE_NO_EVICT) { + DRM_ERROR("Lock flag DRM_BO_LOCK_IGNORE_NO_EVICT not supported yet.\n"); + return -EINVAL; + } + + if (arg->lock_flags & DRM_BO_LOCK_UNLOCK_BM) { ret = drm_bo_write_lock(&dev->bm.bm_lock, file_priv); if (ret) return ret; @@ -2383,7 +2388,7 @@ int drm_mm_unlock_ioctl(struct drm_device *dev, return -EINVAL; } - if (arg->lock_unlock_bm) { + if (arg->lock_flags & DRM_BO_LOCK_UNLOCK_BM) { ret = drm_bo_write_unlock(&dev->bm.bm_lock, file_priv); if (ret) return ret; diff --git a/linux-core/drm_bo_lock.c b/linux-core/drm_bo_lock.c new file mode 100644 index 00000000..e5a86826 --- /dev/null +++ b/linux-core/drm_bo_lock.c @@ -0,0 +1,178 @@ +/************************************************************************** + * + * Copyright (c) 2007 Tungsten Graphics, Inc., Cedar Park, TX., USA + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + **************************************************************************/ +/* + * Authors: Thomas Hellström + */ + +/* + * This file implements a simple replacement for the buffer manager use + * of the heavyweight hardware lock. + * The lock is a read-write lock. Taking it in read mode is fast, and + * intended for in-kernel use only. + * Taking it in write mode is slow. + * + * The write mode is used only when there is a need to block all + * user-space processes from allocating a + * new memory area. + * Typical use in write mode is X server VT switching, and it's allowed + * to leave kernel space with the write lock held. If a user-space process + * dies while having the write-lock, it will be released during the file + * descriptor release. + * + * The read lock is typically placed at the start of an IOCTL- or + * user-space callable function that may end up allocating a memory area. + * This includes setstatus, super-ioctls and no_pfn; the latter may move + * unmappable regions to mappable. It's a bug to leave kernel space with the + * read lock held. + * + * Both read- and write lock taking is interruptible for low signal-delivery + * latency. The locking functions will return -EAGAIN if interrupted by a + * signal. + * + * Locking order: The lock should be taken BEFORE any kernel mutexes + * or spinlocks. + */ + +#include "drmP.h" + +void drm_bo_init_lock(struct drm_bo_lock *lock) +{ + DRM_INIT_WAITQUEUE(&lock->queue); + atomic_set(&lock->write_lock_pending, 0); + atomic_set(&lock->readers, 0); +} + +void drm_bo_read_unlock(struct drm_bo_lock *lock) +{ + if (unlikely(atomic_add_negative(-1, &lock->readers))) + BUG(); + if (atomic_read(&lock->readers) == 0) + wake_up_interruptible(&lock->queue); +} + +EXPORT_SYMBOL(drm_bo_read_unlock); + +int drm_bo_read_lock(struct drm_bo_lock *lock) +{ + while (unlikely(atomic_read(&lock->write_lock_pending) != 0)) { + int ret; + ret = wait_event_interruptible + (lock->queue, atomic_read(&lock->write_lock_pending) == 0); + if (ret) + return -EAGAIN; + } + + while (unlikely(!atomic_add_unless(&lock->readers, 1, -1))) { + int ret; + ret = wait_event_interruptible + (lock->queue, atomic_add_unless(&lock->readers, 1, -1)); + if (ret) + return -EAGAIN; + } + return 0; +} + +EXPORT_SYMBOL(drm_bo_read_lock); + +static int __drm_bo_write_unlock(struct drm_bo_lock *lock) +{ + if (unlikely(atomic_cmpxchg(&lock->readers, -1, 0) != -1)) + return -EINVAL; + if (unlikely(atomic_cmpxchg(&lock->write_lock_pending, 1, 0) != 1)) + return -EINVAL; + wake_up_interruptible(&lock->queue); + return 0; +} + +static void drm_bo_write_lock_remove(struct drm_file *file_priv, + struct drm_user_object *item) +{ + struct drm_bo_lock *lock = container_of(item, struct drm_bo_lock, base); + int ret; + + ret = __drm_bo_write_unlock(lock); + BUG_ON(ret); +} + +int drm_bo_write_lock(struct drm_bo_lock *lock, struct drm_file *file_priv) +{ + int ret = 0; + struct drm_device *dev; + + if (unlikely(atomic_cmpxchg(&lock->write_lock_pending, 0, 1) != 0)) { + return -EINVAL; + } + + while (unlikely(atomic_cmpxchg(&lock->readers, 0, -1) != 0)) { + ret = wait_event_interruptible + (lock->queue, atomic_cmpxchg(&lock->readers, 0, -1) == 0); + + if (ret) { + atomic_set(&lock->write_lock_pending, 0); + wake_up_interruptible(&lock->queue); + return -EAGAIN; + } + } + + /* + * Add a dummy user-object, the destructor of which will + * make sure the lock is released if the client dies + * while holding it. + */ + + dev = file_priv->head->dev; + mutex_lock(&dev->struct_mutex); + ret = drm_add_user_object(file_priv, &lock->base, 0); + lock->base.remove = &drm_bo_write_lock_remove; + lock->base.type = drm_lock_type; + if (ret) { + (void)__drm_bo_write_unlock(lock); + } + mutex_unlock(&dev->struct_mutex); + + return ret; +} + +int drm_bo_write_unlock(struct drm_bo_lock *lock, struct drm_file *file_priv) +{ + struct drm_device *dev = file_priv->head->dev; + struct drm_ref_object *ro; + + mutex_lock(&dev->struct_mutex); + + if (lock->base.owner != file_priv) { + mutex_unlock(&dev->struct_mutex); + return -EINVAL; + } + ro = drm_lookup_ref_object(file_priv, &lock->base, _DRM_REF_USE); + BUG_ON(!ro); + drm_remove_ref_object(file_priv, ro); + lock->base.owner = NULL; + + mutex_unlock(&dev->struct_mutex); + return 0; +} diff --git a/linux-core/drm_compat.c b/linux-core/drm_compat.c index e51aedb7..ae44e500 100644 --- a/linux-core/drm_compat.c +++ b/linux-core/drm_compat.c @@ -212,6 +212,8 @@ static struct page *drm_bo_vm_fault(struct vm_area_struct *vma, unsigned long bus_offset; unsigned long bus_size; + dev = bo->dev; + while(drm_bo_read_lock(&dev->bm.bm_lock)); mutex_lock(&bo->mutex); @@ -289,6 +291,7 @@ static struct page *drm_bo_vm_fault(struct vm_area_struct *vma, data->type = VM_FAULT_OOM; out_unlock: mutex_unlock(&bo->mutex); + drm_bo_read_unlock(&dev->bm.bm_lock); return NULL; } diff --git a/linux-core/drm_vm.c b/linux-core/drm_vm.c index c4e790ef..d2554f31 100644 --- a/linux-core/drm_vm.c +++ b/linux-core/drm_vm.c @@ -728,10 +728,17 @@ static unsigned long drm_bo_vm_nopfn(struct vm_area_struct *vma, if (address > vma->vm_end) return NOPFN_SIGBUS; - err = mutex_lock_interruptible(&bo->mutex); + dev = bo->dev; + err = drm_bo_read_lock(&dev->bm.bm_lock); if (err) return NOPFN_REFAULT; + err = mutex_lock_interruptible(&bo->mutex); + if (err) { + drm_bo_read_unlock(&dev->bm.bm_lock); + return NOPFN_REFAULT; + } + err = drm_bo_wait(bo, 0, 0, 0); if (err) { ret = (err != -EAGAIN) ? NOPFN_SIGBUS : NOPFN_REFAULT; @@ -754,7 +761,6 @@ static unsigned long drm_bo_vm_nopfn(struct vm_area_struct *vma, } } - dev = bo->dev; err = drm_bo_pci_offset(dev, &bo->mem, &bus_base, &bus_offset, &bus_size); @@ -792,6 +798,7 @@ static unsigned long drm_bo_vm_nopfn(struct vm_area_struct *vma, } out_unlock: mutex_unlock(&bo->mutex); + drm_bo_read_unlock(&dev->bm.bm_lock); return ret; } #endif diff --git a/shared-core/drm.h b/shared-core/drm.h index f88192ff..80c1a3e2 100644 --- a/shared-core/drm.h +++ b/shared-core/drm.h @@ -870,9 +870,12 @@ struct drm_bo_op_arg { #define DRM_BO_MEM_TYPES 8 /* For now. */ +#define DRM_BO_LOCK_UNLOCK_BM (1 << 0) +#define DRM_BO_LOCK_IGNORE_NO_EVICT (1 << 1) + struct drm_mm_type_arg { unsigned int mem_type; - int lock_unlock_bm; + unsigned int lock_flags; }; struct drm_mm_init_arg { -- 2.11.0