2019-05-27 14:55:05 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2005-09-12 10:15:07 +08:00
|
|
|
/*
|
2012-07-14 21:08:29 +08:00
|
|
|
* cn_queue.c
|
connector: create connector workqueue only while needed once
The netlink connector uses its own workqueue to relay the datas sent
from userspace to the appropriate callback. If you launch the test
from Documentation/connector and change it a bit to send a high flow
of data, you will see thousands of events coming to the "cqueue"
workqueue by looking at the workqueue tracer.
This flow of events can be sent very quickly. So, to not encumber the
kevent workqueue and delay other jobs, the "cqueue" workqueue should
remain.
But this workqueue is pointless most of the time, it will always be
created (assuming you have built it of course) although only
developpers with specific needs will use it.
So avoid this "most of the time useless task", this patch proposes to
create this workqueue only when needed once. The first jobs to be
sent to connector callbacks will be sent to kevent while the "cqueue"
thread creation will be scheduled to kevent too.
The following jobs will continue to be scheduled to keventd until the
cqueue workqueue is created, and then the rest of the jobs will
continue to perform as usual, through this dedicated workqueue.
Each time I tested this patch, only the first event was sent to
keventd, the rest has been sent to cqueue which have been created
quickly.
Also, this patch fixes some trailing whitespaces on the connector files.
Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Acked-by: Evgeniy Polyakov <zbr@ioremap.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
2009-02-03 15:22:04 +08:00
|
|
|
*
|
2009-07-22 03:43:51 +08:00
|
|
|
* 2004+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net>
|
2005-09-12 10:15:07 +08:00
|
|
|
* All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/suspend.h>
|
|
|
|
#include <linux/connector.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
|
2009-07-18 01:13:21 +08:00
|
|
|
static struct cn_callback_entry *
|
2011-03-28 16:39:36 +08:00
|
|
|
cn_queue_alloc_callback_entry(struct cn_queue_dev *dev, const char *name,
|
2020-12-15 13:15:47 +08:00
|
|
|
const struct cb_id *id,
|
2012-07-14 21:08:29 +08:00
|
|
|
void (*callback)(struct cn_msg *,
|
|
|
|
struct netlink_skb_parms *))
|
2005-09-12 10:15:07 +08:00
|
|
|
{
|
|
|
|
struct cn_callback_entry *cbq;
|
|
|
|
|
|
|
|
cbq = kzalloc(sizeof(*cbq), GFP_KERNEL);
|
|
|
|
if (!cbq) {
|
2012-07-14 21:08:29 +08:00
|
|
|
pr_err("Failed to create new callback queue.\n");
|
2005-09-12 10:15:07 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-10-20 15:23:49 +08:00
|
|
|
refcount_set(&cbq->refcnt, 1);
|
2011-03-28 16:39:36 +08:00
|
|
|
|
|
|
|
atomic_inc(&dev->refcnt);
|
|
|
|
cbq->pdev = dev;
|
|
|
|
|
2005-09-27 06:06:50 +08:00
|
|
|
snprintf(cbq->id.name, sizeof(cbq->id.name), "%s", name);
|
|
|
|
memcpy(&cbq->id.id, id, sizeof(struct cb_id));
|
2011-03-28 16:39:36 +08:00
|
|
|
cbq->callback = callback;
|
2005-09-12 10:15:07 +08:00
|
|
|
return cbq;
|
|
|
|
}
|
|
|
|
|
2011-03-28 16:39:36 +08:00
|
|
|
void cn_queue_release_callback(struct cn_callback_entry *cbq)
|
2005-09-12 10:15:07 +08:00
|
|
|
{
|
2017-10-20 15:23:49 +08:00
|
|
|
if (!refcount_dec_and_test(&cbq->refcnt))
|
2011-03-28 16:39:36 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
atomic_dec(&cbq->pdev->refcnt);
|
2005-09-12 10:15:07 +08:00
|
|
|
kfree(cbq);
|
|
|
|
}
|
|
|
|
|
2020-12-15 13:15:47 +08:00
|
|
|
int cn_cb_equal(const struct cb_id *i1, const struct cb_id *i2)
|
2005-09-12 10:15:07 +08:00
|
|
|
{
|
|
|
|
return ((i1->idx == i2->idx) && (i1->val == i2->val));
|
|
|
|
}
|
|
|
|
|
2011-02-20 07:45:29 +08:00
|
|
|
int cn_queue_add_callback(struct cn_queue_dev *dev, const char *name,
|
2020-12-15 13:15:47 +08:00
|
|
|
const struct cb_id *id,
|
2012-07-14 21:08:29 +08:00
|
|
|
void (*callback)(struct cn_msg *,
|
|
|
|
struct netlink_skb_parms *))
|
2005-09-12 10:15:07 +08:00
|
|
|
{
|
|
|
|
struct cn_callback_entry *cbq, *__cbq;
|
|
|
|
int found = 0;
|
|
|
|
|
2011-03-28 16:39:36 +08:00
|
|
|
cbq = cn_queue_alloc_callback_entry(dev, name, id, callback);
|
2005-09-12 10:15:07 +08:00
|
|
|
if (!cbq)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
spin_lock_bh(&dev->queue_lock);
|
|
|
|
list_for_each_entry(__cbq, &dev->queue_list, callback_entry) {
|
2005-09-27 06:06:50 +08:00
|
|
|
if (cn_cb_equal(&__cbq->id.id, id)) {
|
2005-09-12 10:15:07 +08:00
|
|
|
found = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found)
|
|
|
|
list_add_tail(&cbq->callback_entry, &dev->queue_list);
|
|
|
|
spin_unlock_bh(&dev->queue_lock);
|
|
|
|
|
|
|
|
if (found) {
|
2011-03-28 16:39:36 +08:00
|
|
|
cn_queue_release_callback(cbq);
|
2005-09-12 10:15:07 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
cbq->seq = 0;
|
2005-09-27 06:06:50 +08:00
|
|
|
cbq->group = cbq->id.id.idx;
|
2005-09-12 10:15:07 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-12-15 13:15:47 +08:00
|
|
|
void cn_queue_del_callback(struct cn_queue_dev *dev, const struct cb_id *id)
|
2005-09-12 10:15:07 +08:00
|
|
|
{
|
|
|
|
struct cn_callback_entry *cbq, *n;
|
|
|
|
int found = 0;
|
|
|
|
|
|
|
|
spin_lock_bh(&dev->queue_lock);
|
|
|
|
list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) {
|
2005-09-27 06:06:50 +08:00
|
|
|
if (cn_cb_equal(&cbq->id.id, id)) {
|
2005-09-12 10:15:07 +08:00
|
|
|
list_del(&cbq->callback_entry);
|
|
|
|
found = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spin_unlock_bh(&dev->queue_lock);
|
|
|
|
|
2011-03-28 16:39:36 +08:00
|
|
|
if (found)
|
|
|
|
cn_queue_release_callback(cbq);
|
2005-09-12 10:15:07 +08:00
|
|
|
}
|
|
|
|
|
2011-02-20 07:45:29 +08:00
|
|
|
struct cn_queue_dev *cn_queue_alloc_dev(const char *name, struct sock *nls)
|
2005-09-12 10:15:07 +08:00
|
|
|
{
|
|
|
|
struct cn_queue_dev *dev;
|
|
|
|
|
|
|
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
|
|
|
if (!dev)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
snprintf(dev->name, sizeof(dev->name), "%s", name);
|
|
|
|
atomic_set(&dev->refcnt, 0);
|
|
|
|
INIT_LIST_HEAD(&dev->queue_list);
|
|
|
|
spin_lock_init(&dev->queue_lock);
|
|
|
|
|
|
|
|
dev->nls = nls;
|
|
|
|
|
|
|
|
return dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
void cn_queue_free_dev(struct cn_queue_dev *dev)
|
|
|
|
{
|
|
|
|
struct cn_callback_entry *cbq, *n;
|
|
|
|
|
|
|
|
spin_lock_bh(&dev->queue_lock);
|
|
|
|
list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry)
|
|
|
|
list_del(&cbq->callback_entry);
|
|
|
|
spin_unlock_bh(&dev->queue_lock);
|
|
|
|
|
|
|
|
while (atomic_read(&dev->refcnt)) {
|
2012-07-14 21:08:29 +08:00
|
|
|
pr_info("Waiting for %s to become free: refcnt=%d.\n",
|
2005-09-12 10:15:07 +08:00
|
|
|
dev->name, atomic_read(&dev->refcnt));
|
|
|
|
msleep(1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
kfree(dev);
|
|
|
|
dev = NULL;
|
|
|
|
}
|