mirror of
https://github.com/Stichting-MINIX-Research-Foundation/netbsd.git
synced 2025-08-10 14:39:47 -04:00
380 lines
11 KiB
C
380 lines
11 KiB
C
/* $NetBSD: drm_lock.c,v 1.3 2014/07/16 20:56:25 riastradh Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2013 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Taylor R. Campbell.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* 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.3 2014/07/16 20:56:25 riastradh Exp $");
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <drm/drmP.h>
|
|
|
|
static bool drm_lock_acquire(struct drm_lock_data *, int);
|
|
static void drm_lock_release(struct drm_lock_data *, int);
|
|
static int drm_lock_block_signals(struct drm_device *, struct drm_lock *,
|
|
struct drm_file *);
|
|
static void drm_lock_unblock_signals(struct drm_device *,
|
|
struct drm_lock *, struct drm_file *);
|
|
|
|
/*
|
|
* Take the lock on behalf of userland.
|
|
*/
|
|
int
|
|
drm_lock(struct drm_device *dev, void *data, struct drm_file *file)
|
|
{
|
|
struct drm_lock *lock_request = data;
|
|
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 lock on behalf of the kernel. */
|
|
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++;
|
|
|
|
/* 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) ||
|
|
drm_lock_acquire(&master->lock, lock_request->context)));
|
|
KASSERT(0 < master->lock.user_waiters);
|
|
master->lock.user_waiters--;
|
|
if (error)
|
|
goto out1;
|
|
|
|
/* If the lock is gone, give up. */
|
|
if (master->lock.hw_lock == NULL) {
|
|
#if 0 /* XXX Linux sends SIGTERM, but why? */
|
|
mutex_enter(proc_lock);
|
|
psignal(curproc, SIGTERM);
|
|
mutex_exit(proc_lock);
|
|
error = -EINTR;
|
|
#else
|
|
error = -ENXIO;
|
|
#endif
|
|
goto out1;
|
|
}
|
|
|
|
/* Mark the lock as owned by file. */
|
|
master->lock.file_priv = file;
|
|
master->lock.lock_time = jiffies; /* XXX Unused? */
|
|
|
|
/* Block signals while the lock is held. */
|
|
error = drm_lock_block_signals(dev, lock_request, file);
|
|
if (error)
|
|
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 fail3;
|
|
}
|
|
|
|
/* Success! */
|
|
error = 0;
|
|
goto out1;
|
|
|
|
fail3: drm_lock_unblock_signals(dev, lock_request, file);
|
|
fail2: drm_lock_release(&master->lock, lock_request->context);
|
|
master->lock.file_priv = NULL;
|
|
out1: spin_unlock(&master->lock.spinlock);
|
|
out0: mutex_lock(&drm_global_mutex);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Try to relinquish a lock that userland thinks it holds, per
|
|
* userland's request. Fail if it doesn't actually hold the lock.
|
|
*/
|
|
int
|
|
drm_unlock(struct drm_device *dev, void *data, struct drm_file *file)
|
|
{
|
|
struct drm_lock *lock_request = data;
|
|
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) {
|
|
error = -EINVAL;
|
|
goto out0;
|
|
}
|
|
|
|
/* Lock the internal spin lock to make changes. */
|
|
spin_lock(&master->lock.spinlock);
|
|
|
|
/* Make sure it's actually locked. */
|
|
if (!_DRM_LOCK_IS_HELD(master->lock.hw_lock->lock)) {
|
|
error = -EINVAL; /* XXX Right error? */
|
|
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 out1;
|
|
}
|
|
|
|
/* Make sure it's locked by us. */
|
|
if (master->lock.file_priv != file) {
|
|
error = -EACCES; /* XXX Right error? */
|
|
goto out1;
|
|
}
|
|
|
|
/* Actually release the lock. */
|
|
drm_lock_release(&master->lock, lock_request->context);
|
|
|
|
/* Clear the lock's file pointer, just in case. */
|
|
master->lock.file_priv = NULL;
|
|
|
|
/* Unblock the signals we blocked in drm_lock. */
|
|
drm_lock_unblock_signals(dev, lock_request, file);
|
|
|
|
/* Success! */
|
|
error = 0;
|
|
|
|
out1: spin_unlock(&master->lock.spinlock);
|
|
out0: mutex_lock(&drm_global_mutex);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Drop the lock.
|
|
*
|
|
* 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)
|
|
{
|
|
|
|
spin_lock(&lock_data->spinlock);
|
|
drm_lock_release(lock_data, context);
|
|
spin_unlock(&lock_data->spinlock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Take the lock for the kernel's use.
|
|
*
|
|
* XXX This is unimplemented because it's not clear that the Linux code
|
|
* makes sense at all. Linux's drm_idlelock_take never blocks, but it
|
|
* doesn't guarantee that the kernel holds the lock on return! For
|
|
* now, I'll hope that the code paths relying on this don't matter yet.
|
|
*/
|
|
void
|
|
drm_idlelock_take(struct drm_lock_data *lock_data __unused)
|
|
{
|
|
KASSERT(mutex_is_locked(&drm_global_mutex));
|
|
panic("drm_idlelock_take is not yet implemented"); /* XXX */
|
|
}
|
|
|
|
/*
|
|
* Release the lock from the kernel.
|
|
*/
|
|
void
|
|
drm_idlelock_release(struct drm_lock_data *lock_data __unused)
|
|
{
|
|
KASSERT(mutex_is_locked(&drm_global_mutex));
|
|
panic("drm_idlelock_release is not yet implemented"); /* XXX */
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
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))
|
|
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...
|
|
*/
|
|
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.
|
|
*
|
|
* 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));
|
|
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* Release the lock held in the given context. Wake any waiters,
|
|
* preferring kernel waiters over userland waiters.
|
|
*
|
|
* Lock's spinlock must be held and lock must be held in this context.
|
|
*/
|
|
static void
|
|
drm_lock_release(struct drm_lock_data *lock_data, int context)
|
|
{
|
|
|
|
(void)context; /* ignore */
|
|
KASSERT(spin_is_locked(&lock_data->spinlock));
|
|
KASSERT(_DRM_LOCK_IS_HELD(lock_data->hw_lock->lock));
|
|
KASSERT(_DRM_LOCKING_CONTEXT(lock_data->hw_lock->lock) == context);
|
|
|
|
lock_data->hw_lock->lock = 0;
|
|
DRM_SPIN_WAKEUP_ONE(&lock_data->lock_queue, &lock_data->spinlock);
|
|
}
|
|
|
|
/*
|
|
* Block signals for a process that holds a drm lock.
|
|
*
|
|
* XXX It's not processes but files that hold drm locks, so blocking
|
|
* signals in a process seems wrong, and it's not clear that blocking
|
|
* signals automatically is remotely sensible anyway.
|
|
*/
|
|
static int
|
|
drm_lock_block_signals(struct drm_device *dev __unused,
|
|
struct drm_lock *lock_request __unused, struct drm_file *file __unused)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Unblock the signals that drm_lock_block_signals blocked.
|
|
*/
|
|
static void
|
|
drm_lock_unblock_signals(struct drm_device *dev __unused,
|
|
struct drm_lock *lock_request __unused, struct drm_file *file __unused)
|
|
{
|
|
}
|