2008-11-20 04:10:00 -08:00
/*
* net / sched / sch_drr . c Deficit Round Robin scheduler
*
* Copyright ( c ) 2008 Patrick McHardy < kaber @ trash . net >
*
* 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 <linux/module.h>
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/netdevice.h>
# include <linux/pkt_sched.h>
# include <net/sch_generic.h>
# include <net/pkt_sched.h>
# include <net/pkt_cls.h>
struct drr_class {
struct Qdisc_class_common common ;
unsigned int refcnt ;
unsigned int filter_cnt ;
struct gnet_stats_basic bstats ;
struct gnet_stats_queue qstats ;
struct gnet_stats_rate_est rate_est ;
struct list_head alist ;
struct Qdisc * qdisc ;
u32 quantum ;
u32 deficit ;
} ;
struct drr_sched {
struct list_head active ;
struct tcf_proto * filter_list ;
struct Qdisc_class_hash clhash ;
} ;
static struct drr_class * drr_find_class ( struct Qdisc * sch , u32 classid )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct Qdisc_class_common * clc ;
clc = qdisc_class_find ( & q - > clhash , classid ) ;
if ( clc = = NULL )
return NULL ;
return container_of ( clc , struct drr_class , common ) ;
}
static void drr_purge_queue ( struct drr_class * cl )
{
unsigned int len = cl - > qdisc - > q . qlen ;
qdisc_reset ( cl - > qdisc ) ;
qdisc_tree_decrease_qlen ( cl - > qdisc , len ) ;
}
static const struct nla_policy drr_policy [ TCA_DRR_MAX + 1 ] = {
[ TCA_DRR_QUANTUM ] = { . type = NLA_U32 } ,
} ;
static int drr_change_class ( struct Qdisc * sch , u32 classid , u32 parentid ,
struct nlattr * * tca , unsigned long * arg )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct drr_class * cl = ( struct drr_class * ) * arg ;
struct nlattr * tb [ TCA_DRR_MAX + 1 ] ;
u32 quantum ;
int err ;
err = nla_parse_nested ( tb , TCA_DRR_MAX , tca [ TCA_OPTIONS ] , drr_policy ) ;
if ( err < 0 )
return err ;
if ( tb [ TCA_DRR_QUANTUM ] ) {
quantum = nla_get_u32 ( tb [ TCA_DRR_QUANTUM ] ) ;
if ( quantum = = 0 )
return - EINVAL ;
} else
quantum = psched_mtu ( qdisc_dev ( sch ) ) ;
if ( cl ! = NULL ) {
2008-11-25 21:13:31 -08:00
if ( tca [ TCA_RATE ] ) {
err = gen_replace_estimator ( & cl - > bstats , & cl - > rate_est ,
qdisc_root_sleeping_lock ( sch ) ,
tca [ TCA_RATE ] ) ;
if ( err )
return err ;
}
2008-11-20 04:10:00 -08:00
sch_tree_lock ( sch ) ;
if ( tb [ TCA_DRR_QUANTUM ] )
cl - > quantum = quantum ;
sch_tree_unlock ( sch ) ;
return 0 ;
}
cl = kzalloc ( sizeof ( struct drr_class ) , GFP_KERNEL ) ;
if ( cl = = NULL )
return - ENOBUFS ;
cl - > refcnt = 1 ;
cl - > common . classid = classid ;
cl - > quantum = quantum ;
cl - > qdisc = qdisc_create_dflt ( qdisc_dev ( sch ) , sch - > dev_queue ,
& pfifo_qdisc_ops , classid ) ;
if ( cl - > qdisc = = NULL )
cl - > qdisc = & noop_qdisc ;
2008-11-25 21:13:31 -08:00
if ( tca [ TCA_RATE ] ) {
err = gen_replace_estimator ( & cl - > bstats , & cl - > rate_est ,
qdisc_root_sleeping_lock ( sch ) ,
tca [ TCA_RATE ] ) ;
if ( err ) {
qdisc_destroy ( cl - > qdisc ) ;
kfree ( cl ) ;
return err ;
}
}
2008-11-20 04:10:00 -08:00
sch_tree_lock ( sch ) ;
qdisc_class_hash_insert ( & q - > clhash , & cl - > common ) ;
sch_tree_unlock ( sch ) ;
qdisc_class_hash_grow ( sch , & q - > clhash ) ;
* arg = ( unsigned long ) cl ;
return 0 ;
}
static void drr_destroy_class ( struct Qdisc * sch , struct drr_class * cl )
{
gen_kill_estimator ( & cl - > bstats , & cl - > rate_est ) ;
qdisc_destroy ( cl - > qdisc ) ;
kfree ( cl ) ;
}
static int drr_delete_class ( struct Qdisc * sch , unsigned long arg )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct drr_class * cl = ( struct drr_class * ) arg ;
if ( cl - > filter_cnt > 0 )
return - EBUSY ;
sch_tree_lock ( sch ) ;
drr_purge_queue ( cl ) ;
qdisc_class_hash_remove ( & q - > clhash , & cl - > common ) ;
if ( - - cl - > refcnt = = 0 )
drr_destroy_class ( sch , cl ) ;
sch_tree_unlock ( sch ) ;
return 0 ;
}
static unsigned long drr_get_class ( struct Qdisc * sch , u32 classid )
{
struct drr_class * cl = drr_find_class ( sch , classid ) ;
if ( cl ! = NULL )
cl - > refcnt + + ;
return ( unsigned long ) cl ;
}
static void drr_put_class ( struct Qdisc * sch , unsigned long arg )
{
struct drr_class * cl = ( struct drr_class * ) arg ;
if ( - - cl - > refcnt = = 0 )
drr_destroy_class ( sch , cl ) ;
}
static struct tcf_proto * * drr_tcf_chain ( struct Qdisc * sch , unsigned long cl )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
if ( cl )
return NULL ;
return & q - > filter_list ;
}
static unsigned long drr_bind_tcf ( struct Qdisc * sch , unsigned long parent ,
u32 classid )
{
struct drr_class * cl = drr_find_class ( sch , classid ) ;
if ( cl ! = NULL )
cl - > filter_cnt + + ;
return ( unsigned long ) cl ;
}
static void drr_unbind_tcf ( struct Qdisc * sch , unsigned long arg )
{
struct drr_class * cl = ( struct drr_class * ) arg ;
cl - > filter_cnt - - ;
}
static int drr_graft_class ( struct Qdisc * sch , unsigned long arg ,
struct Qdisc * new , struct Qdisc * * old )
{
struct drr_class * cl = ( struct drr_class * ) arg ;
if ( new = = NULL ) {
new = qdisc_create_dflt ( qdisc_dev ( sch ) , sch - > dev_queue ,
& pfifo_qdisc_ops , cl - > common . classid ) ;
if ( new = = NULL )
new = & noop_qdisc ;
}
sch_tree_lock ( sch ) ;
drr_purge_queue ( cl ) ;
2008-11-20 04:11:36 -08:00
* old = cl - > qdisc ;
cl - > qdisc = new ;
2008-11-20 04:10:00 -08:00
sch_tree_unlock ( sch ) ;
return 0 ;
}
static struct Qdisc * drr_class_leaf ( struct Qdisc * sch , unsigned long arg )
{
struct drr_class * cl = ( struct drr_class * ) arg ;
return cl - > qdisc ;
}
static void drr_qlen_notify ( struct Qdisc * csh , unsigned long arg )
{
struct drr_class * cl = ( struct drr_class * ) arg ;
if ( cl - > qdisc - > q . qlen = = 0 )
list_del ( & cl - > alist ) ;
}
static int drr_dump_class ( struct Qdisc * sch , unsigned long arg ,
struct sk_buff * skb , struct tcmsg * tcm )
{
struct drr_class * cl = ( struct drr_class * ) arg ;
struct nlattr * nest ;
tcm - > tcm_parent = TC_H_ROOT ;
tcm - > tcm_handle = cl - > common . classid ;
tcm - > tcm_info = cl - > qdisc - > handle ;
nest = nla_nest_start ( skb , TCA_OPTIONS ) ;
if ( nest = = NULL )
goto nla_put_failure ;
NLA_PUT_U32 ( skb , TCA_DRR_QUANTUM , cl - > quantum ) ;
return nla_nest_end ( skb , nest ) ;
nla_put_failure :
nla_nest_cancel ( skb , nest ) ;
return - EMSGSIZE ;
}
static int drr_dump_class_stats ( struct Qdisc * sch , unsigned long arg ,
struct gnet_dump * d )
{
struct drr_class * cl = ( struct drr_class * ) arg ;
struct tc_drr_stats xstats ;
memset ( & xstats , 0 , sizeof ( xstats ) ) ;
if ( cl - > qdisc - > q . qlen )
xstats . deficit = cl - > deficit ;
if ( gnet_stats_copy_basic ( d , & cl - > bstats ) < 0 | |
gnet_stats_copy_rate_est ( d , & cl - > rate_est ) < 0 | |
gnet_stats_copy_queue ( d , & cl - > qdisc - > qstats ) < 0 )
return - 1 ;
return gnet_stats_copy_app ( d , & xstats , sizeof ( xstats ) ) ;
}
static void drr_walk ( struct Qdisc * sch , struct qdisc_walker * arg )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct drr_class * cl ;
struct hlist_node * n ;
unsigned int i ;
if ( arg - > stop )
return ;
for ( i = 0 ; i < q - > clhash . hashsize ; i + + ) {
hlist_for_each_entry ( cl , n , & q - > clhash . hash [ i ] , common . hnode ) {
if ( arg - > count < arg - > skip ) {
arg - > count + + ;
continue ;
}
if ( arg - > fn ( sch , ( unsigned long ) cl , arg ) < 0 ) {
arg - > stop = 1 ;
return ;
}
arg - > count + + ;
}
}
}
static struct drr_class * drr_classify ( struct sk_buff * skb , struct Qdisc * sch ,
int * qerr )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct drr_class * cl ;
struct tcf_result res ;
int result ;
if ( TC_H_MAJ ( skb - > priority ^ sch - > handle ) = = 0 ) {
cl = drr_find_class ( sch , skb - > priority ) ;
if ( cl ! = NULL )
return cl ;
}
* qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS ;
result = tc_classify ( skb , q - > filter_list , & res ) ;
if ( result > = 0 ) {
# ifdef CONFIG_NET_CLS_ACT
switch ( result ) {
case TC_ACT_QUEUED :
case TC_ACT_STOLEN :
* qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN ;
case TC_ACT_SHOT :
return NULL ;
}
# endif
cl = ( struct drr_class * ) res . class ;
if ( cl = = NULL )
cl = drr_find_class ( sch , res . classid ) ;
return cl ;
}
return NULL ;
}
static int drr_enqueue ( struct sk_buff * skb , struct Qdisc * sch )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct drr_class * cl ;
unsigned int len ;
int err ;
cl = drr_classify ( skb , sch , & err ) ;
if ( cl = = NULL ) {
if ( err & __NET_XMIT_BYPASS )
sch - > qstats . drops + + ;
kfree_skb ( skb ) ;
return err ;
}
len = qdisc_pkt_len ( skb ) ;
err = qdisc_enqueue ( skb , cl - > qdisc ) ;
if ( unlikely ( err ! = NET_XMIT_SUCCESS ) ) {
if ( net_xmit_drop_count ( err ) ) {
cl - > qstats . drops + + ;
sch - > qstats . drops + + ;
}
return err ;
}
if ( cl - > qdisc - > q . qlen = = 1 ) {
list_add_tail ( & cl - > alist , & q - > active ) ;
cl - > deficit = cl - > quantum ;
}
cl - > bstats . packets + + ;
cl - > bstats . bytes + = len ;
sch - > bstats . packets + + ;
sch - > bstats . bytes + = len ;
sch - > q . qlen + + ;
return err ;
}
static struct sk_buff * drr_dequeue ( struct Qdisc * sch )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct drr_class * cl ;
struct sk_buff * skb ;
unsigned int len ;
2008-11-24 15:46:08 -08:00
if ( list_empty ( & q - > active ) )
goto out ;
while ( 1 ) {
2008-11-20 04:10:00 -08:00
cl = list_first_entry ( & q - > active , struct drr_class , alist ) ;
skb = cl - > qdisc - > ops - > peek ( cl - > qdisc ) ;
if ( skb = = NULL )
2008-11-24 15:46:08 -08:00
goto out ;
2008-11-20 04:10:00 -08:00
len = qdisc_pkt_len ( skb ) ;
if ( len < = cl - > deficit ) {
cl - > deficit - = len ;
skb = qdisc_dequeue_peeked ( cl - > qdisc ) ;
if ( cl - > qdisc - > q . qlen = = 0 )
list_del ( & cl - > alist ) ;
sch - > q . qlen - - ;
return skb ;
}
cl - > deficit + = cl - > quantum ;
list_move_tail ( & cl - > alist , & q - > active ) ;
}
2008-11-24 15:46:08 -08:00
out :
2008-11-20 04:10:00 -08:00
return NULL ;
}
static unsigned int drr_drop ( struct Qdisc * sch )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct drr_class * cl ;
unsigned int len ;
list_for_each_entry ( cl , & q - > active , alist ) {
if ( cl - > qdisc - > ops - > drop ) {
len = cl - > qdisc - > ops - > drop ( cl - > qdisc ) ;
if ( len > 0 ) {
2008-11-21 04:37:27 -08:00
sch - > q . qlen - - ;
2008-11-20 04:10:00 -08:00
if ( cl - > qdisc - > q . qlen = = 0 )
list_del ( & cl - > alist ) ;
return len ;
}
}
}
return 0 ;
}
static int drr_init_qdisc ( struct Qdisc * sch , struct nlattr * opt )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
int err ;
err = qdisc_class_hash_init ( & q - > clhash ) ;
if ( err < 0 )
return err ;
INIT_LIST_HEAD ( & q - > active ) ;
return 0 ;
}
static void drr_reset_qdisc ( struct Qdisc * sch )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct drr_class * cl ;
struct hlist_node * n ;
unsigned int i ;
for ( i = 0 ; i < q - > clhash . hashsize ; i + + ) {
hlist_for_each_entry ( cl , n , & q - > clhash . hash [ i ] , common . hnode ) {
if ( cl - > qdisc - > q . qlen )
list_del ( & cl - > alist ) ;
qdisc_reset ( cl - > qdisc ) ;
}
}
sch - > q . qlen = 0 ;
}
static void drr_destroy_qdisc ( struct Qdisc * sch )
{
struct drr_sched * q = qdisc_priv ( sch ) ;
struct drr_class * cl ;
struct hlist_node * n , * next ;
unsigned int i ;
tcf_destroy_chain ( & q - > filter_list ) ;
for ( i = 0 ; i < q - > clhash . hashsize ; i + + ) {
hlist_for_each_entry_safe ( cl , n , next , & q - > clhash . hash [ i ] ,
common . hnode )
drr_destroy_class ( sch , cl ) ;
}
qdisc_class_hash_destroy ( & q - > clhash ) ;
}
static const struct Qdisc_class_ops drr_class_ops = {
. change = drr_change_class ,
. delete = drr_delete_class ,
. get = drr_get_class ,
. put = drr_put_class ,
. tcf_chain = drr_tcf_chain ,
. bind_tcf = drr_bind_tcf ,
. unbind_tcf = drr_unbind_tcf ,
. graft = drr_graft_class ,
. leaf = drr_class_leaf ,
. qlen_notify = drr_qlen_notify ,
. dump = drr_dump_class ,
. dump_stats = drr_dump_class_stats ,
. walk = drr_walk ,
} ;
static struct Qdisc_ops drr_qdisc_ops __read_mostly = {
. cl_ops = & drr_class_ops ,
. id = " drr " ,
. priv_size = sizeof ( struct drr_sched ) ,
. enqueue = drr_enqueue ,
. dequeue = drr_dequeue ,
. peek = qdisc_peek_dequeued ,
. drop = drr_drop ,
. init = drr_init_qdisc ,
. reset = drr_reset_qdisc ,
. destroy = drr_destroy_qdisc ,
. owner = THIS_MODULE ,
} ;
static int __init drr_init ( void )
{
return register_qdisc ( & drr_qdisc_ops ) ;
}
static void __exit drr_exit ( void )
{
unregister_qdisc ( & drr_qdisc_ops ) ;
}
module_init ( drr_init ) ;
module_exit ( drr_exit ) ;
MODULE_LICENSE ( " GPL " ) ;