2006-09-13 21:24:59 +04:00
/* drivers/net/ifb.c:
2006-01-09 09:34:25 +03:00
The purpose of this driver is to provide a device that allows
for sharing of resources :
1 ) qdiscs / policies that are per device as opposed to system wide .
ifb allows for a device which can be redirected to thus providing
an impression of sharing .
2 ) Allows for queueing incoming traffic for shaping instead of
2006-09-13 21:24:59 +04:00
dropping .
2006-01-09 09:34:25 +03:00
The original concept is based on what is known as the IMQ
driver initially written by Martin Devera , later rewritten
by Patrick McHardy and then maintained by Andre Correa .
You need the tc action mirror or redirect to feed this device
packets .
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 .
2006-09-13 21:24:59 +04:00
2006-01-09 09:34:25 +03:00
Authors : Jamal Hadi Salim ( 2005 )
2006-09-13 21:24:59 +04:00
2006-01-09 09:34:25 +03:00
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/netdevice.h>
# include <linux/etherdevice.h>
# include <linux/init.h>
# include <linux/moduleparam.h>
2006-09-13 21:24:59 +04:00
# include <net/pkt_sched.h>
2006-01-09 09:34:25 +03:00
# define TX_TIMEOUT (2*HZ)
2006-09-13 21:24:59 +04:00
2006-01-09 09:34:25 +03:00
# define TX_Q_LIMIT 32
struct ifb_private {
struct net_device_stats stats ;
struct tasklet_struct ifb_tasklet ;
int tasklet_pending ;
/* mostly debug stats leave in for now */
unsigned long st_task_enter ; /* tasklet entered */
unsigned long st_txq_refl_try ; /* transmit queue refill attempt */
unsigned long st_rxq_enter ; /* receive queue entered */
unsigned long st_rx2tx_tran ; /* receive to trasmit transfers */
unsigned long st_rxq_notenter ; /*receiveQ not entered, resched */
unsigned long st_rx_frm_egr ; /* received from egress path */
unsigned long st_rx_frm_ing ; /* received from ingress path */
unsigned long st_rxq_check ;
unsigned long st_rxq_rsch ;
struct sk_buff_head rq ;
struct sk_buff_head tq ;
} ;
2006-02-24 03:23:51 +03:00
static int numifbs = 2 ;
2006-01-09 09:34:25 +03:00
static void ri_tasklet ( unsigned long dev ) ;
static int ifb_xmit ( struct sk_buff * skb , struct net_device * dev ) ;
static struct net_device_stats * ifb_get_stats ( struct net_device * dev ) ;
static int ifb_open ( struct net_device * dev ) ;
static int ifb_close ( struct net_device * dev ) ;
2006-09-13 21:24:59 +04:00
static void ri_tasklet ( unsigned long dev )
2006-01-09 09:34:25 +03:00
{
struct net_device * _dev = ( struct net_device * ) dev ;
struct ifb_private * dp = netdev_priv ( _dev ) ;
struct net_device_stats * stats = & dp - > stats ;
struct sk_buff * skb ;
dp - > st_task_enter + + ;
if ( ( skb = skb_peek ( & dp - > tq ) ) = = NULL ) {
dp - > st_txq_refl_try + + ;
2006-06-09 23:20:56 +04:00
if ( netif_tx_trylock ( _dev ) ) {
2006-01-09 09:34:25 +03:00
dp - > st_rxq_enter + + ;
while ( ( skb = skb_dequeue ( & dp - > rq ) ) ! = NULL ) {
skb_queue_tail ( & dp - > tq , skb ) ;
dp - > st_rx2tx_tran + + ;
}
2006-06-09 23:20:56 +04:00
netif_tx_unlock ( _dev ) ;
2006-01-09 09:34:25 +03:00
} else {
/* reschedule */
dp - > st_rxq_notenter + + ;
goto resched ;
}
}
while ( ( skb = skb_dequeue ( & dp - > tq ) ) ! = NULL ) {
u32 from = G_TC_FROM ( skb - > tc_verd ) ;
skb - > tc_verd = 0 ;
skb - > tc_verd = SET_TC_NCLS ( skb - > tc_verd ) ;
stats - > tx_packets + + ;
stats - > tx_bytes + = skb - > len ;
2007-03-29 22:46:52 +04:00
skb - > dev = __dev_get_by_index ( skb - > iif ) ;
if ( ! skb - > dev ) {
dev_kfree_skb ( skb ) ;
stats - > tx_dropped + + ;
break ;
}
skb - > iif = _dev - > ifindex ;
2006-01-09 09:34:25 +03:00
if ( from & AT_EGRESS ) {
dp - > st_rx_frm_egr + + ;
dev_queue_xmit ( skb ) ;
} else if ( from & AT_INGRESS ) {
dp - > st_rx_frm_ing + + ;
2007-03-29 22:46:52 +04:00
skb_pull ( skb , skb - > dev - > hard_header_len ) ;
2006-01-09 09:34:25 +03:00
netif_rx ( skb ) ;
2007-03-29 22:46:52 +04:00
} else
BUG ( ) ;
2006-01-09 09:34:25 +03:00
}
2006-06-09 23:20:56 +04:00
if ( netif_tx_trylock ( _dev ) ) {
2006-01-09 09:34:25 +03:00
dp - > st_rxq_check + + ;
if ( ( skb = skb_peek ( & dp - > rq ) ) = = NULL ) {
dp - > tasklet_pending = 0 ;
if ( netif_queue_stopped ( _dev ) )
netif_wake_queue ( _dev ) ;
} else {
dp - > st_rxq_rsch + + ;
2006-06-09 23:20:56 +04:00
netif_tx_unlock ( _dev ) ;
2006-01-09 09:34:25 +03:00
goto resched ;
}
2006-06-09 23:20:56 +04:00
netif_tx_unlock ( _dev ) ;
2006-01-09 09:34:25 +03:00
} else {
resched :
dp - > tasklet_pending = 1 ;
tasklet_schedule ( & dp - > ifb_tasklet ) ;
}
}
2007-06-13 23:05:06 +04:00
static void ifb_setup ( struct net_device * dev )
2006-01-09 09:34:25 +03:00
{
/* Initialize the device structure. */
dev - > get_stats = ifb_get_stats ;
dev - > hard_start_xmit = ifb_xmit ;
dev - > open = & ifb_open ;
dev - > stop = & ifb_close ;
2007-06-13 23:05:06 +04:00
dev - > destructor = free_netdev ;
2006-01-09 09:34:25 +03:00
/* Fill in device structure with ethernet-generic values. */
ether_setup ( dev ) ;
dev - > tx_queue_len = TX_Q_LIMIT ;
dev - > change_mtu = NULL ;
dev - > flags | = IFF_NOARP ;
dev - > flags & = ~ IFF_MULTICAST ;
SET_MODULE_OWNER ( dev ) ;
random_ether_addr ( dev - > dev_addr ) ;
}
static int ifb_xmit ( struct sk_buff * skb , struct net_device * dev )
{
struct ifb_private * dp = netdev_priv ( dev ) ;
struct net_device_stats * stats = & dp - > stats ;
int ret = 0 ;
u32 from = G_TC_FROM ( skb - > tc_verd ) ;
2007-01-02 06:39:09 +03:00
stats - > rx_packets + + ;
stats - > rx_bytes + = skb - > len ;
2006-01-09 09:34:25 +03:00
2007-03-29 22:46:52 +04:00
if ( ! ( from & ( AT_INGRESS | AT_EGRESS ) ) | | ! skb - > iif ) {
2006-01-09 09:34:25 +03:00
dev_kfree_skb ( skb ) ;
stats - > rx_dropped + + ;
return ret ;
}
if ( skb_queue_len ( & dp - > rq ) > = dev - > tx_queue_len ) {
netif_stop_queue ( dev ) ;
}
dev - > trans_start = jiffies ;
skb_queue_tail ( & dp - > rq , skb ) ;
if ( ! dp - > tasklet_pending ) {
dp - > tasklet_pending = 1 ;
tasklet_schedule ( & dp - > ifb_tasklet ) ;
}
return ret ;
}
static struct net_device_stats * ifb_get_stats ( struct net_device * dev )
{
struct ifb_private * dp = netdev_priv ( dev ) ;
struct net_device_stats * stats = & dp - > stats ;
pr_debug ( " tasklets stats %ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld:%ld \n " ,
2006-09-13 21:24:59 +04:00
dp - > st_task_enter , dp - > st_txq_refl_try , dp - > st_rxq_enter ,
2006-10-03 12:16:10 +04:00
dp - > st_rx2tx_tran , dp - > st_rxq_notenter , dp - > st_rx_frm_egr ,
dp - > st_rx_frm_ing , dp - > st_rxq_check , dp - > st_rxq_rsch ) ;
2006-01-09 09:34:25 +03:00
return stats ;
}
static int ifb_close ( struct net_device * dev )
{
struct ifb_private * dp = netdev_priv ( dev ) ;
tasklet_kill ( & dp - > ifb_tasklet ) ;
netif_stop_queue ( dev ) ;
skb_queue_purge ( & dp - > rq ) ;
skb_queue_purge ( & dp - > tq ) ;
return 0 ;
}
static int ifb_open ( struct net_device * dev )
{
struct ifb_private * dp = netdev_priv ( dev ) ;
tasklet_init ( & dp - > ifb_tasklet , ri_tasklet , ( unsigned long ) dev ) ;
skb_queue_head_init ( & dp - > rq ) ;
skb_queue_head_init ( & dp - > tq ) ;
netif_start_queue ( dev ) ;
return 0 ;
}
2007-06-13 23:05:06 +04:00
static struct rtnl_link_ops ifb_link_ops __read_mostly = {
. kind = " ifb " ,
. priv_size = sizeof ( struct ifb_private ) ,
. setup = ifb_setup ,
} ;
2007-07-12 06:42:13 +04:00
/* Number of ifb devices to be set up by this module. */
module_param ( numifbs , int , 0 ) ;
MODULE_PARM_DESC ( numifbs , " Number of ifb devices " ) ;
2006-01-09 09:34:25 +03:00
static int __init ifb_init_one ( int index )
{
struct net_device * dev_ifb ;
int err ;
dev_ifb = alloc_netdev ( sizeof ( struct ifb_private ) ,
" ifb%d " , ifb_setup ) ;
if ( ! dev_ifb )
return - ENOMEM ;
2007-06-13 23:05:06 +04:00
err = dev_alloc_name ( dev_ifb , dev_ifb - > name ) ;
if ( err < 0 )
goto err ;
2006-01-09 09:34:25 +03:00
2007-06-13 23:05:06 +04:00
dev_ifb - > rtnl_link_ops = & ifb_link_ops ;
err = register_netdevice ( dev_ifb ) ;
if ( err < 0 )
goto err ;
return 0 ;
2007-06-13 23:04:51 +04:00
2007-06-13 23:05:06 +04:00
err :
free_netdev ( dev_ifb ) ;
return err ;
2006-09-13 21:24:59 +04:00
}
2006-01-09 09:34:25 +03:00
static int __init ifb_init_module ( void )
2006-09-13 21:24:59 +04:00
{
2007-06-13 23:05:06 +04:00
int i , err ;
rtnl_lock ( ) ;
err = __rtnl_link_register ( & ifb_link_ops ) ;
2007-06-13 23:04:51 +04:00
2006-01-09 09:34:25 +03:00
for ( i = 0 ; i < numifbs & & ! err ; i + + )
2006-09-13 21:24:59 +04:00
err = ifb_init_one ( i ) ;
2007-07-12 06:42:13 +04:00
if ( err )
2007-06-13 23:05:06 +04:00
__rtnl_link_unregister ( & ifb_link_ops ) ;
rtnl_unlock ( ) ;
2006-01-09 09:34:25 +03:00
return err ;
2006-09-13 21:24:59 +04:00
}
2006-01-09 09:34:25 +03:00
static void __exit ifb_cleanup_module ( void )
{
2007-07-12 06:42:13 +04:00
rtnl_link_unregister ( & ifb_link_ops ) ;
2006-01-09 09:34:25 +03:00
}
module_init ( ifb_init_module ) ;
module_exit ( ifb_cleanup_module ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Jamal Hadi Salim " ) ;
2007-06-13 23:05:06 +04:00
MODULE_ALIAS_RTNL_LINK ( " ifb " ) ;