2005-09-12 06:15:07 +04:00
/*
* connector . c
*
* 2004 - 2005 Copyright ( c ) Evgeniy Polyakov < johnpol @ 2 ka . 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
* ( 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>
2006-03-23 14:00:21 +03:00
# include <linux/mutex.h>
2008-06-28 07:03:24 +04:00
# include <linux/proc_fs.h>
# include <linux/spinlock.h>
2005-09-12 06:15:07 +04:00
# include <net/sock.h>
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Evgeniy Polyakov <johnpol@2ka.mipt.ru> " ) ;
MODULE_DESCRIPTION ( " Generic userspace <-> kernelspace 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. " ) ;
2006-03-23 14:00:21 +03:00
static DEFINE_MUTEX ( notify_lock ) ;
2005-09-12 06:15:07 +04:00
static LIST_HEAD ( notify_list ) ;
static struct cn_dev cdev ;
2008-02-27 05:25:53 +03:00
static int cn_already_initialized ;
2005-09-12 06:15:07 +04:00
/*
* 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 the sequence number in
* received message MUST be the same as in original message , and
* acknowledge number MUST be the same + 1.
*
* If we receive a message and its sequence number is not equal to the
* one we are expecting then it is a new message .
*
* If we receive a message and its sequence number is the same as one
* we are expecting but it ' s acknowledgement number is not equal to
* the acknowledgement number in the original message + 1 , then it is
* a new message .
*
*/
2005-10-07 10:46:04 +04:00
int cn_netlink_send ( struct cn_msg * msg , u32 __group , gfp_t gfp_mask )
2005-09-12 06:15:07 +04:00
{
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 group = 0 ;
int found = 0 ;
if ( ! __group ) {
spin_lock_bh ( & dev - > cbdev - > queue_lock ) ;
list_for_each_entry ( __cbq , & dev - > cbdev - > queue_list ,
callback_entry ) {
2005-09-27 02:06:50 +04:00
if ( cn_cb_equal ( & __cbq - > id . id , & msg - > id ) ) {
2005-09-12 06:15:07 +04:00
found = 1 ;
group = __cbq - > group ;
2008-01-04 12:54:38 +03:00
break ;
2005-09-12 06:15:07 +04:00
}
}
spin_unlock_bh ( & dev - > cbdev - > queue_lock ) ;
if ( ! found )
return - ENODEV ;
} else {
group = __group ;
}
2006-03-21 09:21:40 +03:00
if ( ! netlink_has_listeners ( dev - > nls , group ) )
return - ESRCH ;
2005-09-12 06:15:07 +04:00
size = NLMSG_SPACE ( sizeof ( * msg ) + msg - > len ) ;
skb = alloc_skb ( size , gfp_mask ) ;
if ( ! skb )
return - ENOMEM ;
nlh = NLMSG_PUT ( skb , 0 , msg - > seq , NLMSG_DONE , size - sizeof ( * nlh ) ) ;
data = NLMSG_DATA ( nlh ) ;
memcpy ( data , msg , sizeof ( * data ) + msg - > len ) ;
NETLINK_CB ( skb ) . dst_group = group ;
2006-03-21 09:21:40 +03:00
return netlink_broadcast ( dev - > nls , skb , 0 , group , gfp_mask ) ;
2005-09-12 06:15:07 +04:00
nlmsg_failure :
kfree_skb ( skb ) ;
return - EINVAL ;
}
2006-06-23 13:05:46 +04:00
EXPORT_SYMBOL_GPL ( cn_netlink_send ) ;
2005-09-12 06:15:07 +04:00
/*
* 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 )
{
2007-03-07 23:55:39 +03:00
struct cn_callback_entry * __cbq , * __new_cbq ;
2005-09-12 06:15:07 +04:00
struct cn_dev * dev = & cdev ;
2005-09-27 02:06:50 +04:00
int err = - ENODEV ;
2005-09-12 06:15:07 +04:00
spin_lock_bh ( & dev - > cbdev - > queue_lock ) ;
list_for_each_entry ( __cbq , & dev - > cbdev - > queue_list , callback_entry ) {
2005-09-27 02:06:50 +04:00
if ( cn_cb_equal ( & __cbq - > id . id , & msg - > id ) ) {
2006-12-18 12:53:58 +03:00
if ( likely ( ! work_pending ( & __cbq - > work ) & &
2005-09-27 02:06:50 +04:00
__cbq - > data . ddata = = NULL ) ) {
__cbq - > data . callback_priv = msg ;
2005-09-12 06:15:07 +04:00
2005-09-27 02:06:50 +04:00
__cbq - > data . ddata = data ;
__cbq - > data . destruct_data = destruct_data ;
2005-09-12 06:15:07 +04:00
2006-12-18 12:53:58 +03:00
if ( queue_work ( dev - > cbdev - > cn_queue ,
& __cbq - > work ) )
2005-09-27 02:06:50 +04:00
err = 0 ;
2008-01-04 12:52:02 +03:00
else
err = - EINVAL ;
2005-09-12 06:15:07 +04:00
} else {
2005-09-27 02:06:50 +04:00
struct cn_callback_data * d ;
2007-03-07 23:55:39 +03:00
err = - ENOMEM ;
__new_cbq = kzalloc ( sizeof ( struct cn_callback_entry ) , GFP_ATOMIC ) ;
if ( __new_cbq ) {
d = & __new_cbq - > data ;
2005-09-27 02:06:50 +04:00
d - > callback_priv = msg ;
d - > callback = __cbq - > data . callback ;
d - > ddata = data ;
d - > destruct_data = destruct_data ;
2007-03-07 23:55:39 +03:00
d - > free = __new_cbq ;
2005-09-27 02:06:50 +04:00
2007-03-07 23:55:39 +03:00
INIT_WORK ( & __new_cbq - > work ,
2006-12-18 12:53:58 +03:00
& cn_queue_wrapper ) ;
2007-03-07 23:55:39 +03:00
2006-12-18 12:53:58 +03:00
if ( queue_work ( dev - > cbdev - > cn_queue ,
2007-03-07 23:55:39 +03:00
& __new_cbq - > work ) )
2005-09-27 02:06:50 +04:00
err = 0 ;
else {
2007-03-07 23:55:39 +03:00
kfree ( __new_cbq ) ;
2005-09-27 02:06:50 +04:00
err = - EINVAL ;
}
2007-03-07 23:55:39 +03:00
}
2005-09-12 06:15:07 +04:00
}
break ;
}
}
spin_unlock_bh ( & dev - > cbdev - > queue_lock ) ;
2005-09-27 02:06:50 +04:00
return err ;
2005-09-12 06:15:07 +04:00
}
/*
* Main netlink receiving function .
*
2008-01-04 12:55:01 +03:00
* It checks skb , netlink header and msg sizes , and calls callback helper .
2005-09-12 06:15:07 +04:00
*/
static void cn_rx_skb ( struct sk_buff * __skb )
{
2008-01-04 12:55:01 +03:00
struct cn_msg * msg ;
2005-09-12 06:15:07 +04:00
struct nlmsghdr * nlh ;
int err ;
struct sk_buff * skb ;
skb = skb_get ( __skb ) ;
if ( skb - > len > = NLMSG_SPACE ( 0 ) ) {
2007-04-26 06:08:35 +04:00
nlh = nlmsg_hdr ( skb ) ;
2005-09-12 06:15:07 +04:00
if ( nlh - > nlmsg_len < sizeof ( struct cn_msg ) | |
skb - > len < nlh - > nlmsg_len | |
nlh - > nlmsg_len > CONNECTOR_MAX_MSG_SIZE ) {
kfree_skb ( skb ) ;
2007-10-31 06:41:49 +03:00
return ;
2005-09-12 06:15:07 +04:00
}
2008-01-04 12:55:01 +03:00
msg = NLMSG_DATA ( nlh ) ;
err = cn_call_callback ( msg , ( void ( * ) ( void * ) ) kfree_skb , skb ) ;
2005-09-12 06:15:07 +04:00
if ( err < 0 )
kfree_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 the listeners with the
* given notify event .
*
*/
static void cn_notify ( struct cb_id * id , u32 notify_event )
{
struct cn_ctl_entry * ent ;
2006-03-23 14:00:21 +03:00
mutex_lock ( & notify_lock ) ;
2005-09-12 06:15:07 +04:00
list_for_each_entry ( ent , & notify_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 = { . ack = notify_event , } ;
memcpy ( & m . id , id , sizeof ( m . id ) ) ;
cn_netlink_send ( & m , ctl - > group , GFP_KERNEL ) ;
}
}
2006-03-23 14:00:21 +03:00
mutex_unlock ( & notify_lock ) ;
2005-09-12 06:15:07 +04:00
}
/*
* 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 ;
2006-06-20 10:42:53 +04:00
if ( ! cn_already_initialized )
return - EAGAIN ;
2005-09-27 02:06:50 +04:00
err = cn_queue_add_callback ( dev - > cbdev , name , id , callback ) ;
if ( err )
2005-09-12 06:15:07 +04:00
return err ;
cn_notify ( id , 0 ) ;
return 0 ;
}
2006-06-23 13:05:46 +04:00
EXPORT_SYMBOL_GPL ( cn_add_callback ) ;
2005-09-12 06:15:07 +04:00
/*
* 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 ) ;
}
2006-06-23 13:05:46 +04:00
EXPORT_SYMBOL_GPL ( cn_del_callback ) ;
2005-09-12 06:15:07 +04:00
/*
* Checks two connector ' s control messages to be the same .
* Returns 1 if they are the same or if the first one is corrupted .
*/
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 )
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 ( req1 - > first ! = req2 - > first | | req1 - > range ! = req2 - > range )
return 0 ;
req1 + + ;
req2 + + ;
}
for ( i = 0 ; i < m1 - > val_notify_num ; + + i ) {
if ( req1 - > first ! = req2 - > first | | req1 - > range ! = req2 - > range )
return 0 ;
req1 + + ;
req2 + + ;
}
return 1 ;
}
/*
* Main connector device ' s callback .
*
* Used for notification of a request ' s processing .
*/
static void cn_callback ( void * data )
{
struct cn_msg * msg = data ;
struct cn_ctl_msg * ctl ;
struct cn_ctl_entry * ent ;
u32 size ;
if ( 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 )
return ;
if ( ctl - > len + sizeof ( * ctl ) ! = msg - > len )
return ;
/*
* Remove notification .
*/
if ( ctl - > group = = 0 ) {
struct cn_ctl_entry * n ;
2006-03-23 14:00:21 +03:00
mutex_lock ( & notify_lock ) ;
2005-09-12 06:15:07 +04:00
list_for_each_entry_safe ( ent , n , & notify_list , notify_entry ) {
if ( cn_ctl_msg_equals ( ent - > msg , ctl ) ) {
list_del ( & ent - > notify_entry ) ;
kfree ( ent ) ;
}
}
2006-03-23 14:00:21 +03:00
mutex_unlock ( & notify_lock ) ;
2005-09-12 06:15:07 +04:00
return ;
}
size + = sizeof ( * ent ) ;
ent = kzalloc ( size , GFP_KERNEL ) ;
if ( ! ent )
return ;
ent - > msg = ( struct cn_ctl_msg * ) ( ent + 1 ) ;
memcpy ( ent - > msg , ctl , size - sizeof ( * ent ) ) ;
2006-03-23 14:00:21 +03:00
mutex_lock ( & notify_lock ) ;
2005-09-12 06:15:07 +04:00
list_add ( & ent - > notify_entry , & notify_list ) ;
2006-03-23 14:00:21 +03:00
mutex_unlock ( & notify_lock ) ;
2005-09-12 06:15:07 +04:00
}
2008-06-28 07:03:24 +04:00
static int cn_proc_show ( struct seq_file * m , void * v )
{
struct cn_queue_dev * dev = cdev . cbdev ;
struct cn_callback_entry * cbq ;
seq_printf ( m , " Name ID \n " ) ;
spin_lock_bh ( & dev - > queue_lock ) ;
list_for_each_entry ( cbq , & dev - > queue_list , callback_entry ) {
seq_printf ( m , " %-15s %u:%u \n " ,
cbq - > id . name ,
cbq - > id . id . idx ,
cbq - > id . id . val ) ;
}
spin_unlock_bh ( & dev - > queue_lock ) ;
return 0 ;
}
static int cn_proc_open ( struct inode * inode , struct file * file )
{
return single_open ( file , cn_proc_show , NULL ) ;
}
static const struct file_operations cn_file_ops = {
. owner = THIS_MODULE ,
. open = cn_proc_open ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release
} ;
2006-06-20 10:42:53 +04:00
static int __devinit cn_init ( void )
2005-09-12 06:15:07 +04:00
{
struct cn_dev * dev = & cdev ;
int err ;
2007-10-11 08:15:29 +04:00
dev - > input = cn_rx_skb ;
2005-09-12 06:15:07 +04:00
dev - > id . idx = cn_idx ;
dev - > id . val = cn_val ;
2007-09-12 15:05:38 +04:00
dev - > nls = netlink_kernel_create ( & init_net , NETLINK_CONNECTOR ,
2005-09-12 06:15:07 +04:00
CN_NETLINK_USERS + 0xf ,
2007-04-21 01:14:21 +04:00
dev - > input , NULL , THIS_MODULE ) ;
2005-09-12 06:15:07 +04:00
if ( ! dev - > nls )
return - EIO ;
dev - > cbdev = cn_queue_alloc_dev ( " cqueue " , dev - > nls ) ;
if ( ! dev - > cbdev ) {
2008-01-29 01:41:19 +03:00
netlink_kernel_release ( dev - > nls ) ;
2005-09-12 06:15:07 +04:00
return - EINVAL ;
}
2006-06-20 10:42:53 +04:00
cn_already_initialized = 1 ;
2005-09-12 06:15:07 +04:00
err = cn_add_callback ( & dev - > id , " connector " , & cn_callback ) ;
if ( err ) {
2006-06-20 10:42:53 +04:00
cn_already_initialized = 0 ;
2005-09-12 06:15:07 +04:00
cn_queue_free_dev ( dev - > cbdev ) ;
2008-01-29 01:41:19 +03:00
netlink_kernel_release ( dev - > nls ) ;
2005-09-12 06:15:07 +04:00
return - EINVAL ;
}
2008-06-28 07:03:24 +04:00
proc_net_fops_create ( & init_net , " connector " , S_IRUGO , & cn_file_ops ) ;
2005-09-12 06:15:07 +04:00
return 0 ;
}
2006-06-20 10:42:53 +04:00
static void __devexit cn_fini ( void )
2005-09-12 06:15:07 +04:00
{
struct cn_dev * dev = & cdev ;
cn_already_initialized = 0 ;
2008-06-28 07:03:24 +04:00
proc_net_remove ( & init_net , " connector " ) ;
2005-09-12 06:15:07 +04:00
cn_del_callback ( & dev - > id ) ;
cn_queue_free_dev ( dev - > cbdev ) ;
2008-01-29 01:41:19 +03:00
netlink_kernel_release ( dev - > nls ) ;
2005-09-12 06:15:07 +04:00
}
2006-06-20 10:42:53 +04:00
subsys_initcall ( cn_init ) ;
2005-09-12 06:15:07 +04:00
module_exit ( cn_fini ) ;