diff options
Diffstat (limited to 'drivers/connector/cn_queue.c')
-rw-r--r-- | drivers/connector/cn_queue.c | 80 |
1 files changed, 69 insertions, 11 deletions
diff --git a/drivers/connector/cn_queue.c b/drivers/connector/cn_queue.c index b6fe7e7a2c2..c769ef269fb 100644 --- a/drivers/connector/cn_queue.c +++ b/drivers/connector/cn_queue.c @@ -1,9 +1,9 @@ /* * cn_queue.c - * + * * 2004-2005 Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru> * All rights reserved. - * + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -31,6 +31,48 @@ #include <linux/connector.h> #include <linux/delay.h> + +/* + * This job is sent to the kevent workqueue. + * While no event is once sent to any callback, the connector workqueue + * is not created to avoid a useless waiting kernel task. + * Once the first event is received, we create this dedicated workqueue which + * is necessary because the flow of data can be high and we don't want + * to encumber keventd with that. + */ +static void cn_queue_create(struct work_struct *work) +{ + struct cn_queue_dev *dev; + + dev = container_of(work, struct cn_queue_dev, wq_creation); + + dev->cn_queue = create_singlethread_workqueue(dev->name); + /* If we fail, we will use keventd for all following connector jobs */ + WARN_ON(!dev->cn_queue); +} + +/* + * Queue a data sent to a callback. + * If the connector workqueue is already created, we queue the job on it. + * Otherwise, we queue the job to kevent and queue the connector workqueue + * creation too. + */ +int queue_cn_work(struct cn_callback_entry *cbq, struct work_struct *work) +{ + struct cn_queue_dev *pdev = cbq->pdev; + + if (likely(pdev->cn_queue)) + return queue_work(pdev->cn_queue, work); + + /* Don't create the connector workqueue twice */ + if (atomic_inc_return(&pdev->wq_requested) == 1) + schedule_work(&pdev->wq_creation); + else + atomic_dec(&pdev->wq_requested); + + return schedule_work(work); +} + void cn_queue_wrapper(struct work_struct *work) { struct cn_callback_entry *cbq = @@ -58,14 +100,17 @@ static struct cn_callback_entry *cn_queue_alloc_callback_entry(char *name, struc snprintf(cbq->id.name, sizeof(cbq->id.name), "%s", name); memcpy(&cbq->id.id, id, sizeof(struct cb_id)); cbq->data.callback = callback; - + INIT_WORK(&cbq->work, &cn_queue_wrapper); return cbq; } static void cn_queue_free_callback(struct cn_callback_entry *cbq) { - flush_workqueue(cbq->pdev->cn_queue); + /* The first jobs have been sent to kevent, flush them too */ + flush_scheduled_work(); + if (cbq->pdev->cn_queue) + flush_workqueue(cbq->pdev->cn_queue); kfree(cbq); } @@ -143,14 +188,11 @@ struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls) atomic_set(&dev->refcnt, 0); INIT_LIST_HEAD(&dev->queue_list); spin_lock_init(&dev->queue_lock); + init_waitqueue_head(&dev->wq_created); dev->nls = nls; - dev->cn_queue = create_singlethread_workqueue(dev->name); - if (!dev->cn_queue) { - kfree(dev); - return NULL; - } + INIT_WORK(&dev->wq_creation, cn_queue_create); return dev; } @@ -158,9 +200,25 @@ struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls) void cn_queue_free_dev(struct cn_queue_dev *dev) { struct cn_callback_entry *cbq, *n; + long timeout; + DEFINE_WAIT(wait); + + /* Flush the first pending jobs queued on kevent */ + flush_scheduled_work(); + + /* If the connector workqueue creation is still pending, wait for it */ + prepare_to_wait(&dev->wq_created, &wait, TASK_UNINTERRUPTIBLE); + if (atomic_read(&dev->wq_requested) && !dev->cn_queue) { + timeout = schedule_timeout(HZ * 2); + if (!timeout && !dev->cn_queue) + WARN_ON(1); + } + finish_wait(&dev->wq_created, &wait); - flush_workqueue(dev->cn_queue); - destroy_workqueue(dev->cn_queue); + if (dev->cn_queue) { + flush_workqueue(dev->cn_queue); + destroy_workqueue(dev->cn_queue); + } spin_lock_bh(&dev->queue_lock); list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) |