xref: /kernel/linux/linux-5.10/net/sched/sch_cbs.c (revision 8c2ecf20)
18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * net/sched/sch_cbs.c	Credit Based Shaper
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Authors:	Vinicius Costa Gomes <vinicius.gomes@intel.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci/* Credit Based Shaper (CBS)
98c2ecf20Sopenharmony_ci * =========================
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * This is a simple rate-limiting shaper aimed at TSN applications on
128c2ecf20Sopenharmony_ci * systems with known traffic workloads.
138c2ecf20Sopenharmony_ci *
148c2ecf20Sopenharmony_ci * Its algorithm is defined by the IEEE 802.1Q-2014 Specification,
158c2ecf20Sopenharmony_ci * Section 8.6.8.2, and explained in more detail in the Annex L of the
168c2ecf20Sopenharmony_ci * same specification.
178c2ecf20Sopenharmony_ci *
188c2ecf20Sopenharmony_ci * There are four tunables to be considered:
198c2ecf20Sopenharmony_ci *
208c2ecf20Sopenharmony_ci *	'idleslope': Idleslope is the rate of credits that is
218c2ecf20Sopenharmony_ci *	accumulated (in kilobits per second) when there is at least
228c2ecf20Sopenharmony_ci *	one packet waiting for transmission. Packets are transmitted
238c2ecf20Sopenharmony_ci *	when the current value of credits is equal or greater than
248c2ecf20Sopenharmony_ci *	zero. When there is no packet to be transmitted the amount of
258c2ecf20Sopenharmony_ci *	credits is set to zero. This is the main tunable of the CBS
268c2ecf20Sopenharmony_ci *	algorithm.
278c2ecf20Sopenharmony_ci *
288c2ecf20Sopenharmony_ci *	'sendslope':
298c2ecf20Sopenharmony_ci *	Sendslope is the rate of credits that is depleted (it should be a
308c2ecf20Sopenharmony_ci *	negative number of kilobits per second) when a transmission is
318c2ecf20Sopenharmony_ci *	ocurring. It can be calculated as follows, (IEEE 802.1Q-2014 Section
328c2ecf20Sopenharmony_ci *	8.6.8.2 item g):
338c2ecf20Sopenharmony_ci *
348c2ecf20Sopenharmony_ci *	sendslope = idleslope - port_transmit_rate
358c2ecf20Sopenharmony_ci *
368c2ecf20Sopenharmony_ci *	'hicredit': Hicredit defines the maximum amount of credits (in
378c2ecf20Sopenharmony_ci *	bytes) that can be accumulated. Hicredit depends on the
388c2ecf20Sopenharmony_ci *	characteristics of interfering traffic,
398c2ecf20Sopenharmony_ci *	'max_interference_size' is the maximum size of any burst of
408c2ecf20Sopenharmony_ci *	traffic that can delay the transmission of a frame that is
418c2ecf20Sopenharmony_ci *	available for transmission for this traffic class, (IEEE
428c2ecf20Sopenharmony_ci *	802.1Q-2014 Annex L, Equation L-3):
438c2ecf20Sopenharmony_ci *
448c2ecf20Sopenharmony_ci *	hicredit = max_interference_size * (idleslope / port_transmit_rate)
458c2ecf20Sopenharmony_ci *
468c2ecf20Sopenharmony_ci *	'locredit': Locredit is the minimum amount of credits that can
478c2ecf20Sopenharmony_ci *	be reached. It is a function of the traffic flowing through
488c2ecf20Sopenharmony_ci *	this qdisc (IEEE 802.1Q-2014 Annex L, Equation L-2):
498c2ecf20Sopenharmony_ci *
508c2ecf20Sopenharmony_ci *	locredit = max_frame_size * (sendslope / port_transmit_rate)
518c2ecf20Sopenharmony_ci */
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci#include <linux/module.h>
548c2ecf20Sopenharmony_ci#include <linux/types.h>
558c2ecf20Sopenharmony_ci#include <linux/kernel.h>
568c2ecf20Sopenharmony_ci#include <linux/string.h>
578c2ecf20Sopenharmony_ci#include <linux/errno.h>
588c2ecf20Sopenharmony_ci#include <linux/skbuff.h>
598c2ecf20Sopenharmony_ci#include <net/netevent.h>
608c2ecf20Sopenharmony_ci#include <net/netlink.h>
618c2ecf20Sopenharmony_ci#include <net/sch_generic.h>
628c2ecf20Sopenharmony_ci#include <net/pkt_sched.h>
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic LIST_HEAD(cbs_list);
658c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(cbs_list_lock);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci#define BYTES_PER_KBIT (1000LL / 8)
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistruct cbs_sched_data {
708c2ecf20Sopenharmony_ci	bool offload;
718c2ecf20Sopenharmony_ci	int queue;
728c2ecf20Sopenharmony_ci	atomic64_t port_rate; /* in bytes/s */
738c2ecf20Sopenharmony_ci	s64 last; /* timestamp in ns */
748c2ecf20Sopenharmony_ci	s64 credits; /* in bytes */
758c2ecf20Sopenharmony_ci	s32 locredit; /* in bytes */
768c2ecf20Sopenharmony_ci	s32 hicredit; /* in bytes */
778c2ecf20Sopenharmony_ci	s64 sendslope; /* in bytes/s */
788c2ecf20Sopenharmony_ci	s64 idleslope; /* in bytes/s */
798c2ecf20Sopenharmony_ci	struct qdisc_watchdog watchdog;
808c2ecf20Sopenharmony_ci	int (*enqueue)(struct sk_buff *skb, struct Qdisc *sch,
818c2ecf20Sopenharmony_ci		       struct sk_buff **to_free);
828c2ecf20Sopenharmony_ci	struct sk_buff *(*dequeue)(struct Qdisc *sch);
838c2ecf20Sopenharmony_ci	struct Qdisc *qdisc;
848c2ecf20Sopenharmony_ci	struct list_head cbs_list;
858c2ecf20Sopenharmony_ci};
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic int cbs_child_enqueue(struct sk_buff *skb, struct Qdisc *sch,
888c2ecf20Sopenharmony_ci			     struct Qdisc *child,
898c2ecf20Sopenharmony_ci			     struct sk_buff **to_free)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	unsigned int len = qdisc_pkt_len(skb);
928c2ecf20Sopenharmony_ci	int err;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	err = child->ops->enqueue(skb, child, to_free);
958c2ecf20Sopenharmony_ci	if (err != NET_XMIT_SUCCESS)
968c2ecf20Sopenharmony_ci		return err;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	sch->qstats.backlog += len;
998c2ecf20Sopenharmony_ci	sch->q.qlen++;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	return NET_XMIT_SUCCESS;
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_cistatic int cbs_enqueue_offload(struct sk_buff *skb, struct Qdisc *sch,
1058c2ecf20Sopenharmony_ci			       struct sk_buff **to_free)
1068c2ecf20Sopenharmony_ci{
1078c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
1088c2ecf20Sopenharmony_ci	struct Qdisc *qdisc = q->qdisc;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	return cbs_child_enqueue(skb, sch, qdisc, to_free);
1118c2ecf20Sopenharmony_ci}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic int cbs_enqueue_soft(struct sk_buff *skb, struct Qdisc *sch,
1148c2ecf20Sopenharmony_ci			    struct sk_buff **to_free)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
1178c2ecf20Sopenharmony_ci	struct Qdisc *qdisc = q->qdisc;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	if (sch->q.qlen == 0 && q->credits > 0) {
1208c2ecf20Sopenharmony_ci		/* We need to stop accumulating credits when there's
1218c2ecf20Sopenharmony_ci		 * no enqueued packets and q->credits is positive.
1228c2ecf20Sopenharmony_ci		 */
1238c2ecf20Sopenharmony_ci		q->credits = 0;
1248c2ecf20Sopenharmony_ci		q->last = ktime_get_ns();
1258c2ecf20Sopenharmony_ci	}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	return cbs_child_enqueue(skb, sch, qdisc, to_free);
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic int cbs_enqueue(struct sk_buff *skb, struct Qdisc *sch,
1318c2ecf20Sopenharmony_ci		       struct sk_buff **to_free)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	return q->enqueue(skb, sch, to_free);
1368c2ecf20Sopenharmony_ci}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci/* timediff is in ns, slope is in bytes/s */
1398c2ecf20Sopenharmony_cistatic s64 timediff_to_credits(s64 timediff, s64 slope)
1408c2ecf20Sopenharmony_ci{
1418c2ecf20Sopenharmony_ci	return div64_s64(timediff * slope, NSEC_PER_SEC);
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic s64 delay_from_credits(s64 credits, s64 slope)
1458c2ecf20Sopenharmony_ci{
1468c2ecf20Sopenharmony_ci	if (unlikely(slope == 0))
1478c2ecf20Sopenharmony_ci		return S64_MAX;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	return div64_s64(-credits * NSEC_PER_SEC, slope);
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_cistatic s64 credits_from_len(unsigned int len, s64 slope, s64 port_rate)
1538c2ecf20Sopenharmony_ci{
1548c2ecf20Sopenharmony_ci	if (unlikely(port_rate == 0))
1558c2ecf20Sopenharmony_ci		return S64_MAX;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	return div64_s64(len * slope, port_rate);
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic struct sk_buff *cbs_child_dequeue(struct Qdisc *sch, struct Qdisc *child)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	struct sk_buff *skb;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	skb = child->ops->dequeue(child);
1658c2ecf20Sopenharmony_ci	if (!skb)
1668c2ecf20Sopenharmony_ci		return NULL;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	qdisc_qstats_backlog_dec(sch, skb);
1698c2ecf20Sopenharmony_ci	qdisc_bstats_update(sch, skb);
1708c2ecf20Sopenharmony_ci	sch->q.qlen--;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	return skb;
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_cistatic struct sk_buff *cbs_dequeue_soft(struct Qdisc *sch)
1768c2ecf20Sopenharmony_ci{
1778c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
1788c2ecf20Sopenharmony_ci	struct Qdisc *qdisc = q->qdisc;
1798c2ecf20Sopenharmony_ci	s64 now = ktime_get_ns();
1808c2ecf20Sopenharmony_ci	struct sk_buff *skb;
1818c2ecf20Sopenharmony_ci	s64 credits;
1828c2ecf20Sopenharmony_ci	int len;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	/* The previous packet is still being sent */
1858c2ecf20Sopenharmony_ci	if (now < q->last) {
1868c2ecf20Sopenharmony_ci		qdisc_watchdog_schedule_ns(&q->watchdog, q->last);
1878c2ecf20Sopenharmony_ci		return NULL;
1888c2ecf20Sopenharmony_ci	}
1898c2ecf20Sopenharmony_ci	if (q->credits < 0) {
1908c2ecf20Sopenharmony_ci		credits = timediff_to_credits(now - q->last, q->idleslope);
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci		credits = q->credits + credits;
1938c2ecf20Sopenharmony_ci		q->credits = min_t(s64, credits, q->hicredit);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci		if (q->credits < 0) {
1968c2ecf20Sopenharmony_ci			s64 delay;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci			delay = delay_from_credits(q->credits, q->idleslope);
1998c2ecf20Sopenharmony_ci			qdisc_watchdog_schedule_ns(&q->watchdog, now + delay);
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci			q->last = now;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci			return NULL;
2048c2ecf20Sopenharmony_ci		}
2058c2ecf20Sopenharmony_ci	}
2068c2ecf20Sopenharmony_ci	skb = cbs_child_dequeue(sch, qdisc);
2078c2ecf20Sopenharmony_ci	if (!skb)
2088c2ecf20Sopenharmony_ci		return NULL;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	len = qdisc_pkt_len(skb);
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	/* As sendslope is a negative number, this will decrease the
2138c2ecf20Sopenharmony_ci	 * amount of q->credits.
2148c2ecf20Sopenharmony_ci	 */
2158c2ecf20Sopenharmony_ci	credits = credits_from_len(len, q->sendslope,
2168c2ecf20Sopenharmony_ci				   atomic64_read(&q->port_rate));
2178c2ecf20Sopenharmony_ci	credits += q->credits;
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	q->credits = max_t(s64, credits, q->locredit);
2208c2ecf20Sopenharmony_ci	/* Estimate of the transmission of the last byte of the packet in ns */
2218c2ecf20Sopenharmony_ci	if (unlikely(atomic64_read(&q->port_rate) == 0))
2228c2ecf20Sopenharmony_ci		q->last = now;
2238c2ecf20Sopenharmony_ci	else
2248c2ecf20Sopenharmony_ci		q->last = now + div64_s64(len * NSEC_PER_SEC,
2258c2ecf20Sopenharmony_ci					  atomic64_read(&q->port_rate));
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	return skb;
2288c2ecf20Sopenharmony_ci}
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_cistatic struct sk_buff *cbs_dequeue_offload(struct Qdisc *sch)
2318c2ecf20Sopenharmony_ci{
2328c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
2338c2ecf20Sopenharmony_ci	struct Qdisc *qdisc = q->qdisc;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	return cbs_child_dequeue(sch, qdisc);
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_cistatic struct sk_buff *cbs_dequeue(struct Qdisc *sch)
2398c2ecf20Sopenharmony_ci{
2408c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	return q->dequeue(sch);
2438c2ecf20Sopenharmony_ci}
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_cistatic const struct nla_policy cbs_policy[TCA_CBS_MAX + 1] = {
2468c2ecf20Sopenharmony_ci	[TCA_CBS_PARMS]	= { .len = sizeof(struct tc_cbs_qopt) },
2478c2ecf20Sopenharmony_ci};
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_cistatic void cbs_disable_offload(struct net_device *dev,
2508c2ecf20Sopenharmony_ci				struct cbs_sched_data *q)
2518c2ecf20Sopenharmony_ci{
2528c2ecf20Sopenharmony_ci	struct tc_cbs_qopt_offload cbs = { };
2538c2ecf20Sopenharmony_ci	const struct net_device_ops *ops;
2548c2ecf20Sopenharmony_ci	int err;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	if (!q->offload)
2578c2ecf20Sopenharmony_ci		return;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	q->enqueue = cbs_enqueue_soft;
2608c2ecf20Sopenharmony_ci	q->dequeue = cbs_dequeue_soft;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	ops = dev->netdev_ops;
2638c2ecf20Sopenharmony_ci	if (!ops->ndo_setup_tc)
2648c2ecf20Sopenharmony_ci		return;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	cbs.queue = q->queue;
2678c2ecf20Sopenharmony_ci	cbs.enable = 0;
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_CBS, &cbs);
2708c2ecf20Sopenharmony_ci	if (err < 0)
2718c2ecf20Sopenharmony_ci		pr_warn("Couldn't disable CBS offload for queue %d\n",
2728c2ecf20Sopenharmony_ci			cbs.queue);
2738c2ecf20Sopenharmony_ci}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_cistatic int cbs_enable_offload(struct net_device *dev, struct cbs_sched_data *q,
2768c2ecf20Sopenharmony_ci			      const struct tc_cbs_qopt *opt,
2778c2ecf20Sopenharmony_ci			      struct netlink_ext_ack *extack)
2788c2ecf20Sopenharmony_ci{
2798c2ecf20Sopenharmony_ci	const struct net_device_ops *ops = dev->netdev_ops;
2808c2ecf20Sopenharmony_ci	struct tc_cbs_qopt_offload cbs = { };
2818c2ecf20Sopenharmony_ci	int err;
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	if (!ops->ndo_setup_tc) {
2848c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Specified device does not support cbs offload");
2858c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
2868c2ecf20Sopenharmony_ci	}
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	cbs.queue = q->queue;
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	cbs.enable = 1;
2918c2ecf20Sopenharmony_ci	cbs.hicredit = opt->hicredit;
2928c2ecf20Sopenharmony_ci	cbs.locredit = opt->locredit;
2938c2ecf20Sopenharmony_ci	cbs.idleslope = opt->idleslope;
2948c2ecf20Sopenharmony_ci	cbs.sendslope = opt->sendslope;
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_CBS, &cbs);
2978c2ecf20Sopenharmony_ci	if (err < 0) {
2988c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Specified device failed to setup cbs hardware offload");
2998c2ecf20Sopenharmony_ci		return err;
3008c2ecf20Sopenharmony_ci	}
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	q->enqueue = cbs_enqueue_offload;
3038c2ecf20Sopenharmony_ci	q->dequeue = cbs_dequeue_offload;
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	return 0;
3068c2ecf20Sopenharmony_ci}
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_cistatic void cbs_set_port_rate(struct net_device *dev, struct cbs_sched_data *q)
3098c2ecf20Sopenharmony_ci{
3108c2ecf20Sopenharmony_ci	struct ethtool_link_ksettings ecmd;
3118c2ecf20Sopenharmony_ci	int speed = SPEED_10;
3128c2ecf20Sopenharmony_ci	int port_rate;
3138c2ecf20Sopenharmony_ci	int err;
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_ci	err = __ethtool_get_link_ksettings(dev, &ecmd);
3168c2ecf20Sopenharmony_ci	if (err < 0)
3178c2ecf20Sopenharmony_ci		goto skip;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	if (ecmd.base.speed && ecmd.base.speed != SPEED_UNKNOWN)
3208c2ecf20Sopenharmony_ci		speed = ecmd.base.speed;
3218c2ecf20Sopenharmony_ci
3228c2ecf20Sopenharmony_ciskip:
3238c2ecf20Sopenharmony_ci	port_rate = speed * 1000 * BYTES_PER_KBIT;
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	atomic64_set(&q->port_rate, port_rate);
3268c2ecf20Sopenharmony_ci	netdev_dbg(dev, "cbs: set %s's port_rate to: %lld, linkspeed: %d\n",
3278c2ecf20Sopenharmony_ci		   dev->name, (long long)atomic64_read(&q->port_rate),
3288c2ecf20Sopenharmony_ci		   ecmd.base.speed);
3298c2ecf20Sopenharmony_ci}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_cistatic int cbs_dev_notifier(struct notifier_block *nb, unsigned long event,
3328c2ecf20Sopenharmony_ci			    void *ptr)
3338c2ecf20Sopenharmony_ci{
3348c2ecf20Sopenharmony_ci	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
3358c2ecf20Sopenharmony_ci	struct cbs_sched_data *q;
3368c2ecf20Sopenharmony_ci	struct net_device *qdev;
3378c2ecf20Sopenharmony_ci	bool found = false;
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	ASSERT_RTNL();
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	if (event != NETDEV_UP && event != NETDEV_CHANGE)
3428c2ecf20Sopenharmony_ci		return NOTIFY_DONE;
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci	spin_lock(&cbs_list_lock);
3458c2ecf20Sopenharmony_ci	list_for_each_entry(q, &cbs_list, cbs_list) {
3468c2ecf20Sopenharmony_ci		qdev = qdisc_dev(q->qdisc);
3478c2ecf20Sopenharmony_ci		if (qdev == dev) {
3488c2ecf20Sopenharmony_ci			found = true;
3498c2ecf20Sopenharmony_ci			break;
3508c2ecf20Sopenharmony_ci		}
3518c2ecf20Sopenharmony_ci	}
3528c2ecf20Sopenharmony_ci	spin_unlock(&cbs_list_lock);
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	if (found)
3558c2ecf20Sopenharmony_ci		cbs_set_port_rate(dev, q);
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	return NOTIFY_DONE;
3588c2ecf20Sopenharmony_ci}
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_cistatic int cbs_change(struct Qdisc *sch, struct nlattr *opt,
3618c2ecf20Sopenharmony_ci		      struct netlink_ext_ack *extack)
3628c2ecf20Sopenharmony_ci{
3638c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
3648c2ecf20Sopenharmony_ci	struct net_device *dev = qdisc_dev(sch);
3658c2ecf20Sopenharmony_ci	struct nlattr *tb[TCA_CBS_MAX + 1];
3668c2ecf20Sopenharmony_ci	struct tc_cbs_qopt *qopt;
3678c2ecf20Sopenharmony_ci	int err;
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	err = nla_parse_nested_deprecated(tb, TCA_CBS_MAX, opt, cbs_policy,
3708c2ecf20Sopenharmony_ci					  extack);
3718c2ecf20Sopenharmony_ci	if (err < 0)
3728c2ecf20Sopenharmony_ci		return err;
3738c2ecf20Sopenharmony_ci
3748c2ecf20Sopenharmony_ci	if (!tb[TCA_CBS_PARMS]) {
3758c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Missing CBS parameter which are mandatory");
3768c2ecf20Sopenharmony_ci		return -EINVAL;
3778c2ecf20Sopenharmony_ci	}
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	qopt = nla_data(tb[TCA_CBS_PARMS]);
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	if (!qopt->offload) {
3828c2ecf20Sopenharmony_ci		cbs_set_port_rate(dev, q);
3838c2ecf20Sopenharmony_ci		cbs_disable_offload(dev, q);
3848c2ecf20Sopenharmony_ci	} else {
3858c2ecf20Sopenharmony_ci		err = cbs_enable_offload(dev, q, qopt, extack);
3868c2ecf20Sopenharmony_ci		if (err < 0)
3878c2ecf20Sopenharmony_ci			return err;
3888c2ecf20Sopenharmony_ci	}
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	/* Everything went OK, save the parameters used. */
3918c2ecf20Sopenharmony_ci	q->hicredit = qopt->hicredit;
3928c2ecf20Sopenharmony_ci	q->locredit = qopt->locredit;
3938c2ecf20Sopenharmony_ci	q->idleslope = qopt->idleslope * BYTES_PER_KBIT;
3948c2ecf20Sopenharmony_ci	q->sendslope = qopt->sendslope * BYTES_PER_KBIT;
3958c2ecf20Sopenharmony_ci	q->offload = qopt->offload;
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci	return 0;
3988c2ecf20Sopenharmony_ci}
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_cistatic int cbs_init(struct Qdisc *sch, struct nlattr *opt,
4018c2ecf20Sopenharmony_ci		    struct netlink_ext_ack *extack)
4028c2ecf20Sopenharmony_ci{
4038c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
4048c2ecf20Sopenharmony_ci	struct net_device *dev = qdisc_dev(sch);
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci	if (!opt) {
4078c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG(extack, "Missing CBS qdisc options  which are mandatory");
4088c2ecf20Sopenharmony_ci		return -EINVAL;
4098c2ecf20Sopenharmony_ci	}
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci	q->qdisc = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops,
4128c2ecf20Sopenharmony_ci				     sch->handle, extack);
4138c2ecf20Sopenharmony_ci	if (!q->qdisc)
4148c2ecf20Sopenharmony_ci		return -ENOMEM;
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_ci	spin_lock(&cbs_list_lock);
4178c2ecf20Sopenharmony_ci	list_add(&q->cbs_list, &cbs_list);
4188c2ecf20Sopenharmony_ci	spin_unlock(&cbs_list_lock);
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_ci	qdisc_hash_add(q->qdisc, false);
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci	q->queue = sch->dev_queue - netdev_get_tx_queue(dev, 0);
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	q->enqueue = cbs_enqueue_soft;
4258c2ecf20Sopenharmony_ci	q->dequeue = cbs_dequeue_soft;
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	qdisc_watchdog_init(&q->watchdog, sch);
4288c2ecf20Sopenharmony_ci
4298c2ecf20Sopenharmony_ci	return cbs_change(sch, opt, extack);
4308c2ecf20Sopenharmony_ci}
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_cistatic void cbs_destroy(struct Qdisc *sch)
4338c2ecf20Sopenharmony_ci{
4348c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
4358c2ecf20Sopenharmony_ci	struct net_device *dev = qdisc_dev(sch);
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ci	/* Nothing to do if we couldn't create the underlying qdisc */
4388c2ecf20Sopenharmony_ci	if (!q->qdisc)
4398c2ecf20Sopenharmony_ci		return;
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	qdisc_watchdog_cancel(&q->watchdog);
4428c2ecf20Sopenharmony_ci	cbs_disable_offload(dev, q);
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_ci	spin_lock(&cbs_list_lock);
4458c2ecf20Sopenharmony_ci	list_del(&q->cbs_list);
4468c2ecf20Sopenharmony_ci	spin_unlock(&cbs_list_lock);
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	qdisc_put(q->qdisc);
4498c2ecf20Sopenharmony_ci}
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_cistatic int cbs_dump(struct Qdisc *sch, struct sk_buff *skb)
4528c2ecf20Sopenharmony_ci{
4538c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
4548c2ecf20Sopenharmony_ci	struct tc_cbs_qopt opt = { };
4558c2ecf20Sopenharmony_ci	struct nlattr *nest;
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	nest = nla_nest_start_noflag(skb, TCA_OPTIONS);
4588c2ecf20Sopenharmony_ci	if (!nest)
4598c2ecf20Sopenharmony_ci		goto nla_put_failure;
4608c2ecf20Sopenharmony_ci
4618c2ecf20Sopenharmony_ci	opt.hicredit = q->hicredit;
4628c2ecf20Sopenharmony_ci	opt.locredit = q->locredit;
4638c2ecf20Sopenharmony_ci	opt.sendslope = div64_s64(q->sendslope, BYTES_PER_KBIT);
4648c2ecf20Sopenharmony_ci	opt.idleslope = div64_s64(q->idleslope, BYTES_PER_KBIT);
4658c2ecf20Sopenharmony_ci	opt.offload = q->offload;
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	if (nla_put(skb, TCA_CBS_PARMS, sizeof(opt), &opt))
4688c2ecf20Sopenharmony_ci		goto nla_put_failure;
4698c2ecf20Sopenharmony_ci
4708c2ecf20Sopenharmony_ci	return nla_nest_end(skb, nest);
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_cinla_put_failure:
4738c2ecf20Sopenharmony_ci	nla_nest_cancel(skb, nest);
4748c2ecf20Sopenharmony_ci	return -1;
4758c2ecf20Sopenharmony_ci}
4768c2ecf20Sopenharmony_ci
4778c2ecf20Sopenharmony_cistatic int cbs_dump_class(struct Qdisc *sch, unsigned long cl,
4788c2ecf20Sopenharmony_ci			  struct sk_buff *skb, struct tcmsg *tcm)
4798c2ecf20Sopenharmony_ci{
4808c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
4818c2ecf20Sopenharmony_ci
4828c2ecf20Sopenharmony_ci	if (cl != 1 || !q->qdisc)	/* only one class */
4838c2ecf20Sopenharmony_ci		return -ENOENT;
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_ci	tcm->tcm_handle |= TC_H_MIN(1);
4868c2ecf20Sopenharmony_ci	tcm->tcm_info = q->qdisc->handle;
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_ci	return 0;
4898c2ecf20Sopenharmony_ci}
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_cistatic int cbs_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
4928c2ecf20Sopenharmony_ci		     struct Qdisc **old, struct netlink_ext_ack *extack)
4938c2ecf20Sopenharmony_ci{
4948c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
4958c2ecf20Sopenharmony_ci
4968c2ecf20Sopenharmony_ci	if (!new) {
4978c2ecf20Sopenharmony_ci		new = qdisc_create_dflt(sch->dev_queue, &pfifo_qdisc_ops,
4988c2ecf20Sopenharmony_ci					sch->handle, NULL);
4998c2ecf20Sopenharmony_ci		if (!new)
5008c2ecf20Sopenharmony_ci			new = &noop_qdisc;
5018c2ecf20Sopenharmony_ci	}
5028c2ecf20Sopenharmony_ci
5038c2ecf20Sopenharmony_ci	*old = qdisc_replace(sch, new, &q->qdisc);
5048c2ecf20Sopenharmony_ci	return 0;
5058c2ecf20Sopenharmony_ci}
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_cistatic struct Qdisc *cbs_leaf(struct Qdisc *sch, unsigned long arg)
5088c2ecf20Sopenharmony_ci{
5098c2ecf20Sopenharmony_ci	struct cbs_sched_data *q = qdisc_priv(sch);
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_ci	return q->qdisc;
5128c2ecf20Sopenharmony_ci}
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_cistatic unsigned long cbs_find(struct Qdisc *sch, u32 classid)
5158c2ecf20Sopenharmony_ci{
5168c2ecf20Sopenharmony_ci	return 1;
5178c2ecf20Sopenharmony_ci}
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_cistatic void cbs_walk(struct Qdisc *sch, struct qdisc_walker *walker)
5208c2ecf20Sopenharmony_ci{
5218c2ecf20Sopenharmony_ci	if (!walker->stop) {
5228c2ecf20Sopenharmony_ci		if (walker->count >= walker->skip) {
5238c2ecf20Sopenharmony_ci			if (walker->fn(sch, 1, walker) < 0) {
5248c2ecf20Sopenharmony_ci				walker->stop = 1;
5258c2ecf20Sopenharmony_ci				return;
5268c2ecf20Sopenharmony_ci			}
5278c2ecf20Sopenharmony_ci		}
5288c2ecf20Sopenharmony_ci		walker->count++;
5298c2ecf20Sopenharmony_ci	}
5308c2ecf20Sopenharmony_ci}
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_cistatic const struct Qdisc_class_ops cbs_class_ops = {
5338c2ecf20Sopenharmony_ci	.graft		=	cbs_graft,
5348c2ecf20Sopenharmony_ci	.leaf		=	cbs_leaf,
5358c2ecf20Sopenharmony_ci	.find		=	cbs_find,
5368c2ecf20Sopenharmony_ci	.walk		=	cbs_walk,
5378c2ecf20Sopenharmony_ci	.dump		=	cbs_dump_class,
5388c2ecf20Sopenharmony_ci};
5398c2ecf20Sopenharmony_ci
5408c2ecf20Sopenharmony_cistatic struct Qdisc_ops cbs_qdisc_ops __read_mostly = {
5418c2ecf20Sopenharmony_ci	.id		=	"cbs",
5428c2ecf20Sopenharmony_ci	.cl_ops		=	&cbs_class_ops,
5438c2ecf20Sopenharmony_ci	.priv_size	=	sizeof(struct cbs_sched_data),
5448c2ecf20Sopenharmony_ci	.enqueue	=	cbs_enqueue,
5458c2ecf20Sopenharmony_ci	.dequeue	=	cbs_dequeue,
5468c2ecf20Sopenharmony_ci	.peek		=	qdisc_peek_dequeued,
5478c2ecf20Sopenharmony_ci	.init		=	cbs_init,
5488c2ecf20Sopenharmony_ci	.reset		=	qdisc_reset_queue,
5498c2ecf20Sopenharmony_ci	.destroy	=	cbs_destroy,
5508c2ecf20Sopenharmony_ci	.change		=	cbs_change,
5518c2ecf20Sopenharmony_ci	.dump		=	cbs_dump,
5528c2ecf20Sopenharmony_ci	.owner		=	THIS_MODULE,
5538c2ecf20Sopenharmony_ci};
5548c2ecf20Sopenharmony_ci
5558c2ecf20Sopenharmony_cistatic struct notifier_block cbs_device_notifier = {
5568c2ecf20Sopenharmony_ci	.notifier_call = cbs_dev_notifier,
5578c2ecf20Sopenharmony_ci};
5588c2ecf20Sopenharmony_ci
5598c2ecf20Sopenharmony_cistatic int __init cbs_module_init(void)
5608c2ecf20Sopenharmony_ci{
5618c2ecf20Sopenharmony_ci	int err;
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci	err = register_netdevice_notifier(&cbs_device_notifier);
5648c2ecf20Sopenharmony_ci	if (err)
5658c2ecf20Sopenharmony_ci		return err;
5668c2ecf20Sopenharmony_ci
5678c2ecf20Sopenharmony_ci	err = register_qdisc(&cbs_qdisc_ops);
5688c2ecf20Sopenharmony_ci	if (err)
5698c2ecf20Sopenharmony_ci		unregister_netdevice_notifier(&cbs_device_notifier);
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ci	return err;
5728c2ecf20Sopenharmony_ci}
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_cistatic void __exit cbs_module_exit(void)
5758c2ecf20Sopenharmony_ci{
5768c2ecf20Sopenharmony_ci	unregister_qdisc(&cbs_qdisc_ops);
5778c2ecf20Sopenharmony_ci	unregister_netdevice_notifier(&cbs_device_notifier);
5788c2ecf20Sopenharmony_ci}
5798c2ecf20Sopenharmony_cimodule_init(cbs_module_init)
5808c2ecf20Sopenharmony_cimodule_exit(cbs_module_exit)
5818c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
582