2005-04-17 02:20:36 +04:00
/*
* net / sched / sch_prio . c Simple 3 - band priority " scheduler " .
*
* 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 .
*
* Authors : Alexey Kuznetsov , < kuznet @ ms2 . inr . ac . ru >
2007-02-09 17:25:16 +03:00
* Fixes : 19990609 : J Hadi Salim < hadi @ nortelnetworks . com > :
2005-04-17 02:20:36 +04:00
* Init - - EINVAL when opt undefined
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/errno.h>
# include <linux/skbuff.h>
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>
struct prio_sched_data
{
int bands ;
struct tcf_proto * filter_list ;
u8 prio2band [ TC_PRIO_MAX + 1 ] ;
struct Qdisc * queues [ TCQ_PRIO_BANDS ] ;
} ;
static struct Qdisc *
prio_classify ( struct sk_buff * skb , struct Qdisc * sch , int * qerr )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
u32 band = skb - > priority ;
struct tcf_result res ;
2007-07-31 04:07:14 +04:00
int err ;
2005-04-17 02:20:36 +04:00
2008-08-05 09:39:11 +04:00
* qerr = NET_XMIT_SUCCESS | __NET_XMIT_BYPASS ;
2005-04-17 02:20:36 +04:00
if ( TC_H_MAJ ( skb - > priority ) ! = sch - > handle ) {
2007-07-31 04:07:14 +04:00
err = tc_classify ( skb , q - > filter_list , & res ) ;
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_NET_CLS_ACT
2007-08-31 09:35:46 +04:00
switch ( err ) {
2005-04-17 02:20:36 +04:00
case TC_ACT_STOLEN :
case TC_ACT_QUEUED :
2008-08-05 09:31:03 +04:00
* qerr = NET_XMIT_SUCCESS | __NET_XMIT_STOLEN ;
2005-04-17 02:20:36 +04:00
case TC_ACT_SHOT :
return NULL ;
2007-04-21 04:09:22 +04:00
}
2005-04-17 02:20:36 +04:00
# endif
2007-07-31 04:07:14 +04:00
if ( ! q - > filter_list | | err < 0 ) {
2005-04-17 02:20:36 +04:00
if ( TC_H_MAJ ( band ) )
band = 0 ;
2008-07-15 13:52:19 +04:00
return q - > queues [ q - > prio2band [ band & TC_PRIO_MAX ] ] ;
2005-04-17 02:20:36 +04:00
}
band = res . classid ;
}
band = TC_H_MIN ( band ) - 1 ;
2007-05-14 13:57:19 +04:00
if ( band > = q - > bands )
2008-07-15 13:52:19 +04:00
return q - > queues [ q - > prio2band [ 0 ] ] ;
2005-04-17 02:20:36 +04:00
return q - > queues [ band ] ;
}
static int
prio_enqueue ( struct sk_buff * skb , struct Qdisc * sch )
{
struct Qdisc * qdisc ;
int ret ;
qdisc = prio_classify ( skb , sch , & ret ) ;
# ifdef CONFIG_NET_CLS_ACT
if ( qdisc = = NULL ) {
2006-01-09 09:35:55 +03:00
2008-08-05 09:39:11 +04:00
if ( ret & __NET_XMIT_BYPASS )
2005-04-17 02:20:36 +04:00
sch - > qstats . drops + + ;
kfree_skb ( skb ) ;
return ret ;
}
# endif
2008-07-20 11:08:04 +04:00
ret = qdisc_enqueue ( skb , qdisc ) ;
if ( ret = = NET_XMIT_SUCCESS ) {
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 + + ;
sch - > q . qlen + + ;
return NET_XMIT_SUCCESS ;
}
2008-08-05 09:31:03 +04:00
if ( net_xmit_drop_count ( ret ) )
sch - > qstats . drops + + ;
2007-02-09 17:25:16 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
}
2008-10-31 10:44:18 +03:00
static struct sk_buff * prio_peek ( struct Qdisc * sch )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
int prio ;
for ( prio = 0 ; prio < q - > bands ; prio + + ) {
struct Qdisc * qdisc = q - > queues [ prio ] ;
struct sk_buff * skb = qdisc - > ops - > peek ( qdisc ) ;
if ( skb )
return skb ;
}
return NULL ;
}
2005-04-17 02:20:36 +04:00
2008-07-15 13:52:19 +04:00
static struct sk_buff * prio_dequeue ( struct Qdisc * sch )
2005-04-17 02:20:36 +04:00
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
int prio ;
for ( prio = 0 ; prio < q - > bands ; prio + + ) {
2008-07-15 13:52:19 +04:00
struct Qdisc * qdisc = q - > queues [ prio ] ;
struct sk_buff * skb = qdisc - > dequeue ( qdisc ) ;
if ( skb ) {
sch - > q . qlen - - ;
return skb ;
2005-04-17 02:20:36 +04:00
}
}
return NULL ;
}
static unsigned int prio_drop ( struct Qdisc * sch )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
int prio ;
unsigned int len ;
struct Qdisc * qdisc ;
for ( prio = q - > bands - 1 ; prio > = 0 ; prio - - ) {
qdisc = q - > queues [ prio ] ;
2006-03-21 06:00:49 +03:00
if ( qdisc - > ops - > drop & & ( len = qdisc - > ops - > drop ( qdisc ) ) ! = 0 ) {
2005-04-17 02:20:36 +04:00
sch - > q . qlen - - ;
return len ;
}
}
return 0 ;
}
static void
prio_reset ( struct Qdisc * sch )
{
int prio ;
struct prio_sched_data * q = qdisc_priv ( sch ) ;
for ( prio = 0 ; prio < q - > bands ; prio + + )
qdisc_reset ( q - > queues [ prio ] ) ;
sch - > q . qlen = 0 ;
}
static void
prio_destroy ( struct Qdisc * sch )
{
int prio ;
struct prio_sched_data * q = qdisc_priv ( sch ) ;
2008-07-02 06:52:38 +04:00
tcf_destroy_chain ( & q - > filter_list ) ;
2005-04-17 02:20:36 +04:00
for ( prio = 0 ; prio < q - > bands ; prio + + )
qdisc_destroy ( q - > queues [ prio ] ) ;
}
2008-01-23 09:11:17 +03:00
static int prio_tune ( struct Qdisc * sch , struct nlattr * opt )
2005-04-17 02:20:36 +04:00
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
2007-06-29 08:04:31 +04:00
struct tc_prio_qopt * qopt ;
2005-04-17 02:20:36 +04:00
int i ;
2008-07-15 13:52:19 +04:00
if ( nla_len ( opt ) < sizeof ( * qopt ) )
return - EINVAL ;
qopt = nla_data ( opt ) ;
2007-06-29 08:04:31 +04:00
2008-07-15 13:52:19 +04:00
if ( qopt - > bands > TCQ_PRIO_BANDS | | qopt - > bands < 2 )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
for ( i = 0 ; i < = TC_PRIO_MAX ; i + + ) {
2008-07-15 13:52:19 +04:00
if ( qopt - > priomap [ i ] > = qopt - > bands )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
sch_tree_lock ( sch ) ;
2008-07-15 13:52:19 +04:00
q - > bands = qopt - > bands ;
2005-04-17 02:20:36 +04:00
memcpy ( q - > prio2band , qopt - > priomap , TC_PRIO_MAX + 1 ) ;
for ( i = q - > bands ; i < TCQ_PRIO_BANDS ; i + + ) {
2008-11-20 15:11:36 +03:00
struct Qdisc * child = q - > queues [ i ] ;
q - > queues [ i ] = & noop_qdisc ;
2006-11-30 04:36:20 +03:00
if ( child ! = & noop_qdisc ) {
qdisc_tree_decrease_qlen ( child , child - > q . qlen ) ;
2005-04-17 02:20:36 +04:00
qdisc_destroy ( child ) ;
2006-11-30 04:36:20 +03:00
}
2005-04-17 02:20:36 +04:00
}
sch_tree_unlock ( sch ) ;
2006-01-17 13:24:26 +03:00
for ( i = 0 ; i < q - > bands ; i + + ) {
if ( q - > queues [ i ] = = & noop_qdisc ) {
2008-11-20 15:11:36 +03:00
struct Qdisc * child , * old ;
2008-07-09 04:06:30 +04:00
child = qdisc_create_dflt ( qdisc_dev ( sch ) , sch - > dev_queue ,
2008-07-09 03:55:56 +04:00
& pfifo_qdisc_ops ,
2006-11-30 04:35:18 +03:00
TC_H_MAKE ( sch - > handle , i + 1 ) ) ;
2005-04-17 02:20:36 +04:00
if ( child ) {
sch_tree_lock ( sch ) ;
2008-11-20 15:11:36 +03:00
old = q - > queues [ i ] ;
q - > queues [ i ] = child ;
2005-04-17 02:20:36 +04:00
2008-11-20 15:11:36 +03:00
if ( old ! = & noop_qdisc ) {
qdisc_tree_decrease_qlen ( old ,
old - > q . qlen ) ;
qdisc_destroy ( old ) ;
2006-11-30 04:36:20 +03:00
}
2005-04-17 02:20:36 +04:00
sch_tree_unlock ( sch ) ;
}
}
}
return 0 ;
}
2008-01-23 09:11:17 +03:00
static int prio_init ( struct Qdisc * sch , struct nlattr * opt )
2005-04-17 02:20:36 +04:00
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
int i ;
for ( i = 0 ; i < TCQ_PRIO_BANDS ; i + + )
q - > queues [ i ] = & noop_qdisc ;
if ( opt = = NULL ) {
return - EINVAL ;
} else {
int err ;
if ( ( err = prio_tune ( sch , opt ) ) ! = 0 )
return err ;
}
return 0 ;
}
static int prio_dump ( struct Qdisc * sch , struct sk_buff * skb )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
2007-04-20 07:29:13 +04:00
unsigned char * b = skb_tail_pointer ( skb ) ;
2005-04-17 02:20:36 +04:00
struct tc_prio_qopt opt ;
opt . bands = q - > bands ;
memcpy ( & opt . priomap , q - > prio2band , TC_PRIO_MAX + 1 ) ;
2007-06-29 08:04:31 +04:00
2008-09-03 04:30:27 +04:00
NLA_PUT ( skb , TCA_OPTIONS , sizeof ( opt ) , & opt ) ;
2007-06-29 08:04:31 +04:00
2005-04-17 02:20:36 +04:00
return skb - > len ;
2008-01-23 09:11:17 +03:00
nla_put_failure :
2007-03-26 10:06:12 +04:00
nlmsg_trim ( skb , b ) ;
2005-04-17 02:20:36 +04:00
return - 1 ;
}
static int prio_graft ( struct Qdisc * sch , unsigned long arg , struct Qdisc * new ,
struct Qdisc * * old )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
unsigned long band = arg - 1 ;
if ( band > = q - > bands )
return - EINVAL ;
if ( new = = NULL )
new = & noop_qdisc ;
sch_tree_lock ( sch ) ;
* old = q - > queues [ band ] ;
q - > queues [ band ] = new ;
2006-11-30 04:36:20 +03:00
qdisc_tree_decrease_qlen ( * old , ( * old ) - > q . qlen ) ;
2005-04-17 02:20:36 +04:00
qdisc_reset ( * old ) ;
sch_tree_unlock ( sch ) ;
return 0 ;
}
static struct Qdisc *
prio_leaf ( struct Qdisc * sch , unsigned long arg )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
unsigned long band = arg - 1 ;
if ( band > = q - > bands )
return NULL ;
return q - > queues [ band ] ;
}
static unsigned long prio_get ( struct Qdisc * sch , u32 classid )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
unsigned long band = TC_H_MIN ( classid ) ;
if ( band - 1 > = q - > bands )
return 0 ;
return band ;
}
static unsigned long prio_bind ( struct Qdisc * sch , unsigned long parent , u32 classid )
{
return prio_get ( sch , classid ) ;
}
static void prio_put ( struct Qdisc * q , unsigned long cl )
{
return ;
}
2008-01-23 09:11:17 +03:00
static int prio_change ( struct Qdisc * sch , u32 handle , u32 parent , struct nlattr * * tca , unsigned long * arg )
2005-04-17 02:20:36 +04:00
{
unsigned long cl = * arg ;
struct prio_sched_data * q = qdisc_priv ( sch ) ;
if ( cl - 1 > q - > bands )
return - ENOENT ;
return 0 ;
}
static int prio_delete ( struct Qdisc * sch , unsigned long cl )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
if ( cl - 1 > q - > bands )
return - ENOENT ;
return 0 ;
}
static int prio_dump_class ( struct Qdisc * sch , unsigned long cl , struct sk_buff * skb ,
struct tcmsg * tcm )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
if ( cl - 1 > q - > bands )
return - ENOENT ;
tcm - > tcm_handle | = TC_H_MIN ( cl ) ;
if ( q - > queues [ cl - 1 ] )
tcm - > tcm_info = q - > queues [ cl - 1 ] - > handle ;
return 0 ;
}
2007-01-31 23:21:24 +03:00
static int prio_dump_class_stats ( struct Qdisc * sch , unsigned long cl ,
struct gnet_dump * d )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
struct Qdisc * cl_q ;
cl_q = q - > queues [ cl - 1 ] ;
if ( gnet_stats_copy_basic ( d , & cl_q - > bstats ) < 0 | |
gnet_stats_copy_queue ( d , & cl_q - > qstats ) < 0 )
return - 1 ;
return 0 ;
}
2005-04-17 02:20:36 +04:00
static void prio_walk ( struct Qdisc * sch , struct qdisc_walker * arg )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
int prio ;
if ( arg - > stop )
return ;
for ( prio = 0 ; prio < q - > bands ; prio + + ) {
if ( arg - > count < arg - > skip ) {
arg - > count + + ;
continue ;
}
if ( arg - > fn ( sch , prio + 1 , arg ) < 0 ) {
arg - > stop = 1 ;
break ;
}
arg - > count + + ;
}
}
static struct tcf_proto * * prio_find_tcf ( struct Qdisc * sch , unsigned long cl )
{
struct prio_sched_data * q = qdisc_priv ( sch ) ;
if ( cl )
return NULL ;
return & q - > filter_list ;
}
2007-11-14 12:44:41 +03:00
static const struct Qdisc_class_ops prio_class_ops = {
2005-04-17 02:20:36 +04:00
. graft = prio_graft ,
. leaf = prio_leaf ,
. get = prio_get ,
. put = prio_put ,
. change = prio_change ,
. delete = prio_delete ,
. walk = prio_walk ,
. tcf_chain = prio_find_tcf ,
. bind_tcf = prio_bind ,
. unbind_tcf = prio_put ,
. dump = prio_dump_class ,
2007-01-31 23:21:24 +03:00
. dump_stats = prio_dump_class_stats ,
2005-04-17 02:20:36 +04:00
} ;
2007-11-14 12:44:41 +03:00
static struct Qdisc_ops prio_qdisc_ops __read_mostly = {
2005-04-17 02:20:36 +04:00
. next = NULL ,
. cl_ops = & prio_class_ops ,
. id = " prio " ,
. priv_size = sizeof ( struct prio_sched_data ) ,
. enqueue = prio_enqueue ,
. dequeue = prio_dequeue ,
2008-10-31 10:44:18 +03:00
. peek = prio_peek ,
2005-04-17 02:20:36 +04:00
. drop = prio_drop ,
. init = prio_init ,
. reset = prio_reset ,
. destroy = prio_destroy ,
. change = prio_tune ,
. dump = prio_dump ,
. owner = THIS_MODULE ,
} ;
static int __init prio_module_init ( void )
{
2008-07-15 13:52:19 +04:00
return register_qdisc ( & prio_qdisc_ops ) ;
2005-04-17 02:20:36 +04:00
}
2007-02-09 17:25:16 +03:00
static void __exit prio_module_exit ( void )
2005-04-17 02:20:36 +04:00
{
unregister_qdisc ( & prio_qdisc_ops ) ;
}
module_init ( prio_module_init )
module_exit ( prio_module_exit )
MODULE_LICENSE ( " GPL " ) ;