162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * net/sched/sch_cbs.c Credit Based Shaper 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Authors: Vinicius Costa Gomes <vinicius.gomes@intel.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci/* Credit Based Shaper (CBS) 962306a36Sopenharmony_ci * ========================= 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * This is a simple rate-limiting shaper aimed at TSN applications on 1262306a36Sopenharmony_ci * systems with known traffic workloads. 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * Its algorithm is defined by the IEEE 802.1Q-2014 Specification, 1562306a36Sopenharmony_ci * Section 8.6.8.2, and explained in more detail in the Annex L of the 1662306a36Sopenharmony_ci * same specification. 1762306a36Sopenharmony_ci * 1862306a36Sopenharmony_ci * There are four tunables to be considered: 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * 'idleslope': Idleslope is the rate of credits that is 2162306a36Sopenharmony_ci * accumulated (in kilobits per second) when there is at least 2262306a36Sopenharmony_ci * one packet waiting for transmission. Packets are transmitted 2362306a36Sopenharmony_ci * when the current value of credits is equal or greater than 2462306a36Sopenharmony_ci * zero. When there is no packet to be transmitted the amount of 2562306a36Sopenharmony_ci * credits is set to zero. This is the main tunable of the CBS 2662306a36Sopenharmony_ci * algorithm. 2762306a36Sopenharmony_ci * 2862306a36Sopenharmony_ci * 'sendslope': 2962306a36Sopenharmony_ci * Sendslope is the rate of credits that is depleted (it should be a 3062306a36Sopenharmony_ci * negative number of kilobits per second) when a transmission is 3162306a36Sopenharmony_ci * ocurring. It can be calculated as follows, (IEEE 802.1Q-2014 Section 3262306a36Sopenharmony_ci * 8.6.8.2 item g): 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * sendslope = idleslope - port_transmit_rate 3562306a36Sopenharmony_ci * 3662306a36Sopenharmony_ci * 'hicredit': Hicredit defines the maximum amount of credits (in 3762306a36Sopenharmony_ci * bytes) that can be accumulated. Hicredit depends on the 3862306a36Sopenharmony_ci * characteristics of interfering traffic, 3962306a36Sopenharmony_ci * 'max_interference_size' is the maximum size of any burst of 4062306a36Sopenharmony_ci * traffic that can delay the transmission of a frame that is 4162306a36Sopenharmony_ci * available for transmission for this traffic class, (IEEE 4262306a36Sopenharmony_ci * 802.1Q-2014 Annex L, Equation L-3): 4362306a36Sopenharmony_ci * 4462306a36Sopenharmony_ci * hicredit = max_interference_size * (idleslope / port_transmit_rate) 4562306a36Sopenharmony_ci * 4662306a36Sopenharmony_ci * 'locredit': Locredit is the minimum amount of credits that can 4762306a36Sopenharmony_ci * be reached. It is a function of the traffic flowing through 4862306a36Sopenharmony_ci * this qdisc (IEEE 802.1Q-2014 Annex L, Equation L-2): 4962306a36Sopenharmony_ci * 5062306a36Sopenharmony_ci * locredit = max_frame_size * (sendslope / port_transmit_rate) 5162306a36Sopenharmony_ci */ 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci#include <linux/ethtool.h> 5462306a36Sopenharmony_ci#include <linux/module.h> 5562306a36Sopenharmony_ci#include <linux/types.h> 5662306a36Sopenharmony_ci#include <linux/kernel.h> 5762306a36Sopenharmony_ci#include <linux/string.h> 5862306a36Sopenharmony_ci#include <linux/errno.h> 5962306a36Sopenharmony_ci#include <linux/skbuff.h> 6062306a36Sopenharmony_ci#include <net/netevent.h> 6162306a36Sopenharmony_ci#include <net/netlink.h> 6262306a36Sopenharmony_ci#include <net/sch_generic.h> 6362306a36Sopenharmony_ci#include <net/pkt_sched.h> 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic LIST_HEAD(cbs_list); 6662306a36Sopenharmony_cistatic DEFINE_SPINLOCK(cbs_list_lock); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci#define BYTES_PER_KBIT (1000LL / 8) 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistruct cbs_sched_data { 7162306a36Sopenharmony_ci bool offload; 7262306a36Sopenharmony_ci int queue; 7362306a36Sopenharmony_ci atomic64_t port_rate; /* in bytes/s */ 7462306a36Sopenharmony_ci s64 last; /* timestamp in ns */ 7562306a36Sopenharmony_ci s64 credits; /* in bytes */ 7662306a36Sopenharmony_ci s32 locredit; /* in bytes */ 7762306a36Sopenharmony_ci s32 hicredit; /* in bytes */ 7862306a36Sopenharmony_ci s64 sendslope; /* in bytes/s */ 7962306a36Sopenharmony_ci s64 idleslope; /* in bytes/s */ 8062306a36Sopenharmony_ci struct qdisc_watchdog watchdog; 8162306a36Sopenharmony_ci int (*enqueue)(struct sk_buff *skb, struct Qdisc *sch, 8262306a36Sopenharmony_ci struct sk_buff **to_free); 8362306a36Sopenharmony_ci struct sk_buff *(*dequeue)(struct Qdisc *sch); 8462306a36Sopenharmony_ci struct Qdisc *qdisc; 8562306a36Sopenharmony_ci struct list_head cbs_list; 8662306a36Sopenharmony_ci}; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int cbs_child_enqueue(struct sk_buff *skb, struct Qdisc *sch, 8962306a36Sopenharmony_ci struct Qdisc *child, 9062306a36Sopenharmony_ci struct sk_buff **to_free) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci unsigned int len = qdisc_pkt_len(skb); 9362306a36Sopenharmony_ci int err; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci err = child->ops->enqueue(skb, child, to_free); 9662306a36Sopenharmony_ci if (err != NET_XMIT_SUCCESS) 9762306a36Sopenharmony_ci return err; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci sch->qstats.backlog += len; 10062306a36Sopenharmony_ci sch->q.qlen++; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci return NET_XMIT_SUCCESS; 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic int cbs_enqueue_offload(struct sk_buff *skb, struct Qdisc *sch, 10662306a36Sopenharmony_ci struct sk_buff **to_free) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 10962306a36Sopenharmony_ci struct Qdisc *qdisc = q->qdisc; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci return cbs_child_enqueue(skb, sch, qdisc, to_free); 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic int cbs_enqueue_soft(struct sk_buff *skb, struct Qdisc *sch, 11562306a36Sopenharmony_ci struct sk_buff **to_free) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 11862306a36Sopenharmony_ci struct Qdisc *qdisc = q->qdisc; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (sch->q.qlen == 0 && q->credits > 0) { 12162306a36Sopenharmony_ci /* We need to stop accumulating credits when there's 12262306a36Sopenharmony_ci * no enqueued packets and q->credits is positive. 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_ci q->credits = 0; 12562306a36Sopenharmony_ci q->last = ktime_get_ns(); 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return cbs_child_enqueue(skb, sch, qdisc, to_free); 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic int cbs_enqueue(struct sk_buff *skb, struct Qdisc *sch, 13262306a36Sopenharmony_ci struct sk_buff **to_free) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci return q->enqueue(skb, sch, to_free); 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci/* timediff is in ns, slope is in bytes/s */ 14062306a36Sopenharmony_cistatic s64 timediff_to_credits(s64 timediff, s64 slope) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci return div64_s64(timediff * slope, NSEC_PER_SEC); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic s64 delay_from_credits(s64 credits, s64 slope) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci if (unlikely(slope == 0)) 14862306a36Sopenharmony_ci return S64_MAX; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci return div64_s64(-credits * NSEC_PER_SEC, slope); 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic s64 credits_from_len(unsigned int len, s64 slope, s64 port_rate) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci if (unlikely(port_rate == 0)) 15662306a36Sopenharmony_ci return S64_MAX; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return div64_s64(len * slope, port_rate); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic struct sk_buff *cbs_child_dequeue(struct Qdisc *sch, struct Qdisc *child) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci struct sk_buff *skb; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci skb = child->ops->dequeue(child); 16662306a36Sopenharmony_ci if (!skb) 16762306a36Sopenharmony_ci return NULL; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci qdisc_qstats_backlog_dec(sch, skb); 17062306a36Sopenharmony_ci qdisc_bstats_update(sch, skb); 17162306a36Sopenharmony_ci sch->q.qlen--; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci return skb; 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic struct sk_buff *cbs_dequeue_soft(struct Qdisc *sch) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 17962306a36Sopenharmony_ci struct Qdisc *qdisc = q->qdisc; 18062306a36Sopenharmony_ci s64 now = ktime_get_ns(); 18162306a36Sopenharmony_ci struct sk_buff *skb; 18262306a36Sopenharmony_ci s64 credits; 18362306a36Sopenharmony_ci int len; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* The previous packet is still being sent */ 18662306a36Sopenharmony_ci if (now < q->last) { 18762306a36Sopenharmony_ci qdisc_watchdog_schedule_ns(&q->watchdog, q->last); 18862306a36Sopenharmony_ci return NULL; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci if (q->credits < 0) { 19162306a36Sopenharmony_ci credits = timediff_to_credits(now - q->last, q->idleslope); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci credits = q->credits + credits; 19462306a36Sopenharmony_ci q->credits = min_t(s64, credits, q->hicredit); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (q->credits < 0) { 19762306a36Sopenharmony_ci s64 delay; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci delay = delay_from_credits(q->credits, q->idleslope); 20062306a36Sopenharmony_ci qdisc_watchdog_schedule_ns(&q->watchdog, now + delay); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci q->last = now; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return NULL; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci skb = cbs_child_dequeue(sch, qdisc); 20862306a36Sopenharmony_ci if (!skb) 20962306a36Sopenharmony_ci return NULL; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci len = qdisc_pkt_len(skb); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* As sendslope is a negative number, this will decrease the 21462306a36Sopenharmony_ci * amount of q->credits. 21562306a36Sopenharmony_ci */ 21662306a36Sopenharmony_ci credits = credits_from_len(len, q->sendslope, 21762306a36Sopenharmony_ci atomic64_read(&q->port_rate)); 21862306a36Sopenharmony_ci credits += q->credits; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci q->credits = max_t(s64, credits, q->locredit); 22162306a36Sopenharmony_ci /* Estimate of the transmission of the last byte of the packet in ns */ 22262306a36Sopenharmony_ci if (unlikely(atomic64_read(&q->port_rate) == 0)) 22362306a36Sopenharmony_ci q->last = now; 22462306a36Sopenharmony_ci else 22562306a36Sopenharmony_ci q->last = now + div64_s64(len * NSEC_PER_SEC, 22662306a36Sopenharmony_ci atomic64_read(&q->port_rate)); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci return skb; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic struct sk_buff *cbs_dequeue_offload(struct Qdisc *sch) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 23462306a36Sopenharmony_ci struct Qdisc *qdisc = q->qdisc; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci return cbs_child_dequeue(sch, qdisc); 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic struct sk_buff *cbs_dequeue(struct Qdisc *sch) 24062306a36Sopenharmony_ci{ 24162306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci return q->dequeue(sch); 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic const struct nla_policy cbs_policy[TCA_CBS_MAX + 1] = { 24762306a36Sopenharmony_ci [TCA_CBS_PARMS] = { .len = sizeof(struct tc_cbs_qopt) }, 24862306a36Sopenharmony_ci}; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic void cbs_disable_offload(struct net_device *dev, 25162306a36Sopenharmony_ci struct cbs_sched_data *q) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci struct tc_cbs_qopt_offload cbs = { }; 25462306a36Sopenharmony_ci const struct net_device_ops *ops; 25562306a36Sopenharmony_ci int err; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (!q->offload) 25862306a36Sopenharmony_ci return; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci q->enqueue = cbs_enqueue_soft; 26162306a36Sopenharmony_ci q->dequeue = cbs_dequeue_soft; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci ops = dev->netdev_ops; 26462306a36Sopenharmony_ci if (!ops->ndo_setup_tc) 26562306a36Sopenharmony_ci return; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci cbs.queue = q->queue; 26862306a36Sopenharmony_ci cbs.enable = 0; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_CBS, &cbs); 27162306a36Sopenharmony_ci if (err < 0) 27262306a36Sopenharmony_ci pr_warn("Couldn't disable CBS offload for queue %d\n", 27362306a36Sopenharmony_ci cbs.queue); 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistatic int cbs_enable_offload(struct net_device *dev, struct cbs_sched_data *q, 27762306a36Sopenharmony_ci const struct tc_cbs_qopt *opt, 27862306a36Sopenharmony_ci struct netlink_ext_ack *extack) 27962306a36Sopenharmony_ci{ 28062306a36Sopenharmony_ci const struct net_device_ops *ops = dev->netdev_ops; 28162306a36Sopenharmony_ci struct tc_cbs_qopt_offload cbs = { }; 28262306a36Sopenharmony_ci int err; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci if (!ops->ndo_setup_tc) { 28562306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Specified device does not support cbs offload"); 28662306a36Sopenharmony_ci return -EOPNOTSUPP; 28762306a36Sopenharmony_ci } 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci cbs.queue = q->queue; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci cbs.enable = 1; 29262306a36Sopenharmony_ci cbs.hicredit = opt->hicredit; 29362306a36Sopenharmony_ci cbs.locredit = opt->locredit; 29462306a36Sopenharmony_ci cbs.idleslope = opt->idleslope; 29562306a36Sopenharmony_ci cbs.sendslope = opt->sendslope; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_CBS, &cbs); 29862306a36Sopenharmony_ci if (err < 0) { 29962306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Specified device failed to setup cbs hardware offload"); 30062306a36Sopenharmony_ci return err; 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci q->enqueue = cbs_enqueue_offload; 30462306a36Sopenharmony_ci q->dequeue = cbs_dequeue_offload; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci return 0; 30762306a36Sopenharmony_ci} 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cistatic void cbs_set_port_rate(struct net_device *dev, struct cbs_sched_data *q) 31062306a36Sopenharmony_ci{ 31162306a36Sopenharmony_ci struct ethtool_link_ksettings ecmd; 31262306a36Sopenharmony_ci int speed = SPEED_10; 31362306a36Sopenharmony_ci int port_rate; 31462306a36Sopenharmony_ci int err; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci err = __ethtool_get_link_ksettings(dev, &ecmd); 31762306a36Sopenharmony_ci if (err < 0) 31862306a36Sopenharmony_ci goto skip; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci if (ecmd.base.speed && ecmd.base.speed != SPEED_UNKNOWN) 32162306a36Sopenharmony_ci speed = ecmd.base.speed; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ciskip: 32462306a36Sopenharmony_ci port_rate = speed * 1000 * BYTES_PER_KBIT; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci atomic64_set(&q->port_rate, port_rate); 32762306a36Sopenharmony_ci netdev_dbg(dev, "cbs: set %s's port_rate to: %lld, linkspeed: %d\n", 32862306a36Sopenharmony_ci dev->name, (long long)atomic64_read(&q->port_rate), 32962306a36Sopenharmony_ci ecmd.base.speed); 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistatic int cbs_dev_notifier(struct notifier_block *nb, unsigned long event, 33362306a36Sopenharmony_ci void *ptr) 33462306a36Sopenharmony_ci{ 33562306a36Sopenharmony_ci struct net_device *dev = netdev_notifier_info_to_dev(ptr); 33662306a36Sopenharmony_ci struct cbs_sched_data *q; 33762306a36Sopenharmony_ci struct net_device *qdev; 33862306a36Sopenharmony_ci bool found = false; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci ASSERT_RTNL(); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci if (event != NETDEV_UP && event != NETDEV_CHANGE) 34362306a36Sopenharmony_ci return NOTIFY_DONE; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci spin_lock(&cbs_list_lock); 34662306a36Sopenharmony_ci list_for_each_entry(q, &cbs_list, cbs_list) { 34762306a36Sopenharmony_ci qdev = qdisc_dev(q->qdisc); 34862306a36Sopenharmony_ci if (qdev == dev) { 34962306a36Sopenharmony_ci found = true; 35062306a36Sopenharmony_ci break; 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci } 35362306a36Sopenharmony_ci spin_unlock(&cbs_list_lock); 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci if (found) 35662306a36Sopenharmony_ci cbs_set_port_rate(dev, q); 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci return NOTIFY_DONE; 35962306a36Sopenharmony_ci} 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_cistatic int cbs_change(struct Qdisc *sch, struct nlattr *opt, 36262306a36Sopenharmony_ci struct netlink_ext_ack *extack) 36362306a36Sopenharmony_ci{ 36462306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 36562306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 36662306a36Sopenharmony_ci struct nlattr *tb[TCA_CBS_MAX + 1]; 36762306a36Sopenharmony_ci struct tc_cbs_qopt *qopt; 36862306a36Sopenharmony_ci int err; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_CBS_MAX, opt, cbs_policy, 37162306a36Sopenharmony_ci extack); 37262306a36Sopenharmony_ci if (err < 0) 37362306a36Sopenharmony_ci return err; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci if (!tb[TCA_CBS_PARMS]) { 37662306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Missing CBS parameter which are mandatory"); 37762306a36Sopenharmony_ci return -EINVAL; 37862306a36Sopenharmony_ci } 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci qopt = nla_data(tb[TCA_CBS_PARMS]); 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci if (!qopt->offload) { 38362306a36Sopenharmony_ci cbs_set_port_rate(dev, q); 38462306a36Sopenharmony_ci cbs_disable_offload(dev, q); 38562306a36Sopenharmony_ci } else { 38662306a36Sopenharmony_ci err = cbs_enable_offload(dev, q, qopt, extack); 38762306a36Sopenharmony_ci if (err < 0) 38862306a36Sopenharmony_ci return err; 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci /* Everything went OK, save the parameters used. */ 39262306a36Sopenharmony_ci q->hicredit = qopt->hicredit; 39362306a36Sopenharmony_ci q->locredit = qopt->locredit; 39462306a36Sopenharmony_ci q->idleslope = qopt->idleslope * BYTES_PER_KBIT; 39562306a36Sopenharmony_ci q->sendslope = qopt->sendslope * BYTES_PER_KBIT; 39662306a36Sopenharmony_ci q->offload = qopt->offload; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci return 0; 39962306a36Sopenharmony_ci} 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_cistatic int cbs_init(struct Qdisc *sch, struct nlattr *opt, 40262306a36Sopenharmony_ci struct netlink_ext_ack *extack) 40362306a36Sopenharmony_ci{ 40462306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 40562306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci if (!opt) { 40862306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Missing CBS qdisc options which are mandatory"); 40962306a36Sopenharmony_ci return -EINVAL; 41062306a36Sopenharmony_ci } 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci q->qdisc = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, 41362306a36Sopenharmony_ci sch->handle, extack); 41462306a36Sopenharmony_ci if (!q->qdisc) 41562306a36Sopenharmony_ci return -ENOMEM; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci spin_lock(&cbs_list_lock); 41862306a36Sopenharmony_ci list_add(&q->cbs_list, &cbs_list); 41962306a36Sopenharmony_ci spin_unlock(&cbs_list_lock); 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci qdisc_hash_add(q->qdisc, false); 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci q->queue = sch->dev_queue - netdev_get_tx_queue(dev, 0); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci q->enqueue = cbs_enqueue_soft; 42662306a36Sopenharmony_ci q->dequeue = cbs_dequeue_soft; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci qdisc_watchdog_init(&q->watchdog, sch); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci return cbs_change(sch, opt, extack); 43162306a36Sopenharmony_ci} 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_cistatic void cbs_destroy(struct Qdisc *sch) 43462306a36Sopenharmony_ci{ 43562306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 43662306a36Sopenharmony_ci struct net_device *dev = qdisc_dev(sch); 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci /* Nothing to do if we couldn't create the underlying qdisc */ 43962306a36Sopenharmony_ci if (!q->qdisc) 44062306a36Sopenharmony_ci return; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci qdisc_watchdog_cancel(&q->watchdog); 44362306a36Sopenharmony_ci cbs_disable_offload(dev, q); 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci spin_lock(&cbs_list_lock); 44662306a36Sopenharmony_ci list_del(&q->cbs_list); 44762306a36Sopenharmony_ci spin_unlock(&cbs_list_lock); 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci qdisc_put(q->qdisc); 45062306a36Sopenharmony_ci} 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_cistatic int cbs_dump(struct Qdisc *sch, struct sk_buff *skb) 45362306a36Sopenharmony_ci{ 45462306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 45562306a36Sopenharmony_ci struct tc_cbs_qopt opt = { }; 45662306a36Sopenharmony_ci struct nlattr *nest; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci nest = nla_nest_start_noflag(skb, TCA_OPTIONS); 45962306a36Sopenharmony_ci if (!nest) 46062306a36Sopenharmony_ci goto nla_put_failure; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci opt.hicredit = q->hicredit; 46362306a36Sopenharmony_ci opt.locredit = q->locredit; 46462306a36Sopenharmony_ci opt.sendslope = div64_s64(q->sendslope, BYTES_PER_KBIT); 46562306a36Sopenharmony_ci opt.idleslope = div64_s64(q->idleslope, BYTES_PER_KBIT); 46662306a36Sopenharmony_ci opt.offload = q->offload; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci if (nla_put(skb, TCA_CBS_PARMS, sizeof(opt), &opt)) 46962306a36Sopenharmony_ci goto nla_put_failure; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci return nla_nest_end(skb, nest); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_cinla_put_failure: 47462306a36Sopenharmony_ci nla_nest_cancel(skb, nest); 47562306a36Sopenharmony_ci return -1; 47662306a36Sopenharmony_ci} 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_cistatic int cbs_dump_class(struct Qdisc *sch, unsigned long cl, 47962306a36Sopenharmony_ci struct sk_buff *skb, struct tcmsg *tcm) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci if (cl != 1 || !q->qdisc) /* only one class */ 48462306a36Sopenharmony_ci return -ENOENT; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci tcm->tcm_handle |= TC_H_MIN(1); 48762306a36Sopenharmony_ci tcm->tcm_info = q->qdisc->handle; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci return 0; 49062306a36Sopenharmony_ci} 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_cistatic int cbs_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new, 49362306a36Sopenharmony_ci struct Qdisc **old, struct netlink_ext_ack *extack) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci if (!new) { 49862306a36Sopenharmony_ci new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops, 49962306a36Sopenharmony_ci sch->handle, NULL); 50062306a36Sopenharmony_ci if (!new) 50162306a36Sopenharmony_ci new = &noop_qdisc; 50262306a36Sopenharmony_ci } 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci *old = qdisc_replace(sch, new, &q->qdisc); 50562306a36Sopenharmony_ci return 0; 50662306a36Sopenharmony_ci} 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_cistatic struct Qdisc *cbs_leaf(struct Qdisc *sch, unsigned long arg) 50962306a36Sopenharmony_ci{ 51062306a36Sopenharmony_ci struct cbs_sched_data *q = qdisc_priv(sch); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci return q->qdisc; 51362306a36Sopenharmony_ci} 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_cistatic unsigned long cbs_find(struct Qdisc *sch, u32 classid) 51662306a36Sopenharmony_ci{ 51762306a36Sopenharmony_ci return 1; 51862306a36Sopenharmony_ci} 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_cistatic void cbs_walk(struct Qdisc *sch, struct qdisc_walker *walker) 52162306a36Sopenharmony_ci{ 52262306a36Sopenharmony_ci if (!walker->stop) { 52362306a36Sopenharmony_ci tc_qdisc_stats_dump(sch, 1, walker); 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci} 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_cistatic const struct Qdisc_class_ops cbs_class_ops = { 52862306a36Sopenharmony_ci .graft = cbs_graft, 52962306a36Sopenharmony_ci .leaf = cbs_leaf, 53062306a36Sopenharmony_ci .find = cbs_find, 53162306a36Sopenharmony_ci .walk = cbs_walk, 53262306a36Sopenharmony_ci .dump = cbs_dump_class, 53362306a36Sopenharmony_ci}; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_cistatic struct Qdisc_ops cbs_qdisc_ops __read_mostly = { 53662306a36Sopenharmony_ci .id = "cbs", 53762306a36Sopenharmony_ci .cl_ops = &cbs_class_ops, 53862306a36Sopenharmony_ci .priv_size = sizeof(struct cbs_sched_data), 53962306a36Sopenharmony_ci .enqueue = cbs_enqueue, 54062306a36Sopenharmony_ci .dequeue = cbs_dequeue, 54162306a36Sopenharmony_ci .peek = qdisc_peek_dequeued, 54262306a36Sopenharmony_ci .init = cbs_init, 54362306a36Sopenharmony_ci .reset = qdisc_reset_queue, 54462306a36Sopenharmony_ci .destroy = cbs_destroy, 54562306a36Sopenharmony_ci .change = cbs_change, 54662306a36Sopenharmony_ci .dump = cbs_dump, 54762306a36Sopenharmony_ci .owner = THIS_MODULE, 54862306a36Sopenharmony_ci}; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_cistatic struct notifier_block cbs_device_notifier = { 55162306a36Sopenharmony_ci .notifier_call = cbs_dev_notifier, 55262306a36Sopenharmony_ci}; 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_cistatic int __init cbs_module_init(void) 55562306a36Sopenharmony_ci{ 55662306a36Sopenharmony_ci int err; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci err = register_netdevice_notifier(&cbs_device_notifier); 55962306a36Sopenharmony_ci if (err) 56062306a36Sopenharmony_ci return err; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci err = register_qdisc(&cbs_qdisc_ops); 56362306a36Sopenharmony_ci if (err) 56462306a36Sopenharmony_ci unregister_netdevice_notifier(&cbs_device_notifier); 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci return err; 56762306a36Sopenharmony_ci} 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_cistatic void __exit cbs_module_exit(void) 57062306a36Sopenharmony_ci{ 57162306a36Sopenharmony_ci unregister_qdisc(&cbs_qdisc_ops); 57262306a36Sopenharmony_ci unregister_netdevice_notifier(&cbs_device_notifier); 57362306a36Sopenharmony_ci} 57462306a36Sopenharmony_cimodule_init(cbs_module_init) 57562306a36Sopenharmony_cimodule_exit(cbs_module_exit) 57662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 577