3 * (C) COPYRIGHT 2012 ARM Limited. All rights reserved.
5 * This program is free software and is provided to you under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation, and any use by you of this program is subject to the terms of such GNU licence.
8 * A copy of the licence is included with the program, and can also be obtained from Free Software
9 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 #include <linux/slab.h>
16 #include <linux/list.h>
17 #include <linux/mutex.h>
18 #include <linux/wait.h>
19 #include <linux/sched.h>
20 #include <linux/err.h>
21 #include <linux/module.h>
22 #include <linux/workqueue.h>
23 #include "linux/kds.h"
26 #define KDS_LINK_TRIGGERED (1u << 0)
27 #define KDS_LINK_EXCLUSIVE (1u << 1)
29 #define KDS_IGNORED NULL
30 #define KDS_INVALID (void*)-2
31 #define KDS_RESOURCE (void*)-1
33 struct kds_resource_set
35 unsigned long num_resources;
36 unsigned long pending;
37 unsigned long locked_resources;
38 struct kds_callback * cb;
39 void * callback_parameter;
40 void * callback_extra_parameter;
41 struct list_head callback_link;
42 struct work_struct callback_work;
43 struct kds_link resources[0];
46 static DEFINE_MUTEX(kds_lock);
48 int kds_callback_init(struct kds_callback * cb, int direct, kds_callback_fn user_cb)
53 cb->user_cb = user_cb;
57 cb->wq = alloc_workqueue("kds", WQ_UNBOUND | WQ_HIGHPRI, WQ_UNBOUND_MAX_ACTIVE);
68 EXPORT_SYMBOL(kds_callback_init);
70 void kds_callback_term(struct kds_callback * cb)
75 destroy_workqueue(cb->wq);
83 EXPORT_SYMBOL(kds_callback_term);
85 static void kds_do_user_callback(struct kds_resource_set * rset)
87 rset->cb->user_cb(rset->callback_parameter, rset->callback_extra_parameter);
90 static void kds_queued_callback(struct work_struct * work)
92 struct kds_resource_set * rset;
93 rset = container_of( work, struct kds_resource_set, callback_work);
95 kds_do_user_callback(rset);
98 static void kds_callback_perform(struct kds_resource_set * rset)
100 if (rset->cb->direct)
101 kds_do_user_callback(rset);
105 result = queue_work(rset->cb->wq, &rset->callback_work);
106 /* if we got a 0 return it means we've triggered the same rset twice! */
111 void kds_resource_init(struct kds_resource * res)
114 INIT_LIST_HEAD(&res->waiters.link);
115 res->waiters.parent = KDS_RESOURCE;
117 EXPORT_SYMBOL(kds_resource_init);
119 void kds_resource_term(struct kds_resource * res)
122 BUG_ON(!list_empty(&res->waiters.link));
123 res->waiters.parent = KDS_INVALID;
125 EXPORT_SYMBOL(kds_resource_term);
127 int kds_async_waitall(
128 struct kds_resource_set ** pprset,
130 struct kds_callback * cb,
131 void * callback_parameter,
132 void * callback_extra_parameter,
133 int number_resources,
134 unsigned long * exclusive_access_bitmap,
135 struct kds_resource ** resource_list)
137 struct kds_resource_set * rset = NULL;
143 BUG_ON(!resource_list);
146 mutex_lock(&kds_lock);
148 if ((flags & KDS_FLAG_LOCKED_ACTION) == KDS_FLAG_LOCKED_FAIL)
150 for (i = 0; i < number_resources; i++)
152 if (resource_list[i]->lock_count)
160 rset = kmalloc(sizeof(*rset) + number_resources * sizeof(struct kds_link), GFP_KERNEL);
167 rset->num_resources = number_resources;
168 rset->pending = number_resources;
169 rset->locked_resources = 0;
171 rset->callback_parameter = callback_parameter;
172 rset->callback_extra_parameter = callback_extra_parameter;
173 INIT_LIST_HEAD(&rset->callback_link);
174 INIT_WORK(&rset->callback_work, kds_queued_callback);
176 for (i = 0; i < number_resources; i++)
178 unsigned long link_state = 0;
180 INIT_LIST_HEAD(&rset->resources[i].link);
181 rset->resources[i].parent = rset;
183 if (test_bit(i, exclusive_access_bitmap))
185 link_state |= KDS_LINK_EXCLUSIVE;
188 /* no-one else waiting? */
189 if (list_empty(&resource_list[i]->waiters.link))
191 link_state |= KDS_LINK_TRIGGERED;
194 /* Adding a non-exclusive and the current tail is a triggered non-exclusive? */
195 else if (((link_state & KDS_LINK_EXCLUSIVE) == 0) &&
196 (((list_entry(resource_list[i]->waiters.link.prev, struct kds_link, link)->state & (KDS_LINK_EXCLUSIVE | KDS_LINK_TRIGGERED)) == KDS_LINK_TRIGGERED)))
198 link_state |= KDS_LINK_TRIGGERED;
201 /* locked & ignore locked? */
202 else if ((resource_list[i]->lock_count) && ((flags & KDS_FLAG_LOCKED_ACTION) == KDS_FLAG_LOCKED_IGNORE) )
204 link_state |= KDS_LINK_TRIGGERED;
206 rset->resources[i].parent = KDS_IGNORED; /* to disable decrementing the pending count when we get the ignored resource */
208 rset->resources[i].state = link_state;
209 list_add_tail(&rset->resources[i].link, &resource_list[i]->waiters.link);
212 triggered = (rset->pending == 0);
214 mutex_unlock(&kds_lock);
216 /* set the pointer before the callback is called so it sees it */
221 /* all resources obtained, trigger callback */
222 kds_callback_perform(rset);
228 mutex_unlock(&kds_lock);
231 EXPORT_SYMBOL(kds_async_waitall);
233 static void wake_up_sync_call(void * callback_parameter, void * callback_extra_parameter)
235 wait_queue_head_t * wait = (wait_queue_head_t*)callback_parameter;
239 static struct kds_callback sync_cb =
246 struct kds_resource_set * kds_waitall(
247 int number_resources,
248 unsigned long * exclusive_access_bitmap,
249 struct kds_resource ** resource_list,
250 unsigned long jiffies_timeout)
252 struct kds_resource_set * rset;
255 DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
257 rset = kmalloc(sizeof(*rset) + number_resources * sizeof(struct kds_link), GFP_KERNEL);
261 rset->num_resources = number_resources;
262 rset->pending = number_resources;
263 rset->locked_resources = 1;
264 INIT_LIST_HEAD(&rset->callback_link);
266 mutex_lock(&kds_lock);
268 for (i = 0; i < number_resources; i++)
270 unsigned long link_state = 0;
272 if (likely(resource_list[i]->lock_count < ULONG_MAX))
273 resource_list[i]->lock_count++;
277 if (test_bit(i, exclusive_access_bitmap))
279 link_state |= KDS_LINK_EXCLUSIVE;
282 if (list_empty(&resource_list[i]->waiters.link))
284 link_state |= KDS_LINK_TRIGGERED;
287 /* Adding a non-exclusive and the current tail is a triggered non-exclusive? */
288 else if (((link_state & KDS_LINK_EXCLUSIVE) == 0) &&
289 (((list_entry(resource_list[i]->waiters.link.prev, struct kds_link, link)->state & (KDS_LINK_EXCLUSIVE | KDS_LINK_TRIGGERED)) == KDS_LINK_TRIGGERED)))
291 link_state |= KDS_LINK_TRIGGERED;
295 INIT_LIST_HEAD(&rset->resources[i].link);
296 rset->resources[i].parent = rset;
297 rset->resources[i].state = link_state;
298 list_add_tail(&rset->resources[i].link, &resource_list[i]->waiters.link);
301 if (i < number_resources)
303 /* an overflow was detected, roll back */
306 list_del(&rset->resources[i].link);
307 resource_list[i]->lock_count--;
309 mutex_unlock(&kds_lock);
311 return ERR_PTR(-EFAULT);
314 if (rset->pending == 0)
319 rset->callback_parameter = &wake;
320 rset->callback_extra_parameter = NULL;
323 mutex_unlock(&kds_lock);
328 if ( KDS_WAIT_BLOCKING == jiffies_timeout )
330 wait_res = wait_event_interruptible(wake, rset->pending == 0);
334 wait_res = wait_event_interruptible_timeout(wake, rset->pending == 0, jiffies_timeout);
336 if ((wait_res == -ERESTARTSYS) || (wait_res == 0))
338 /* use \a kds_resource_set_release to roll back */
339 kds_resource_set_release(&rset);
340 return ERR_PTR(wait_res);
345 EXPORT_SYMBOL(kds_waitall);
347 void kds_resource_set_release(struct kds_resource_set ** pprset)
349 struct list_head triggered = LIST_HEAD_INIT(triggered);
350 struct kds_resource_set * rset;
351 struct kds_resource_set * it;
356 mutex_lock(&kds_lock);
361 /* caught a race between a cancelation
362 * and a completion, nothing to do */
363 mutex_unlock(&kds_lock);
367 /* clear user pointer so we'll be the only
368 * thread handling the release */
371 for (i = 0; i < rset->num_resources; i++)
373 struct kds_resource * resource;
374 struct kds_link * it = NULL;
376 /* fetch the previous entry on the linked list */
377 it = list_entry(rset->resources[i].link.prev, struct kds_link, link);
379 list_del(&rset->resources[i].link);
382 if (list_empty(&it->link))
385 /* were we the head of the list? (head if prev is a resource) */
386 if (it->parent != KDS_RESOURCE)
389 /* we were the head, find the kds_resource */
390 resource = container_of(it, struct kds_resource, waiters);
392 if (rset->locked_resources)
394 resource->lock_count--;
397 /* we know there is someone waiting from the any-waiters test above */
399 /* find the head of the waiting list */
400 it = list_first_entry(&resource->waiters.link, struct kds_link, link);
402 /* new exclusive owner? */
403 if (it->state & KDS_LINK_EXCLUSIVE)
405 /* link now triggered */
406 it->state |= KDS_LINK_TRIGGERED;
407 /* a parent to update? */
408 if (it->parent != KDS_IGNORED)
410 if (0 == --it->parent->pending)
412 /* new owner now triggered, track for callback later */
413 list_add(&it->parent->callback_link, &triggered);
417 /* exclusive releasing ? */
418 else if (rset->resources[i].state & KDS_LINK_EXCLUSIVE)
420 /* trigger non-exclusive until end-of-list or first exclusive */
421 list_for_each_entry(it, &resource->waiters.link, link)
423 /* exclusive found, stop triggering */
424 if (it->state & KDS_LINK_EXCLUSIVE)
427 it->state |= KDS_LINK_TRIGGERED;
428 /* a parent to update? */
429 if (it->parent != KDS_IGNORED)
431 if (0 == --it->parent->pending)
433 /* new owner now triggered, track for callback later */
434 list_add(&it->parent->callback_link, &triggered);
442 mutex_unlock(&kds_lock);
444 while (!list_empty(&triggered))
446 it = list_first_entry(&triggered, struct kds_resource_set, callback_link);
447 list_del(&it->callback_link);
448 kds_callback_perform(it);
451 cancel_work_sync(&rset->callback_work);
453 /* free the resource set */
456 EXPORT_SYMBOL(kds_resource_set_release);
458 MODULE_LICENSE("GPL");
459 MODULE_AUTHOR("ARM Ltd.");
460 MODULE_VERSION("1.0");