1 /* lock.c -- IOCTLs for locking -*- linux-c -*-
2  * Created: Tue Feb  2 08:37:54 1999 by faith@valinux.com
3  *
4  * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
5  * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
6  * All Rights Reserved.
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the "Software"),
10  * to deal in the Software without restriction, including without limitation
11  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12  * and/or sell copies of the Software, and to permit persons to whom the
13  * Software is furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice (including the next
16  * paragraph) shall be included in all copies or substantial portions of the
17  * Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
22  * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
23  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
24  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25  * OTHER DEALINGS IN THE SOFTWARE.
26  *
27  * Authors:
28  *    Rickard E. (Rik) Faith <faith@valinux.com>
29  *    Gareth Hughes <gareth@valinux.com>
30  */
31 
32 #include "drmP.h"
33 
DRM(block)34 int DRM(block)(struct inode *inode, struct file *filp, unsigned int cmd,
35 	       unsigned long arg)
36 {
37 	DRM_DEBUG("\n");
38 	return 0;
39 }
40 
DRM(unblock)41 int DRM(unblock)(struct inode *inode, struct file *filp, unsigned int cmd,
42 		 unsigned long arg)
43 {
44 	DRM_DEBUG("\n");
45 	return 0;
46 }
47 
DRM(lock_take)48 int DRM(lock_take)(__volatile__ unsigned int *lock, unsigned int context)
49 {
50 	unsigned int old, new, prev;
51 
52 	do {
53 		old = *lock;
54 		if (old & _DRM_LOCK_HELD) new = old | _DRM_LOCK_CONT;
55 		else			  new = context | _DRM_LOCK_HELD;
56 		prev = cmpxchg(lock, old, new);
57 	} while (prev != old);
58 	if (_DRM_LOCKING_CONTEXT(old) == context) {
59 		if (old & _DRM_LOCK_HELD) {
60 			if (context != DRM_KERNEL_CONTEXT) {
61 				DRM_ERROR("%d holds heavyweight lock\n",
62 					  context);
63 			}
64 			return 0;
65 		}
66 	}
67 	if (new == (context | _DRM_LOCK_HELD)) {
68 				/* Have lock */
69 		return 1;
70 	}
71 	return 0;
72 }
73 
74 /* This takes a lock forcibly and hands it to context.	Should ONLY be used
75    inside *_unlock to give lock to kernel before calling *_dma_schedule. */
DRM(lock_transfer)76 int DRM(lock_transfer)(drm_device_t *dev,
77 		       __volatile__ unsigned int *lock, unsigned int context)
78 {
79 	unsigned int old, new, prev;
80 
81 	dev->lock.pid = 0;
82 	do {
83 		old  = *lock;
84 		new  = context | _DRM_LOCK_HELD;
85 		prev = cmpxchg(lock, old, new);
86 	} while (prev != old);
87 	return 1;
88 }
89 
DRM(lock_free)90 int DRM(lock_free)(drm_device_t *dev,
91 		   __volatile__ unsigned int *lock, unsigned int context)
92 {
93 	unsigned int old, new, prev;
94 	pid_t        pid = dev->lock.pid;
95 
96 	dev->lock.pid = 0;
97 	do {
98 		old  = *lock;
99 		new  = 0;
100 		prev = cmpxchg(lock, old, new);
101 	} while (prev != old);
102 	if (_DRM_LOCK_IS_HELD(old) && _DRM_LOCKING_CONTEXT(old) != context) {
103 		DRM_ERROR("%d freed heavyweight lock held by %d (pid %d)\n",
104 			  context,
105 			  _DRM_LOCKING_CONTEXT(old),
106 			  pid);
107 		return 1;
108 	}
109 	wake_up_interruptible(&dev->lock.lock_queue);
110 	return 0;
111 }
112 
DRM(flush_queue)113 static int DRM(flush_queue)(drm_device_t *dev, int context)
114 {
115 	DECLARE_WAITQUEUE(entry, current);
116 	int		  ret	= 0;
117 	drm_queue_t	  *q	= dev->queuelist[context];
118 
119 	DRM_DEBUG("\n");
120 
121 	atomic_inc(&q->use_count);
122 	if (atomic_read(&q->use_count) > 1) {
123 		atomic_inc(&q->block_write);
124 		add_wait_queue(&q->flush_queue, &entry);
125 		atomic_inc(&q->block_count);
126 		for (;;) {
127 			current->state = TASK_INTERRUPTIBLE;
128 			if (!DRM_BUFCOUNT(&q->waitlist)) break;
129 			schedule();
130 			if (signal_pending(current)) {
131 				ret = -EINTR; /* Can't restart */
132 				break;
133 			}
134 		}
135 		atomic_dec(&q->block_count);
136 		current->state = TASK_RUNNING;
137 		remove_wait_queue(&q->flush_queue, &entry);
138 	}
139 	atomic_dec(&q->use_count);
140 
141 				/* NOTE: block_write is still incremented!
142 				   Use drm_flush_unlock_queue to decrement. */
143 	return ret;
144 }
145 
DRM(flush_unblock_queue)146 static int DRM(flush_unblock_queue)(drm_device_t *dev, int context)
147 {
148 	drm_queue_t	  *q	= dev->queuelist[context];
149 
150 	DRM_DEBUG("\n");
151 
152 	atomic_inc(&q->use_count);
153 	if (atomic_read(&q->use_count) > 1) {
154 		if (atomic_read(&q->block_write)) {
155 			atomic_dec(&q->block_write);
156 			wake_up_interruptible(&q->write_queue);
157 		}
158 	}
159 	atomic_dec(&q->use_count);
160 	return 0;
161 }
162 
DRM(flush_block_and_flush)163 int DRM(flush_block_and_flush)(drm_device_t *dev, int context,
164 			       drm_lock_flags_t flags)
165 {
166 	int ret = 0;
167 	int i;
168 
169 	DRM_DEBUG("\n");
170 
171 	if (flags & _DRM_LOCK_FLUSH) {
172 		ret = DRM(flush_queue)(dev, DRM_KERNEL_CONTEXT);
173 		if (!ret) ret = DRM(flush_queue)(dev, context);
174 	}
175 	if (flags & _DRM_LOCK_FLUSH_ALL) {
176 		for (i = 0; !ret && i < dev->queue_count; i++) {
177 			ret = DRM(flush_queue)(dev, i);
178 		}
179 	}
180 	return ret;
181 }
182 
DRM(flush_unblock)183 int DRM(flush_unblock)(drm_device_t *dev, int context, drm_lock_flags_t flags)
184 {
185 	int ret = 0;
186 	int i;
187 
188 	DRM_DEBUG("\n");
189 
190 	if (flags & _DRM_LOCK_FLUSH) {
191 		ret = DRM(flush_unblock_queue)(dev, DRM_KERNEL_CONTEXT);
192 		if (!ret) ret = DRM(flush_unblock_queue)(dev, context);
193 	}
194 	if (flags & _DRM_LOCK_FLUSH_ALL) {
195 		for (i = 0; !ret && i < dev->queue_count; i++) {
196 			ret = DRM(flush_unblock_queue)(dev, i);
197 		}
198 	}
199 
200 	return ret;
201 }
202 
DRM(finish)203 int DRM(finish)(struct inode *inode, struct file *filp, unsigned int cmd,
204 		unsigned long arg)
205 {
206 	drm_file_t	  *priv	  = filp->private_data;
207 	drm_device_t	  *dev	  = priv->dev;
208 	int		  ret	  = 0;
209 	drm_lock_t	  lock;
210 
211 	DRM_DEBUG("\n");
212 
213 	if (copy_from_user(&lock, (drm_lock_t *)arg, sizeof(lock)))
214 		return -EFAULT;
215 	ret = DRM(flush_block_and_flush)(dev, lock.context, lock.flags);
216 	DRM(flush_unblock)(dev, lock.context, lock.flags);
217 	return ret;
218 }
219 
220 /* If we get here, it means that the process has called DRM_IOCTL_LOCK
221    without calling DRM_IOCTL_UNLOCK.
222 
223    If the lock is not held, then let the signal proceed as usual.
224 
225    If the lock is held, then set the contended flag and keep the signal
226    blocked.
227 
228 
229    Return 1 if the signal should be delivered normally.
230    Return 0 if the signal should be blocked.  */
231 
DRM(notifier)232 int DRM(notifier)(void *priv)
233 {
234 	drm_sigdata_t *s = (drm_sigdata_t *)priv;
235 	unsigned int  old, new, prev;
236 
237 
238 				/* Allow signal delivery if lock isn't held */
239 	if (!s->lock || !_DRM_LOCK_IS_HELD(s->lock->lock)
240 	    || _DRM_LOCKING_CONTEXT(s->lock->lock) != s->context) return 1;
241 
242 				/* Otherwise, set flag to force call to
243                                    drmUnlock */
244 	do {
245 		old  = s->lock->lock;
246 		new  = old | _DRM_LOCK_CONT;
247 		prev = cmpxchg(&s->lock->lock, old, new);
248 	} while (prev != old);
249 	return 0;
250 }
251