2005-04-17 02:20:36 +04:00
/* net/sched/sch_atm.c - ATM VC selection "queueing discipline" */
/* Written 1998-2000 by Werner Almesberger, EPFL ICA */
# include <linux/module.h>
# include <linux/init.h>
# include <linux/string.h>
# include <linux/errno.h>
# include <linux/skbuff.h>
# include <linux/interrupt.h>
# include <linux/atmdev.h>
# include <linux/atmclip.h>
# include <linux/netdevice.h>
# include <linux/rtnetlink.h>
# include <linux/file.h> /* for fput */
# include <net/pkt_sched.h>
# include <net/sock.h>
extern struct socket * sockfd_lookup ( int fd , int * err ) ; /* @@@ fix this */
#if 0 /* control */
# define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
# else
# define DPRINTK(format,args...)
# endif
#if 0 /* data */
# define D2PRINTK(format,args...) printk(KERN_DEBUG format,##args)
# else
# define D2PRINTK(format,args...)
# endif
/*
* The ATM queuing discipline provides a framework for invoking classifiers
* ( aka " filters " ) , which in turn select classes of this queuing discipline .
* Each class maps the flow ( s ) it is handling to a given VC . Multiple classes
* may share the same VC .
*
* When creating a class , VCs are specified by passing the number of the open
* socket descriptor by which the calling process references the VC . The kernel
* keeps the VC open at least until all classes using it are removed .
*
* In this file , most functions are named atm_tc_ * to avoid confusion with all
* the atm_ * in net / atm . This naming convention differs from what ' s used in the
* rest of net / sched .
*
* Known bugs :
* - sometimes messes up the IP stack
* - any manipulations besides the few operations described in the README , are
* untested and likely to crash the system
* - should lock the flow while there is data in the queue ( ? )
*/
# define PRIV(sch) qdisc_priv(sch)
# define VCC2FLOW(vcc) ((struct atm_flow_data *) ((vcc)->user_back))
struct atm_flow_data {
struct Qdisc * q ; /* FIFO, TBF, etc. */
struct tcf_proto * filter_list ;
struct atm_vcc * vcc ; /* VCC; NULL if VCC is closed */
void ( * old_pop ) ( struct atm_vcc * vcc , struct sk_buff * skb ) ; /* chaining */
struct atm_qdisc_data * parent ; /* parent qdisc */
struct socket * sock ; /* for closing */
u32 classid ; /* x:y type ID */
int ref ; /* reference count */
struct gnet_stats_basic bstats ;
struct gnet_stats_queue qstats ;
spinlock_t * stats_lock ;
struct atm_flow_data * next ;
struct atm_flow_data * excess ; /* flow for excess traffic;
NULL to set CLP instead */
int hdr_len ;
unsigned char hdr [ 0 ] ; /* header data; MUST BE LAST */
} ;
struct atm_qdisc_data {
struct atm_flow_data link ; /* unclassified skbs go here */
struct atm_flow_data * flows ; /* NB: "link" is also on this
list */
struct tasklet_struct task ; /* requeue tasklet */
} ;
/* ------------------------- Class/flow operations ------------------------- */
static int find_flow ( struct atm_qdisc_data * qdisc , struct atm_flow_data * flow )
{
struct atm_flow_data * walk ;
DPRINTK ( " find_flow(qdisc %p,flow %p) \n " , qdisc , flow ) ;
for ( walk = qdisc - > flows ; walk ; walk = walk - > next )
if ( walk = = flow ) return 1 ;
DPRINTK ( " find_flow: not found \n " ) ;
return 0 ;
}
static __inline__ struct atm_flow_data * lookup_flow ( struct Qdisc * sch ,
u32 classid )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow ;
for ( flow = p - > flows ; flow ; flow = flow - > next )
if ( flow - > classid = = classid ) break ;
return flow ;
}
static int atm_tc_graft ( struct Qdisc * sch , unsigned long arg ,
struct Qdisc * new , struct Qdisc * * old )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow = ( struct atm_flow_data * ) arg ;
DPRINTK ( " atm_tc_graft(sch %p,[qdisc %p],flow %p,new %p,old %p) \n " , sch ,
p , flow , new , old ) ;
if ( ! find_flow ( p , flow ) ) return - EINVAL ;
if ( ! new ) new = & noop_qdisc ;
* old = xchg ( & flow - > q , new ) ;
if ( * old ) qdisc_reset ( * old ) ;
return 0 ;
}
static struct Qdisc * atm_tc_leaf ( struct Qdisc * sch , unsigned long cl )
{
struct atm_flow_data * flow = ( struct atm_flow_data * ) cl ;
DPRINTK ( " atm_tc_leaf(sch %p,flow %p) \n " , sch , flow ) ;
return flow ? flow - > q : NULL ;
}
static unsigned long atm_tc_get ( struct Qdisc * sch , u32 classid )
{
struct atm_qdisc_data * p __attribute__ ( ( unused ) ) = PRIV ( sch ) ;
struct atm_flow_data * flow ;
DPRINTK ( " atm_tc_get(sch %p,[qdisc %p],classid %x) \n " , sch , p , classid ) ;
flow = lookup_flow ( sch , classid ) ;
if ( flow ) flow - > ref + + ;
DPRINTK ( " atm_tc_get: flow %p \n " , flow ) ;
return ( unsigned long ) flow ;
}
static unsigned long atm_tc_bind_filter ( struct Qdisc * sch ,
unsigned long parent , u32 classid )
{
return atm_tc_get ( sch , classid ) ;
}
static void destroy_filters ( struct atm_flow_data * flow )
{
struct tcf_proto * filter ;
while ( ( filter = flow - > filter_list ) ) {
DPRINTK ( " destroy_filters: destroying filter %p \n " , filter ) ;
flow - > filter_list = filter - > next ;
tcf_destroy ( filter ) ;
}
}
/*
* atm_tc_put handles all destructions , including the ones that are explicitly
* requested ( atm_tc_destroy , etc . ) . The assumption here is that we never drop
* anything that still seems to be in use .
*/
static void atm_tc_put ( struct Qdisc * sch , unsigned long cl )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow = ( struct atm_flow_data * ) cl ;
struct atm_flow_data * * prev ;
DPRINTK ( " atm_tc_put(sch %p,[qdisc %p],flow %p) \n " , sch , p , flow ) ;
if ( - - flow - > ref ) return ;
DPRINTK ( " atm_tc_put: destroying \n " ) ;
for ( prev = & p - > flows ; * prev ; prev = & ( * prev ) - > next )
if ( * prev = = flow ) break ;
if ( ! * prev ) {
printk ( KERN_CRIT " atm_tc_put: class %p not found \n " , flow ) ;
return ;
}
* prev = flow - > next ;
DPRINTK ( " atm_tc_put: qdisc %p \n " , flow - > q ) ;
qdisc_destroy ( flow - > q ) ;
destroy_filters ( flow ) ;
if ( flow - > sock ) {
DPRINTK ( " atm_tc_put: f_count %d \n " ,
file_count ( flow - > sock - > file ) ) ;
flow - > vcc - > pop = flow - > old_pop ;
sockfd_put ( flow - > sock ) ;
}
if ( flow - > excess ) atm_tc_put ( sch , ( unsigned long ) flow - > excess ) ;
if ( flow ! = & p - > link ) kfree ( flow ) ;
/*
* If flow = = & p - > link , the qdisc no longer works at this point and
* needs to be removed . ( By the caller of atm_tc_put . )
*/
}
static void sch_atm_pop ( struct atm_vcc * vcc , struct sk_buff * skb )
{
struct atm_qdisc_data * p = VCC2FLOW ( vcc ) - > parent ;
D2PRINTK ( " sch_atm_pop(vcc %p,skb %p,[qdisc %p]) \n " , vcc , skb , p ) ;
VCC2FLOW ( vcc ) - > old_pop ( vcc , skb ) ;
tasklet_schedule ( & p - > task ) ;
}
static const u8 llc_oui_ip [ ] = {
0xaa , /* DSAP: non-ISO */
0xaa , /* SSAP: non-ISO */
0x03 , /* Ctrl: Unnumbered Information Command PDU */
0x00 , /* OUI: EtherType */
0x00 , 0x00 ,
0x08 , 0x00 } ; /* Ethertype IP (0800) */
static int atm_tc_change ( struct Qdisc * sch , u32 classid , u32 parent ,
struct rtattr * * tca , unsigned long * arg )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow = ( struct atm_flow_data * ) * arg ;
struct atm_flow_data * excess = NULL ;
struct rtattr * opt = tca [ TCA_OPTIONS - 1 ] ;
struct rtattr * tb [ TCA_ATM_MAX ] ;
struct socket * sock ;
int fd , error , hdr_len ;
void * hdr ;
DPRINTK ( " atm_tc_change(sch %p,[qdisc %p],classid %x,parent %x, "
" flow %p,opt %p) \n " , sch , p , classid , parent , flow , opt ) ;
/*
* The concept of parents doesn ' t apply for this qdisc .
*/
if ( parent & & parent ! = TC_H_ROOT & & parent ! = sch - > handle )
return - EINVAL ;
/*
* ATM classes cannot be changed . In order to change properties of the
* ATM connection , that socket needs to be modified directly ( via the
* native ATM API . In order to send a flow to a different VC , the old
* class needs to be removed and a new one added . ( This may be changed
* later . )
*/
if ( flow ) return - EBUSY ;
if ( opt = = NULL | | rtattr_parse_nested ( tb , TCA_ATM_MAX , opt ) )
return - EINVAL ;
if ( ! tb [ TCA_ATM_FD - 1 ] | | RTA_PAYLOAD ( tb [ TCA_ATM_FD - 1 ] ) < sizeof ( fd ) )
return - EINVAL ;
fd = * ( int * ) RTA_DATA ( tb [ TCA_ATM_FD - 1 ] ) ;
DPRINTK ( " atm_tc_change: fd %d \n " , fd ) ;
if ( tb [ TCA_ATM_HDR - 1 ] ) {
hdr_len = RTA_PAYLOAD ( tb [ TCA_ATM_HDR - 1 ] ) ;
hdr = RTA_DATA ( tb [ TCA_ATM_HDR - 1 ] ) ;
}
else {
hdr_len = RFC1483LLC_LEN ;
hdr = NULL ; /* default LLC/SNAP for IP */
}
if ( ! tb [ TCA_ATM_EXCESS - 1 ] ) excess = NULL ;
else {
if ( RTA_PAYLOAD ( tb [ TCA_ATM_EXCESS - 1 ] ) ! = sizeof ( u32 ) )
return - EINVAL ;
excess = ( struct atm_flow_data * ) atm_tc_get ( sch ,
* ( u32 * ) RTA_DATA ( tb [ TCA_ATM_EXCESS - 1 ] ) ) ;
if ( ! excess ) return - ENOENT ;
}
DPRINTK ( " atm_tc_change: type %d, payload %d, hdr_len %d \n " ,
opt - > rta_type , RTA_PAYLOAD ( opt ) , hdr_len ) ;
if ( ! ( sock = sockfd_lookup ( fd , & error ) ) ) return error ; /* f_count++ */
DPRINTK ( " atm_tc_change: f_count %d \n " , file_count ( sock - > file ) ) ;
if ( sock - > ops - > family ! = PF_ATMSVC & & sock - > ops - > family ! = PF_ATMPVC ) {
error = - EPROTOTYPE ;
goto err_out ;
}
/* @@@ should check if the socket is really operational or we'll crash
on vcc - > send */
if ( classid ) {
if ( TC_H_MAJ ( classid ^ sch - > handle ) ) {
DPRINTK ( " atm_tc_change: classid mismatch \n " ) ;
error = - EINVAL ;
goto err_out ;
}
if ( find_flow ( p , flow ) ) {
error = - EEXIST ;
goto err_out ;
}
}
else {
int i ;
unsigned long cl ;
for ( i = 1 ; i < 0x8000 ; i + + ) {
classid = TC_H_MAKE ( sch - > handle , 0x8000 | i ) ;
if ( ! ( cl = atm_tc_get ( sch , classid ) ) ) break ;
atm_tc_put ( sch , cl ) ;
}
}
DPRINTK ( " atm_tc_change: new id %x \n " , classid ) ;
flow = kmalloc ( sizeof ( struct atm_flow_data ) + hdr_len , GFP_KERNEL ) ;
DPRINTK ( " atm_tc_change: flow %p \n " , flow ) ;
if ( ! flow ) {
error = - ENOBUFS ;
goto err_out ;
}
memset ( flow , 0 , sizeof ( * flow ) ) ;
flow - > filter_list = NULL ;
2006-11-30 04:35:18 +03:00
if ( ! ( flow - > q = qdisc_create_dflt ( sch - > dev , & pfifo_qdisc_ops , classid ) ) )
2005-04-17 02:20:36 +04:00
flow - > q = & noop_qdisc ;
DPRINTK ( " atm_tc_change: qdisc %p \n " , flow - > q ) ;
flow - > sock = sock ;
flow - > vcc = ATM_SD ( sock ) ; /* speedup */
flow - > vcc - > user_back = flow ;
DPRINTK ( " atm_tc_change: vcc %p \n " , flow - > vcc ) ;
flow - > old_pop = flow - > vcc - > pop ;
flow - > parent = p ;
flow - > vcc - > pop = sch_atm_pop ;
flow - > classid = classid ;
flow - > ref = 1 ;
flow - > excess = excess ;
flow - > next = p - > link . next ;
p - > link . next = flow ;
flow - > hdr_len = hdr_len ;
if ( hdr )
memcpy ( flow - > hdr , hdr , hdr_len ) ;
else
memcpy ( flow - > hdr , llc_oui_ip , sizeof ( llc_oui_ip ) ) ;
* arg = ( unsigned long ) flow ;
return 0 ;
err_out :
if ( excess ) atm_tc_put ( sch , ( unsigned long ) excess ) ;
sockfd_put ( sock ) ;
return error ;
}
static int atm_tc_delete ( struct Qdisc * sch , unsigned long arg )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow = ( struct atm_flow_data * ) arg ;
DPRINTK ( " atm_tc_delete(sch %p,[qdisc %p],flow %p) \n " , sch , p , flow ) ;
if ( ! find_flow ( PRIV ( sch ) , flow ) ) return - EINVAL ;
if ( flow - > filter_list | | flow = = & p - > link ) return - EBUSY ;
/*
* Reference count must be 2 : one for " keepalive " ( set at class
* creation ) , and one for the reference held when calling delete .
*/
if ( flow - > ref < 2 ) {
printk ( KERN_ERR " atm_tc_delete: flow->ref == %d \n " , flow - > ref ) ;
return - EINVAL ;
}
if ( flow - > ref > 2 ) return - EBUSY ; /* catch references via excess, etc.*/
atm_tc_put ( sch , arg ) ;
return 0 ;
}
static void atm_tc_walk ( struct Qdisc * sch , struct qdisc_walker * walker )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow ;
DPRINTK ( " atm_tc_walk(sch %p,[qdisc %p],walker %p) \n " , sch , p , walker ) ;
if ( walker - > stop ) return ;
for ( flow = p - > flows ; flow ; flow = flow - > next ) {
if ( walker - > count > = walker - > skip )
if ( walker - > fn ( sch , ( unsigned long ) flow , walker ) < 0 ) {
walker - > stop = 1 ;
break ;
}
walker - > count + + ;
}
}
static struct tcf_proto * * atm_tc_find_tcf ( struct Qdisc * sch , unsigned long cl )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow = ( struct atm_flow_data * ) cl ;
DPRINTK ( " atm_tc_find_tcf(sch %p,[qdisc %p],flow %p) \n " , sch , p , flow ) ;
return flow ? & flow - > filter_list : & p - > link . filter_list ;
}
/* --------------------------- Qdisc operations ---------------------------- */
static int atm_tc_enqueue ( struct sk_buff * skb , struct Qdisc * sch )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow = NULL ; /* @@@ */
struct tcf_result res ;
int result ;
int ret = NET_XMIT_POLICED ;
D2PRINTK ( " atm_tc_enqueue(skb %p,sch %p,[qdisc %p]) \n " , skb , sch , p ) ;
result = TC_POLICE_OK ; /* be nice to gcc */
if ( TC_H_MAJ ( skb - > priority ) ! = sch - > handle | |
! ( flow = ( struct atm_flow_data * ) atm_tc_get ( sch , skb - > priority ) ) )
for ( flow = p - > flows ; flow ; flow = flow - > next )
if ( flow - > filter_list ) {
result = tc_classify ( skb , flow - > filter_list ,
& res ) ;
if ( result < 0 ) continue ;
flow = ( struct atm_flow_data * ) res . class ;
if ( ! flow ) flow = lookup_flow ( sch , res . classid ) ;
break ;
}
if ( ! flow ) flow = & p - > link ;
else {
if ( flow - > vcc )
ATM_SKB ( skb ) - > atm_options = flow - > vcc - > atm_options ;
/*@@@ looks good ... but it's not supposed to work :-)*/
# ifdef CONFIG_NET_CLS_POLICE
switch ( result ) {
case TC_POLICE_SHOT :
kfree_skb ( skb ) ;
break ;
case TC_POLICE_RECLASSIFY :
if ( flow - > excess ) flow = flow - > excess ;
else {
ATM_SKB ( skb ) - > atm_options | =
ATM_ATMOPT_CLP ;
break ;
}
/* fall through */
case TC_POLICE_OK :
/* fall through */
default :
break ;
}
# endif
}
if (
# ifdef CONFIG_NET_CLS_POLICE
result = = TC_POLICE_SHOT | |
# endif
( ret = flow - > q - > enqueue ( skb , flow - > q ) ) ! = 0 ) {
sch - > qstats . drops + + ;
if ( flow ) flow - > qstats . drops + + ;
return ret ;
}
sch - > bstats . bytes + = skb - > len ;
sch - > bstats . packets + + ;
flow - > bstats . bytes + = skb - > len ;
flow - > bstats . packets + + ;
/*
* Okay , this may seem weird . We pretend we ' ve dropped the packet if
* it goes via ATM . The reason for this is that the outer qdisc
* expects to be able to q - > dequeue the packet later on if we return
* success at this place . Also , sch - > q . qdisc needs to reflect whether
* there is a packet egligible for dequeuing or not . Note that the
* statistics of the outer qdisc are necessarily wrong because of all
* this . There ' s currently no correct solution for this .
*/
if ( flow = = & p - > link ) {
sch - > q . qlen + + ;
return 0 ;
}
tasklet_schedule ( & p - > task ) ;
return NET_XMIT_BYPASS ;
}
/*
* Dequeue packets and send them over ATM . Note that we quite deliberately
* avoid checking net_device ' s flow control here , simply because sch_atm
* uses its own channels , which have nothing to do with any CLIP / LANE / or
* non - ATM interfaces .
*/
static void sch_atm_dequeue ( unsigned long data )
{
struct Qdisc * sch = ( struct Qdisc * ) data ;
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow ;
struct sk_buff * skb ;
D2PRINTK ( " sch_atm_dequeue(sch %p,[qdisc %p]) \n " , sch , p ) ;
for ( flow = p - > link . next ; flow ; flow = flow - > next )
/*
* If traffic is properly shaped , this won ' t generate nasty
* little bursts . Otherwise , it may . . . ( but that ' s okay )
*/
while ( ( skb = flow - > q - > dequeue ( flow - > q ) ) ) {
if ( ! atm_may_send ( flow - > vcc , skb - > truesize ) ) {
( void ) flow - > q - > ops - > requeue ( skb , flow - > q ) ;
break ;
}
D2PRINTK ( " atm_tc_dequeue: sending on class %p \n " , flow ) ;
/* remove any LL header somebody else has attached */
skb_pull ( skb , ( char * ) skb - > nh . iph - ( char * ) skb - > data ) ;
if ( skb_headroom ( skb ) < flow - > hdr_len ) {
struct sk_buff * new ;
new = skb_realloc_headroom ( skb , flow - > hdr_len ) ;
dev_kfree_skb ( skb ) ;
if ( ! new ) continue ;
skb = new ;
}
D2PRINTK ( " sch_atm_dequeue: ip %p, data %p \n " ,
skb - > nh . iph , skb - > data ) ;
ATM_SKB ( skb ) - > vcc = flow - > vcc ;
memcpy ( skb_push ( skb , flow - > hdr_len ) , flow - > hdr ,
flow - > hdr_len ) ;
atomic_add ( skb - > truesize ,
& sk_atm ( flow - > vcc ) - > sk_wmem_alloc ) ;
/* atm.atm_options are already set by atm_tc_enqueue */
( void ) flow - > vcc - > send ( flow - > vcc , skb ) ;
}
}
static struct sk_buff * atm_tc_dequeue ( struct Qdisc * sch )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct sk_buff * skb ;
D2PRINTK ( " atm_tc_dequeue(sch %p,[qdisc %p]) \n " , sch , p ) ;
tasklet_schedule ( & p - > task ) ;
skb = p - > link . q - > dequeue ( p - > link . q ) ;
if ( skb ) sch - > q . qlen - - ;
return skb ;
}
static int atm_tc_requeue ( struct sk_buff * skb , struct Qdisc * sch )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
int ret ;
D2PRINTK ( " atm_tc_requeue(skb %p,sch %p,[qdisc %p]) \n " , skb , sch , p ) ;
ret = p - > link . q - > ops - > requeue ( skb , p - > link . q ) ;
if ( ! ret ) {
sch - > q . qlen + + ;
sch - > qstats . requeues + + ;
} else {
sch - > qstats . drops + + ;
p - > link . qstats . drops + + ;
}
return ret ;
}
static unsigned int atm_tc_drop ( struct Qdisc * sch )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow ;
unsigned int len ;
DPRINTK ( " atm_tc_drop(sch %p,[qdisc %p]) \n " , sch , p ) ;
for ( flow = p - > flows ; flow ; flow = flow - > next )
if ( flow - > q - > ops - > drop & & ( len = flow - > q - > ops - > drop ( flow - > q ) ) )
return len ;
return 0 ;
}
static int atm_tc_init ( struct Qdisc * sch , struct rtattr * opt )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
DPRINTK ( " atm_tc_init(sch %p,[qdisc %p],opt %p) \n " , sch , p , opt ) ;
p - > flows = & p - > link ;
2006-11-30 04:35:18 +03:00
if ( ! ( p - > link . q = qdisc_create_dflt ( sch - > dev , & pfifo_qdisc_ops ,
sch - > handle ) ) )
2005-04-17 02:20:36 +04:00
p - > link . q = & noop_qdisc ;
DPRINTK ( " atm_tc_init: link (%p) qdisc %p \n " , & p - > link , p - > link . q ) ;
p - > link . filter_list = NULL ;
p - > link . vcc = NULL ;
p - > link . sock = NULL ;
p - > link . classid = sch - > handle ;
p - > link . ref = 1 ;
p - > link . next = NULL ;
tasklet_init ( & p - > task , sch_atm_dequeue , ( unsigned long ) sch ) ;
return 0 ;
}
static void atm_tc_reset ( struct Qdisc * sch )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow ;
DPRINTK ( " atm_tc_reset(sch %p,[qdisc %p]) \n " , sch , p ) ;
for ( flow = p - > flows ; flow ; flow = flow - > next ) qdisc_reset ( flow - > q ) ;
sch - > q . qlen = 0 ;
}
static void atm_tc_destroy ( struct Qdisc * sch )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow ;
DPRINTK ( " atm_tc_destroy(sch %p,[qdisc %p]) \n " , sch , p ) ;
/* races ? */
while ( ( flow = p - > flows ) ) {
destroy_filters ( flow ) ;
if ( flow - > ref > 1 )
printk ( KERN_ERR " atm_destroy: %p->ref = %d \n " , flow ,
flow - > ref ) ;
atm_tc_put ( sch , ( unsigned long ) flow ) ;
if ( p - > flows = = flow ) {
printk ( KERN_ERR " atm_destroy: putting flow %p didn't "
" kill it \n " , flow ) ;
p - > flows = flow - > next ; /* brute force */
break ;
}
}
tasklet_kill ( & p - > task ) ;
}
static int atm_tc_dump_class ( struct Qdisc * sch , unsigned long cl ,
struct sk_buff * skb , struct tcmsg * tcm )
{
struct atm_qdisc_data * p = PRIV ( sch ) ;
struct atm_flow_data * flow = ( struct atm_flow_data * ) cl ;
unsigned char * b = skb - > tail ;
struct rtattr * rta ;
DPRINTK ( " atm_tc_dump_class(sch %p,[qdisc %p],flow %p,skb %p,tcm %p) \n " ,
sch , p , flow , skb , tcm ) ;
if ( ! find_flow ( p , flow ) ) return - EINVAL ;
tcm - > tcm_handle = flow - > classid ;
2006-03-21 06:01:06 +03:00
tcm - > tcm_info = flow - > q - > handle ;
2005-04-17 02:20:36 +04:00
rta = ( struct rtattr * ) b ;
RTA_PUT ( skb , TCA_OPTIONS , 0 , NULL ) ;
RTA_PUT ( skb , TCA_ATM_HDR , flow - > hdr_len , flow - > hdr ) ;
if ( flow - > vcc ) {
struct sockaddr_atmpvc pvc ;
int state ;
pvc . sap_family = AF_ATMPVC ;
pvc . sap_addr . itf = flow - > vcc - > dev ? flow - > vcc - > dev - > number : - 1 ;
pvc . sap_addr . vpi = flow - > vcc - > vpi ;
pvc . sap_addr . vci = flow - > vcc - > vci ;
RTA_PUT ( skb , TCA_ATM_ADDR , sizeof ( pvc ) , & pvc ) ;
state = ATM_VF2VS ( flow - > vcc - > flags ) ;
RTA_PUT ( skb , TCA_ATM_STATE , sizeof ( state ) , & state ) ;
}
if ( flow - > excess )
RTA_PUT ( skb , TCA_ATM_EXCESS , sizeof ( u32 ) , & flow - > classid ) ;
else {
static u32 zero ;
RTA_PUT ( skb , TCA_ATM_EXCESS , sizeof ( zero ) , & zero ) ;
}
rta - > rta_len = skb - > tail - b ;
return skb - > len ;
rtattr_failure :
skb_trim ( skb , b - skb - > data ) ;
return - 1 ;
}
static int
atm_tc_dump_class_stats ( struct Qdisc * sch , unsigned long arg ,
struct gnet_dump * d )
{
struct atm_flow_data * flow = ( struct atm_flow_data * ) arg ;
flow - > qstats . qlen = flow - > q - > q . qlen ;
if ( gnet_stats_copy_basic ( d , & flow - > bstats ) < 0 | |
gnet_stats_copy_queue ( d , & flow - > qstats ) < 0 )
return - 1 ;
return 0 ;
}
static int atm_tc_dump ( struct Qdisc * sch , struct sk_buff * skb )
{
return 0 ;
}
static struct Qdisc_class_ops atm_class_ops = {
. graft = atm_tc_graft ,
. leaf = atm_tc_leaf ,
. get = atm_tc_get ,
. put = atm_tc_put ,
. change = atm_tc_change ,
. delete = atm_tc_delete ,
. walk = atm_tc_walk ,
. tcf_chain = atm_tc_find_tcf ,
. bind_tcf = atm_tc_bind_filter ,
. unbind_tcf = atm_tc_put ,
. dump = atm_tc_dump_class ,
. dump_stats = atm_tc_dump_class_stats ,
} ;
static struct Qdisc_ops atm_qdisc_ops = {
. next = NULL ,
. cl_ops = & atm_class_ops ,
. id = " atm " ,
. priv_size = sizeof ( struct atm_qdisc_data ) ,
. enqueue = atm_tc_enqueue ,
. dequeue = atm_tc_dequeue ,
. requeue = atm_tc_requeue ,
. drop = atm_tc_drop ,
. init = atm_tc_init ,
. reset = atm_tc_reset ,
. destroy = atm_tc_destroy ,
. change = NULL ,
. dump = atm_tc_dump ,
. owner = THIS_MODULE ,
} ;
static int __init atm_init ( void )
{
return register_qdisc ( & atm_qdisc_ops ) ;
}
static void __exit atm_exit ( void )
{
unregister_qdisc ( & atm_qdisc_ops ) ;
}
module_init ( atm_init )
module_exit ( atm_exit )
MODULE_LICENSE ( " GPL " ) ;