drm_lock.c revision 182469
1/*-
2 * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
3 * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
4 * All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the next
14 * paragraph) shall be included in all copies or substantial portions of the
15 * Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20 * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 * OTHER DEALINGS IN THE SOFTWARE.
24 *
25 * Authors:
26 *    Rickard E. (Rik) Faith <faith@valinux.com>
27 *    Gareth Hughes <gareth@valinux.com>
28 *
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: head/sys/dev/drm/drm_lock.c 182469 2008-08-30 01:00:18Z rnoland $");
33
34/** @file drm_lock.c
35 * Implementation of the ioctls and other support code for dealing with the
36 * hardware lock.
37 *
38 * The DRM hardware lock is a shared structure between the kernel and userland.
39 *
40 * On uncontended access where the new context was the last context, the
41 * client may take the lock without dropping down into the kernel, using atomic
42 * compare-and-set.
43 *
44 * If the client finds during compare-and-set that it was not the last owner
45 * of the lock, it calls the DRM lock ioctl, which may sleep waiting for the
46 * lock, and may have side-effects of kernel-managed context switching.
47 *
48 * When the client releases the lock, if the lock is marked as being contended
49 * by another client, then the DRM unlock ioctl is called so that the
50 * contending client may be woken up.
51 */
52
53#include "dev/drm/drmP.h"
54
55int drm_lock_take(__volatile__ unsigned int *lock, unsigned int context)
56{
57	unsigned int old, new;
58
59	do {
60		old = *lock;
61		if (old & _DRM_LOCK_HELD) new = old | _DRM_LOCK_CONT;
62		else			  new = context | _DRM_LOCK_HELD;
63	} while (!atomic_cmpset_int(lock, old, new));
64
65	if (_DRM_LOCKING_CONTEXT(old) == context) {
66		if (old & _DRM_LOCK_HELD) {
67			if (context != DRM_KERNEL_CONTEXT) {
68				DRM_ERROR("%d holds heavyweight lock\n",
69					  context);
70			}
71			return 0;
72		}
73	}
74	if (new == (context | _DRM_LOCK_HELD)) {
75				/* Have lock */
76		return 1;
77	}
78	return 0;
79}
80
81/* This takes a lock forcibly and hands it to context.	Should ONLY be used
82   inside *_unlock to give lock to kernel before calling *_dma_schedule. */
83int drm_lock_transfer(struct drm_device *dev,
84		       __volatile__ unsigned int *lock, unsigned int context)
85{
86	unsigned int old, new;
87
88	dev->lock.file_priv = NULL;
89	do {
90		old  = *lock;
91		new  = context | _DRM_LOCK_HELD;
92	} while (!atomic_cmpset_int(lock, old, new));
93
94	return 1;
95}
96
97int drm_lock_free(struct drm_device *dev,
98		   __volatile__ unsigned int *lock, unsigned int context)
99{
100	unsigned int old, new;
101
102	dev->lock.file_priv = NULL;
103	do {
104		old  = *lock;
105		new  = 0;
106	} while (!atomic_cmpset_int(lock, old, new));
107
108	if (_DRM_LOCK_IS_HELD(old) && _DRM_LOCKING_CONTEXT(old) != context) {
109		DRM_ERROR("%d freed heavyweight lock held by %d\n",
110			  context, _DRM_LOCKING_CONTEXT(old));
111		return 1;
112	}
113	DRM_WAKEUP_INT((void *)&dev->lock.lock_queue);
114	return 0;
115}
116
117int drm_lock(struct drm_device *dev, void *data, struct drm_file *file_priv)
118{
119        drm_lock_t *lock = data;
120        int ret = 0;
121
122        if (lock->context == DRM_KERNEL_CONTEXT) {
123                DRM_ERROR("Process %d using kernel context %d\n",
124		    DRM_CURRENTPID, lock->context);
125                return EINVAL;
126        }
127
128        DRM_DEBUG("%d (pid %d) requests lock (0x%08x), flags = 0x%08x\n",
129	    lock->context, DRM_CURRENTPID, dev->lock.hw_lock->lock,
130	    lock->flags);
131
132        if (dev->driver.use_dma_queue && lock->context < 0)
133                return EINVAL;
134
135	DRM_LOCK();
136	for (;;) {
137		if (drm_lock_take(&dev->lock.hw_lock->lock, lock->context)) {
138			dev->lock.file_priv = file_priv;
139			dev->lock.lock_time = jiffies;
140			atomic_inc(&dev->counts[_DRM_STAT_LOCKS]);
141			break;  /* Got lock */
142		}
143
144		/* Contention */
145#if defined(__FreeBSD__) && __FreeBSD_version > 500000
146		ret = mtx_sleep((void *)&dev->lock.lock_queue, &dev->dev_lock,
147		    PZERO | PCATCH, "drmlk2", 0);
148#else
149		ret = tsleep((void *)&dev->lock.lock_queue, PZERO | PCATCH,
150		    "drmlk2", 0);
151#endif
152		if (ret != 0)
153			break;
154	}
155	DRM_UNLOCK();
156	DRM_DEBUG("%d %s\n", lock->context, ret ? "interrupted" : "has lock");
157
158	if (ret != 0)
159		return ret;
160
161	/* XXX: Add signal blocking here */
162
163	if (dev->driver.dma_quiescent != NULL &&
164	    (lock->flags & _DRM_LOCK_QUIESCENT))
165		dev->driver.dma_quiescent(dev);
166
167	return 0;
168}
169
170int drm_unlock(struct drm_device *dev, void *data, struct drm_file *file_priv)
171{
172	drm_lock_t *lock = data;
173
174        DRM_DEBUG("%d (pid %d) requests unlock (0x%08x), flags = 0x%08x\n",
175	    lock->context, DRM_CURRENTPID, dev->lock.hw_lock->lock,
176	    lock->flags);
177
178	if (lock->context == DRM_KERNEL_CONTEXT) {
179		DRM_ERROR("Process %d using kernel context %d\n",
180		    DRM_CURRENTPID, lock->context);
181		return EINVAL;
182	}
183#if 0
184	/* Check that the context unlock being requested actually matches
185	 * who currently holds the lock.
186	 */
187	if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock) ||
188	    _DRM_LOCKING_CONTEXT(dev->lock.hw_lock->lock) != lock->context)
189		return EINVAL;
190#endif
191	DRM_SPINLOCK(&dev->tsk_lock);
192	if (dev->locked_task_call != NULL) {
193		dev->locked_task_call(dev);
194		dev->locked_task_call = NULL;
195	}
196	DRM_SPINUNLOCK(&dev->tsk_lock);
197
198	atomic_inc(&dev->counts[_DRM_STAT_UNLOCKS]);
199
200	DRM_LOCK();
201	drm_lock_transfer(dev, &dev->lock.hw_lock->lock, DRM_KERNEL_CONTEXT);
202
203	if (drm_lock_free(dev, &dev->lock.hw_lock->lock, DRM_KERNEL_CONTEXT)) {
204		DRM_ERROR("\n");
205	}
206	DRM_UNLOCK();
207
208	return 0;
209}
210