2010-03-30 17:56:27 +04:00
/*
* Copyright ( C ) ST - Ericsson AB 2010
* Authors : Sjur Brendeland / sjur . brandeland @ stericsson . com
* Daniel Martensson / Daniel . Martensson @ stericsson . com
* License terms : GNU General Public License ( GPL ) version 2
*/
2010-09-06 01:31:11 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ":%s(): " fmt, __func__
2010-03-30 17:56:27 +04:00
# include <linux/fs.h>
2011-06-06 14:43:46 +04:00
# include <linux/hardirq.h>
2010-03-30 17:56:27 +04:00
# include <linux/init.h>
# include <linux/module.h>
# include <linux/netdevice.h>
# include <linux/if_ether.h>
# include <linux/moduleparam.h>
# include <linux/ip.h>
# include <linux/sched.h>
# include <linux/sockios.h>
# include <linux/caif/if_caif.h>
# include <net/rtnetlink.h>
# include <net/caif/caif_layer.h>
# include <net/caif/cfpkt.h>
# include <net/caif/caif_dev.h>
2010-04-28 12:54:39 +04:00
/* GPRS PDP connection has MTU to 1500 */
2010-06-17 10:55:40 +04:00
# define GPRS_PDP_MTU 1500
2010-04-28 12:54:39 +04:00
/* 5 sec. connect timeout */
# define CONNECT_TIMEOUT (5 * HZ)
2010-03-30 17:56:27 +04:00
# define CAIF_NET_DEFAULT_QUEUE_LEN 500
2012-03-11 14:28:32 +04:00
# define UNDEF_CONNID 0xffffffff
2010-03-30 17:56:27 +04:00
/*This list is protected by the rtnl lock. */
static LIST_HEAD ( chnl_net_list ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_RTNL_LINK ( " caif " ) ;
2010-04-28 12:54:39 +04:00
enum caif_states {
CAIF_CONNECTED = 1 ,
CAIF_CONNECTING ,
CAIF_DISCONNECTED ,
CAIF_SHUTDOWN
} ;
2010-03-30 17:56:27 +04:00
struct chnl_net {
struct cflayer chnl ;
struct net_device_stats stats ;
struct caif_connect_request conn_req ;
struct list_head list_field ;
struct net_device * netdev ;
char name [ 256 ] ;
wait_queue_head_t netmgmt_wq ;
/* Flow status to remember and control the transmission. */
bool flowenabled ;
2010-04-28 12:54:39 +04:00
enum caif_states state ;
2010-03-30 17:56:27 +04:00
} ;
static void robust_list_del ( struct list_head * delete_node )
{
struct list_head * list_node ;
struct list_head * n ;
ASSERT_RTNL ( ) ;
list_for_each_safe ( list_node , n , & chnl_net_list ) {
if ( list_node = = delete_node ) {
list_del ( list_node ) ;
2010-04-28 12:54:39 +04:00
return ;
2010-03-30 17:56:27 +04:00
}
}
2010-04-28 12:54:39 +04:00
WARN_ON ( 1 ) ;
2010-03-30 17:56:27 +04:00
}
static int chnl_recv_cb ( struct cflayer * layr , struct cfpkt * pkt )
{
struct sk_buff * skb ;
2012-02-07 01:20:15 +04:00
struct chnl_net * priv ;
2010-03-30 17:56:27 +04:00
int pktlen ;
2011-01-07 04:57:08 +03:00
const u8 * ip_version ;
u8 buf ;
2010-03-30 17:56:27 +04:00
priv = container_of ( layr , struct chnl_net , chnl ) ;
if ( ! priv )
return - EINVAL ;
2011-05-13 06:44:08 +04:00
skb = ( struct sk_buff * ) cfpkt_tonative ( pkt ) ;
2010-03-30 17:56:27 +04:00
/* Get length of CAIF packet. */
2011-05-13 06:44:08 +04:00
pktlen = skb - > len ;
2010-03-30 17:56:27 +04:00
/* Pass some minimum information and
* send the packet to the net stack .
*/
skb - > dev = priv - > netdev ;
2011-01-07 04:57:08 +03:00
/* check the version of IP */
ip_version = skb_header_pointer ( skb , 0 , 1 , & buf ) ;
2012-02-03 08:36:20 +04:00
2011-01-07 04:57:08 +03:00
switch ( * ip_version > > 4 ) {
case 4 :
skb - > protocol = htons ( ETH_P_IP ) ;
break ;
case 6 :
skb - > protocol = htons ( ETH_P_IPV6 ) ;
break ;
default :
2012-02-03 08:36:20 +04:00
priv - > netdev - > stats . rx_errors + + ;
2011-01-07 04:57:08 +03:00
return - EINVAL ;
}
2010-03-30 17:56:27 +04:00
/* If we change the header in loop mode, the checksum is corrupted. */
if ( priv - > conn_req . protocol = = CAIFPROTO_DATAGRAM_LOOP )
skb - > ip_summed = CHECKSUM_UNNECESSARY ;
else
skb - > ip_summed = CHECKSUM_NONE ;
if ( in_interrupt ( ) )
netif_rx ( skb ) ;
else
netif_rx_ni ( skb ) ;
/* Update statistics. */
priv - > netdev - > stats . rx_packets + + ;
priv - > netdev - > stats . rx_bytes + = pktlen ;
2012-02-03 08:36:20 +04:00
return 0 ;
2010-03-30 17:56:27 +04:00
}
static int delete_device ( struct chnl_net * dev )
{
ASSERT_RTNL ( ) ;
if ( dev - > netdev )
unregister_netdevice ( dev - > netdev ) ;
return 0 ;
}
static void close_work ( struct work_struct * work )
{
struct chnl_net * dev = NULL ;
struct list_head * list_node ;
struct list_head * _tmp ;
2011-06-01 04:55:37 +04:00
rtnl_lock ( ) ;
2010-03-30 17:56:27 +04:00
list_for_each_safe ( list_node , _tmp , & chnl_net_list ) {
dev = list_entry ( list_node , struct chnl_net , list_field ) ;
2010-04-28 12:54:39 +04:00
if ( dev - > state = = CAIF_SHUTDOWN )
dev_close ( dev - > netdev ) ;
2010-03-30 17:56:27 +04:00
}
2011-06-01 04:55:37 +04:00
rtnl_unlock ( ) ;
2010-03-30 17:56:27 +04:00
}
static DECLARE_WORK ( close_worker , close_work ) ;
2011-05-13 06:44:04 +04:00
static void chnl_hold ( struct cflayer * lyr )
{
struct chnl_net * priv = container_of ( lyr , struct chnl_net , chnl ) ;
dev_hold ( priv - > netdev ) ;
}
static void chnl_put ( struct cflayer * lyr )
{
struct chnl_net * priv = container_of ( lyr , struct chnl_net , chnl ) ;
dev_put ( priv - > netdev ) ;
}
2010-03-30 17:56:27 +04:00
static void chnl_flowctrl_cb ( struct cflayer * layr , enum caif_ctrlcmd flow ,
int phyid )
{
2010-04-28 12:54:39 +04:00
struct chnl_net * priv = container_of ( layr , struct chnl_net , chnl ) ;
2010-09-06 01:31:11 +04:00
pr_debug ( " NET flowctrl func called flow: %s \n " ,
2010-03-30 17:56:27 +04:00
flow = = CAIF_CTRLCMD_FLOW_ON_IND ? " ON " :
flow = = CAIF_CTRLCMD_INIT_RSP ? " INIT " :
flow = = CAIF_CTRLCMD_FLOW_OFF_IND ? " OFF " :
flow = = CAIF_CTRLCMD_DEINIT_RSP ? " CLOSE/DEINIT " :
flow = = CAIF_CTRLCMD_INIT_FAIL_RSP ? " OPEN_FAIL " :
flow = = CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND ?
" REMOTE_SHUTDOWN " : " UKNOWN CTRL COMMAND " ) ;
2010-04-28 12:54:39 +04:00
2010-03-30 17:56:27 +04:00
switch ( flow ) {
case CAIF_CTRLCMD_FLOW_OFF_IND :
2010-04-28 12:54:39 +04:00
priv - > flowenabled = false ;
netif_stop_queue ( priv - > netdev ) ;
break ;
2010-03-30 17:56:27 +04:00
case CAIF_CTRLCMD_DEINIT_RSP :
2010-04-28 12:54:39 +04:00
priv - > state = CAIF_DISCONNECTED ;
break ;
2010-03-30 17:56:27 +04:00
case CAIF_CTRLCMD_INIT_FAIL_RSP :
2010-04-28 12:54:39 +04:00
priv - > state = CAIF_DISCONNECTED ;
wake_up_interruptible ( & priv - > netmgmt_wq ) ;
break ;
2010-03-30 17:56:27 +04:00
case CAIF_CTRLCMD_REMOTE_SHUTDOWN_IND :
2010-04-28 12:54:39 +04:00
priv - > state = CAIF_SHUTDOWN ;
2010-03-30 17:56:27 +04:00
netif_tx_disable ( priv - > netdev ) ;
schedule_work ( & close_worker ) ;
break ;
case CAIF_CTRLCMD_FLOW_ON_IND :
2010-04-28 12:54:39 +04:00
priv - > flowenabled = true ;
netif_wake_queue ( priv - > netdev ) ;
break ;
2010-03-30 17:56:27 +04:00
case CAIF_CTRLCMD_INIT_RSP :
2011-05-13 06:44:04 +04:00
caif_client_register_refcnt ( & priv - > chnl , chnl_hold , chnl_put ) ;
2010-04-28 12:54:39 +04:00
priv - > state = CAIF_CONNECTED ;
2010-03-30 17:56:27 +04:00
priv - > flowenabled = true ;
netif_wake_queue ( priv - > netdev ) ;
wake_up_interruptible ( & priv - > netmgmt_wq ) ;
break ;
default :
break ;
}
}
static int chnl_net_start_xmit ( struct sk_buff * skb , struct net_device * dev )
{
struct chnl_net * priv ;
struct cfpkt * pkt = NULL ;
int len ;
int result = - 1 ;
/* Get our private data. */
priv = netdev_priv ( dev ) ;
if ( skb - > len > priv - > netdev - > mtu ) {
2010-09-06 01:31:11 +04:00
pr_warn ( " Size of skb exceeded MTU \n " ) ;
2012-02-03 08:36:20 +04:00
dev - > stats . tx_errors + + ;
2010-03-30 17:56:27 +04:00
return - ENOSPC ;
}
if ( ! priv - > flowenabled ) {
2010-09-06 01:31:11 +04:00
pr_debug ( " dropping packets flow off \n " ) ;
2012-02-03 08:36:20 +04:00
dev - > stats . tx_dropped + + ;
2010-03-30 17:56:27 +04:00
return NETDEV_TX_BUSY ;
}
if ( priv - > conn_req . protocol = = CAIFPROTO_DATAGRAM_LOOP )
swap ( ip_hdr ( skb ) - > saddr , ip_hdr ( skb ) - > daddr ) ;
/* Store original SKB length. */
len = skb - > len ;
pkt = cfpkt_fromnative ( CAIF_DIR_OUT , ( void * ) skb ) ;
/* Send the packet down the stack. */
result = priv - > chnl . dn - > transmit ( priv - > chnl . dn , pkt ) ;
if ( result ) {
2012-02-03 08:36:20 +04:00
dev - > stats . tx_dropped + + ;
2010-03-30 17:56:27 +04:00
return result ;
}
/* Update statistics. */
dev - > stats . tx_packets + + ;
dev - > stats . tx_bytes + = len ;
return NETDEV_TX_OK ;
}
static int chnl_net_open ( struct net_device * dev )
{
struct chnl_net * priv = NULL ;
int result = - 1 ;
2010-06-17 10:55:40 +04:00
int llifindex , headroom , tailroom , mtu ;
struct net_device * lldev ;
2010-03-30 17:56:27 +04:00
ASSERT_RTNL ( ) ;
priv = netdev_priv ( dev ) ;
if ( ! priv ) {
2010-09-06 01:31:11 +04:00
pr_debug ( " chnl_net_open: no priv \n " ) ;
2010-03-30 17:56:27 +04:00
return - ENODEV ;
}
2010-04-28 12:54:39 +04:00
if ( priv - > state ! = CAIF_CONNECTING ) {
priv - > state = CAIF_CONNECTING ;
2011-05-13 06:44:05 +04:00
result = caif_connect_client ( dev_net ( dev ) , & priv - > conn_req ,
& priv - > chnl , & llifindex ,
& headroom , & tailroom ) ;
2010-04-28 12:54:39 +04:00
if ( result ! = 0 ) {
2010-09-06 01:31:11 +04:00
pr_debug ( " err: "
" Unable to register and open device, "
" Err:%d \n " ,
result ) ;
2010-06-17 10:55:40 +04:00
goto error ;
}
lldev = dev_get_by_index ( dev_net ( dev ) , llifindex ) ;
if ( lldev = = NULL ) {
2010-09-06 01:31:11 +04:00
pr_debug ( " no interface? \n " ) ;
2010-06-17 10:55:40 +04:00
result = - ENODEV ;
goto error ;
}
dev - > needed_tailroom = tailroom + lldev - > needed_tailroom ;
dev - > hard_header_len = headroom + lldev - > hard_header_len +
lldev - > needed_tailroom ;
/*
* MTU , head - room etc is not know before we have a
* CAIF link layer device available . MTU calculation may
* override initial RTNL configuration .
* MTU is minimum of current mtu , link layer mtu pluss
* CAIF head and tail , and PDP GPRS contexts max MTU .
*/
mtu = min_t ( int , dev - > mtu , lldev - > mtu - ( headroom + tailroom ) ) ;
mtu = min_t ( int , GPRS_PDP_MTU , mtu ) ;
dev_set_mtu ( dev , mtu ) ;
dev_put ( lldev ) ;
if ( mtu < 100 ) {
2010-09-06 01:31:11 +04:00
pr_warn ( " CAIF Interface MTU too small (%d) \n " , mtu ) ;
2010-06-17 10:55:40 +04:00
result = - ENODEV ;
goto error ;
2010-04-28 12:54:39 +04:00
}
2010-03-30 17:56:27 +04:00
}
2010-04-28 12:54:39 +04:00
2010-06-17 10:55:40 +04:00
rtnl_unlock ( ) ; /* Release RTNL lock during connect wait */
2010-04-28 12:54:39 +04:00
result = wait_event_interruptible_timeout ( priv - > netmgmt_wq ,
priv - > state ! = CAIF_CONNECTING ,
CONNECT_TIMEOUT ) ;
2010-03-30 17:56:27 +04:00
2010-06-17 10:55:40 +04:00
rtnl_lock ( ) ;
2010-03-30 17:56:27 +04:00
if ( result = = - ERESTARTSYS ) {
2010-09-06 01:31:11 +04:00
pr_debug ( " wait_event_interruptible woken by a signal \n " ) ;
2010-06-17 10:55:40 +04:00
result = - ERESTARTSYS ;
goto error ;
2010-04-28 12:54:39 +04:00
}
2010-06-17 10:55:40 +04:00
2010-04-28 12:54:39 +04:00
if ( result = = 0 ) {
2010-09-06 01:31:11 +04:00
pr_debug ( " connect timeout \n " ) ;
2011-05-13 06:44:05 +04:00
caif_disconnect_client ( dev_net ( dev ) , & priv - > chnl ) ;
2010-04-28 12:54:39 +04:00
priv - > state = CAIF_DISCONNECTED ;
2010-09-06 01:31:11 +04:00
pr_debug ( " state disconnected \n " ) ;
2010-06-17 10:55:40 +04:00
result = - ETIMEDOUT ;
goto error ;
2010-04-28 12:54:39 +04:00
}
2010-03-30 17:56:27 +04:00
2010-04-28 12:54:39 +04:00
if ( priv - > state ! = CAIF_CONNECTED ) {
2010-09-06 01:31:11 +04:00
pr_debug ( " connect failed \n " ) ;
2010-06-17 10:55:40 +04:00
result = - ECONNREFUSED ;
goto error ;
2010-04-28 12:54:39 +04:00
}
2010-09-06 01:31:11 +04:00
pr_debug ( " CAIF Netdevice connected \n " ) ;
2010-03-30 17:56:27 +04:00
return 0 ;
2010-06-17 10:55:40 +04:00
error :
2011-05-13 06:44:05 +04:00
caif_disconnect_client ( dev_net ( dev ) , & priv - > chnl ) ;
2010-06-17 10:55:40 +04:00
priv - > state = CAIF_DISCONNECTED ;
2010-09-06 01:31:11 +04:00
pr_debug ( " state disconnected \n " ) ;
2010-06-17 10:55:40 +04:00
return result ;
2010-03-30 17:56:27 +04:00
}
static int chnl_net_stop ( struct net_device * dev )
{
struct chnl_net * priv ;
2010-04-28 12:54:39 +04:00
2010-03-30 17:56:27 +04:00
ASSERT_RTNL ( ) ;
priv = netdev_priv ( dev ) ;
2010-04-28 12:54:39 +04:00
priv - > state = CAIF_DISCONNECTED ;
2011-05-13 06:44:05 +04:00
caif_disconnect_client ( dev_net ( dev ) , & priv - > chnl ) ;
2010-03-30 17:56:27 +04:00
return 0 ;
}
static int chnl_net_init ( struct net_device * dev )
{
struct chnl_net * priv ;
ASSERT_RTNL ( ) ;
priv = netdev_priv ( dev ) ;
strncpy ( priv - > name , dev - > name , sizeof ( priv - > name ) ) ;
return 0 ;
}
static void chnl_net_uninit ( struct net_device * dev )
{
struct chnl_net * priv ;
ASSERT_RTNL ( ) ;
priv = netdev_priv ( dev ) ;
robust_list_del ( & priv - > list_field ) ;
}
static const struct net_device_ops netdev_ops = {
. ndo_open = chnl_net_open ,
. ndo_stop = chnl_net_stop ,
. ndo_init = chnl_net_init ,
. ndo_uninit = chnl_net_uninit ,
. ndo_start_xmit = chnl_net_start_xmit ,
} ;
2011-05-13 06:44:04 +04:00
static void chnl_net_destructor ( struct net_device * dev )
{
struct chnl_net * priv = netdev_priv ( dev ) ;
caif_free_client ( & priv - > chnl ) ;
free_netdev ( dev ) ;
}
2010-03-30 17:56:27 +04:00
static void ipcaif_net_setup ( struct net_device * dev )
{
struct chnl_net * priv ;
dev - > netdev_ops = & netdev_ops ;
2011-05-13 06:44:04 +04:00
dev - > destructor = chnl_net_destructor ;
2010-03-30 17:56:27 +04:00
dev - > flags | = IFF_NOARP ;
dev - > flags | = IFF_POINTOPOINT ;
2010-06-17 10:55:40 +04:00
dev - > mtu = GPRS_PDP_MTU ;
2010-03-30 17:56:27 +04:00
dev - > tx_queue_len = CAIF_NET_DEFAULT_QUEUE_LEN ;
priv = netdev_priv ( dev ) ;
priv - > chnl . receive = chnl_recv_cb ;
priv - > chnl . ctrlcmd = chnl_flowctrl_cb ;
priv - > netdev = dev ;
priv - > conn_req . protocol = CAIFPROTO_DATAGRAM ;
priv - > conn_req . link_selector = CAIF_LINK_HIGH_BANDW ;
priv - > conn_req . priority = CAIF_PRIO_LOW ;
/* Insert illegal value */
2012-03-11 14:28:32 +04:00
priv - > conn_req . sockaddr . u . dgm . connection_id = UNDEF_CONNID ;
2010-03-30 17:56:27 +04:00
priv - > flowenabled = false ;
init_waitqueue_head ( & priv - > netmgmt_wq ) ;
}
static int ipcaif_fill_info ( struct sk_buff * skb , const struct net_device * dev )
{
struct chnl_net * priv ;
u8 loop ;
priv = netdev_priv ( dev ) ;
NLA_PUT_U32 ( skb , IFLA_CAIF_IPV4_CONNID ,
priv - > conn_req . sockaddr . u . dgm . connection_id ) ;
NLA_PUT_U32 ( skb , IFLA_CAIF_IPV6_CONNID ,
priv - > conn_req . sockaddr . u . dgm . connection_id ) ;
loop = priv - > conn_req . protocol = = CAIFPROTO_DATAGRAM_LOOP ;
NLA_PUT_U8 ( skb , IFLA_CAIF_LOOPBACK , loop ) ;
return 0 ;
nla_put_failure :
return - EMSGSIZE ;
}
static void caif_netlink_parms ( struct nlattr * data [ ] ,
struct caif_connect_request * conn_req )
{
if ( ! data ) {
2010-09-06 01:31:11 +04:00
pr_warn ( " no params data found \n " ) ;
2010-03-30 17:56:27 +04:00
return ;
}
if ( data [ IFLA_CAIF_IPV4_CONNID ] )
conn_req - > sockaddr . u . dgm . connection_id =
nla_get_u32 ( data [ IFLA_CAIF_IPV4_CONNID ] ) ;
if ( data [ IFLA_CAIF_IPV6_CONNID ] )
conn_req - > sockaddr . u . dgm . connection_id =
nla_get_u32 ( data [ IFLA_CAIF_IPV6_CONNID ] ) ;
if ( data [ IFLA_CAIF_LOOPBACK ] ) {
if ( nla_get_u8 ( data [ IFLA_CAIF_LOOPBACK ] ) )
conn_req - > protocol = CAIFPROTO_DATAGRAM_LOOP ;
else
conn_req - > protocol = CAIFPROTO_DATAGRAM ;
}
}
static int ipcaif_newlink ( struct net * src_net , struct net_device * dev ,
struct nlattr * tb [ ] , struct nlattr * data [ ] )
{
int ret ;
struct chnl_net * caifdev ;
ASSERT_RTNL ( ) ;
caifdev = netdev_priv ( dev ) ;
caif_netlink_parms ( data , & caifdev - > conn_req ) ;
2010-04-28 12:54:39 +04:00
dev_net_set ( caifdev - > netdev , src_net ) ;
2010-03-30 17:56:27 +04:00
ret = register_netdevice ( dev ) ;
if ( ret )
2010-09-06 01:31:11 +04:00
pr_warn ( " device rtml registration failed \n " ) ;
2011-02-09 01:31:31 +03:00
else
list_add ( & caifdev - > list_field , & chnl_net_list ) ;
2011-05-13 06:44:04 +04:00
2012-03-11 14:28:32 +04:00
/* Use ifindex as connection id, and use loopback channel default. */
if ( caifdev - > conn_req . sockaddr . u . dgm . connection_id = = UNDEF_CONNID ) {
2011-05-13 06:44:04 +04:00
caifdev - > conn_req . sockaddr . u . dgm . connection_id = dev - > ifindex ;
2012-03-11 14:28:32 +04:00
caifdev - > conn_req . protocol = CAIFPROTO_DATAGRAM_LOOP ;
}
2010-03-30 17:56:27 +04:00
return ret ;
}
static int ipcaif_changelink ( struct net_device * dev , struct nlattr * tb [ ] ,
struct nlattr * data [ ] )
{
struct chnl_net * caifdev ;
ASSERT_RTNL ( ) ;
caifdev = netdev_priv ( dev ) ;
caif_netlink_parms ( data , & caifdev - > conn_req ) ;
netdev_state_change ( dev ) ;
return 0 ;
}
static size_t ipcaif_get_size ( const struct net_device * dev )
{
return
/* IFLA_CAIF_IPV4_CONNID */
nla_total_size ( 4 ) +
/* IFLA_CAIF_IPV6_CONNID */
nla_total_size ( 4 ) +
/* IFLA_CAIF_LOOPBACK */
nla_total_size ( 2 ) +
0 ;
}
static const struct nla_policy ipcaif_policy [ IFLA_CAIF_MAX + 1 ] = {
[ IFLA_CAIF_IPV4_CONNID ] = { . type = NLA_U32 } ,
[ IFLA_CAIF_IPV6_CONNID ] = { . type = NLA_U32 } ,
[ IFLA_CAIF_LOOPBACK ] = { . type = NLA_U8 }
} ;
static struct rtnl_link_ops ipcaif_link_ops __read_mostly = {
. kind = " caif " ,
. priv_size = sizeof ( struct chnl_net ) ,
. setup = ipcaif_net_setup ,
. maxtype = IFLA_CAIF_MAX ,
. policy = ipcaif_policy ,
. newlink = ipcaif_newlink ,
. changelink = ipcaif_changelink ,
. get_size = ipcaif_get_size ,
. fill_info = ipcaif_fill_info ,
} ;
static int __init chnl_init_module ( void )
{
return rtnl_link_register ( & ipcaif_link_ops ) ;
}
static void __exit chnl_exit_module ( void )
{
struct chnl_net * dev = NULL ;
struct list_head * list_node ;
struct list_head * _tmp ;
rtnl_link_unregister ( & ipcaif_link_ops ) ;
rtnl_lock ( ) ;
list_for_each_safe ( list_node , _tmp , & chnl_net_list ) {
dev = list_entry ( list_node , struct chnl_net , list_field ) ;
list_del ( list_node ) ;
delete_device ( dev ) ;
}
rtnl_unlock ( ) ;
}
module_init ( chnl_init_module ) ;
module_exit ( chnl_exit_module ) ;