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);
}
/*