17672d0b5SEvgeniy Polyakov /* 27672d0b5SEvgeniy Polyakov * cn_queue.c 37672d0b5SEvgeniy Polyakov * 4acb9c1b2SEvgeniy Polyakov * 2004+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net> 57672d0b5SEvgeniy Polyakov * All rights reserved. 67672d0b5SEvgeniy Polyakov * 77672d0b5SEvgeniy Polyakov * This program is free software; you can redistribute it and/or modify 87672d0b5SEvgeniy Polyakov * it under the terms of the GNU General Public License as published by 97672d0b5SEvgeniy Polyakov * the Free Software Foundation; either version 2 of the License, or 107672d0b5SEvgeniy Polyakov * (at your option) any later version. 117672d0b5SEvgeniy Polyakov * 127672d0b5SEvgeniy Polyakov * This program is distributed in the hope that it will be useful, 137672d0b5SEvgeniy Polyakov * but WITHOUT ANY WARRANTY; without even the implied warranty of 147672d0b5SEvgeniy Polyakov * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 157672d0b5SEvgeniy Polyakov * GNU General Public License for more details. 167672d0b5SEvgeniy Polyakov * 177672d0b5SEvgeniy Polyakov * You should have received a copy of the GNU General Public License 187672d0b5SEvgeniy Polyakov * along with this program; if not, write to the Free Software 197672d0b5SEvgeniy Polyakov * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 207672d0b5SEvgeniy Polyakov * 217672d0b5SEvgeniy Polyakov */ 227672d0b5SEvgeniy Polyakov 237672d0b5SEvgeniy Polyakov #include <linux/kernel.h> 247672d0b5SEvgeniy Polyakov #include <linux/module.h> 257672d0b5SEvgeniy Polyakov #include <linux/list.h> 267672d0b5SEvgeniy Polyakov #include <linux/workqueue.h> 277672d0b5SEvgeniy Polyakov #include <linux/spinlock.h> 287672d0b5SEvgeniy Polyakov #include <linux/slab.h> 297672d0b5SEvgeniy Polyakov #include <linux/skbuff.h> 307672d0b5SEvgeniy Polyakov #include <linux/suspend.h> 317672d0b5SEvgeniy Polyakov #include <linux/connector.h> 327672d0b5SEvgeniy Polyakov #include <linux/delay.h> 337672d0b5SEvgeniy Polyakov 341a5645bcSFrederic Weisbecker 351a5645bcSFrederic Weisbecker /* 361a5645bcSFrederic Weisbecker * This job is sent to the kevent workqueue. 371a5645bcSFrederic Weisbecker * While no event is once sent to any callback, the connector workqueue 381a5645bcSFrederic Weisbecker * is not created to avoid a useless waiting kernel task. 391a5645bcSFrederic Weisbecker * Once the first event is received, we create this dedicated workqueue which 401a5645bcSFrederic Weisbecker * is necessary because the flow of data can be high and we don't want 411a5645bcSFrederic Weisbecker * to encumber keventd with that. 421a5645bcSFrederic Weisbecker */ 431a5645bcSFrederic Weisbecker static void cn_queue_create(struct work_struct *work) 441a5645bcSFrederic Weisbecker { 451a5645bcSFrederic Weisbecker struct cn_queue_dev *dev; 461a5645bcSFrederic Weisbecker 471a5645bcSFrederic Weisbecker dev = container_of(work, struct cn_queue_dev, wq_creation); 481a5645bcSFrederic Weisbecker 491a5645bcSFrederic Weisbecker dev->cn_queue = create_singlethread_workqueue(dev->name); 501a5645bcSFrederic Weisbecker /* If we fail, we will use keventd for all following connector jobs */ 511a5645bcSFrederic Weisbecker WARN_ON(!dev->cn_queue); 521a5645bcSFrederic Weisbecker } 531a5645bcSFrederic Weisbecker 541a5645bcSFrederic Weisbecker /* 551a5645bcSFrederic Weisbecker * Queue a data sent to a callback. 561a5645bcSFrederic Weisbecker * If the connector workqueue is already created, we queue the job on it. 571a5645bcSFrederic Weisbecker * Otherwise, we queue the job to kevent and queue the connector workqueue 581a5645bcSFrederic Weisbecker * creation too. 591a5645bcSFrederic Weisbecker */ 601a5645bcSFrederic Weisbecker int queue_cn_work(struct cn_callback_entry *cbq, struct work_struct *work) 611a5645bcSFrederic Weisbecker { 621a5645bcSFrederic Weisbecker struct cn_queue_dev *pdev = cbq->pdev; 631a5645bcSFrederic Weisbecker 641a5645bcSFrederic Weisbecker if (likely(pdev->cn_queue)) 651a5645bcSFrederic Weisbecker return queue_work(pdev->cn_queue, work); 661a5645bcSFrederic Weisbecker 671a5645bcSFrederic Weisbecker /* Don't create the connector workqueue twice */ 681a5645bcSFrederic Weisbecker if (atomic_inc_return(&pdev->wq_requested) == 1) 691a5645bcSFrederic Weisbecker schedule_work(&pdev->wq_creation); 701a5645bcSFrederic Weisbecker else 711a5645bcSFrederic Weisbecker atomic_dec(&pdev->wq_requested); 721a5645bcSFrederic Weisbecker 731a5645bcSFrederic Weisbecker return schedule_work(work); 741a5645bcSFrederic Weisbecker } 751a5645bcSFrederic Weisbecker 76c4028958SDavid Howells void cn_queue_wrapper(struct work_struct *work) 777672d0b5SEvgeniy Polyakov { 78c4028958SDavid Howells struct cn_callback_entry *cbq = 79a240d9f1SEvgeniy Polyakov container_of(work, struct cn_callback_entry, work); 80c4028958SDavid Howells struct cn_callback_data *d = &cbq->data; 817672d0b5SEvgeniy Polyakov 82acd042bbSEvgeniy Polyakov d->callback(d->callback_priv); 83acd042bbSEvgeniy Polyakov 84acd042bbSEvgeniy Polyakov d->destruct_data(d->ddata); 85acd042bbSEvgeniy Polyakov d->ddata = NULL; 86acd042bbSEvgeniy Polyakov 87acd042bbSEvgeniy Polyakov kfree(d->free); 887672d0b5SEvgeniy Polyakov } 897672d0b5SEvgeniy Polyakov 90acd042bbSEvgeniy Polyakov static struct cn_callback_entry *cn_queue_alloc_callback_entry(char *name, struct cb_id *id, void (*callback)(void *)) 917672d0b5SEvgeniy Polyakov { 927672d0b5SEvgeniy Polyakov struct cn_callback_entry *cbq; 937672d0b5SEvgeniy Polyakov 947672d0b5SEvgeniy Polyakov cbq = kzalloc(sizeof(*cbq), GFP_KERNEL); 957672d0b5SEvgeniy Polyakov if (!cbq) { 967672d0b5SEvgeniy Polyakov printk(KERN_ERR "Failed to create new callback queue.\n"); 977672d0b5SEvgeniy Polyakov return NULL; 987672d0b5SEvgeniy Polyakov } 997672d0b5SEvgeniy Polyakov 100acd042bbSEvgeniy Polyakov snprintf(cbq->id.name, sizeof(cbq->id.name), "%s", name); 101acd042bbSEvgeniy Polyakov memcpy(&cbq->id.id, id, sizeof(struct cb_id)); 102acd042bbSEvgeniy Polyakov cbq->data.callback = callback; 103acd042bbSEvgeniy Polyakov 104a240d9f1SEvgeniy Polyakov INIT_WORK(&cbq->work, &cn_queue_wrapper); 1057672d0b5SEvgeniy Polyakov return cbq; 1067672d0b5SEvgeniy Polyakov } 1077672d0b5SEvgeniy Polyakov 1087672d0b5SEvgeniy Polyakov static void cn_queue_free_callback(struct cn_callback_entry *cbq) 1097672d0b5SEvgeniy Polyakov { 1101a5645bcSFrederic Weisbecker /* The first jobs have been sent to kevent, flush them too */ 1111a5645bcSFrederic Weisbecker flush_scheduled_work(); 1121a5645bcSFrederic Weisbecker if (cbq->pdev->cn_queue) 1137672d0b5SEvgeniy Polyakov flush_workqueue(cbq->pdev->cn_queue); 1147672d0b5SEvgeniy Polyakov 1157672d0b5SEvgeniy Polyakov kfree(cbq); 1167672d0b5SEvgeniy Polyakov } 1177672d0b5SEvgeniy Polyakov 1187672d0b5SEvgeniy Polyakov int cn_cb_equal(struct cb_id *i1, struct cb_id *i2) 1197672d0b5SEvgeniy Polyakov { 1207672d0b5SEvgeniy Polyakov return ((i1->idx == i2->idx) && (i1->val == i2->val)); 1217672d0b5SEvgeniy Polyakov } 1227672d0b5SEvgeniy Polyakov 123acd042bbSEvgeniy Polyakov int cn_queue_add_callback(struct cn_queue_dev *dev, char *name, struct cb_id *id, void (*callback)(void *)) 1247672d0b5SEvgeniy Polyakov { 1257672d0b5SEvgeniy Polyakov struct cn_callback_entry *cbq, *__cbq; 1267672d0b5SEvgeniy Polyakov int found = 0; 1277672d0b5SEvgeniy Polyakov 128acd042bbSEvgeniy Polyakov cbq = cn_queue_alloc_callback_entry(name, id, callback); 1297672d0b5SEvgeniy Polyakov if (!cbq) 1307672d0b5SEvgeniy Polyakov return -ENOMEM; 1317672d0b5SEvgeniy Polyakov 1327672d0b5SEvgeniy Polyakov atomic_inc(&dev->refcnt); 1337672d0b5SEvgeniy Polyakov cbq->pdev = dev; 1347672d0b5SEvgeniy Polyakov 1357672d0b5SEvgeniy Polyakov spin_lock_bh(&dev->queue_lock); 1367672d0b5SEvgeniy Polyakov list_for_each_entry(__cbq, &dev->queue_list, callback_entry) { 137acd042bbSEvgeniy Polyakov if (cn_cb_equal(&__cbq->id.id, id)) { 1387672d0b5SEvgeniy Polyakov found = 1; 1397672d0b5SEvgeniy Polyakov break; 1407672d0b5SEvgeniy Polyakov } 1417672d0b5SEvgeniy Polyakov } 1427672d0b5SEvgeniy Polyakov if (!found) 1437672d0b5SEvgeniy Polyakov list_add_tail(&cbq->callback_entry, &dev->queue_list); 1447672d0b5SEvgeniy Polyakov spin_unlock_bh(&dev->queue_lock); 1457672d0b5SEvgeniy Polyakov 1467672d0b5SEvgeniy Polyakov if (found) { 1477672d0b5SEvgeniy Polyakov cn_queue_free_callback(cbq); 148cf585ae8SLi Zefan atomic_dec(&dev->refcnt); 1497672d0b5SEvgeniy Polyakov return -EINVAL; 1507672d0b5SEvgeniy Polyakov } 1517672d0b5SEvgeniy Polyakov 1527672d0b5SEvgeniy Polyakov cbq->seq = 0; 153acd042bbSEvgeniy Polyakov cbq->group = cbq->id.id.idx; 1547672d0b5SEvgeniy Polyakov 1557672d0b5SEvgeniy Polyakov return 0; 1567672d0b5SEvgeniy Polyakov } 1577672d0b5SEvgeniy Polyakov 1587672d0b5SEvgeniy Polyakov void cn_queue_del_callback(struct cn_queue_dev *dev, struct cb_id *id) 1597672d0b5SEvgeniy Polyakov { 1607672d0b5SEvgeniy Polyakov struct cn_callback_entry *cbq, *n; 1617672d0b5SEvgeniy Polyakov int found = 0; 1627672d0b5SEvgeniy Polyakov 1637672d0b5SEvgeniy Polyakov spin_lock_bh(&dev->queue_lock); 1647672d0b5SEvgeniy Polyakov list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) { 165acd042bbSEvgeniy Polyakov if (cn_cb_equal(&cbq->id.id, id)) { 1667672d0b5SEvgeniy Polyakov list_del(&cbq->callback_entry); 1677672d0b5SEvgeniy Polyakov found = 1; 1687672d0b5SEvgeniy Polyakov break; 1697672d0b5SEvgeniy Polyakov } 1707672d0b5SEvgeniy Polyakov } 1717672d0b5SEvgeniy Polyakov spin_unlock_bh(&dev->queue_lock); 1727672d0b5SEvgeniy Polyakov 1737672d0b5SEvgeniy Polyakov if (found) { 1747672d0b5SEvgeniy Polyakov cn_queue_free_callback(cbq); 175cec6f7f3SAndreas Schwab atomic_dec(&dev->refcnt); 1767672d0b5SEvgeniy Polyakov } 1777672d0b5SEvgeniy Polyakov } 1787672d0b5SEvgeniy Polyakov 1797672d0b5SEvgeniy Polyakov struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls) 1807672d0b5SEvgeniy Polyakov { 1817672d0b5SEvgeniy Polyakov struct cn_queue_dev *dev; 1827672d0b5SEvgeniy Polyakov 1837672d0b5SEvgeniy Polyakov dev = kzalloc(sizeof(*dev), GFP_KERNEL); 1847672d0b5SEvgeniy Polyakov if (!dev) 1857672d0b5SEvgeniy Polyakov return NULL; 1867672d0b5SEvgeniy Polyakov 1877672d0b5SEvgeniy Polyakov snprintf(dev->name, sizeof(dev->name), "%s", name); 1887672d0b5SEvgeniy Polyakov atomic_set(&dev->refcnt, 0); 1897672d0b5SEvgeniy Polyakov INIT_LIST_HEAD(&dev->queue_list); 1907672d0b5SEvgeniy Polyakov spin_lock_init(&dev->queue_lock); 1911a5645bcSFrederic Weisbecker init_waitqueue_head(&dev->wq_created); 1927672d0b5SEvgeniy Polyakov 1937672d0b5SEvgeniy Polyakov dev->nls = nls; 1947672d0b5SEvgeniy Polyakov 1951a5645bcSFrederic Weisbecker INIT_WORK(&dev->wq_creation, cn_queue_create); 1967672d0b5SEvgeniy Polyakov 1977672d0b5SEvgeniy Polyakov return dev; 1987672d0b5SEvgeniy Polyakov } 1997672d0b5SEvgeniy Polyakov 2007672d0b5SEvgeniy Polyakov void cn_queue_free_dev(struct cn_queue_dev *dev) 2017672d0b5SEvgeniy Polyakov { 2027672d0b5SEvgeniy Polyakov struct cn_callback_entry *cbq, *n; 2031a5645bcSFrederic Weisbecker long timeout; 2041a5645bcSFrederic Weisbecker DEFINE_WAIT(wait); 2057672d0b5SEvgeniy Polyakov 2061a5645bcSFrederic Weisbecker /* Flush the first pending jobs queued on kevent */ 2071a5645bcSFrederic Weisbecker flush_scheduled_work(); 2081a5645bcSFrederic Weisbecker 2091a5645bcSFrederic Weisbecker /* If the connector workqueue creation is still pending, wait for it */ 2101a5645bcSFrederic Weisbecker prepare_to_wait(&dev->wq_created, &wait, TASK_UNINTERRUPTIBLE); 2111a5645bcSFrederic Weisbecker if (atomic_read(&dev->wq_requested) && !dev->cn_queue) { 2121a5645bcSFrederic Weisbecker timeout = schedule_timeout(HZ * 2); 2131a5645bcSFrederic Weisbecker if (!timeout && !dev->cn_queue) 2141a5645bcSFrederic Weisbecker WARN_ON(1); 2151a5645bcSFrederic Weisbecker } 2161a5645bcSFrederic Weisbecker finish_wait(&dev->wq_created, &wait); 2171a5645bcSFrederic Weisbecker 2181a5645bcSFrederic Weisbecker if (dev->cn_queue) { 2197672d0b5SEvgeniy Polyakov flush_workqueue(dev->cn_queue); 2207672d0b5SEvgeniy Polyakov destroy_workqueue(dev->cn_queue); 2211a5645bcSFrederic Weisbecker } 2227672d0b5SEvgeniy Polyakov 2237672d0b5SEvgeniy Polyakov spin_lock_bh(&dev->queue_lock); 2247672d0b5SEvgeniy Polyakov list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) 2257672d0b5SEvgeniy Polyakov list_del(&cbq->callback_entry); 2267672d0b5SEvgeniy Polyakov spin_unlock_bh(&dev->queue_lock); 2277672d0b5SEvgeniy Polyakov 2287672d0b5SEvgeniy Polyakov while (atomic_read(&dev->refcnt)) { 2297672d0b5SEvgeniy Polyakov printk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n", 2307672d0b5SEvgeniy Polyakov dev->name, atomic_read(&dev->refcnt)); 2317672d0b5SEvgeniy Polyakov msleep(1000); 2327672d0b5SEvgeniy Polyakov } 2337672d0b5SEvgeniy Polyakov 2347672d0b5SEvgeniy Polyakov kfree(dev); 2357672d0b5SEvgeniy Polyakov dev = NULL; 2367672d0b5SEvgeniy Polyakov } 237