Module Name:    src
Committed By:   riastradh
Date:           Wed Jan 15 13:53:53 UTC 2014

Modified Files:
        src/sys/external/bsd/drm2/dist/drm [riastradh-drm2]: drm_bufs.c
            drm_context.c drm_stub.c
        src/sys/external/bsd/drm2/dist/include/drm [riastradh-drm2]: drmP.h
        src/sys/external/bsd/drm2/drm [riastradh-drm2]: drm_fops.c drm_lock.c

Log Message:
Rewrite drm locking support for NetBSD.


To generate a diff of this commit:
cvs rdiff -u -r1.1.1.1.2.13 -r1.1.1.1.2.14 \
    src/sys/external/bsd/drm2/dist/drm/drm_bufs.c
cvs rdiff -u -r1.1.1.1.2.7 -r1.1.1.1.2.8 \
    src/sys/external/bsd/drm2/dist/drm/drm_context.c
cvs rdiff -u -r1.1.1.1.2.10 -r1.1.1.1.2.11 \
    src/sys/external/bsd/drm2/dist/drm/drm_stub.c
cvs rdiff -u -r1.1.1.1.2.53 -r1.1.1.1.2.54 \
    src/sys/external/bsd/drm2/dist/include/drm/drmP.h
cvs rdiff -u -r1.1.2.6 -r1.1.2.7 src/sys/external/bsd/drm2/drm/drm_fops.c
cvs rdiff -u -r1.1.2.3 -r1.1.2.4 src/sys/external/bsd/drm2/drm/drm_lock.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/external/bsd/drm2/dist/drm/drm_bufs.c
diff -u src/sys/external/bsd/drm2/dist/drm/drm_bufs.c:1.1.1.1.2.13 src/sys/external/bsd/drm2/dist/drm/drm_bufs.c:1.1.1.1.2.14
--- src/sys/external/bsd/drm2/dist/drm/drm_bufs.c:1.1.1.1.2.13	Sun Sep  8 16:10:23 2013
+++ src/sys/external/bsd/drm2/dist/drm/drm_bufs.c	Wed Jan 15 13:53:53 2014
@@ -39,6 +39,7 @@
 #include <linux/log2.h>
 #include <linux/export.h>
 #include <linux/mm.h>
+#include <asm/bug.h>
 #include <asm/shmparam.h>
 #include <drm/drmP.h>
 
