2005-08-10 07:14:34 +04:00
/*
* net / dccp / ccid . c
*
* An implementation of the DCCP protocol
* Arnaldo Carvalho de Melo < acme @ conectiva . com . br >
*
* CCID infrastructure
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include "ccid.h"
2006-03-21 06:21:44 +03:00
static struct ccid_operations * ccids [ CCID_MAX ] ;
2005-08-10 07:14:34 +04:00
# if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT)
static atomic_t ccids_lockct = ATOMIC_INIT ( 0 ) ;
static DEFINE_SPINLOCK ( ccids_lock ) ;
/*
* The strategy is : modifications ccids vector are short , do not sleep and
* veeery rare , but read access should be free of any exclusive locks .
*/
static void ccids_write_lock ( void )
{
spin_lock ( & ccids_lock ) ;
while ( atomic_read ( & ccids_lockct ) ! = 0 ) {
spin_unlock ( & ccids_lock ) ;
yield ( ) ;
spin_lock ( & ccids_lock ) ;
}
}
static inline void ccids_write_unlock ( void )
{
spin_unlock ( & ccids_lock ) ;
}
static inline void ccids_read_lock ( void )
{
atomic_inc ( & ccids_lockct ) ;
2007-08-11 02:21:17 +04:00
smp_mb__after_atomic_inc ( ) ;
2005-08-10 07:14:34 +04:00
spin_unlock_wait ( & ccids_lock ) ;
}
static inline void ccids_read_unlock ( void )
{
atomic_dec ( & ccids_lockct ) ;
}
# else
# define ccids_write_lock() do { } while(0)
# define ccids_write_unlock() do { } while(0)
# define ccids_read_lock() do { } while(0)
# define ccids_read_unlock() do { } while(0)
# endif
2006-12-07 07:33:20 +03:00
static struct kmem_cache * ccid_kmem_cache_create ( int obj_size , const char * fmt , . . . )
2005-08-10 07:14:34 +04:00
{
2006-12-07 07:33:20 +03:00
struct kmem_cache * slab ;
2006-03-21 06:21:44 +03:00
char slab_name_fmt [ 32 ] , * slab_name ;
va_list args ;
va_start ( args , fmt ) ;
vsnprintf ( slab_name_fmt , sizeof ( slab_name_fmt ) , fmt , args ) ;
va_end ( args ) ;
slab_name = kstrdup ( slab_name_fmt , GFP_KERNEL ) ;
if ( slab_name = = NULL )
return NULL ;
slab = kmem_cache_create ( slab_name , sizeof ( struct ccid ) + obj_size , 0 ,
2007-07-20 05:11:58 +04:00
SLAB_HWCACHE_ALIGN , NULL ) ;
2006-03-21 06:21:44 +03:00
if ( slab = = NULL )
kfree ( slab_name ) ;
return slab ;
}
2006-12-07 07:33:20 +03:00
static void ccid_kmem_cache_destroy ( struct kmem_cache * slab )
2006-03-21 06:21:44 +03:00
{
if ( slab ! = NULL ) {
const char * name = kmem_cache_name ( slab ) ;
kmem_cache_destroy ( slab ) ;
kfree ( name ) ;
}
}
int ccid_register ( struct ccid_operations * ccid_ops )
{
int err = - ENOBUFS ;
ccid_ops - > ccid_hc_rx_slab =
ccid_kmem_cache_create ( ccid_ops - > ccid_hc_rx_obj_size ,
2007-12-14 04:33:25 +03:00
" ccid%u_hc_rx_sock " ,
ccid_ops - > ccid_id ) ;
2006-03-21 06:21:44 +03:00
if ( ccid_ops - > ccid_hc_rx_slab = = NULL )
goto out ;
ccid_ops - > ccid_hc_tx_slab =
ccid_kmem_cache_create ( ccid_ops - > ccid_hc_tx_obj_size ,
2007-12-14 04:33:25 +03:00
" ccid%u_hc_tx_sock " ,
ccid_ops - > ccid_id ) ;
2006-03-21 06:21:44 +03:00
if ( ccid_ops - > ccid_hc_tx_slab = = NULL )
goto out_free_rx_slab ;
2005-08-10 07:14:34 +04:00
ccids_write_lock ( ) ;
err = - EEXIST ;
2006-03-21 06:21:44 +03:00
if ( ccids [ ccid_ops - > ccid_id ] = = NULL ) {
ccids [ ccid_ops - > ccid_id ] = ccid_ops ;
2005-08-10 07:14:34 +04:00
err = 0 ;
}
ccids_write_unlock ( ) ;
2006-03-21 06:21:44 +03:00
if ( err ! = 0 )
goto out_free_tx_slab ;
pr_info ( " CCID: Registered CCID %d (%s) \n " ,
ccid_ops - > ccid_id , ccid_ops - > ccid_name ) ;
out :
2005-08-10 07:14:34 +04:00
return err ;
2006-03-21 06:21:44 +03:00
out_free_tx_slab :
ccid_kmem_cache_destroy ( ccid_ops - > ccid_hc_tx_slab ) ;
ccid_ops - > ccid_hc_tx_slab = NULL ;
goto out ;
out_free_rx_slab :
ccid_kmem_cache_destroy ( ccid_ops - > ccid_hc_rx_slab ) ;
ccid_ops - > ccid_hc_rx_slab = NULL ;
goto out ;
2005-08-10 07:14:34 +04:00
}
EXPORT_SYMBOL_GPL ( ccid_register ) ;
2006-03-21 06:21:44 +03:00
int ccid_unregister ( struct ccid_operations * ccid_ops )
2005-08-10 07:14:34 +04:00
{
ccids_write_lock ( ) ;
2006-03-21 06:21:44 +03:00
ccids [ ccid_ops - > ccid_id ] = NULL ;
2005-08-10 07:14:34 +04:00
ccids_write_unlock ( ) ;
2006-03-21 06:21:44 +03:00
ccid_kmem_cache_destroy ( ccid_ops - > ccid_hc_tx_slab ) ;
ccid_ops - > ccid_hc_tx_slab = NULL ;
ccid_kmem_cache_destroy ( ccid_ops - > ccid_hc_rx_slab ) ;
ccid_ops - > ccid_hc_rx_slab = NULL ;
2005-08-10 07:14:34 +04:00
pr_info ( " CCID: Unregistered CCID %d (%s) \n " ,
2006-03-21 06:21:44 +03:00
ccid_ops - > ccid_id , ccid_ops - > ccid_name ) ;
2005-08-10 07:14:34 +04:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( ccid_unregister ) ;
2006-03-21 06:21:44 +03:00
struct ccid * ccid_new ( unsigned char id , struct sock * sk , int rx , gfp_t gfp )
2005-08-10 07:14:34 +04:00
{
2006-03-21 06:21:44 +03:00
struct ccid_operations * ccid_ops ;
struct ccid * ccid = NULL ;
2005-08-10 07:14:34 +04:00
2006-03-21 06:21:44 +03:00
ccids_read_lock ( ) ;
2005-08-10 07:14:34 +04:00
# ifdef CONFIG_KMOD
2006-03-21 06:21:44 +03:00
if ( ccids [ id ] = = NULL ) {
/* We only try to load if in process context */
ccids_read_unlock ( ) ;
if ( gfp & GFP_ATOMIC )
goto out ;
2005-08-10 07:14:34 +04:00
request_module ( " net-dccp-ccid-%d " , id ) ;
2006-03-21 06:21:44 +03:00
ccids_read_lock ( ) ;
}
2005-08-10 07:14:34 +04:00
# endif
2006-03-21 06:21:44 +03:00
ccid_ops = ccids [ id ] ;
if ( ccid_ops = = NULL )
goto out_unlock ;
2005-08-10 07:14:34 +04:00
2006-03-21 06:21:44 +03:00
if ( ! try_module_get ( ccid_ops - > ccid_owner ) )
goto out_unlock ;
2005-08-10 07:14:34 +04:00
2006-03-21 06:21:44 +03:00
ccids_read_unlock ( ) ;
2005-08-10 07:14:34 +04:00
2006-03-21 06:21:44 +03:00
ccid = kmem_cache_alloc ( rx ? ccid_ops - > ccid_hc_rx_slab :
ccid_ops - > ccid_hc_tx_slab , gfp ) ;
if ( ccid = = NULL )
2005-08-10 07:14:34 +04:00
goto out_module_put ;
2006-03-21 06:21:44 +03:00
ccid - > ccid_ops = ccid_ops ;
if ( rx ) {
memset ( ccid + 1 , 0 , ccid_ops - > ccid_hc_rx_obj_size ) ;
if ( ccid - > ccid_ops - > ccid_hc_rx_init ! = NULL & &
ccid - > ccid_ops - > ccid_hc_rx_init ( ccid , sk ) ! = 0 )
goto out_free_ccid ;
} else {
memset ( ccid + 1 , 0 , ccid_ops - > ccid_hc_tx_obj_size ) ;
if ( ccid - > ccid_ops - > ccid_hc_tx_init ! = NULL & &
ccid - > ccid_ops - > ccid_hc_tx_init ( ccid , sk ) ! = 0 )
goto out_free_ccid ;
}
2005-08-10 07:14:34 +04:00
out :
return ccid ;
2006-03-21 06:21:44 +03:00
out_unlock :
ccids_read_unlock ( ) ;
goto out ;
out_free_ccid :
kmem_cache_free ( rx ? ccid_ops - > ccid_hc_rx_slab :
ccid_ops - > ccid_hc_tx_slab , ccid ) ;
2005-08-10 07:14:34 +04:00
ccid = NULL ;
2006-03-21 06:21:44 +03:00
out_module_put :
module_put ( ccid_ops - > ccid_owner ) ;
2005-08-10 07:14:34 +04:00
goto out ;
}
2006-03-21 06:21:44 +03:00
EXPORT_SYMBOL_GPL ( ccid_new ) ;
2005-08-10 07:14:34 +04:00
2006-03-21 06:21:44 +03:00
struct ccid * ccid_hc_rx_new ( unsigned char id , struct sock * sk , gfp_t gfp )
2005-08-10 07:14:34 +04:00
{
2006-03-21 06:21:44 +03:00
return ccid_new ( id , sk , 1 , gfp ) ;
}
EXPORT_SYMBOL_GPL ( ccid_hc_rx_new ) ;
struct ccid * ccid_hc_tx_new ( unsigned char id , struct sock * sk , gfp_t gfp )
{
return ccid_new ( id , sk , 0 , gfp ) ;
}
EXPORT_SYMBOL_GPL ( ccid_hc_tx_new ) ;
static void ccid_delete ( struct ccid * ccid , struct sock * sk , int rx )
{
struct ccid_operations * ccid_ops ;
2005-08-10 07:14:34 +04:00
if ( ccid = = NULL )
return ;
2006-03-21 06:21:44 +03:00
ccid_ops = ccid - > ccid_ops ;
if ( rx ) {
if ( ccid_ops - > ccid_hc_rx_exit ! = NULL )
ccid_ops - > ccid_hc_rx_exit ( sk ) ;
kmem_cache_free ( ccid_ops - > ccid_hc_rx_slab , ccid ) ;
} else {
if ( ccid_ops - > ccid_hc_tx_exit ! = NULL )
ccid_ops - > ccid_hc_tx_exit ( sk ) ;
kmem_cache_free ( ccid_ops - > ccid_hc_tx_slab , ccid ) ;
}
2005-08-10 07:14:34 +04:00
ccids_read_lock ( ) ;
2006-03-21 06:21:44 +03:00
if ( ccids [ ccid_ops - > ccid_id ] ! = NULL )
module_put ( ccid_ops - > ccid_owner ) ;
ccids_read_unlock ( ) ;
}
2005-08-10 07:14:34 +04:00
2006-03-21 06:21:44 +03:00
void ccid_hc_rx_delete ( struct ccid * ccid , struct sock * sk )
{
ccid_delete ( ccid , sk , 1 ) ;
}
2005-08-10 07:14:34 +04:00
2006-03-21 06:21:44 +03:00
EXPORT_SYMBOL_GPL ( ccid_hc_rx_delete ) ;
void ccid_hc_tx_delete ( struct ccid * ccid , struct sock * sk )
{
ccid_delete ( ccid , sk , 0 ) ;
2005-08-10 07:14:34 +04:00
}
2006-03-21 06:21:44 +03:00
EXPORT_SYMBOL_GPL ( ccid_hc_tx_delete ) ;