drm: Import the ttm memory manager from FreeBSD
[dragonfly.git] / sys / dev / drm2 / ttm / ttm_lock.c
1 /**************************************************************************
2  *
3  * Copyright (c) 2007-2009 VMware, Inc., Palo Alto, CA., USA
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
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sub license, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice (including the
15  * next paragraph) shall be included in all copies or substantial portions
16  * of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
22  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24  * USE OR OTHER DEALINGS IN THE SOFTWARE.
25  *
26  **************************************************************************/
27 /*
28  * Authors: Thomas Hellstrom <thellstrom-at-vmware-dot-com>
29  */
30 /*
31  * Copyright (c) 2013 The FreeBSD Foundation
32  * All rights reserved.
33  *
34  * Portions of this software were developed by Konstantin Belousov
35  * <kib@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
36  *
37  * $FreeBSD: head/sys/dev/drm2/ttm/ttm_lock.c 247835 2013-03-05 09:49:34Z kib $
38  */
39
40 #include <dev/drm2/ttm/ttm_lock.h>
41 #include <dev/drm2/ttm/ttm_module.h>
42
43 #define TTM_WRITE_LOCK_PENDING    (1 << 0)
44 #define TTM_VT_LOCK_PENDING       (1 << 1)
45 #define TTM_SUSPEND_LOCK_PENDING  (1 << 2)
46 #define TTM_VT_LOCK               (1 << 3)
47 #define TTM_SUSPEND_LOCK          (1 << 4)
48
49 void ttm_lock_init(struct ttm_lock *lock)
50 {
51         mtx_init(&lock->lock, "ttmlk", NULL, MTX_DEF);
52         lock->rw = 0;
53         lock->flags = 0;
54         lock->kill_takers = false;
55         lock->signal = SIGKILL;
56 }
57
58 static void
59 ttm_lock_send_sig(int signo)
60 {
61         struct proc *p;
62
63         p = curproc;    /* XXXKIB curthread ? */
64         PROC_LOCK(p);
65         kern_psignal(p, signo);
66         PROC_UNLOCK(p);
67 }
68
69 void ttm_read_unlock(struct ttm_lock *lock)
70 {
71         mtx_lock(&lock->lock);
72         if (--lock->rw == 0)
73                 wakeup(lock);
74         mtx_unlock(&lock->lock);
75 }
76
77 static bool __ttm_read_lock(struct ttm_lock *lock)
78 {
79         bool locked = false;
80
81         if (unlikely(lock->kill_takers)) {
82                 ttm_lock_send_sig(lock->signal);
83                 return false;
84         }
85         if (lock->rw >= 0 && lock->flags == 0) {
86                 ++lock->rw;
87                 locked = true;
88         }
89         return locked;
90 }
91
92 int
93 ttm_read_lock(struct ttm_lock *lock, bool interruptible)
94 {
95         const char *wmsg;
96         int flags, ret;
97
98         ret = 0;
99         if (interruptible) {
100                 flags = PCATCH;
101                 wmsg = "ttmri";
102         } else {
103                 flags = 0;
104                 wmsg = "ttmr";
105         }
106         mtx_lock(&lock->lock);
107         while (!__ttm_read_lock(lock)) {
108                 ret = msleep(lock, &lock->lock, flags, wmsg, 0);
109                 if (ret != 0)
110                         break;
111         }
112         return (-ret);
113 }
114
115 static bool __ttm_read_trylock(struct ttm_lock *lock, bool *locked)
116 {
117         bool block = true;
118
119         *locked = false;
120
121         if (unlikely(lock->kill_takers)) {
122                 ttm_lock_send_sig(lock->signal);
123                 return false;
124         }
125         if (lock->rw >= 0 && lock->flags == 0) {
126                 ++lock->rw;
127                 block = false;
128                 *locked = true;
129         } else if (lock->flags == 0) {
130                 block = false;
131         }
132
133         return !block;
134 }
135
136 int ttm_read_trylock(struct ttm_lock *lock, bool interruptible)
137 {
138         const char *wmsg;
139         int flags, ret;
140         bool locked;
141
142         ret = 0;
143         if (interruptible) {
144                 flags = PCATCH;
145                 wmsg = "ttmrti";
146         } else {
147                 flags = 0;
148                 wmsg = "ttmrt";
149         }
150         mtx_lock(&lock->lock);
151         while (!__ttm_read_trylock(lock, &locked)) {
152                 ret = msleep(lock, &lock->lock, flags, wmsg, 0);
153                 if (ret != 0)
154                         break;
155         }
156         MPASS(!locked || ret == 0);
157         mtx_unlock(&lock->lock);
158
159         return (locked) ? 0 : -EBUSY;
160 }
161
162 void ttm_write_unlock(struct ttm_lock *lock)
163 {
164         mtx_lock(&lock->lock);
165         lock->rw = 0;
166         wakeup(lock);
167         mtx_unlock(&lock->lock);
168 }
169
170 static bool __ttm_write_lock(struct ttm_lock *lock)
171 {
172         bool locked = false;
173
174         if (unlikely(lock->kill_takers)) {
175                 ttm_lock_send_sig(lock->signal);
176                 return false;
177         }
178         if (lock->rw == 0 && ((lock->flags & ~TTM_WRITE_LOCK_PENDING) == 0)) {
179                 lock->rw = -1;
180                 lock->flags &= ~TTM_WRITE_LOCK_PENDING;
181                 locked = true;
182         } else {
183                 lock->flags |= TTM_WRITE_LOCK_PENDING;
184         }
185         return locked;
186 }
187
188 int
189 ttm_write_lock(struct ttm_lock *lock, bool interruptible)
190 {
191         const char *wmsg;
192         int flags, ret;
193
194         ret = 0;
195         if (interruptible) {
196                 flags = PCATCH;
197                 wmsg = "ttmwi";
198         } else {
199                 flags = 0;
200                 wmsg = "ttmw";
201         }
202         mtx_lock(&lock->lock);
203         /* XXXKIB: linux uses __ttm_read_lock for uninterruptible sleeps */
204         while (!__ttm_write_lock(lock)) {
205                 ret = msleep(lock, &lock->lock, flags, wmsg, 0);
206                 if (interruptible && ret != 0) {
207                         lock->flags &= ~TTM_WRITE_LOCK_PENDING;
208                         wakeup(lock);
209                         break;
210                 }
211         }
212         mtx_unlock(&lock->lock);
213
214         return (-ret);
215 }
216
217 void ttm_write_lock_downgrade(struct ttm_lock *lock)
218 {
219         mtx_lock(&lock->lock);
220         lock->rw = 1;
221         wakeup(lock);
222         mtx_unlock(&lock->lock);
223 }
224
225 static int __ttm_vt_unlock(struct ttm_lock *lock)
226 {
227         int ret = 0;
228
229         mtx_lock(&lock->lock);
230         if (unlikely(!(lock->flags & TTM_VT_LOCK)))
231                 ret = -EINVAL;
232         lock->flags &= ~TTM_VT_LOCK;
233         wakeup(lock);
234         mtx_unlock(&lock->lock);
235
236         return ret;
237 }
238
239 static void ttm_vt_lock_remove(struct ttm_base_object **p_base)
240 {
241         struct ttm_base_object *base = *p_base;
242         struct ttm_lock *lock = container_of(base, struct ttm_lock, base);
243         int ret;
244
245         *p_base = NULL;
246         ret = __ttm_vt_unlock(lock);
247         MPASS(ret == 0);
248 }
249
250 static bool __ttm_vt_lock(struct ttm_lock *lock)
251 {
252         bool locked = false;
253
254         if (lock->rw == 0) {
255                 lock->flags &= ~TTM_VT_LOCK_PENDING;
256                 lock->flags |= TTM_VT_LOCK;
257                 locked = true;
258         } else {
259                 lock->flags |= TTM_VT_LOCK_PENDING;
260         }
261         return locked;
262 }
263
264 int ttm_vt_lock(struct ttm_lock *lock,
265                 bool interruptible,
266                 struct ttm_object_file *tfile)
267 {
268         const char *wmsg;
269         int flags, ret;
270
271         ret = 0;
272         if (interruptible) {
273                 flags = PCATCH;
274                 wmsg = "ttmwi";
275         } else {
276                 flags = 0;
277                 wmsg = "ttmw";
278         }
279         mtx_lock(&lock->lock);
280         while (!__ttm_vt_lock(lock)) {
281                 ret = msleep(lock, &lock->lock, flags, wmsg, 0);
282                 if (interruptible && ret != 0) {
283                         lock->flags &= ~TTM_VT_LOCK_PENDING;
284                         wakeup(lock);
285                         break;
286                 }
287         }
288
289         /*
290          * Add a base-object, the destructor of which will
291          * make sure the lock is released if the client dies
292          * while holding it.
293          */
294
295         ret = ttm_base_object_init(tfile, &lock->base, false,
296                                    ttm_lock_type, &ttm_vt_lock_remove, NULL);
297         if (ret)
298                 (void)__ttm_vt_unlock(lock);
299         else
300                 lock->vt_holder = tfile;
301
302         return (-ret);
303 }
304
305 int ttm_vt_unlock(struct ttm_lock *lock)
306 {
307         return ttm_ref_object_base_unref(lock->vt_holder,
308                                          lock->base.hash.key, TTM_REF_USAGE);
309 }
310
311 void ttm_suspend_unlock(struct ttm_lock *lock)
312 {
313         mtx_lock(&lock->lock);
314         lock->flags &= ~TTM_SUSPEND_LOCK;
315         wakeup(lock);
316         mtx_unlock(&lock->lock);
317 }
318
319 static bool __ttm_suspend_lock(struct ttm_lock *lock)
320 {
321         bool locked = false;
322
323         if (lock->rw == 0) {
324                 lock->flags &= ~TTM_SUSPEND_LOCK_PENDING;
325                 lock->flags |= TTM_SUSPEND_LOCK;
326                 locked = true;
327         } else {
328                 lock->flags |= TTM_SUSPEND_LOCK_PENDING;
329         }
330         return locked;
331 }
332
333 void ttm_suspend_lock(struct ttm_lock *lock)
334 {
335         mtx_lock(&lock->lock);
336         while (!__ttm_suspend_lock(lock))
337                 msleep(lock, &lock->lock, 0, "ttms", 0);
338         mtx_unlock(&lock->lock);
339 }