@@ -246,12 +247,15 @@ static int drm_addmap_core(struct drm_de
 		map->offset = (unsigned long)map->handle;
 		if (map->flags & _DRM_CONTAINS_LOCK) {
 			/* Prevent a 2nd X Server from creating a 2nd lock */
+			spin_lock(&dev->primary->master->lock.spinlock);
 			if (dev->primary->master->lock.hw_lock != NULL) {
 				vfree(map->handle);
 				kfree(map);
+				spin_unlock(&dev->primary->master->lock.spinlock);
 				return -EBUSY;
 			}
 			dev->sigdata.lock = dev->primary->master->lock.hw_lock = map->handle;	/* Pointer to lock */
+			spin_unlock(&dev->primary->master->lock.spinlock);
 		}
 		break;
 	case _DRM_AGP: {
@@ -492,19 +496,36 @@ int drm_rmmap_locked(struct drm_device *
 		}
 		break;
 	case _DRM_SHM:
-		vfree(map->handle);
 		if (master) {
+			spin_lock(&master->lock.spinlock);
+			/*
+			 * If we successfully removed this mapping,
+			 * then the mapping must have been there in the
+			 * first place, and we must have had a
+			 * heavyweight lock, so we assert here instead
+			 * of just checking and failing.
+			 *
+			 * XXX What about the _DRM_CONTAINS_LOCK flag?
+			 * Where is that supposed to be set?  Is it
+			 * equivalent to having a master set?
+			 *
+			 * XXX There is copypasta of this in
+			 * drm_fops.c.
+			 */
+			BUG_ON(master->lock.hw_lock == NULL);
 			if (dev->sigdata.lock == master->lock.hw_lock)
 				dev->sigdata.lock = NULL;
 			master->lock.hw_lock = NULL;   /* SHM removed */
 			master->lock.file_priv = NULL;
 #ifdef __NetBSD__
-			DRM_WAKEUP_ALL(&master->lock.lock_queue,
-			    &drm_global_mutex);
+			DRM_SPIN_WAKEUP_ALL(&master->lock.lock_queue,
+			    &master->lock.spinlock);
 #else
 			wake_up_interruptible_all(&master->lock.lock_queue);
 #endif
+			spin_unlock(&master->lock.spinlock);
 		}
+		vfree(map->handle);
 		break;
 	case _DRM_AGP:
 	case _DRM_SCATTER_GATHER:

Index: src/sys/external/bsd/drm2/dist/drm/drm_context.c
diff -u src/sys/external/bsd/drm2/dist/drm/drm_context.c:1.1.1.1.2.7 src/sys/external/bsd/drm2/dist/drm/drm_context.c:1.1.1.1.2.8
--- src/sys/external/bsd/drm2/dist/drm/drm_context.c:1.1.1.1.2.7	Sun Sep  8 16:11:29 2013
+++ src/sys/external/bsd/drm2/dist/drm/drm_context.c	Wed Jan 15 13:53:53 2014
@@ -267,9 +267,12 @@ static int drm_context_switch_complete(s
 	dev->last_context = new;	/* PRE/POST: This is the _only_ writer. */
 	dev->last_switch = jiffies;
 
-	if (!_DRM_LOCK_IS_HELD(file_priv->master->lock.hw_lock->lock)) {
+	spin_lock(&file_priv->master->lock.spinlock);
+	if (file_priv->master->lock.hw_lock == NULL ||
+	    !_DRM_LOCK_IS_HELD(file_priv->master->lock.hw_lock->lock)) {
 		DRM_ERROR("Lock isn't held after context switch\n");
 	}
+	spin_unlock(&file_priv->master->lock.spinlock);
 
 	/* If a context switch is ever initiated
 	   when the kernel holds the lock, release

Index: src/sys/external/bsd/drm2/dist/drm/drm_stub.c
diff -u src/sys/external/bsd/drm2/dist/drm/drm_stub.c:1.1.1.1.2.10 src/sys/external/bsd/drm2/dist/drm/drm_stub.c:1.1.1.1.2.11
--- src/sys/external/bsd/drm2/dist/drm/drm_stub.c:1.1.1.1.2.10	Wed Jul 24 04:03:45 2013
+++ src/sys/external/bsd/drm2/dist/drm/drm_stub.c	Wed Jan 15 13:53:53 2014
@@ -170,8 +170,7 @@ struct drm_master *drm_master_create(str
 	kref_init(&master->refcount);
 	spin_lock_init(&master->lock.spinlock);
 #ifdef __NetBSD__
-	DRM_INIT_WAITQUEUE(&master->lock.lock_queue, "drmulckq");
-	DRM_INIT_WAITQUEUE(&master->lock.kernel_lock_queue, "drmklckq");
+	DRM_INIT_WAITQUEUE(&master->lock.lock_queue, "drmlockq");
 #else
 	init_waitqueue_head(&master->lock.lock_queue);
 #endif
@@ -230,7 +229,6 @@ static void drm_master_destroy(struct kr
 #ifdef __NetBSD__
 	spin_lock_destroy(&master->lock.spinlock);
 	DRM_DESTROY_WAITQUEUE(&master->lock.lock_queue);
-	DRM_DESTROY_WAITQUEUE(&master->lock.kernel_lock_queue);
 #endif
 
 	kfree(master);

Index: src/sys/external/bsd/drm2/dist/include/drm/drmP.h
diff -u src/sys/external/bsd/drm2/dist/include/drm/drmP.h:1.1.1.1.2.53 src/sys/external/bsd/drm2/dist/include/drm/drmP.h:1.1.1.1.2.54
--- src/sys/external/bsd/drm2/dist/include/drm/drmP.h:1.1.1.1.2.53	Sun Sep  8 16:30:13 2013
+++ src/sys/external/bsd/drm2/dist/include/drm/drmP.h	Wed Jan 15 13:53:53 2014
@@ -546,18 +546,11 @@ struct drm_lock_data {
 #else
 	wait_queue_head_t lock_queue;	/**< Queue of blocked processes */
 #endif
-#ifndef __NetBSD__		 /* XXX nothing seems to use this */
 	unsigned long lock_time;	/**< Time of last lock in jiffies */
-#endif
 	spinlock_t spinlock;
-#ifdef __NetBSD__
-	unsigned int n_kernel_waiters;
-	drm_waitqueue_t kernel_lock_queue;
-#else
 	uint32_t kernel_waiters;
 	uint32_t user_waiters;
 	int idle_has_lock;
-#endif
 };
 
 /**

Index: src/sys/external/bsd/drm2/drm/drm_fops.c
diff -u src/sys/external/bsd/drm2/drm/drm_fops.c:1.1.2.6 src/sys/external/bsd/drm2/drm/drm_fops.c:1.1.2.7
--- src/sys/external/bsd/drm2/drm/drm_fops.c:1.1.2.6	Wed Jan 15 13:52:39 2014
+++ src/sys/external/bsd/drm2/drm/drm_fops.c	Wed Jan 15 13:53:53 2014
@@ -1,4 +1,4 @@
-/*	$NetBSD: drm_fops.c,v 1.1.2.6 2014/01/15 13:52:39 riastradh Exp $	*/
+/*	$NetBSD: drm_fops.c,v 1.1.2.7 2014/01/15 13:53:53 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2013 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: drm_fops.c,v 1.1.2.6 2014/01/15 13:52:39 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: drm_fops.c,v 1.1.2.7 2014/01/15 13:53:53 riastradh Exp $");
 
 #include <drm/drmP.h>
 
@@ -243,6 +243,11 @@ static void
 drm_master_release(struct drm_file *file)
 {
 
+	/*
+	 * XXX I think this locking concept is wrong -- we need to hold
+	 * file->master->lock.spinlock across the two calls to
+	 * drm_i_have_hw_lock and drm_lock_free.
+	 */
 	if (drm_i_have_hw_lock(file->minor->dev, file))
 		drm_lock_free(&file->master->lock,
 		    _DRM_LOCKING_CONTEXT(file->master->lock.hw_lock->lock));
@@ -323,15 +328,17 @@ drm_close_file_master(struct drm_file *f
 			other_file->authenticated = 0;
 		}
 
+		spin_lock(&master->lock.spinlock);
 		if (master->lock.hw_lock) {
 			/* XXX There is copypasta of this in drm_bufs.c.  */
 			if (dev->sigdata.lock == master->lock.hw_lock)
 				dev->sigdata.lock = NULL;
 			master->lock.hw_lock = NULL;
 			master->lock.file_priv = NULL;
-			DRM_WAKEUP_ALL(&master->lock.lock_queue,
-			    &drm_global_mutex);
+			DRM_SPIN_WAKEUP_ALL(&master->lock.lock_queue,
+			    &master->lock.spinlock);
 		}
+		spin_unlock(&master->lock.spinlock);
 
 		if (file->minor->master == file->master) {
 			if (dev->driver->master_drop)

Index: src/sys/external/bsd/drm2/drm/drm_lock.c
diff -u src/sys/external/bsd/drm2/drm/drm_lock.c:1.1.2.3 src/sys/external/bsd/drm2/drm/drm_lock.c:1.1.2.4
--- src/sys/external/bsd/drm2/drm/drm_lock.c:1.1.2.3	Wed Jul 24 02:38:23 2013
+++ src/sys/external/bsd/drm2/drm/drm_lock.c	Wed Jan 15 13:53:53 2014
@@ -1,4 +1,4 @@
-/*	$NetBSD: drm_lock.c,v 1.1.2.3 2013/07/24 02:38:23 riastradh Exp $	*/
+/*	$NetBSD: drm_lock.c,v 1.1.2.4 2014/01/15 13:53:53 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2013 The NetBSD Foundation, Inc.
@@ -30,20 +30,23 @@
  */
 
 /*
- * DRM lock.  Each drm master has a lock to provide mutual exclusion
- * for access to something about the hardware (XXX what?).  The lock
- * can be held by the kernel or by a drm file (XXX not by a process!
- * and multiple processes may share a common drm file).  (XXX What
- * excludes different kthreads?)  The DRM_LOCK ioctl grants the lock to
- * the userland.  The kernel may need to steal this lock in order to
- * close a drm file; I believe drm_global_mutex excludes different
- * kernel threads from doing this simultaneously.
+ * DRM lock.  Each drm master has a heavy-weight lock to provide mutual
+ * exclusion for access to the hardware.  The lock can be held by the
+ * kernel or by a drm file; the kernel takes access only for unusual
+ * purposes, with drm_idlelock_take, mainly for idling the GPU when
+ * closing down.
  *
- * XXX This description is incoherent and incomplete.
+ * The physical memory storing the lock state is shared between
+ * userland and kernel: the pointer at dev->master->lock->hw_lock is
+ * mapped into both userland and kernel address spaces.  This way,
+ * userland can try to take the hardware lock without a system call,
+ * although if it fails then it will use the DRM_LOCK ioctl to block
+ * atomically until the lock is available.  All this means that the
+ * kernel must use atomic_ops to manage the lock state.
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: drm_lock.c,v 1.1.2.3 2013/07/24 02:38:23 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: drm_lock.c,v 1.1.2.4 2014/01/15 13:53:53 riastradh Exp $");
 
 #include <sys/types.h>
 #include <sys/errno.h>
@@ -66,14 +69,24 @@ drm_lock(struct drm_device *dev, void *d
 {
 	struct drm_lock *lock_request = data;
 	struct drm_master *master = file->master;
-	bool acquired;
 	int error;
 
+	/* Sanitize the drm global mutex bollocks until we get rid of it.  */
 	KASSERT(mutex_is_locked(&drm_global_mutex));
+	mutex_unlock(&drm_global_mutex);
 
 	/* Refuse to lock on behalf of the kernel.  */
-	if (lock_request->context == DRM_KERNEL_CONTEXT)
-		return -EINVAL;
+	if (lock_request->context == DRM_KERNEL_CONTEXT) {
+		error = -EINVAL;
+		goto out0;
+	}
+
+	/* Refuse to set the magic bits.  */
+	if (lock_request->context !=
+	    _DRM_LOCKING_CONTEXT(lock_request->context)) {
+		error = -EINVAL;
+		goto out0;
+	}
 
 	/* Count it in the file and device statistics (XXX why here?).  */
 	file->lock_count++;
@@ -81,13 +94,21 @@ drm_lock(struct drm_device *dev, void *d
 
 	/* Wait until the hardware lock is gone or we can acquire it.   */
 	spin_lock(&master->lock.spinlock);
+
+	if (master->lock.user_waiters == UINT32_MAX) {
+		error = -EBUSY;
+		goto out1;
+	}
+
+	master->lock.user_waiters++;
 	DRM_SPIN_WAIT_UNTIL(error, &master->lock.lock_queue,
 	    &master->lock.spinlock,
 	    ((master->lock.hw_lock == NULL) ||
-		(acquired = drm_lock_acquire(&master->lock,
-		    lock_request->context))));
+		drm_lock_acquire(&master->lock, lock_request->context)));
+	KASSERT(0 < master->lock.user_waiters);
+	master->lock.user_waiters--;
 	if (error)
-		goto out;
+		goto out1;
 
 	/* If the lock is gone, give up.  */
 	if (master->lock.hw_lock == NULL) {
@@ -99,36 +120,36 @@ drm_lock(struct drm_device *dev, void *d
 #else
 		error = -ENXIO;
 #endif
-		goto out;
+		goto out1;
 	}
 
 	/* Mark the lock as owned by file.  */
 	master->lock.file_priv = file;
-#ifndef __NetBSD__
 	master->lock.lock_time = jiffies; /* XXX Unused?  */
-#endif
 
 	/* Block signals while the lock is held.  */
 	error = drm_lock_block_signals(dev, lock_request, file);
 	if (error)
-		goto fail0;
+		goto fail2;
 
 	/* Enter the DMA quiescent state if requested and available.  */
+	/* XXX Drop the spin lock first...  */
 	if (ISSET(lock_request->flags, _DRM_LOCK_QUIESCENT) &&
 	    (dev->driver->dma_quiescent != NULL)) {
 		error = (*dev->driver->dma_quiescent)(dev);
 		if (error)
-			goto fail1;
+			goto fail3;
 	}
 
 	/* Success!  */
 	error = 0;
-	goto out;
+	goto out1;
 
-fail1:	drm_lock_unblock_signals(dev, lock_request, file);
-fail0:	drm_lock_release(&master->lock, lock_request->context);
+fail3:	drm_lock_unblock_signals(dev, lock_request, file);
+fail2:	drm_lock_release(&master->lock, lock_request->context);
 	master->lock.file_priv = NULL;
-out:	spin_unlock(&master->lock.spinlock);
+out1:	spin_unlock(&master->lock.spinlock);
+out0:	mutex_lock(&drm_global_mutex);
 	return error;
 }
 
@@ -143,11 +164,15 @@ drm_unlock(struct drm_device *dev, void 
 	struct drm_master *master = file->master;
 	int error;
 
+	/* Sanitize the drm global mutex bollocks until we get rid of it.  */
 	KASSERT(mutex_is_locked(&drm_global_mutex));
+	mutex_unlock(&drm_global_mutex);
 
 	/* Refuse to unlock on behalf of the kernel.  */
-	if (lock_request->context == DRM_KERNEL_CONTEXT)
-		return -EINVAL;
+	if (lock_request->context == DRM_KERNEL_CONTEXT) {
+		error = -EINVAL;
+		goto out0;
+	}
 
 	/* Count it in the device statistics.  */
 	atomic_inc(&dev->counts[_DRM_STAT_UNLOCKS]);
@@ -158,20 +183,20 @@ drm_unlock(struct drm_device *dev, void 
 	/* Make sure it's actually locked.  */
 	if (!_DRM_LOCK_IS_HELD(master->lock.hw_lock->lock)) {
 		error = -EINVAL;	/* XXX Right error?  */
-		goto out;
+		goto out1;
 	}
 
 	/* Make sure it's locked in the right context.  */
 	if (_DRM_LOCKING_CONTEXT(master->lock.hw_lock->lock) !=
 	    lock_request->context) {
 		error = -EACCES;	/* XXX Right error?  */
-		goto out;
+		goto out1;
 	}
 
 	/* Make sure it's locked by us.  */
 	if (master->lock.file_priv != file) {
 		error = -EACCES;	/* XXX Right error?  */
-		goto out;
+		goto out1;
 	}
 
 	/* Actually release the lock.  */
@@ -186,7 +211,8 @@ drm_unlock(struct drm_device *dev, void 
 	/* Success!  */
 	error = 0;
 
-out:	spin_unlock(&master->lock.spinlock);
+out1:	spin_unlock(&master->lock.spinlock);
+out0:	mutex_lock(&drm_global_mutex);
 	return error;
 }
 
@@ -195,6 +221,8 @@ out:	spin_unlock(&master->lock.spinlock)
  *
  * Return value is an artefact of Linux.  Caller must guarantee
  * preconditions; failure is fatal.
+ *
+ * XXX Should we also unblock signals like drm_unlock does?
  */
 int
 drm_lock_free(struct drm_lock_data *lock_data, unsigned int context)
@@ -234,49 +262,82 @@ drm_idlelock_release(struct drm_lock_dat
 
 /*
  * Does this file hold this drm device's hardware lock?
+ *
+ * Used to decide whether to release the lock when the file is being
+ * closed.
+ *
+ * XXX I don't think this answers correctly in the case that the
+ * userland has taken the lock and it is uncontended.  But I don't
+ * think we can know what the correct answer is in that case.
  */
 int
 drm_i_have_hw_lock(struct drm_device *dev, struct drm_file *file)
 {
 	struct drm_lock_data *const lock_data = &file->master->lock;
+	int answer = 0;
 
 	/* If this file has never locked anything, then no.  */
 	if (file->lock_count == 0)
 		return 0;
 
+	spin_lock(&lock_data->spinlock);
+
 	/* If there is no lock, then this file doesn't hold it.  */
 	if (lock_data->hw_lock == NULL)
-		return 0;
+		goto out;
 
 	/* If this lock is not held, then this file doesn't hold it.   */
 	if (!_DRM_LOCK_IS_HELD(lock_data->hw_lock->lock))
-		return 0;
+		goto out;
 
 	/*
 	 * Otherwise, it boils down to whether this file is the owner
 	 * or someone else.
+	 *
+	 * XXX This is not reliable!  Userland doesn't update this when
+	 * it takes the lock...
 	 */
-	return (file == lock_data->file_priv);
+	answer = (file == lock_data->file_priv);
+
+out:	spin_unlock(&lock_data->spinlock);
+	return answer;
 }
 
 /*
  * Try to acquire the lock.  Return true if successful, false if not.
  *
- * Lock's spinlock must be held.
+ * This is hairy because it races with userland, and if userland
+ * already holds the lock, we must tell it, by marking it
+ * _DRM_LOCK_CONT (contended), that it must call ioctl(DRM_UNLOCK) to
+ * release the lock so that we can wake waiters.
+ *
+ * XXX What happens if the process is interrupted?
  */
 static bool
 drm_lock_acquire(struct drm_lock_data *lock_data, int context)
 {
+        volatile unsigned int *const lock = &lock_data->hw_lock->lock;
+	unsigned int old, new;
 
 	KASSERT(spin_is_locked(&lock_data->spinlock));
 
-	/* Test and set.  */
-	if (_DRM_LOCK_IS_HELD(lock_data->hw_lock->lock)) {
-		return false;
-	} else {
-		lock_data->hw_lock->lock = (context | _DRM_LOCK_HELD);
-		return true;
-	}
+	do {
+		old = *lock;
+		if (!_DRM_LOCK_IS_HELD(old)) {
+			new = (context | _DRM_LOCK_HELD);
+			if ((0 < lock_data->user_waiters) ||
+			    (0 < lock_data->kernel_waiters))
+				new |= _DRM_LOCK_CONT;
+		} else if (_DRM_LOCKING_CONTEXT(old) != context) {
+			new = (old | _DRM_LOCK_CONT);
+		} else {
+			DRM_ERROR("%d already holds heavyweight lock\n",
+			    context);
+			return false;
+		}
+	} while (atomic_cas_uint(lock, old, new) != old);
+
+	return !_DRM_LOCK_IS_HELD(old);
 }
 
 /*
@@ -294,20 +355,8 @@ drm_lock_release(struct drm_lock_data *l
 	KASSERT(_DRM_LOCK_IS_HELD(lock_data->hw_lock->lock));
 	KASSERT(_DRM_LOCKING_CONTEXT(lock_data->hw_lock->lock) == context);
 
-	/* Are there any kernel waiters?  */
-	if (lock_data->n_kernel_waiters > 0) {
-		/* If there's a kernel waiter, grant it ownership.  */
-		lock_data->hw_lock->lock = (DRM_KERNEL_CONTEXT |
-		    _DRM_LOCK_HELD);
-		lock_data->n_kernel_waiters--;
-		DRM_SPIN_WAKEUP_ONE(&lock_data->kernel_lock_queue,
-		    &lock_data->spinlock);
-	} else {
-		/* Otherwise, free it up and wake someone else.  */
-		lock_data->hw_lock->lock = 0;
-		DRM_SPIN_WAKEUP_ONE(&lock_data->lock_queue,
-		    &lock_data->spinlock);
-	}
+	lock_data->hw_lock->lock = 0;
+	DRM_SPIN_WAKEUP_ONE(&lock_data->lock_queue, &lock_data->spinlock);
 }
 
 /*

Reply via email to