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/atmdev.h>
# include <linux/atmclip.h>
# include <linux/rtnetlink.h>
2007-07-15 11:01:25 +04:00
# include <linux/file.h> /* for fput */
2007-03-26 10:06:12 +04:00
# include <net/netlink.h>
2005-04-17 02:20:36 +04:00
# include <net/pkt_sched.h>
2007-07-15 11:01:25 +04:00
extern struct socket * sockfd_lookup ( int fd , int * err ) ; /* @@@ fix this */
2005-04-17 02:20:36 +04:00
/*
* 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 VCC2FLOW(vcc) ((struct atm_flow_data *) ((vcc)->user_back))
struct atm_flow_data {
2007-07-15 11:01:25 +04:00
struct Qdisc * q ; /* FIFO, TBF, etc. */
2005-04-17 02:20:36 +04:00
struct tcf_proto * filter_list ;
2007-07-15 11:01:25 +04:00
struct atm_vcc * vcc ; /* VCC; NULL if VCC is closed */
void ( * old_pop ) ( struct atm_vcc * vcc ,
2008-01-21 13:25:29 +03:00
struct sk_buff * skb ) ; /* chaining */
2005-04-17 02:20:36 +04:00
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 ;
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 */
2008-11-14 09:56:30 +03:00
struct tasklet_struct task ; /* dequeue tasklet */
2005-04-17 02:20:36 +04:00
} ;
/* ------------------------- Class/flow operations ------------------------- */
2007-07-15 11:01:25 +04:00
static int find_flow ( struct atm_qdisc_data * qdisc , struct atm_flow_data * flow )
2005-04-17 02:20:36 +04:00
{
struct atm_flow_data * walk ;
2008-01-21 13:25:29 +03:00
pr_debug ( " find_flow(qdisc %p,flow %p) \n " , qdisc , flow ) ;
2005-04-17 02:20:36 +04:00
for ( walk = qdisc - > flows ; walk ; walk = walk - > next )
2007-07-15 11:01:25 +04:00
if ( walk = = flow )
return 1 ;
2008-01-21 13:25:29 +03:00
pr_debug ( " find_flow: not found \n " ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-07-15 11:01:25 +04:00
static inline struct atm_flow_data * lookup_flow ( struct Qdisc * sch , u32 classid )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2005-04-17 02:20:36 +04:00
struct atm_flow_data * flow ;
2007-02-09 17:25:16 +03:00
for ( flow = p - > flows ; flow ; flow = flow - > next )
2007-07-15 11:01:25 +04:00
if ( flow - > classid = = classid )
break ;
2005-04-17 02:20:36 +04:00
return flow ;
}
2007-07-15 11:01:25 +04:00
static int atm_tc_graft ( struct Qdisc * sch , unsigned long arg ,
struct Qdisc * new , struct Qdisc * * old )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2007-07-15 11:01:25 +04:00
struct atm_flow_data * flow = ( struct atm_flow_data * ) arg ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_graft(sch %p,[qdisc %p],flow %p,new %p,old %p) \n " ,
2007-07-15 11:01:25 +04:00
sch , p , flow , new , old ) ;
if ( ! find_flow ( p , flow ) )
return - EINVAL ;
if ( ! new )
new = & noop_qdisc ;
2008-11-20 15:11:36 +03:00
* old = flow - > q ;
flow - > q = new ;
2007-07-15 11:01:25 +04:00
if ( * old )
qdisc_reset ( * old ) ;
2007-02-09 17:25:16 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2007-07-15 11:01:25 +04:00
static struct Qdisc * atm_tc_leaf ( struct Qdisc * sch , unsigned long cl )
2005-04-17 02:20:36 +04:00
{
2007-07-15 11:01:25 +04:00
struct atm_flow_data * flow = ( struct atm_flow_data * ) cl ;
2005-04-17 02:20:36 +04:00
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_leaf(sch %p,flow %p) \n " , sch , flow ) ;
2005-04-17 02:20:36 +04:00
return flow ? flow - > q : NULL ;
}
2007-07-15 11:01:25 +04:00
static unsigned long atm_tc_get ( struct Qdisc * sch , u32 classid )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p __maybe_unused = qdisc_priv ( sch ) ;
2005-04-17 02:20:36 +04:00
struct atm_flow_data * flow ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_get(sch %p,[qdisc %p],classid %x) \n " , sch , p , classid ) ;
2007-07-15 11:01:25 +04:00
flow = lookup_flow ( sch , classid ) ;
if ( flow )
flow - > ref + + ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_get: flow %p \n " , flow ) ;
2007-07-15 11:01:25 +04:00
return ( unsigned long ) flow ;
2005-04-17 02:20:36 +04:00
}
static unsigned long atm_tc_bind_filter ( struct Qdisc * sch ,
2007-07-15 11:01:25 +04:00
unsigned long parent , u32 classid )
2005-04-17 02:20:36 +04:00
{
2007-07-15 11:01:25 +04:00
return atm_tc_get ( sch , classid ) ;
2005-04-17 02:20:36 +04:00
}
/*
* 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 )
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2007-07-15 11:01:25 +04:00
struct atm_flow_data * flow = ( struct atm_flow_data * ) cl ;
2005-04-17 02:20:36 +04:00
struct atm_flow_data * * prev ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_put(sch %p,[qdisc %p],flow %p) \n " , sch , p , flow ) ;
2007-07-15 11:01:25 +04:00
if ( - - flow - > ref )
return ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_put: destroying \n " ) ;
2005-04-17 02:20:36 +04:00
for ( prev = & p - > flows ; * prev ; prev = & ( * prev ) - > next )
2007-07-15 11:01:25 +04:00
if ( * prev = = flow )
break ;
2005-04-17 02:20:36 +04:00
if ( ! * prev ) {
2007-07-15 11:01:25 +04:00
printk ( KERN_CRIT " atm_tc_put: class %p not found \n " , flow ) ;
2005-04-17 02:20:36 +04:00
return ;
}
* prev = flow - > next ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_put: qdisc %p \n " , flow - > q ) ;
2005-04-17 02:20:36 +04:00
qdisc_destroy ( flow - > q ) ;
2008-07-02 06:52:38 +04:00
tcf_destroy_chain ( & flow - > filter_list ) ;
2005-04-17 02:20:36 +04:00
if ( flow - > sock ) {
2008-07-26 08:39:17 +04:00
pr_debug ( " atm_tc_put: f_count %ld \n " ,
2007-07-15 11:01:25 +04:00
file_count ( flow - > sock - > file ) ) ;
2005-04-17 02:20:36 +04:00
flow - > vcc - > pop = flow - > old_pop ;
sockfd_put ( flow - > sock ) ;
}
2007-07-15 11:01:25 +04:00
if ( flow - > excess )
atm_tc_put ( sch , ( unsigned long ) flow - > excess ) ;
if ( flow ! = & p - > link )
kfree ( flow ) ;
2005-04-17 02:20:36 +04:00
/*
* If flow = = & p - > link , the qdisc no longer works at this point and
* needs to be removed . ( By the caller of atm_tc_put . )
*/
}
2007-07-15 11:01:25 +04:00
static void sch_atm_pop ( struct atm_vcc * vcc , struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
struct atm_qdisc_data * p = VCC2FLOW ( vcc ) - > parent ;
2008-01-21 13:25:29 +03:00
pr_debug ( " sch_atm_pop(vcc %p,skb %p,[qdisc %p]) \n " , vcc , skb , p ) ;
2007-07-15 11:01:25 +04:00
VCC2FLOW ( vcc ) - > old_pop ( vcc , skb ) ;
2005-04-17 02:20:36 +04:00
tasklet_schedule ( & p - > task ) ;
}
static const u8 llc_oui_ip [ ] = {
2007-07-15 11:01:25 +04:00
0xaa , /* DSAP: non-ISO */
0xaa , /* SSAP: non-ISO */
0x03 , /* Ctrl: Unnumbered Information Command PDU */
0x00 , /* OUI: EtherType */
2005-04-17 02:20:36 +04:00
0x00 , 0x00 ,
2007-07-15 11:01:25 +04:00
0x08 , 0x00
} ; /* Ethertype IP (0800) */
2005-04-17 02:20:36 +04:00
2008-01-24 07:35:39 +03:00
static const struct nla_policy atm_policy [ TCA_ATM_MAX + 1 ] = {
[ TCA_ATM_FD ] = { . type = NLA_U32 } ,
[ TCA_ATM_EXCESS ] = { . type = NLA_U32 } ,
} ;
2005-04-17 02:20:36 +04:00
static int atm_tc_change ( struct Qdisc * sch , u32 classid , u32 parent ,
2008-01-23 09:11:17 +03:00
struct nlattr * * tca , unsigned long * arg )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2007-07-15 11:01:25 +04:00
struct atm_flow_data * flow = ( struct atm_flow_data * ) * arg ;
2005-04-17 02:20:36 +04:00
struct atm_flow_data * excess = NULL ;
2008-01-23 09:11:17 +03:00
struct nlattr * opt = tca [ TCA_OPTIONS ] ;
struct nlattr * tb [ TCA_ATM_MAX + 1 ] ;
2005-04-17 02:20:36 +04:00
struct socket * sock ;
2007-07-15 11:01:25 +04:00
int fd , error , hdr_len ;
2005-04-17 02:20:36 +04:00
void * hdr ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_change(sch %p,[qdisc %p],classid %x,parent %x, "
2007-07-15 11:01:25 +04:00
" flow %p,opt %p) \n " , sch , p , classid , parent , flow , opt ) ;
2005-04-17 02:20:36 +04:00
/*
* 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 . )
*/
2007-07-15 11:01:25 +04:00
if ( flow )
return - EBUSY ;
2008-01-24 07:33:32 +03:00
if ( opt = = NULL )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2008-01-24 07:35:39 +03:00
error = nla_parse_nested ( tb , TCA_ATM_MAX , opt , atm_policy ) ;
2008-01-24 07:33:32 +03:00
if ( error < 0 )
return error ;
2008-01-24 07:35:39 +03:00
if ( ! tb [ TCA_ATM_FD ] )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2008-01-24 07:35:03 +03:00
fd = nla_get_u32 ( tb [ TCA_ATM_FD ] ) ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_change: fd %d \n " , fd ) ;
2008-01-23 09:11:17 +03:00
if ( tb [ TCA_ATM_HDR ] ) {
hdr_len = nla_len ( tb [ TCA_ATM_HDR ] ) ;
hdr = nla_data ( tb [ TCA_ATM_HDR ] ) ;
2007-07-15 11:01:25 +04:00
} else {
2005-04-17 02:20:36 +04:00
hdr_len = RFC1483LLC_LEN ;
2007-07-15 11:01:25 +04:00
hdr = NULL ; /* default LLC/SNAP for IP */
2005-04-17 02:20:36 +04:00
}
2008-01-23 09:11:17 +03:00
if ( ! tb [ TCA_ATM_EXCESS ] )
2007-07-15 11:01:25 +04:00
excess = NULL ;
2005-04-17 02:20:36 +04:00
else {
2007-07-15 11:01:25 +04:00
excess = ( struct atm_flow_data * )
2008-01-24 07:35:03 +03:00
atm_tc_get ( sch , nla_get_u32 ( tb [ TCA_ATM_EXCESS ] ) ) ;
2007-07-15 11:01:25 +04:00
if ( ! excess )
return - ENOENT ;
2005-04-17 02:20:36 +04:00
}
2008-01-24 07:32:06 +03:00
pr_debug ( " atm_tc_change: type %d, payload %d, hdr_len %d \n " ,
2008-01-23 09:11:17 +03:00
opt - > nla_type , nla_len ( opt ) , hdr_len ) ;
2008-01-21 13:25:29 +03:00
sock = sockfd_lookup ( fd , & error ) ;
if ( ! sock )
2007-07-15 11:01:25 +04:00
return error ; /* f_count++ */
2008-07-26 08:39:17 +04:00
pr_debug ( " atm_tc_change: f_count %ld \n " , file_count ( sock - > file ) ) ;
2007-02-09 17:25:16 +03:00
if ( sock - > ops - > family ! = PF_ATMSVC & & sock - > ops - > family ! = PF_ATMPVC ) {
2005-04-17 02:20:36 +04:00
error = - EPROTOTYPE ;
2007-02-09 17:25:16 +03:00
goto err_out ;
2005-04-17 02:20:36 +04:00
}
/* @@@ should check if the socket is really operational or we'll crash
on vcc - > send */
if ( classid ) {
if ( TC_H_MAJ ( classid ^ sch - > handle ) ) {
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_change: classid mismatch \n " ) ;
2005-04-17 02:20:36 +04:00
error = - EINVAL ;
goto err_out ;
}
2007-07-15 11:01:25 +04:00
if ( find_flow ( p , flow ) ) {
2005-04-17 02:20:36 +04:00
error = - EEXIST ;
goto err_out ;
}
2007-07-15 11:01:25 +04:00
} else {
2005-04-17 02:20:36 +04:00
int i ;
unsigned long cl ;
for ( i = 1 ; i < 0x8000 ; i + + ) {
2007-07-15 11:01:25 +04:00
classid = TC_H_MAKE ( sch - > handle , 0x8000 | i ) ;
2008-01-21 13:25:29 +03:00
cl = atm_tc_get ( sch , classid ) ;
if ( ! cl )
2007-07-15 11:01:25 +04:00
break ;
atm_tc_put ( sch , cl ) ;
2005-04-17 02:20:36 +04:00
}
}
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_change: new id %x \n " , classid ) ;
2007-07-17 05:30:36 +04:00
flow = kzalloc ( sizeof ( struct atm_flow_data ) + hdr_len , GFP_KERNEL ) ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_change: flow %p \n " , flow ) ;
2005-04-17 02:20:36 +04:00
if ( ! flow ) {
error = - ENOBUFS ;
goto err_out ;
}
flow - > filter_list = NULL ;
2008-07-09 04:06:30 +04:00
flow - > q = qdisc_create_dflt ( qdisc_dev ( sch ) , sch - > dev_queue ,
2008-07-09 03:55:56 +04:00
& pfifo_qdisc_ops , classid ) ;
2008-01-21 13:25:29 +03:00
if ( ! flow - > q )
2005-04-17 02:20:36 +04:00
flow - > q = & noop_qdisc ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_change: qdisc %p \n " , flow - > q ) ;
2005-04-17 02:20:36 +04:00
flow - > sock = sock ;
2007-07-15 11:01:25 +04:00
flow - > vcc = ATM_SD ( sock ) ; /* speedup */
2005-04-17 02:20:36 +04:00
flow - > vcc - > user_back = flow ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_change: vcc %p \n " , flow - > vcc ) ;
2005-04-17 02:20:36 +04:00
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 )
2007-07-15 11:01:25 +04:00
memcpy ( flow - > hdr , hdr , hdr_len ) ;
2005-04-17 02:20:36 +04:00
else
2007-07-15 11:01:25 +04:00
memcpy ( flow - > hdr , llc_oui_ip , sizeof ( llc_oui_ip ) ) ;
* arg = ( unsigned long ) flow ;
2005-04-17 02:20:36 +04:00
return 0 ;
err_out :
2007-07-15 11:01:25 +04:00
if ( excess )
atm_tc_put ( sch , ( unsigned long ) excess ) ;
2005-04-17 02:20:36 +04:00
sockfd_put ( sock ) ;
return error ;
}
2007-07-15 11:01:25 +04:00
static int atm_tc_delete ( struct Qdisc * sch , unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2007-07-15 11:01:25 +04:00
struct atm_flow_data * flow = ( struct atm_flow_data * ) arg ;
2005-04-17 02:20:36 +04:00
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_delete(sch %p,[qdisc %p],flow %p) \n " , sch , p , flow ) ;
if ( ! find_flow ( qdisc_priv ( sch ) , flow ) )
2007-07-15 11:01:25 +04:00
return - EINVAL ;
if ( flow - > filter_list | | flow = = & p - > link )
return - EBUSY ;
2005-04-17 02:20:36 +04:00
/*
* 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 ) {
2007-07-15 11:01:25 +04:00
printk ( KERN_ERR " atm_tc_delete: flow->ref == %d \n " , flow - > ref ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2007-07-15 11:01:25 +04:00
if ( flow - > ref > 2 )
return - EBUSY ; /* catch references via excess, etc. */
atm_tc_put ( sch , arg ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-07-15 11:01:25 +04:00
static void atm_tc_walk ( struct Qdisc * sch , struct qdisc_walker * walker )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2005-04-17 02:20:36 +04:00
struct atm_flow_data * flow ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_walk(sch %p,[qdisc %p],walker %p) \n " , sch , p , walker ) ;
2007-07-15 11:01:25 +04:00
if ( walker - > stop )
return ;
2005-04-17 02:20:36 +04:00
for ( flow = p - > flows ; flow ; flow = flow - > next ) {
if ( walker - > count > = walker - > skip )
2007-07-15 11:01:25 +04:00
if ( walker - > fn ( sch , ( unsigned long ) flow , walker ) < 0 ) {
2005-04-17 02:20:36 +04:00
walker - > stop = 1 ;
break ;
}
walker - > count + + ;
}
}
2007-07-15 11:01:25 +04:00
static struct tcf_proto * * atm_tc_find_tcf ( struct Qdisc * sch , unsigned long cl )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2007-07-15 11:01:25 +04:00
struct atm_flow_data * flow = ( struct atm_flow_data * ) cl ;
2005-04-17 02:20:36 +04:00
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_find_tcf(sch %p,[qdisc %p],flow %p) \n " , sch , p , flow ) ;
2007-02-09 17:25:16 +03:00
return flow ? & flow - > filter_list : & p - > link . filter_list ;
2005-04-17 02:20:36 +04:00
}
/* --------------------------- Qdisc operations ---------------------------- */
2007-07-15 11:01:25 +04:00
static int atm_tc_enqueue ( struct sk_buff * skb , struct Qdisc * sch )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2007-07-15 11:01:25 +04:00
struct atm_flow_data * flow = NULL ; /* @@@ */
2005-04-17 02:20:36 +04:00
struct tcf_result res ;
int result ;
int ret = NET_XMIT_POLICED ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_enqueue(skb %p,sch %p,[qdisc %p]) \n " , skb , sch , p ) ;
2007-07-15 11:01:25 +04:00
result = TC_POLICE_OK ; /* be nice to gcc */
2005-04-17 02:20:36 +04:00
if ( TC_H_MAJ ( skb - > priority ) ! = sch - > handle | |
2007-07-15 11:01:25 +04:00
! ( flow = ( struct atm_flow_data * ) atm_tc_get ( sch , skb - > priority ) ) )
2005-04-17 02:20:36 +04:00
for ( flow = p - > flows ; flow ; flow = flow - > next )
if ( flow - > filter_list ) {
2007-07-15 11:02:31 +04:00
result = tc_classify_compat ( skb ,
flow - > filter_list ,
& res ) ;
2007-07-15 11:01:25 +04:00
if ( result < 0 )
continue ;
flow = ( struct atm_flow_data * ) res . class ;
if ( ! flow )
flow = lookup_flow ( sch , res . classid ) ;
2005-04-17 02:20:36 +04:00
break ;
}
2007-07-15 11:01:25 +04:00
if ( ! flow )
flow = & p - > link ;
2005-04-17 02:20:36 +04:00
else {
if ( flow - > vcc )
ATM_SKB ( skb ) - > atm_options = flow - > vcc - > atm_options ;
2007-07-15 11:01:25 +04:00
/*@@@ looks good ... but it's not supposed to work :-) */
2007-07-15 11:01:49 +04:00
# ifdef CONFIG_NET_CLS_ACT
switch ( result ) {
case TC_ACT_QUEUED :
case TC_ACT_STOLEN :
kfree_skb ( skb ) ;
2008-08-05 09:31:03 +04:00
return NET_XMIT_SUCCESS | __NET_XMIT_STOLEN ;
2007-07-15 11:01:49 +04:00
case TC_ACT_SHOT :
kfree_skb ( skb ) ;
goto drop ;
2007-07-15 11:02:31 +04:00
case TC_POLICE_RECLASSIFY :
if ( flow - > excess )
flow = flow - > excess ;
else
ATM_SKB ( skb ) - > atm_options | = ATM_ATMOPT_CLP ;
break ;
2007-07-15 11:01:49 +04:00
}
2005-04-17 02:20:36 +04:00
# endif
}
2007-07-15 11:03:05 +04:00
2008-07-20 11:08:04 +04:00
ret = qdisc_enqueue ( skb , flow - > q ) ;
2008-01-21 13:25:29 +03:00
if ( ret ! = 0 ) {
2007-07-15 11:01:49 +04:00
drop : __maybe_unused
2008-08-05 09:31:03 +04:00
if ( net_xmit_drop_count ( ret ) ) {
sch - > qstats . drops + + ;
if ( flow )
flow - > qstats . drops + + ;
}
2005-04-17 02:20:36 +04:00
return ret ;
}
2008-07-20 11:08:27 +04:00
sch - > bstats . bytes + = qdisc_pkt_len ( skb ) ;
2005-04-17 02:20:36 +04:00
sch - > bstats . packets + + ;
2008-07-20 11:08:27 +04:00
flow - > bstats . bytes + = qdisc_pkt_len ( skb ) ;
2005-04-17 02:20:36 +04:00
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 ) ;
2008-08-05 09:39:11 +04:00
return NET_XMIT_SUCCESS | __NET_XMIT_BYPASS ;
2005-04-17 02:20:36 +04:00
}
/*
* 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 )
{
2007-07-15 11:01:25 +04:00
struct Qdisc * sch = ( struct Qdisc * ) data ;
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2005-04-17 02:20:36 +04:00
struct atm_flow_data * flow ;
struct sk_buff * skb ;
2008-01-21 13:25:29 +03:00
pr_debug ( " sch_atm_dequeue(sch %p,[qdisc %p]) \n " , sch , p ) ;
2005-04-17 02:20:36 +04:00
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 )
*/
2008-10-31 10:46:19 +03:00
while ( ( skb = flow - > q - > ops - > peek ( flow - > q ) ) ) {
if ( ! atm_may_send ( flow - > vcc , skb - > truesize ) )
2005-04-17 02:20:36 +04:00
break ;
2008-10-31 10:46:19 +03:00
2008-10-31 10:47:01 +03:00
skb = qdisc_dequeue_peeked ( flow - > q ) ;
2008-10-31 10:46:19 +03:00
if ( unlikely ( ! skb ) )
break ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_dequeue: sending on class %p \n " , flow ) ;
2005-04-17 02:20:36 +04:00
/* remove any LL header somebody else has attached */
2007-04-21 09:47:35 +04:00
skb_pull ( skb , skb_network_offset ( skb ) ) ;
2005-04-17 02:20:36 +04:00
if ( skb_headroom ( skb ) < flow - > hdr_len ) {
struct sk_buff * new ;
2007-07-15 11:01:25 +04:00
new = skb_realloc_headroom ( skb , flow - > hdr_len ) ;
2005-04-17 02:20:36 +04:00
dev_kfree_skb ( skb ) ;
2007-07-15 11:01:25 +04:00
if ( ! new )
continue ;
2005-04-17 02:20:36 +04:00
skb = new ;
}
2008-01-21 13:25:29 +03:00
pr_debug ( " sch_atm_dequeue: ip %p, data %p \n " ,
2007-04-21 09:47:35 +04:00
skb_network_header ( skb ) , skb - > data ) ;
2005-04-17 02:20:36 +04:00
ATM_SKB ( skb ) - > vcc = flow - > vcc ;
2007-07-15 11:01:25 +04:00
memcpy ( skb_push ( skb , flow - > hdr_len ) , flow - > hdr ,
flow - > hdr_len ) ;
2005-04-17 02:20:36 +04:00
atomic_add ( skb - > truesize ,
& sk_atm ( flow - > vcc ) - > sk_wmem_alloc ) ;
/* atm.atm_options are already set by atm_tc_enqueue */
2007-07-15 11:01:25 +04:00
flow - > vcc - > send ( flow - > vcc , skb ) ;
2005-04-17 02:20:36 +04:00
}
}
static struct sk_buff * atm_tc_dequeue ( struct Qdisc * sch )
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2005-04-17 02:20:36 +04:00
struct sk_buff * skb ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_dequeue(sch %p,[qdisc %p]) \n " , sch , p ) ;
2005-04-17 02:20:36 +04:00
tasklet_schedule ( & p - > task ) ;
2008-10-31 10:47:01 +03:00
skb = qdisc_dequeue_peeked ( p - > link . q ) ;
2007-07-15 11:01:25 +04:00
if ( skb )
sch - > q . qlen - - ;
2005-04-17 02:20:36 +04:00
return skb ;
}
2008-10-31 10:45:55 +03:00
static struct sk_buff * atm_tc_peek ( struct Qdisc * sch )
{
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
pr_debug ( " atm_tc_peek(sch %p,[qdisc %p]) \n " , sch , p ) ;
return p - > link . q - > ops - > peek ( p - > link . q ) ;
}
2005-04-17 02:20:36 +04:00
static unsigned int atm_tc_drop ( struct Qdisc * sch )
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2005-04-17 02:20:36 +04:00
struct atm_flow_data * flow ;
unsigned int len ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_drop(sch %p,[qdisc %p]) \n " , sch , p ) ;
2005-04-17 02:20:36 +04:00
for ( flow = p - > flows ; flow ; flow = flow - > next )
if ( flow - > q - > ops - > drop & & ( len = flow - > q - > ops - > drop ( flow - > q ) ) )
return len ;
return 0 ;
}
2008-01-23 09:11:17 +03:00
static int atm_tc_init ( struct Qdisc * sch , struct nlattr * opt )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2005-04-17 02:20:36 +04:00
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_init(sch %p,[qdisc %p],opt %p) \n " , sch , p , opt ) ;
2005-04-17 02:20:36 +04:00
p - > flows = & p - > link ;
2008-07-09 04:06:30 +04:00
p - > link . q = qdisc_create_dflt ( qdisc_dev ( sch ) , sch - > dev_queue ,
2008-07-09 03:55:56 +04:00
& pfifo_qdisc_ops , sch - > handle ) ;
2008-01-21 13:25:29 +03:00
if ( ! p - > link . q )
2005-04-17 02:20:36 +04:00
p - > link . q = & noop_qdisc ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_init: link (%p) qdisc %p \n " , & p - > link , p - > link . q ) ;
2005-04-17 02:20:36 +04:00
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 ;
2007-07-15 11:01:25 +04:00
tasklet_init ( & p - > task , sch_atm_dequeue , ( unsigned long ) sch ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static void atm_tc_reset ( struct Qdisc * sch )
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2005-04-17 02:20:36 +04:00
struct atm_flow_data * flow ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_reset(sch %p,[qdisc %p]) \n " , sch , p ) ;
2007-07-15 11:01:25 +04:00
for ( flow = p - > flows ; flow ; flow = flow - > next )
qdisc_reset ( flow - > q ) ;
2005-04-17 02:20:36 +04:00
sch - > q . qlen = 0 ;
}
static void atm_tc_destroy ( struct Qdisc * sch )
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2005-04-17 02:20:36 +04:00
struct atm_flow_data * flow ;
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_destroy(sch %p,[qdisc %p]) \n " , sch , p ) ;
2008-07-02 06:53:09 +04:00
for ( flow = p - > flows ; flow ; flow = flow - > next )
tcf_destroy_chain ( & flow - > filter_list ) ;
2005-04-17 02:20:36 +04:00
/* races ? */
while ( ( flow = p - > flows ) ) {
if ( flow - > ref > 1 )
2007-07-15 11:01:25 +04:00
printk ( KERN_ERR " atm_destroy: %p->ref = %d \n " , flow ,
flow - > ref ) ;
atm_tc_put ( sch , ( unsigned long ) flow ) ;
2005-04-17 02:20:36 +04:00
if ( p - > flows = = flow ) {
printk ( KERN_ERR " atm_destroy: putting flow %p didn't "
2007-07-15 11:01:25 +04:00
" kill it \n " , flow ) ;
p - > flows = flow - > next ; /* brute force */
2005-04-17 02:20:36 +04:00
break ;
}
}
tasklet_kill ( & p - > task ) ;
}
static int atm_tc_dump_class ( struct Qdisc * sch , unsigned long cl ,
2007-07-15 11:01:25 +04:00
struct sk_buff * skb , struct tcmsg * tcm )
2005-04-17 02:20:36 +04:00
{
2008-01-21 13:25:29 +03:00
struct atm_qdisc_data * p = qdisc_priv ( sch ) ;
2007-07-15 11:01:25 +04:00
struct atm_flow_data * flow = ( struct atm_flow_data * ) cl ;
2008-01-24 07:34:11 +03:00
struct nlattr * nest ;
2005-04-17 02:20:36 +04:00
2008-01-21 13:25:29 +03:00
pr_debug ( " atm_tc_dump_class(sch %p,[qdisc %p],flow %p,skb %p,tcm %p) \n " ,
2007-07-15 11:01:25 +04:00
sch , p , flow , skb , tcm ) ;
if ( ! find_flow ( p , flow ) )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
tcm - > tcm_handle = flow - > classid ;
2006-03-21 06:01:06 +03:00
tcm - > tcm_info = flow - > q - > handle ;
2008-01-24 07:34:11 +03:00
nest = nla_nest_start ( skb , TCA_OPTIONS ) ;
if ( nest = = NULL )
goto nla_put_failure ;
2008-01-23 09:11:17 +03:00
NLA_PUT ( skb , TCA_ATM_HDR , flow - > hdr_len , flow - > hdr ) ;
2005-04-17 02:20:36 +04:00
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 ;
2008-01-23 09:11:17 +03:00
NLA_PUT ( skb , TCA_ATM_ADDR , sizeof ( pvc ) , & pvc ) ;
2005-04-17 02:20:36 +04:00
state = ATM_VF2VS ( flow - > vcc - > flags ) ;
2008-01-24 07:34:48 +03:00
NLA_PUT_U32 ( skb , TCA_ATM_STATE , state ) ;
2005-04-17 02:20:36 +04:00
}
if ( flow - > excess )
2008-01-24 07:34:48 +03:00
NLA_PUT_U32 ( skb , TCA_ATM_EXCESS , flow - > classid ) ;
2005-04-17 02:20:36 +04:00
else {
2008-01-24 07:34:48 +03:00
NLA_PUT_U32 ( skb , TCA_ATM_EXCESS , 0 ) ;
2005-04-17 02:20:36 +04:00
}
2008-01-24 07:34:11 +03:00
nla_nest_end ( skb , nest ) ;
2005-04-17 02:20:36 +04:00
return skb - > len ;
2008-01-23 09:11:17 +03:00
nla_put_failure :
2008-01-24 07:34:11 +03:00
nla_nest_cancel ( skb , nest ) ;
2005-04-17 02:20:36 +04:00
return - 1 ;
}
static int
atm_tc_dump_class_stats ( struct Qdisc * sch , unsigned long arg ,
2007-07-15 11:01:25 +04:00
struct gnet_dump * d )
2005-04-17 02:20:36 +04:00
{
2007-07-15 11:01:25 +04:00
struct atm_flow_data * flow = ( struct atm_flow_data * ) arg ;
2005-04-17 02:20:36 +04:00
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 ;
}
2007-11-14 12:44:41 +03:00
static const struct Qdisc_class_ops atm_class_ops = {
2007-07-15 11:01:25 +04:00
. 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 ,
2005-04-17 02:20:36 +04:00
} ;
2007-11-14 12:44:41 +03:00
static struct Qdisc_ops atm_qdisc_ops __read_mostly = {
2007-07-15 11:01:25 +04:00
. cl_ops = & atm_class_ops ,
. id = " atm " ,
. priv_size = sizeof ( struct atm_qdisc_data ) ,
. enqueue = atm_tc_enqueue ,
. dequeue = atm_tc_dequeue ,
2008-10-31 10:45:55 +03:00
. peek = atm_tc_peek ,
2007-07-15 11:01:25 +04:00
. drop = atm_tc_drop ,
. init = atm_tc_init ,
. reset = atm_tc_reset ,
. destroy = atm_tc_destroy ,
. dump = atm_tc_dump ,
. owner = THIS_MODULE ,
2005-04-17 02:20:36 +04:00
} ;
static int __init atm_init ( void )
{
return register_qdisc ( & atm_qdisc_ops ) ;
}
2007-02-09 17:25:16 +03:00
static void __exit atm_exit ( void )
2005-04-17 02:20:36 +04:00
{
unregister_qdisc ( & atm_qdisc_ops ) ;
}
module_init ( atm_init )
module_exit ( atm_exit )
MODULE_LICENSE ( " GPL " ) ;