If noone has any objection, please consider for inclusion. Thank you.
On Fri, Aug 26, 2005 at 02:09:38PM +0400, Evgeniy Polyakov ([EMAIL PROTECTED]) wrote: > Kernel connector - new userspace <-> kernel space easy to > use communication module which implements easy to use bidirectional > message bus using netlink as it's backend. > Connector was created to eliminate complex skb handling both in send and > receive message bus direction. > > Connector driver adds possibility to connect various agents using > as one of it's backends netlink based network. > One must register callback and identifier. When driver receives > special netlink message with appropriate identifier, appropriate > callback will be called. > > >From the userspace point of view it's quite straightforward: > socket(); > bind(); > send(); > recv(); > X-Spam-Status: No, hits=0.000000 required=0.900000 > > But if kernelspace want to use full power of such connections, driver > writer must create special sockets, must know about struct sk_buff > handling... > Connector allows any kernelspace agents to use netlink based > networking for inter-process communication in a significantly easier > way: > > int cn_add_callback(struct cb_id *id, char *name, void (*callback) (void > *)); > void cn_netlink_send(struct cn_msg *msg, u32 __groups, int gfp_mask); > > struct cb_id > { > __u32 idx; > __u32 val; > }; > > idx and val are unique identifiers which must be registered in > connector.h for in-kernel usage. > void (*callback) (void *) - is a callback function which will be called > when message with above idx.val will be received by connector core. > > Using connector completely hides low-level transport layer from > it's users. > > Connector uses new netlink ability to have many groups in one socket. > > Sorry for long carbon-copy list - I've added all people answering about > connector and related stuff before. > > Thank you. > > Signed-off-by: Evgeniy Polyakov <[EMAIL PROTECTED]> > > diff --git a/include/linux/netlink.h b/include/linux/netlink.h > --- a/include/linux/netlink.h > +++ b/include/linux/netlink.h > @@ -15,6 +15,7 @@ > #define NETLINK_ISCSI 8 /* Open-iSCSI */ > #define NETLINK_AUDIT 9 /* auditing */ > #define NETLINK_FIB_LOOKUP 10 > +#define NETLINK_CONNECTOR 11 > #define NETLINK_NETFILTER 12 /* netfilter subsystem */ > #define NETLINK_IP6_FW 13 > #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ > > diff --git a/Documentation/connector/cn_test.c > b/Documentation/connector/cn_test.c > new file mode 100644 > --- /dev/null > +++ b/Documentation/connector/cn_test.c > @@ -0,0 +1,195 @@ > +/* > + * cn_test.c > + * > + * 2004-2005 Copyright (c) Evgeniy Polyakov <[EMAIL PROTECTED]> > + * 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 > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/moduleparam.h> > +#include <linux/skbuff.h> > +#include <linux/timer.h> > + > +#include "connector.h" > + > +static struct cb_id cn_test_id = { 0x123, 0x456 }; > +static char cn_test_name[] = "cn_test"; > +static struct sock *nls; > +static struct timer_list cn_test_timer; > + > +void cn_test_callback(void *data) > +{ > + struct cn_msg *msg = (struct cn_msg *)data; > + > + printk("%s: %lu: idx=%x, val=%x, seq=%u, ack=%u, len=%d: %s.\n", > + __func__, jiffies, msg->id.idx, msg->id.val, > + msg->seq, msg->ack, msg->len, (char *)msg->data); > +} > + > +static int cn_test_want_notify(void) > +{ > + struct cn_ctl_msg *ctl; > + struct cn_notify_req *req; > + struct cn_msg *msg = NULL; > + int size, size0; > + struct sk_buff *skb; > + struct nlmsghdr *nlh; > + u32 group = 1; > + > + size0 = sizeof(*msg) + sizeof(*ctl) + 3 * sizeof(*req); > + > + size = NLMSG_SPACE(size0); > + > + skb = alloc_skb(size, GFP_ATOMIC); > + if (!skb) { > + printk(KERN_ERR "Failed to allocate new skb with size=%u.\n", > + size); > + > + return -ENOMEM; > + } > + > + nlh = NLMSG_PUT(skb, 0, 0x123, NLMSG_DONE, size - sizeof(*nlh)); > + > + msg = (struct cn_msg *)NLMSG_DATA(nlh); > + > + memset(msg, 0, size0); > + > + msg->id.idx = -1; > + msg->id.val = -1; > + msg->seq = 0x123; > + msg->ack = 0x345; > + msg->len = size0 - sizeof(*msg); > + > + ctl = (struct cn_ctl_msg *)(msg + 1); > + > + ctl->idx_notify_num = 1; > + ctl->val_notify_num = 2; > + ctl->group = group; > + ctl->len = msg->len - sizeof(*ctl); > + > + req = (struct cn_notify_req *)(ctl + 1); > + > + /* > + * Idx. > + */ > + req->first = cn_test_id.idx; > + req->range = 10; > + > + /* > + * Val 0. > + */ > + req++; > + req->first = cn_test_id.val; > + req->range = 10; > + > + /* > + * Val 1. > + */ > + req++; > + req->first = cn_test_id.val + 20; > + req->range = 10; > + > + NETLINK_CB(skb).dst_groups = ctl->group; > + //netlink_broadcast(nls, skb, 0, ctl->group, GFP_ATOMIC); > + netlink_unicast(nls, skb, 0, 0); > + > + printk(KERN_INFO "Request was sent. Group=0x%x.\n", ctl->group); > + > + return 0; > + > +nlmsg_failure: > + printk(KERN_ERR "Failed to send %u.%u\n", msg->seq, msg->ack); > + kfree_skb(skb); > + return -EINVAL; > +} > + > +static u32 cn_test_timer_counter; > +static void cn_test_timer_func(unsigned long __data) > +{ > + struct cn_msg *m; > + char data[32]; > + > + m = kmalloc(sizeof(*m) + sizeof(data), GFP_ATOMIC); > + if (m) { > + memset(m, 0, sizeof(*m) + sizeof(data)); > + > + memcpy(&m->id, &cn_test_id, sizeof(m->id)); > + m->seq = cn_test_timer_counter; > + m->len = sizeof(data); > + > + m->len = > + scnprintf(data, sizeof(data), "counter = %u", > + cn_test_timer_counter) + 1; > + > + memcpy(m + 1, data, m->len); > + > + cbus_insert(m, gfp_any()); > + //cn_netlink_send(m, gfp_any()); > + kfree(m); > + } > + > + cn_test_timer_counter++; > + > + mod_timer(&cn_test_timer, jiffies + HZ); > +} > + > +static int cn_test_init(void) > +{ > + int err; > + > + err = cn_add_callback(&cn_test_id, cn_test_name, cn_test_callback); > + if (err) > + goto err_out; > + cn_test_id.val++; > + err = cn_add_callback(&cn_test_id, cn_test_name, cn_test_callback); > + if (err) { > + cn_del_callback(&cn_test_id); > + goto err_out; > + } > + > + init_timer(&cn_test_timer); > + cn_test_timer.function = cn_test_timer_func; > + cn_test_timer.expires = jiffies + HZ; > + cn_test_timer.data = 0; > + add_timer(&cn_test_timer); > + > + return 0; > + > + err_out: > + if (nls && nls->sk_socket) > + sock_release(nls->sk_socket); > + > + return err; > +} > + > +static void cn_test_fini(void) > +{ > + del_timer_sync(&cn_test_timer); > + cn_del_callback(&cn_test_id); > + cn_test_id.val--; > + cn_del_callback(&cn_test_id); > + if (nls && nls->sk_socket) > + sock_release(nls->sk_socket); > +} > + > +module_init(cn_test_init); > +module_exit(cn_test_fini); > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Evgeniy Polyakov <[EMAIL PROTECTED]>"); > +MODULE_DESCRIPTION("Connector's test module"); > diff --git a/Documentation/connector/connector.txt > b/Documentation/connector/connector.txt > new file mode 100644 > --- /dev/null > +++ b/Documentation/connector/connector.txt > @@ -0,0 +1,158 @@ > +/*****************************************/ > +Kernel Connector. > +/*****************************************/ > + > +Kernel connector - new netlink based userspace <-> kernel space easy to use > communication module. > + > +Connector driver adds possibility to connect various agents using > +netlink based network. > +One must register callback and identifier. When driver receives > +special netlink message with appropriate identifier, appropriate > +callback will be called. > + > +From the userspace point of view it's quite straightforward: > +socket(); > +bind(); > +send(); > +recv(); > + > +But if kernelspace want to use full power of such connections, driver > +writer must create special sockets, must know about struct sk_buff > +handling... > +Connector allows any kernelspace agents to use netlink based > +networking for inter-process communication in a significantly easier > +way: > + > +int cn_add_callback(struct cb_id *id, char *name, void (*callback) (void *)); > +void cn_netlink_send(struct cn_msg *msg, u32 __groups, int gfp_mask); > + > +struct cb_id > +{ > + __u32 idx; > + __u32 val; > +}; > + > +idx and val are unique identifiers which must be registered in connector.h > +for in-kernel usage. > +void (*callback) (void *) - is a callback function which will be called > +when message with above idx.val will be received by connector core. > +Argument for that function must be dereferenced to struct cn_msg *. > + > +struct cn_msg > +{ > + struct cb_id id; > + > + __u32 seq; > + __u32 ack; > + > + __u32 len; /* Length of the following data > */ > + __u8 data[0]; > +}; > + > +/*****************************************/ > +Connector interfaces. > +/*****************************************/ > + > +int cn_add_callback(struct cb_id *id, char *name, void (*callback) (void *)); > +Registers new callback with connector core. > + > +struct cb_id *id - unique connector's user identifier. > + It must be registered in connector.h for > legal in-kernel users. > +char *name - connector's callback symbolic name. > +void (*callback) (void *) - connector's callback. > + Argument must be dereferenced to struct > cn_msg *. > + > +void cn_del_callback(struct cb_id *id); > +Unregisters new callback with connector core. > + > +struct cb_id *id - unique connector's user identifier. > + > +void cn_netlink_send(struct cn_msg *msg, u32 __groups, int gfp_mask); > +Sends message to the specified groups. > +It can be safely called from any context, but may silently > +fail under strong memory pressure. > + > +struct cn_msg * - message header(with attached data). > +u32 __groups - destination groups. > + If __groups is zero, then appropriate group > will > + be searched through all registered connector > users, > + and message will be delivered to the group > which was > + created for user with the same ID as in msg. > + If __groups is not zero, then message will be > delivered > + to the specified group. > +int gfp_mask - GFP mask. > + > +Note: When registering new callback user, connector core assigns netlink > group > +to the user which is equal to it's id.idx. > + > +/*****************************************/ > +Protocol description. > +/*****************************************/ > + > +Current offers transport layer with fixed header. > +Recommended protocol which uses such header is following: > + > +msg->seq and msg->ack are used to determine message genealogy. > +When someone sends message it puts there locally unique sequence > +and random acknowledge numbers. > +Sequence number may be copied into nlmsghdr->nlmsg_seq too. > + > +Sequence number is incremented with each message to be sent. > + > +If we expect reply to our message, then sequence number in received > +message MUST be the same as in original message, and acknowledge > +number MUST be the same + 1. > + > +If we receive message and it's sequence number is not equal to one > +we are expecting, then it is new message. > +If we receive message and it's sequence number is the same as one we > +are expecting, but it's acknowledge is not equal acknowledge number > +in original message + 1, then it is new message. > + > +Obviously, protocol header contains above id. > + > +connector allows event notification in the following form: > +kernel driver or userspace process can ask connector to notify it > +when selected id's will be turned on or off(registered or unregistered > +it's callback). It is done by sending special command to connector > +driver(it also registers itself with id={-1, -1}). > + > +As example of usage Documentation/connector now contains cn_test.c - > +testing module which uses connector to request notification > +and to send messages. > + > + > +/*****************************************/ > +CBUS. > +/*****************************************/ > + > +This message bus allows message passing between different agents > +using connector's infrastructure. > +It is extremely fast for insert operations so it can be used in performance > +critical pathes in any context [including hard IRQs] > +instead of direct connector's methods calls. > + > +CBUS uses per CPU variables and thus allows message reordering, > +caller must be prepared (and use CPU id in it's messages). > + > +Usage is very simple - just call cbus_insert(struct cn_msg *msg, int > gfp_mask); > +It can fail, so caller must check return value - zero on success > +and negative error code otherwise. > + > +Benchmark with modified fork connector and fork bomb on 2-way and 1-way > systems > +did not show any differences between vanilla 2.6.11 and CBUS. > + > +This feature is not implemented in vanilla kernel and requires > +some number of points to be included, so please contact > +me and/or [EMAIL PROTECTED] > + > +/*****************************************/ > +Reliability. > +/*****************************************/ > +Netlink itself is not reliable protocol, > +that means that messages can be lost > +due to memory pressure or process' receiving > +queue overflowed, so caller is warned > +must be prepared. That is why struct cn_msg > +[main connector's message header] contains > +u32 seq and u32 ack fields. > diff --git a/drivers/Kconfig b/drivers/Kconfig > --- a/drivers/Kconfig > +++ b/drivers/Kconfig > @@ -4,6 +4,8 @@ menu "Device Drivers" > > source "drivers/base/Kconfig" > > +source "drivers/connector/Kconfig" > + > source "drivers/mtd/Kconfig" > > source "drivers/parport/Kconfig" > diff --git a/drivers/Makefile b/drivers/Makefile > --- a/drivers/Makefile > +++ b/drivers/Makefile > @@ -17,6 +17,8 @@ obj-$(CONFIG_PNP) += pnp/ > # default. > obj-y += char/ > > +obj-$(CONFIG_CONNECTOR) += connector/ > + > # i810fb and intelfb depend on char/agp/ > obj-$(CONFIG_FB_I810) += video/i810/ > obj-$(CONFIG_FB_INTEL) += video/intelfb/ > diff --git a/drivers/connector/Kconfig b/drivers/connector/Kconfig > new file mode 100644 > --- /dev/null > +++ b/drivers/connector/Kconfig > @@ -0,0 +1,22 @@ > +menu "Connector - unified userspace <-> kernelspace linker" > + > +config CONNECTOR > + tristate "Connector - unified userspace <-> kernelspace linker" > + depends on NET > + ---help--- > + This is unified userspace <-> kernelspace connector working on top > + of the netlink socket protocol. > + > + Connector support can also be built as a module. If so, the module > + will be called cn.ko. > + > +config CBUS > + tristate "CBUS - extremely fast message bus in respect to INSERT > operations" > + depends on CONNECTOR > + ---help--- > + This bus allows to use connector in any context, improves cache > utilisation > + by using special sending thread and, as stated above, is extremely > fast > + for INSERT operations. One can find benchmarks on SMP and UP systems > in > + linux-kernel@ archive. > + > +endmenu > diff --git a/drivers/connector/Makefile b/drivers/connector/Makefile > new file mode 100644 > --- /dev/null > +++ b/drivers/connector/Makefile > @@ -0,0 +1,5 @@ > +obj-$(CONFIG_CONNECTOR) += cn.o > +obj-$(CONFIG_CONNECTOR_TEST) += cn_test.o > +obj-$(CONFIG_CBUS) += cbus.o > + > +cn-y += cn_queue.o connector.o > diff --git a/drivers/connector/cbus.c b/drivers/connector/cbus.c > new file mode 100644 > --- /dev/null > +++ b/drivers/connector/cbus.c > @@ -0,0 +1,274 @@ > +/* > + * cbus.c > + * > + * 2005 Copyright (c) Evgeniy Polyakov <[EMAIL PROTECTED]> > + * 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 > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/connector.h> > +#include <linux/list.h> > +#include <linux/moduleparam.h> > +#include <linux/kthread.h> > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Evgeniy Polyakov <[EMAIL PROTECTED]>"); > +MODULE_DESCRIPTION("Ultrafast message bus based on kernel connector."); > + > +static DEFINE_PER_CPU(struct cbus_event_container, cbus_event_list); > +static struct task_struct *cbus_thread; > +static int cbus_need_exit; > +static DECLARE_WAIT_QUEUE_HEAD(cbus_wait_queue); > +static int cbus_max_queue_len = 1024; > +module_param(cbus_max_queue_len, int, 0); > +MODULE_PARM_DESC(cbus_max_queue_len, "Maximum CBUS queue length, " > + "if it is overflowed events will be delivered using direct > connector's methods."); > + > +static char cbus_name[] = "cbus"; > + > +struct cbus_event_container { > + struct list_head event_list; > + spinlock_t event_lock; > + int qlen; > +}; > + > +struct cbus_event { > + struct list_head event_entry; > + u32 cpu; > + struct cn_msg msg; > +}; > + > +static inline struct cbus_event *__cbus_dequeue(struct cbus_event_container > *c) > +{ > + struct list_head *next = c->event_list.next; > + > + list_del(next); > + c->qlen--; > + > + if (c->qlen < 0) { > + printk(KERN_ERR "%s: qlen=%d after dequeue on CPU%u.\n", > + cbus_name, c->qlen, smp_processor_id()); > + c->qlen = 0; > + } > + > + return list_entry(next, struct cbus_event, event_entry); > +} > + > +static inline struct cbus_event *cbus_dequeue(struct cbus_event_container *c) > +{ > + struct cbus_event *event; > + unsigned long flags; > + > + if (list_empty(&c->event_list)) > + return NULL; > + > + spin_lock_irqsave(&c->event_lock, flags); > + event = __cbus_dequeue(c); > + spin_unlock_irqrestore(&c->event_lock, flags); > + > + return event; > +} > + > +static inline void __cbus_enqueue(struct cbus_event_container *c, > + struct cbus_event *event) > +{ > + list_add_tail(&event->event_entry, &c->event_list); > + c->qlen++; > +} > + > +static int cbus_enqueue(struct cbus_event_container *c, struct cn_msg *msg, > + int gfp_mask) > +{ > + int err, enq = 0; > + struct cbus_event *event; > + unsigned long flags; > + > + event = kmalloc(sizeof(*event) + msg->len, gfp_mask); > + if (!event) { > + err = -ENOMEM; > + goto err_out_exit; > + } > + > + memcpy(&event->msg, msg, sizeof(event->msg)); > + > + if (msg->len) > + memcpy(event + 1, msg->data, msg->len); > + > + spin_lock_irqsave(&c->event_lock, flags); > + if (c->qlen <= cbus_max_queue_len) { > + __cbus_enqueue(c, event); > + enq = 1; > + } > + spin_unlock_irqrestore(&c->event_lock, flags); > + > + if (!enq) { > + kfree(event); > + cn_netlink_send(msg, 0, gfp_mask); > + } > + //wake_up_interruptible(&cbus_wait_queue); > + > + return 0; > + > +err_out_exit: > + return err; > +} > + > +static int cbus_process(struct cbus_event_container *c, int all) > +{ > + struct cbus_event *event; > + int len, i, num; > + > + if (list_empty(&c->event_list)) > + return 0; > + > + if (all) > + len = c->qlen; > + else > + len = 1; > + > + num = 0; > + for (i = 0; i < len; ++i) { > + event = cbus_dequeue(c); > + if (!event) > + continue; > + > + cn_netlink_send(&event->msg, 0, GFP_KERNEL); > + num++; > + > + kfree(event); > + } > + > + return num; > +} > + > +static int cbus_init_event_container(struct cbus_event_container *c) > +{ > + INIT_LIST_HEAD(&c->event_list); > + spin_lock_init(&c->event_lock); > + c->qlen = 0; > + > + return 0; > +} > + > +static void cbus_fini_event_container(struct cbus_event_container *c) > +{ > + cbus_process(c, 1); > +} > + > +int cbus_insert(struct cn_msg *msg, int gfp_flags) > +{ > + struct cbus_event_container *c; > + int err; > + > + /* > + * If CBUS is being removed, > + * do not allow to add new events, > + * it still has a race, when > + * event may be added after > + * all queues are processed, > + * but we do not care if one > + * message in each queue will not > + * be delivered and CBUS is removed. > + */ > + if (cbus_need_exit) > + return -ENODEV; > + > + preempt_disable(); > + c = &__get_cpu_var(cbus_event_list); > + > + err = cbus_enqueue(c, msg, gfp_flags); > + > + preempt_enable(); > + > + return err; > +} > + > +static int cbus_event_thread(void *data) > +{ > + int i, non_empty = 0, empty = 0; > + struct cbus_event_container *c; > + > + set_user_nice(current, 19); > + > + while (!cbus_need_exit) { > + if (empty || non_empty == 0 || non_empty > 10) { > + wait_event_interruptible_timeout(cbus_wait_queue, 0, > + HZ / 100); > + non_empty = 0; > + empty = 0; > + } > + > + for_each_cpu(i) { > + c = &per_cpu(cbus_event_list, i); > + > + if (cbus_process(c, 0)) > + non_empty++; > + else > + empty = 1; > + } > + } > + > + for_each_cpu(i) { > + c = &per_cpu(cbus_event_list, i); > + cbus_fini_event_container(c); > + } > + > + return 0; > +} > + > +int __devinit cbus_init(void) > +{ > + int i, err = 0; > + struct cbus_event_container *c; > + > + for_each_cpu(i) { > + c = &per_cpu(cbus_event_list, i); > + cbus_init_event_container(c); > + } > + > + cbus_thread = kthread_run(cbus_event_thread, NULL, "%s", cbus_name); > + if (IS_ERR(cbus_thread)) { > + printk(KERN_ERR > + "%s: Failed to create cbus event thread: err=%ld.\n", > + cbus_name, PTR_ERR(cbus_thread)); > + err = PTR_ERR(cbus_thread); > + goto err_out_exit; > + } > + > +err_out_exit: > + return err; > +} > + > +void __devexit cbus_fini(void) > +{ > + int i; > + struct cbus_event_container *c; > + > + cbus_need_exit = 1; > + kthread_stop(cbus_thread); > + > + for_each_cpu(i) { > + c = &per_cpu(cbus_event_list, i); > + cbus_fini_event_container(c); > + } > +} > + > +module_init(cbus_init); > +module_exit(cbus_fini); > + > +EXPORT_SYMBOL_GPL(cbus_insert); > diff --git a/drivers/connector/cn_queue.c b/drivers/connector/cn_queue.c > new file mode 100644 > --- /dev/null > +++ b/drivers/connector/cn_queue.c > @@ -0,0 +1,196 @@ > +/* > + * cn_queue.c > + * > + * 2004-2005 Copyright (c) Evgeniy Polyakov <[EMAIL PROTECTED]> > + * 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 > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + */ > + > +#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> > + > +static void cn_queue_wrapper(void *data) > +{ > + struct cn_callback_entry *cbq = (struct cn_callback_entry *)data; > + > + cbq->cb->callback(cbq->cb->priv); > + > + cbq->destruct_data(cbq->ddata); > + > + cbq->ddata = NULL; > +} > + > +static struct cn_callback_entry *cn_queue_alloc_callback_entry(struct > cn_callback *cb) > +{ > + struct cn_callback_entry *cbq; > + > + cbq = kmalloc(sizeof(*cbq), GFP_KERNEL); > + if (!cbq) { > + printk(KERN_ERR "Failed to create new callback queue.\n"); > + return NULL; > + } > + > + memset(cbq, 0, sizeof(*cbq)); > + > + cbq->cb = cb; > + > + INIT_WORK(&cbq->work, &cn_queue_wrapper, cbq); > + > + return cbq; > +} > + > +static void cn_queue_free_callback(struct cn_callback_entry *cbq) > +{ > + cancel_delayed_work(&cbq->work); > + flush_workqueue(cbq->pdev->cn_queue); > + > + kfree(cbq); > +} > + > +int cn_cb_equal(struct cb_id *i1, struct cb_id *i2) > +{ > +#if 0 > + printk(KERN_INFO "%s: comparing %04x.%04x and %04x.%04x\n", > + __func__, > + i1->idx, i1->val, > + i2->idx, i2->val); > +#endif > + return ((i1->idx == i2->idx) && (i1->val == i2->val)); > +} > + > +int cn_queue_add_callback(struct cn_queue_dev *dev, struct cn_callback *cb) > +{ > + struct cn_callback_entry *cbq, *__cbq; > + int found = 0; > + > + cbq = cn_queue_alloc_callback_entry(cb); > + if (!cbq) > + return -ENOMEM; > + > + atomic_inc(&dev->refcnt); > + cbq->pdev = dev; > + > + spin_lock_bh(&dev->queue_lock); > + list_for_each_entry(__cbq, &dev->queue_list, callback_entry) { > + if (cn_cb_equal(&__cbq->cb->id, &cb->id)) { > + found = 1; > + break; > + } > + } > + if (!found) { > + list_add_tail(&cbq->callback_entry, &dev->queue_list); > + } > + spin_unlock_bh(&dev->queue_lock); > + > + if (found) { > + atomic_dec(&dev->refcnt); > + cn_queue_free_callback(cbq); > + return -EINVAL; > + } > + > + cbq->nls = dev->nls; > + cbq->seq = 0; > + cbq->group = cbq->cb->id.idx; > + > + return 0; > +} > + > +void cn_queue_del_callback(struct cn_queue_dev *dev, struct cb_id *id) > +{ > + struct cn_callback_entry *cbq = NULL, *n; > + int found = 0; > + > + spin_lock_bh(&dev->queue_lock); > + list_for_each_entry_safe(cbq, n, &dev->queue_list, callback_entry) { > + if (cn_cb_equal(&cbq->cb->id, id)) { > + list_del(&cbq->callback_entry); > + found = 1; > + break; > + } > + } > + spin_unlock_bh(&dev->queue_lock); > + > + if (found) { > + cn_queue_free_callback(cbq); > + atomic_dec_and_test(&dev->refcnt); > + } > +} > + > +struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *nls) > +{ > + struct cn_queue_dev *dev; > + > + dev = kmalloc(sizeof(*dev), GFP_KERNEL); > + if (!dev) { > + printk(KERN_ERR "%s: Failed to allocte new struct > cn_queue_dev.\n", > + name); > + return NULL; > + } > + > + memset(dev, 0, sizeof(*dev)); > + > + 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; > + dev->netlink_groups = 0; > + > + dev->cn_queue = create_workqueue(dev->name); > + if (!dev->cn_queue) { > + printk(KERN_ERR "Failed to create %s queue.\n", dev->name); > + kfree(dev); > + return NULL; > + } > + > + return dev; > +} > + > +void cn_queue_free_dev(struct cn_queue_dev *dev) > +{ > + struct cn_callback_entry *cbq, *n; > + > + 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) { > + list_del(&cbq->callback_entry); > + } > + spin_unlock_bh(&dev->queue_lock); > + > + while (atomic_read(&dev->refcnt)) { > + printk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n", > + dev->name, atomic_read(&dev->refcnt)); > + > + msleep(1000); > + } > + > + memset(dev, 0, sizeof(*dev)); > + kfree(dev); > + dev = NULL; > +} > diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c > new file mode 100644 > --- /dev/null > +++ b/drivers/connector/connector.c > @@ -0,0 +1,545 @@ > +/* > + * connector.c > + * > + * 2004-2005 Copyright (c) Evgeniy Polyakov <[EMAIL PROTECTED]> > + * 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 > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > + > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/list.h> > +#include <linux/skbuff.h> > +#include <linux/netlink.h> > +#include <linux/moduleparam.h> > +#include <linux/connector.h> > + > +#include <net/sock.h> > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Evgeniy Polyakov <[EMAIL PROTECTED]>"); > +MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector."); > + > +static int unit = NETLINK_CONNECTOR; > +static u32 cn_idx = CN_IDX_CONNECTOR; > +static u32 cn_val = CN_VAL_CONNECTOR; > + > +module_param(cn_idx, uint, 0); > +module_param(cn_val, uint, 0); > +MODULE_PARM_DESC(cn_idx, "Connector's main device idx."); > +MODULE_PARM_DESC(cn_val, "Connector's main device val."); > + > +static DECLARE_MUTEX(notify_lock); > +static LIST_HEAD(notify_list); > + > +static struct cn_dev cdev; > + > +int cn_already_initialized = 0; > + > +/* > + * msg->seq and msg->ack are used to determine message genealogy. > + * When someone sends message it puts there locally unique sequence > + * and random acknowledge numbers. > + * Sequence number may be copied into nlmsghdr->nlmsg_seq too. > + * > + * Sequence number is incremented with each message to be sent. > + * > + * If we expect reply to our message, > + * then sequence number in received message MUST be the same as in original > message, > + * and acknowledge number MUST be the same + 1. > + * > + * If we receive message and it's sequence number is not equal to one we are > expecting, > + * then it is new message. > + * If we receive message and it's sequence number is the same as one we are > expecting, > + * but it's acknowledge is not equal acknowledge number in original message > + 1, > + * then it is new message. > + * > + */ > +int cn_netlink_send(struct cn_msg *msg, u32 __groups, int gfp_mask) > +{ > + struct cn_callback_entry *__cbq; > + unsigned int size; > + struct sk_buff *skb; > + struct nlmsghdr *nlh; > + struct cn_msg *data; > + struct cn_dev *dev = &cdev; > + u32 groups = 0; > + int found = 0; > + > + if (!__groups) { > + spin_lock_bh(&dev->cbdev->queue_lock); > + list_for_each_entry(__cbq, &dev->cbdev->queue_list, > + callback_entry) { > + if (cn_cb_equal(&__cbq->cb->id, &msg->id)) { > + found = 1; > + groups = __cbq->group; > + } > + } > + spin_unlock_bh(&dev->cbdev->queue_lock); > + > + if (!found) { > + printk(KERN_ERR > + "Failed to find multicast netlink group for " > + "callback[0x%x.0x%x]. seq=%u\n", > + msg->id.idx, msg->id.val, msg->seq); > + return -ENODEV; > + } > + } else > + groups = __groups; > + > + size = NLMSG_SPACE(sizeof(*msg) + msg->len); > + > + skb = alloc_skb(size, gfp_mask); > + if (!skb) { > + printk(KERN_ERR "Failed to allocate new skb with size=%u.\n", > + size); > + return -ENOMEM; > + } > + > + nlh = NLMSG_PUT(skb, 0, msg->seq, NLMSG_DONE, size - sizeof(*nlh)); > + > + data = (struct cn_msg *)NLMSG_DATA(nlh); > + > + memcpy(data, msg, sizeof(*data) + msg->len); > +#if 0 > + printk("%s: len=%u, seq=%u, ack=%u, group=%u.\n", > + __func__, msg->len, msg->seq, msg->ack, groups); > +#endif > + > + NETLINK_CB(skb).dst_groups = groups; > + > + netlink_broadcast(dev->nls, skb, 0, groups, gfp_mask); > + > + return 0; > + > +nlmsg_failure: > + kfree_skb(skb); > + return -EINVAL; > +} > + > +/* > + * Callback helper - queues work and setup destructor for given data. > + */ > +static int cn_call_callback(struct cn_msg *msg, > + void (*destruct_data) (void *), > + void *data) > +{ > + struct cn_callback_entry *__cbq; > + struct cn_dev *dev = &cdev; > + int found = 0; > + > + spin_lock_bh(&dev->cbdev->queue_lock); > + list_for_each_entry(__cbq, &dev->cbdev->queue_list, callback_entry) { > + if (cn_cb_equal(&__cbq->cb->id, &msg->id)) { > + /* > + * Let's scream if there is some magic > + * and data will arrive asynchronously here > + * [i.e. netlink messages will be queued]. > + * After the first warning I will fix it quickly, > + * but now I think it is impossible. --zbr (2004_04_27). > + */ > + if (likely(!test_bit(0, &__cbq->work.pending) && > __cbq->ddata == NULL)) { > + __cbq->cb->priv = msg; > + > + __cbq->ddata = data; > + __cbq->destruct_data = destruct_data; > + > + if (queue_work(dev->cbdev->cn_queue, > &__cbq->work)) > + found = 1; > + } else { > + printk("%s: cbq->data=%p, > work->pending=%08lx.\n", __func__, __cbq->ddata, __cbq->work.pending); > + WARN_ON(1); > + } > + break; > + } > + } > + spin_unlock_bh(&dev->cbdev->queue_lock); > + > + return (found) ? 0 : -ENODEV; > +} > + > +/* > + * Skb receive helper - checks skb and msg size > + * and calls callback helper. > + */ > +static int __cn_rx_skb(struct sk_buff *skb, struct nlmsghdr *nlh) > +{ > + u32 pid, uid, seq, group; > + struct cn_msg *msg; > + > + pid = NETLINK_CREDS(skb)->pid; > + uid = NETLINK_CREDS(skb)->uid; > + seq = nlh->nlmsg_seq; > + group = NETLINK_CB((skb)).groups; > + msg = (struct cn_msg *)NLMSG_DATA(nlh); > + > +#if 0 > + printk(KERN_INFO "pid=%u, uid=%u, seq=%u, group=%u.\n", > + pid, uid, seq, group); > +#endif > + return cn_call_callback(msg, (void (*)(void *))kfree_skb, skb); > +} > + > +/* > + * Main netlink receiving function - > + * it checks skb and netlink header sizes > + * and calls skb receive helper with shared skb. > + */ > +static void cn_rx_skb(struct sk_buff *__skb) > +{ > + struct nlmsghdr *nlh; > + u32 len; > + int err; > + struct sk_buff *skb; > + > + skb = skb_get(__skb); > + if (!skb) { > + printk(KERN_ERR "Failed to reference an skb.\n"); > + kfree_skb(__skb); > + return; > + } > +#if 0 > + printk(KERN_INFO > + "skb: len=%u, data_len=%u, truesize=%u, proto=%u, cloned=%d, > shared=%d.\n", > + skb->len, skb->data_len, skb->truesize, skb->protocol, > + skb_cloned(skb), skb_shared(skb)); > +#endif > + if (skb->len >= NLMSG_SPACE(0)) { > + nlh = (struct nlmsghdr *)skb->data; > + > + if (nlh->nlmsg_len < sizeof(struct cn_msg) || > + skb->len < nlh->nlmsg_len || > + nlh->nlmsg_len > CONNECTOR_MAX_MSG_SIZE) { > +#if 1 > + printk(KERN_INFO "nlmsg_len=%u, sizeof(*nlh)=%zu\n", > + nlh->nlmsg_len, sizeof(*nlh)); > +#endif > + kfree_skb(skb); > + goto out; > + } > + > + len = NLMSG_ALIGN(nlh->nlmsg_len); > + if (len > skb->len) > + len = skb->len; > + > + err = __cn_rx_skb(skb, nlh); > + if (err < 0) > + kfree_skb(skb); > + } > + > + out: > + kfree_skb(__skb); > +} > + > +/* > + * Netlink socket input callback - dequeues skb and > + * calls main netlink receiving function. > + */ > +static void cn_input(struct sock *sk, int len) > +{ > + struct sk_buff *skb; > + > + while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) > + cn_rx_skb(skb); > +} > + > +/* > + * Notification routing. > + * Gets id and checks if > + * there are notification request for it's idx and val. > + * If there are such requests notify it's listeners > + * with given notify event. > + */ > +static void cn_notify(struct cb_id *id, u32 notify_event) > +{ > + struct cn_ctl_entry *ent; > + > + down(¬ify_lock); > + list_for_each_entry(ent, ¬ify_list, notify_entry) { > + int i; > + struct cn_notify_req *req; > + struct cn_ctl_msg *ctl = ent->msg; > + int idx_found, val_found; > + > + idx_found = val_found = 0; > + > + req = (struct cn_notify_req *)ctl->data; > + for (i = 0; i < ctl->idx_notify_num; ++i, ++req) { > + if (id->idx >= req->first && > + id->idx < req->first + req->range) { > + idx_found = 1; > + break; > + } > + } > + > + for (i = 0; i < ctl->val_notify_num; ++i, ++req) { > + if (id->val >= req->first && > + id->val < req->first + req->range) { > + val_found = 1; > + break; > + } > + } > + > + if (idx_found && val_found) { > + struct cn_msg m; > + > + printk(KERN_INFO > + "Notifying group %x with event %u about > %x.%x.\n", > + ctl->group, notify_event, id->idx, id->val); > + > + memset(&m, 0, sizeof(m)); > + m.ack = notify_event; > + > + memcpy(&m.id, id, sizeof(m.id)); > + cn_netlink_send(&m, ctl->group, GFP_KERNEL); > + } > + } > + up(¬ify_lock); > +} > + > +/* > + * Callback add routing - adds callback > + * with given ID and name. > + * If there is registered callback with the same > + * ID it will not be added. > + * > + * May sleep. > + */ > +int cn_add_callback(struct cb_id *id, char *name, void (*callback) (void *)) > +{ > + int err; > + struct cn_dev *dev = &cdev; > + struct cn_callback *cb; > + > + cb = kmalloc(sizeof(*cb), GFP_KERNEL); > + if (!cb) { > + printk(KERN_INFO > + "%s: Failed to allocate new struct cn_callback.\n", > + dev->cbdev->name); > + return -ENOMEM; > + } > + > + memset(cb, 0, sizeof(*cb)); > + > + scnprintf(cb->name, sizeof(cb->name), "%s", name); > + > + memcpy(&cb->id, id, sizeof(cb->id)); > + cb->callback = callback; > + > + err = cn_queue_add_callback(dev->cbdev, cb); > + if (err) { > + kfree(cb); > + return err; > + } > + > + cn_notify(id, 0); > + > + return 0; > +} > + > +/* > + * Callback remove routing - removes callback > + * with given ID. > + * If there is no registered callback with given > + * ID nothing happens. > + * > + * May sleep while waiting for reference counter to become zero. > + */ > +void cn_del_callback(struct cb_id *id) > +{ > + struct cn_dev *dev = &cdev; > + > + cn_queue_del_callback(dev->cbdev, id); > + cn_notify(id, 1); > +} > + > +/* > + * Checks two connector's control messages to be the same. > + * Returns 1 if they are the same or firts one is broken. > + */ > +static int cn_ctl_msg_equals(struct cn_ctl_msg *m1, struct cn_ctl_msg *m2) > +{ > + int i; > + struct cn_notify_req *req1, *req2; > + > + if (m1->idx_notify_num != m2->idx_notify_num) > + return 0; > + > + if (m1->val_notify_num != m2->val_notify_num) > + return 0; > + > + if (m1->len != m2->len) > + return 0; > + > + if ((m1->idx_notify_num + m1->val_notify_num) * sizeof(*req1) != > m1->len) { > + printk(KERN_ERR > + "Notify entry[idx_num=%x, val_num=%x, len=%u] contains > garbage. Removing.\n", > + m1->idx_notify_num, m1->val_notify_num, m1->len); > + return 1; > + } > + > + req1 = (struct cn_notify_req *)m1->data; > + req2 = (struct cn_notify_req *)m2->data; > + > + for (i = 0; i < m1->idx_notify_num; ++i) { > + if (memcmp(req1, req2, sizeof(*req1))) > + return 0; > + > + req1++; > + req2++; > + } > + > + for (i = 0; i < m1->val_notify_num; ++i) { > + if (memcmp(req1, req2, sizeof(*req1))) > + return 0; > + > + req1++; > + req2++; > + } > + > + return 1; > +} > + > +/* > + * Main connector device's callback. > + * Is used for notification requests processing. > + */ > +static void cn_callback(void *data) > +{ > + struct cn_msg *msg = (struct cn_msg *)data; > + struct cn_ctl_msg *ctl; > + struct cn_ctl_entry *ent; > + u32 size; > + > + if (msg->len < sizeof(*ctl)) { > + printk(KERN_ERR > + "Wrong connector request size %u, must be >= %zu.\n", > + msg->len, sizeof(*ctl)); > + return; > + } > + > + ctl = (struct cn_ctl_msg *)msg->data; > + > + size = sizeof(*ctl) + (ctl->idx_notify_num + > + ctl->val_notify_num) * sizeof(struct cn_notify_req); > + > + if (msg->len != size) { > + printk(KERN_ERR > + "Wrong connector request size %u, must be == %u.\n", > + msg->len, size); > + return; > + } > + > + if (ctl->len + sizeof(*ctl) != msg->len) { > + printk(KERN_ERR > + "Wrong message: msg->len=%u must be equal to > inner_len=%u [+%zu].\n", > + msg->len, ctl->len, sizeof(*ctl)); > + return; > + } > + > + /* > + * Remove notification. > + */ > + if (ctl->group == 0) { > + struct cn_ctl_entry *n; > + > + down(¬ify_lock); > + list_for_each_entry_safe(ent, n, ¬ify_list, notify_entry) { > + if (cn_ctl_msg_equals(ent->msg, ctl)) { > + list_del(&ent->notify_entry); > + kfree(ent); > + } > + } > + up(¬ify_lock); > + > + return; > + } > + > + size += sizeof(*ent); > + > + ent = kmalloc(size, GFP_KERNEL); > + if (!ent) { > + printk(KERN_ERR > + "Failed to allocate %d bytes for new notify entry.\n", > + size); > + return; > + } > + > + memset(ent, 0, size); > + > + ent->msg = (struct cn_ctl_msg *)(ent + 1); > + > + memcpy(ent->msg, ctl, size - sizeof(*ent)); > + > + down(¬ify_lock); > + list_add(&ent->notify_entry, ¬ify_list); > + up(¬ify_lock); > +} > + > +static int cn_init(void) > +{ > + struct cn_dev *dev = &cdev; > + int err; > + > + dev->input = cn_input; > + dev->id.idx = cn_idx; > + dev->id.val = cn_val; > + > + dev->nls = netlink_kernel_create(unit, CN_NETLINK_USERS + 0xf, > dev->input, THIS_MODULE); > + if (!dev->nls) { > + printk(KERN_ERR "Failed to create new netlink socket(%u).\n", > + unit); > + return -EIO; > + } > + > + dev->cbdev = cn_queue_alloc_dev("cqueue", dev->nls); > + if (!dev->cbdev) { > + if (dev->nls->sk_socket) > + sock_release(dev->nls->sk_socket); > + return -EINVAL; > + } > + > + err = cn_add_callback(&dev->id, "connector", &cn_callback); > + if (err) { > + cn_queue_free_dev(dev->cbdev); > + if (dev->nls->sk_socket) > + sock_release(dev->nls->sk_socket); > + return -EINVAL; > + } > + > + cn_already_initialized = 1; > + > + return 0; > +} > + > +static void cn_fini(void) > +{ > + struct cn_dev *dev = &cdev; > + > + cn_already_initialized = 0; > + > + cn_del_callback(&dev->id); > + cn_queue_free_dev(dev->cbdev); > + if (dev->nls->sk_socket) > + sock_release(dev->nls->sk_socket); > +} > + > +module_init(cn_init); > +module_exit(cn_fini); > + > +EXPORT_SYMBOL_GPL(cn_add_callback); > +EXPORT_SYMBOL_GPL(cn_del_callback); > +EXPORT_SYMBOL_GPL(cn_netlink_send); > diff --git a/include/linux/connector.h b/include/linux/connector.h > new file mode 100644 > --- /dev/null > +++ b/include/linux/connector.h > @@ -0,0 +1,160 @@ > +/* > + * connector.h > + * > + * 2004-2005 Copyright (c) Evgeniy Polyakov <[EMAIL PROTECTED]> > + * 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 > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > + > +#ifndef __CONNECTOR_H > +#define __CONNECTOR_H > + > +#include <asm/types.h> > + > +#define CN_IDX_CONNECTOR 0xffffffff > +#define CN_VAL_CONNECTOR 0xffffffff > + > +#define CN_NETLINK_USERS 1 > + > +/* > + * Maximum connector's message size. > + */ > +#define CONNECTOR_MAX_MSG_SIZE 1024 > + > +/* > + * idx and val are unique identifiers which > + * are used for message routing and > + * must be registered in connector.h for in-kernel usage. > + */ > + > +struct cb_id { > + __u32 idx; > + __u32 val; > +}; > + > +struct cn_msg { > + struct cb_id id; > + > + __u32 seq; > + __u32 ack; > + > + __u16 len; /* Length of the following data */ > + __u16 flags; > + __u8 data[0]; > +}; > + > +/* > + * Notify structure - requests notification about > + * registering/unregistering idx/val in range [first, first+range]. > + */ > +struct cn_notify_req { > + __u32 first; > + __u32 range; > +}; > + > +/* > + * Main notification control message > + * *_notify_num - number of appropriate cn_notify_req structures after > + * this struct. > + * group - notification receiver's idx. > + * len - total length of the attached data. > + */ > +struct cn_ctl_msg { > + __u32 idx_notify_num; > + __u32 val_notify_num; > + __u32 group; > + __u32 len; > + __u8 data[0]; > +}; > + > +#ifdef __KERNEL__ > + > +#include <asm/atomic.h> > + > +#include <linux/list.h> > +#include <linux/workqueue.h> > + > +#include <net/sock.h> > + > +#define CN_CBQ_NAMELEN 32 > + > +struct cn_queue_dev { > + atomic_t refcnt; > + unsigned char name[CN_CBQ_NAMELEN]; > + > + struct workqueue_struct *cn_queue; > + > + struct list_head queue_list; > + spinlock_t queue_lock; > + > + int netlink_groups; > + struct sock *nls; > +}; > + > +struct cn_callback { > + unsigned char name[CN_CBQ_NAMELEN]; > + > + struct cb_id id; > + void (*callback) (void *); > + void *priv; > +}; > + > +struct cn_callback_entry { > + struct list_head callback_entry; > + struct cn_callback *cb; > + struct work_struct work; > + struct cn_queue_dev *pdev; > + > + void (*destruct_data) (void *); > + void *ddata; > + > + int seq, group; > + struct sock *nls; > +}; > + > +struct cn_ctl_entry { > + struct list_head notify_entry; > + struct cn_ctl_msg *msg; > +}; > + > +struct cn_dev { > + struct cb_id id; > + > + u32 seq, groups; > + struct sock *nls; > + void (*input) (struct sock * sk, int len); > + > + struct cn_queue_dev *cbdev; > +}; > + > +int cn_add_callback(struct cb_id *, char *, void (*callback) (void *)); > +void cn_del_callback(struct cb_id *); > +int cn_netlink_send(struct cn_msg *, u32, int); > + > +int cn_queue_add_callback(struct cn_queue_dev *dev, struct cn_callback *cb); > +void cn_queue_del_callback(struct cn_queue_dev *dev, struct cb_id *id); > + > +struct cn_queue_dev *cn_queue_alloc_dev(char *name, struct sock *); > +void cn_queue_free_dev(struct cn_queue_dev *dev); > + > +int cn_cb_equal(struct cb_id *, struct cb_id *); > + > +extern int cn_already_initialized; > + > +extern int cbus_insert(struct cn_msg *, int); > + > +#endif /* __KERNEL__ */ > +#endif /* __CONNECTOR_H */ > > > -- > Evgeniy Polyakov -- Evgeniy Polyakov - To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html