2017-10-17 04:01:26 +03:00
/*
* net / sched / sch_cbs . c Credit Based Shaper
*
* 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 : Vinicius Costa Gomes < vinicius . gomes @ intel . com >
*
*/
/* Credit Based Shaper (CBS)
* = = = = = = = = = = = = = = = = = = = = = = = = =
*
* This is a simple rate - limiting shaper aimed at TSN applications on
* systems with known traffic workloads .
*
* Its algorithm is defined by the IEEE 802.1 Q - 2014 Specification ,
* Section 8.6 .8 .2 , and explained in more detail in the Annex L of the
* same specification .
*
* There are four tunables to be considered :
*
* ' idleslope ' : Idleslope is the rate of credits that is
* accumulated ( in kilobits per second ) when there is at least
* one packet waiting for transmission . Packets are transmitted
* when the current value of credits is equal or greater than
* zero . When there is no packet to be transmitted the amount of
* credits is set to zero . This is the main tunable of the CBS
* algorithm .
*
* ' sendslope ' :
* Sendslope is the rate of credits that is depleted ( it should be a
* negative number of kilobits per second ) when a transmission is
* ocurring . It can be calculated as follows , ( IEEE 802.1 Q - 2014 Section
* 8.6 .8 .2 item g ) :
*
* sendslope = idleslope - port_transmit_rate
*
* ' hicredit ' : Hicredit defines the maximum amount of credits ( in
* bytes ) that can be accumulated . Hicredit depends on the
* characteristics of interfering traffic ,
* ' max_interference_size ' is the maximum size of any burst of
* traffic that can delay the transmission of a frame that is
* available for transmission for this traffic class , ( IEEE
* 802.1 Q - 2014 Annex L , Equation L - 3 ) :
*
* hicredit = max_interference_size * ( idleslope / port_transmit_rate )
*
* ' locredit ' : Locredit is the minimum amount of credits that can
* be reached . It is a function of the traffic flowing through
* this qdisc ( IEEE 802.1 Q - 2014 Annex L , Equation L - 2 ) :
*
* locredit = max_frame_size * ( sendslope / port_transmit_rate )
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/errno.h>
# include <linux/skbuff.h>
# include <net/netlink.h>
# include <net/sch_generic.h>
# include <net/pkt_sched.h>
# define BYTES_PER_KBIT (1000LL / 8)
struct cbs_sched_data {
2017-10-17 04:01:27 +03:00
bool offload ;
int queue ;
2017-10-17 04:01:26 +03:00
s64 port_rate ; /* in bytes/s */
s64 last ; /* timestamp in ns */
s64 credits ; /* in bytes */
s32 locredit ; /* in bytes */
s32 hicredit ; /* in bytes */
s64 sendslope ; /* in bytes/s */
s64 idleslope ; /* in bytes/s */
struct qdisc_watchdog watchdog ;
2018-07-24 03:08:00 +03:00
int ( * enqueue ) ( struct sk_buff * skb , struct Qdisc * sch ,
struct sk_buff * * to_free ) ;
2017-10-17 04:01:26 +03:00
struct sk_buff * ( * dequeue ) ( struct Qdisc * sch ) ;
2018-07-24 03:08:00 +03:00
struct Qdisc * qdisc ;
2017-10-17 04:01:26 +03:00
} ;
2018-07-24 03:08:00 +03:00
static int cbs_child_enqueue ( struct sk_buff * skb , struct Qdisc * sch ,
struct Qdisc * child ,
struct sk_buff * * to_free )
2017-10-17 04:01:27 +03:00
{
2019-01-09 19:09:42 +03:00
unsigned int len = qdisc_pkt_len ( skb ) ;
2018-07-24 03:08:00 +03:00
int err ;
err = child - > ops - > enqueue ( skb , child , to_free ) ;
if ( err ! = NET_XMIT_SUCCESS )
return err ;
2019-01-09 19:09:42 +03:00
sch - > qstats . backlog + = len ;
2018-07-24 03:08:00 +03:00
sch - > q . qlen + + ;
return NET_XMIT_SUCCESS ;
2017-10-17 04:01:27 +03:00
}
2018-07-24 03:08:00 +03:00
static int cbs_enqueue_offload ( struct sk_buff * skb , struct Qdisc * sch ,
struct sk_buff * * to_free )
2017-10-17 04:01:26 +03:00
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
2018-07-24 03:08:00 +03:00
struct Qdisc * qdisc = q - > qdisc ;
return cbs_child_enqueue ( skb , sch , qdisc , to_free ) ;
}
static int cbs_enqueue_soft ( struct sk_buff * skb , struct Qdisc * sch ,
struct sk_buff * * to_free )
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
struct Qdisc * qdisc = q - > qdisc ;
2017-10-17 04:01:26 +03:00
if ( sch - > q . qlen = = 0 & & q - > credits > 0 ) {
/* We need to stop accumulating credits when there's
* no enqueued packets and q - > credits is positive .
*/
q - > credits = 0 ;
q - > last = ktime_get_ns ( ) ;
}
2018-07-24 03:08:00 +03:00
return cbs_child_enqueue ( skb , sch , qdisc , to_free ) ;
2017-10-17 04:01:26 +03:00
}
static int cbs_enqueue ( struct sk_buff * skb , struct Qdisc * sch ,
struct sk_buff * * to_free )
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
2018-07-24 03:08:00 +03:00
return q - > enqueue ( skb , sch , to_free ) ;
2017-10-17 04:01:26 +03:00
}
/* timediff is in ns, slope is in bytes/s */
static s64 timediff_to_credits ( s64 timediff , s64 slope )
{
return div64_s64 ( timediff * slope , NSEC_PER_SEC ) ;
}
static s64 delay_from_credits ( s64 credits , s64 slope )
{
if ( unlikely ( slope = = 0 ) )
return S64_MAX ;
return div64_s64 ( - credits * NSEC_PER_SEC , slope ) ;
}
static s64 credits_from_len ( unsigned int len , s64 slope , s64 port_rate )
{
if ( unlikely ( port_rate = = 0 ) )
return S64_MAX ;
return div64_s64 ( len * slope , port_rate ) ;
}
2018-07-24 03:08:00 +03:00
static struct sk_buff * cbs_child_dequeue ( struct Qdisc * sch , struct Qdisc * child )
{
struct sk_buff * skb ;
skb = child - > ops - > dequeue ( child ) ;
if ( ! skb )
return NULL ;
qdisc_qstats_backlog_dec ( sch , skb ) ;
qdisc_bstats_update ( sch , skb ) ;
sch - > q . qlen - - ;
return skb ;
}
2017-10-17 04:01:26 +03:00
static struct sk_buff * cbs_dequeue_soft ( struct Qdisc * sch )
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
2018-07-24 03:08:00 +03:00
struct Qdisc * qdisc = q - > qdisc ;
2017-10-17 04:01:26 +03:00
s64 now = ktime_get_ns ( ) ;
struct sk_buff * skb ;
s64 credits ;
int len ;
if ( q - > credits < 0 ) {
credits = timediff_to_credits ( now - q - > last , q - > idleslope ) ;
credits = q - > credits + credits ;
q - > credits = min_t ( s64 , credits , q - > hicredit ) ;
if ( q - > credits < 0 ) {
s64 delay ;
delay = delay_from_credits ( q - > credits , q - > idleslope ) ;
qdisc_watchdog_schedule_ns ( & q - > watchdog , now + delay ) ;
q - > last = now ;
return NULL ;
}
}
2018-07-24 03:08:00 +03:00
skb = cbs_child_dequeue ( sch , qdisc ) ;
2017-10-17 04:01:26 +03:00
if ( ! skb )
return NULL ;
len = qdisc_pkt_len ( skb ) ;
/* As sendslope is a negative number, this will decrease the
* amount of q - > credits .
*/
credits = credits_from_len ( len , q - > sendslope , q - > port_rate ) ;
credits + = q - > credits ;
q - > credits = max_t ( s64 , credits , q - > locredit ) ;
q - > last = now ;
return skb ;
}
2017-10-17 04:01:27 +03:00
static struct sk_buff * cbs_dequeue_offload ( struct Qdisc * sch )
{
2018-07-24 03:08:00 +03:00
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
struct Qdisc * qdisc = q - > qdisc ;
return cbs_child_dequeue ( sch , qdisc ) ;
2017-10-17 04:01:27 +03:00
}
2017-10-17 04:01:26 +03:00
static struct sk_buff * cbs_dequeue ( struct Qdisc * sch )
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
return q - > dequeue ( sch ) ;
}
static const struct nla_policy cbs_policy [ TCA_CBS_MAX + 1 ] = {
[ TCA_CBS_PARMS ] = { . len = sizeof ( struct tc_cbs_qopt ) } ,
} ;
2017-10-17 04:01:27 +03:00
static void cbs_disable_offload ( struct net_device * dev ,
struct cbs_sched_data * q )
{
struct tc_cbs_qopt_offload cbs = { } ;
const struct net_device_ops * ops ;
int err ;
if ( ! q - > offload )
return ;
q - > enqueue = cbs_enqueue_soft ;
q - > dequeue = cbs_dequeue_soft ;
ops = dev - > netdev_ops ;
if ( ! ops - > ndo_setup_tc )
return ;
cbs . queue = q - > queue ;
cbs . enable = 0 ;
2017-11-06 09:23:43 +03:00
err = ops - > ndo_setup_tc ( dev , TC_SETUP_QDISC_CBS , & cbs ) ;
2017-10-17 04:01:27 +03:00
if ( err < 0 )
pr_warn ( " Couldn't disable CBS offload for queue %d \n " ,
cbs . queue ) ;
}
static int cbs_enable_offload ( struct net_device * dev , struct cbs_sched_data * q ,
2017-12-20 20:35:23 +03:00
const struct tc_cbs_qopt * opt ,
struct netlink_ext_ack * extack )
2017-10-17 04:01:27 +03:00
{
const struct net_device_ops * ops = dev - > netdev_ops ;
struct tc_cbs_qopt_offload cbs = { } ;
int err ;
2017-12-20 20:35:23 +03:00
if ( ! ops - > ndo_setup_tc ) {
NL_SET_ERR_MSG ( extack , " Specified device does not support cbs offload " ) ;
2017-10-17 04:01:27 +03:00
return - EOPNOTSUPP ;
2017-12-20 20:35:23 +03:00
}
2017-10-17 04:01:27 +03:00
cbs . queue = q - > queue ;
cbs . enable = 1 ;
cbs . hicredit = opt - > hicredit ;
cbs . locredit = opt - > locredit ;
cbs . idleslope = opt - > idleslope ;
cbs . sendslope = opt - > sendslope ;
2017-11-06 09:23:43 +03:00
err = ops - > ndo_setup_tc ( dev , TC_SETUP_QDISC_CBS , & cbs ) ;
2017-12-20 20:35:23 +03:00
if ( err < 0 ) {
NL_SET_ERR_MSG ( extack , " Specified device failed to setup cbs hardware offload " ) ;
2017-10-17 04:01:27 +03:00
return err ;
2017-12-20 20:35:23 +03:00
}
2017-10-17 04:01:27 +03:00
q - > enqueue = cbs_enqueue_offload ;
q - > dequeue = cbs_dequeue_offload ;
return 0 ;
}
2017-12-20 20:35:14 +03:00
static int cbs_change ( struct Qdisc * sch , struct nlattr * opt ,
struct netlink_ext_ack * extack )
2017-10-17 04:01:26 +03:00
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
struct net_device * dev = qdisc_dev ( sch ) ;
struct nlattr * tb [ TCA_CBS_MAX + 1 ] ;
struct tc_cbs_qopt * qopt ;
int err ;
2017-12-20 20:35:23 +03:00
err = nla_parse_nested ( tb , TCA_CBS_MAX , opt , cbs_policy , extack ) ;
2017-10-17 04:01:26 +03:00
if ( err < 0 )
return err ;
2017-12-20 20:35:23 +03:00
if ( ! tb [ TCA_CBS_PARMS ] ) {
NL_SET_ERR_MSG ( extack , " Missing CBS parameter which are mandatory " ) ;
2017-10-17 04:01:26 +03:00
return - EINVAL ;
2017-12-20 20:35:23 +03:00
}
2017-10-17 04:01:26 +03:00
qopt = nla_data ( tb [ TCA_CBS_PARMS ] ) ;
2017-10-17 04:01:27 +03:00
if ( ! qopt - > offload ) {
struct ethtool_link_ksettings ecmd ;
s64 link_speed ;
2017-10-17 04:01:26 +03:00
2017-10-17 04:01:27 +03:00
if ( ! __ethtool_get_link_ksettings ( dev , & ecmd ) )
link_speed = ecmd . base . speed ;
else
link_speed = SPEED_1000 ;
2017-10-17 04:01:26 +03:00
2017-10-17 04:01:27 +03:00
q - > port_rate = link_speed * 1000 * BYTES_PER_KBIT ;
2017-10-17 04:01:26 +03:00
2017-10-17 04:01:27 +03:00
cbs_disable_offload ( dev , q ) ;
} else {
2017-12-20 20:35:23 +03:00
err = cbs_enable_offload ( dev , q , qopt , extack ) ;
2017-10-17 04:01:27 +03:00
if ( err < 0 )
return err ;
}
2017-10-17 04:01:26 +03:00
2017-10-17 04:01:27 +03:00
/* Everything went OK, save the parameters used. */
2017-10-17 04:01:26 +03:00
q - > hicredit = qopt - > hicredit ;
q - > locredit = qopt - > locredit ;
q - > idleslope = qopt - > idleslope * BYTES_PER_KBIT ;
q - > sendslope = qopt - > sendslope * BYTES_PER_KBIT ;
2017-10-17 04:01:27 +03:00
q - > offload = qopt - > offload ;
2017-10-17 04:01:26 +03:00
return 0 ;
}
2017-12-20 20:35:13 +03:00
static int cbs_init ( struct Qdisc * sch , struct nlattr * opt ,
struct netlink_ext_ack * extack )
2017-10-17 04:01:26 +03:00
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
2017-10-17 04:01:27 +03:00
struct net_device * dev = qdisc_dev ( sch ) ;
2017-10-17 04:01:26 +03:00
2017-12-20 20:35:23 +03:00
if ( ! opt ) {
NL_SET_ERR_MSG ( extack , " Missing CBS qdisc options which are mandatory " ) ;
2017-10-17 04:01:26 +03:00
return - EINVAL ;
2017-12-20 20:35:23 +03:00
}
2017-10-17 04:01:26 +03:00
2018-07-24 03:08:00 +03:00
q - > qdisc = qdisc_create_dflt ( sch - > dev_queue , & pfifo_qdisc_ops ,
sch - > handle , extack ) ;
if ( ! q - > qdisc )
return - ENOMEM ;
qdisc_hash_add ( q - > qdisc , false ) ;
2017-10-17 04:01:27 +03:00
q - > queue = sch - > dev_queue - netdev_get_tx_queue ( dev , 0 ) ;
q - > enqueue = cbs_enqueue_soft ;
q - > dequeue = cbs_dequeue_soft ;
2017-10-17 04:01:26 +03:00
qdisc_watchdog_init ( & q - > watchdog , sch ) ;
2017-12-20 20:35:14 +03:00
return cbs_change ( sch , opt , extack ) ;
2017-10-17 04:01:26 +03:00
}
static void cbs_destroy ( struct Qdisc * sch )
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
2017-10-17 04:01:27 +03:00
struct net_device * dev = qdisc_dev ( sch ) ;
2017-10-17 04:01:26 +03:00
qdisc_watchdog_cancel ( & q - > watchdog ) ;
2017-10-17 04:01:27 +03:00
cbs_disable_offload ( dev , q ) ;
2018-07-24 03:08:00 +03:00
if ( q - > qdisc )
2018-09-24 19:22:50 +03:00
qdisc_put ( q - > qdisc ) ;
2017-10-17 04:01:26 +03:00
}
static int cbs_dump ( struct Qdisc * sch , struct sk_buff * skb )
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
struct tc_cbs_qopt opt = { } ;
struct nlattr * nest ;
nest = nla_nest_start ( skb , TCA_OPTIONS ) ;
if ( ! nest )
goto nla_put_failure ;
opt . hicredit = q - > hicredit ;
opt . locredit = q - > locredit ;
opt . sendslope = div64_s64 ( q - > sendslope , BYTES_PER_KBIT ) ;
opt . idleslope = div64_s64 ( q - > idleslope , BYTES_PER_KBIT ) ;
2017-10-17 04:01:27 +03:00
opt . offload = q - > offload ;
2017-10-17 04:01:26 +03:00
if ( nla_put ( skb , TCA_CBS_PARMS , sizeof ( opt ) , & opt ) )
goto nla_put_failure ;
return nla_nest_end ( skb , nest ) ;
nla_put_failure :
nla_nest_cancel ( skb , nest ) ;
return - 1 ;
}
2018-07-24 03:08:00 +03:00
static int cbs_dump_class ( struct Qdisc * sch , unsigned long cl ,
struct sk_buff * skb , struct tcmsg * tcm )
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
if ( cl ! = 1 | | ! q - > qdisc ) /* only one class */
return - ENOENT ;
tcm - > tcm_handle | = TC_H_MIN ( 1 ) ;
tcm - > tcm_info = q - > qdisc - > handle ;
return 0 ;
}
static int cbs_graft ( struct Qdisc * sch , unsigned long arg , struct Qdisc * new ,
struct Qdisc * * old , struct netlink_ext_ack * extack )
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
if ( ! new ) {
new = qdisc_create_dflt ( sch - > dev_queue , & pfifo_qdisc_ops ,
sch - > handle , NULL ) ;
if ( ! new )
new = & noop_qdisc ;
}
* old = qdisc_replace ( sch , new , & q - > qdisc ) ;
return 0 ;
}
static struct Qdisc * cbs_leaf ( struct Qdisc * sch , unsigned long arg )
{
struct cbs_sched_data * q = qdisc_priv ( sch ) ;
return q - > qdisc ;
}
static unsigned long cbs_find ( struct Qdisc * sch , u32 classid )
{
return 1 ;
}
static void cbs_walk ( struct Qdisc * sch , struct qdisc_walker * walker )
{
if ( ! walker - > stop ) {
if ( walker - > count > = walker - > skip ) {
if ( walker - > fn ( sch , 1 , walker ) < 0 ) {
walker - > stop = 1 ;
return ;
}
}
walker - > count + + ;
}
}
static const struct Qdisc_class_ops cbs_class_ops = {
. graft = cbs_graft ,
. leaf = cbs_leaf ,
. find = cbs_find ,
. walk = cbs_walk ,
. dump = cbs_dump_class ,
} ;
2017-10-17 04:01:26 +03:00
static struct Qdisc_ops cbs_qdisc_ops __read_mostly = {
. id = " cbs " ,
2018-07-24 03:08:00 +03:00
. cl_ops = & cbs_class_ops ,
2017-10-17 04:01:26 +03:00
. priv_size = sizeof ( struct cbs_sched_data ) ,
. enqueue = cbs_enqueue ,
. dequeue = cbs_dequeue ,
. peek = qdisc_peek_dequeued ,
. init = cbs_init ,
. reset = qdisc_reset_queue ,
. destroy = cbs_destroy ,
. change = cbs_change ,
. dump = cbs_dump ,
. owner = THIS_MODULE ,
} ;
static int __init cbs_module_init ( void )
{
return register_qdisc ( & cbs_qdisc_ops ) ;
}
static void __exit cbs_module_exit ( void )
{
unregister_qdisc ( & cbs_qdisc_ops ) ;
}
module_init ( cbs_module_init )
module_exit ( cbs_module_exit )
MODULE_LICENSE ( " GPL " ) ;