atmel_lcdfb: correct fifo size for some products
[safe/jmp/linux-2.6] / drivers / connector / cn_queue.c
index 9f2f00d..c769ef2 100644 (file)
@@ -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
 #include <linux/connector.h>
 #include <linux/delay.h>
 
-void cn_queue_wrapper(void *data)
+
+/*
+ * 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_callback_data *d = data;
+       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 =
+               container_of(work, struct cn_callback_entry, work);
+       struct cn_callback_data *d = &cbq->data;
 
        d->callback(d->callback_priv);
 
@@ -56,15 +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, &cbq->data);
+
+       INIT_WORK(&cbq->work, &cn_queue_wrapper);
        return cbq;
 }
 
 static void cn_queue_free_callback(struct cn_callback_entry *cbq)
 {
-       cancel_delayed_work(&cbq->work);
-       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);
 }
@@ -98,12 +144,11 @@ int cn_queue_add_callback(struct cn_queue_dev *dev, char *name, struct cb_id *id
        spin_unlock_bh(&dev->queue_lock);
 
        if (found) {
-               atomic_dec(&dev->refcnt);
                cn_queue_free_callback(cbq);
+               atomic_dec(&dev->refcnt);
                return -EINVAL;
        }
 
-       cbq->nls = dev->nls;
        cbq->seq = 0;
        cbq->group = cbq->id.id.idx;
 
@@ -127,7 +172,7 @@ void cn_queue_del_callback(struct cn_queue_dev *dev, struct cb_id *id)
 
        if (found) {
                cn_queue_free_callback(cbq);
-               atomic_dec_and_test(&dev->refcnt);
+               atomic_dec(&dev->refcnt);
        }
 }
 
@@ -143,15 +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->netlink_groups = 0;
 
-       dev->cn_queue = create_workqueue(dev->name);
-       if (!dev->cn_queue) {
-               kfree(dev);
-               return NULL;
-       }
+       INIT_WORK(&dev->wq_creation, cn_queue_create);
 
        return dev;
 }
@@ -159,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)