xref: /openbmc/linux/net/sched/sch_etf.c (revision 88cab771)
125db26a9SVinicius Costa Gomes // SPDX-License-Identifier: GPL-2.0
225db26a9SVinicius Costa Gomes 
325db26a9SVinicius Costa Gomes /* net/sched/sch_etf.c  Earliest TxTime First queueing discipline.
425db26a9SVinicius Costa Gomes  *
525db26a9SVinicius Costa Gomes  * Authors:	Jesus Sanchez-Palencia <jesus.sanchez-palencia@intel.com>
625db26a9SVinicius Costa Gomes  *		Vinicius Costa Gomes <vinicius.gomes@intel.com>
725db26a9SVinicius Costa Gomes  */
825db26a9SVinicius Costa Gomes 
925db26a9SVinicius Costa Gomes #include <linux/module.h>
1025db26a9SVinicius Costa Gomes #include <linux/types.h>
1125db26a9SVinicius Costa Gomes #include <linux/kernel.h>
1225db26a9SVinicius Costa Gomes #include <linux/string.h>
1325db26a9SVinicius Costa Gomes #include <linux/errno.h>
1425db26a9SVinicius Costa Gomes #include <linux/rbtree.h>
1525db26a9SVinicius Costa Gomes #include <linux/skbuff.h>
1625db26a9SVinicius Costa Gomes #include <linux/posix-timers.h>
1725db26a9SVinicius Costa Gomes #include <net/netlink.h>
1825db26a9SVinicius Costa Gomes #include <net/sch_generic.h>
1925db26a9SVinicius Costa Gomes #include <net/pkt_sched.h>
2025db26a9SVinicius Costa Gomes #include <net/sock.h>
2125db26a9SVinicius Costa Gomes 
2225db26a9SVinicius Costa Gomes #define DEADLINE_MODE_IS_ON(x) ((x)->flags & TC_ETF_DEADLINE_MODE_ON)
2388cab771SJesus Sanchez-Palencia #define OFFLOAD_IS_ON(x) ((x)->flags & TC_ETF_OFFLOAD_ON)
2425db26a9SVinicius Costa Gomes 
2525db26a9SVinicius Costa Gomes struct etf_sched_data {
2688cab771SJesus Sanchez-Palencia 	bool offload;
2725db26a9SVinicius Costa Gomes 	bool deadline_mode;
2825db26a9SVinicius Costa Gomes 	int clockid;
2925db26a9SVinicius Costa Gomes 	int queue;
3025db26a9SVinicius Costa Gomes 	s32 delta; /* in ns */
3125db26a9SVinicius Costa Gomes 	ktime_t last; /* The txtime of the last skb sent to the netdevice. */
3225db26a9SVinicius Costa Gomes 	struct rb_root head;
3325db26a9SVinicius Costa Gomes 	struct qdisc_watchdog watchdog;
3425db26a9SVinicius Costa Gomes 	ktime_t (*get_time)(void);
3525db26a9SVinicius Costa Gomes };
3625db26a9SVinicius Costa Gomes 
3725db26a9SVinicius Costa Gomes static const struct nla_policy etf_policy[TCA_ETF_MAX + 1] = {
3825db26a9SVinicius Costa Gomes 	[TCA_ETF_PARMS]	= { .len = sizeof(struct tc_etf_qopt) },
3925db26a9SVinicius Costa Gomes };
4025db26a9SVinicius Costa Gomes 
4125db26a9SVinicius Costa Gomes static inline int validate_input_params(struct tc_etf_qopt *qopt,
4225db26a9SVinicius Costa Gomes 					struct netlink_ext_ack *extack)
4325db26a9SVinicius Costa Gomes {
4425db26a9SVinicius Costa Gomes 	/* Check if params comply to the following rules:
4525db26a9SVinicius Costa Gomes 	 *	* Clockid and delta must be valid.
4625db26a9SVinicius Costa Gomes 	 *
4725db26a9SVinicius Costa Gomes 	 *	* Dynamic clockids are not supported.
4825db26a9SVinicius Costa Gomes 	 *
4925db26a9SVinicius Costa Gomes 	 *	* Delta must be a positive integer.
5088cab771SJesus Sanchez-Palencia 	 *
5188cab771SJesus Sanchez-Palencia 	 * Also note that for the HW offload case, we must
5288cab771SJesus Sanchez-Palencia 	 * expect that system clocks have been synchronized to PHC.
5325db26a9SVinicius Costa Gomes 	 */
5425db26a9SVinicius Costa Gomes 	if (qopt->clockid < 0) {
5525db26a9SVinicius Costa Gomes 		NL_SET_ERR_MSG(extack, "Dynamic clockids are not supported");
5625db26a9SVinicius Costa Gomes 		return -ENOTSUPP;
5725db26a9SVinicius Costa Gomes 	}
5825db26a9SVinicius Costa Gomes 
5925db26a9SVinicius Costa Gomes 	if (qopt->clockid != CLOCK_TAI) {
6025db26a9SVinicius Costa Gomes 		NL_SET_ERR_MSG(extack, "Invalid clockid. CLOCK_TAI must be used");
6125db26a9SVinicius Costa Gomes 		return -EINVAL;
6225db26a9SVinicius Costa Gomes 	}
6325db26a9SVinicius Costa Gomes 
6425db26a9SVinicius Costa Gomes 	if (qopt->delta < 0) {
6525db26a9SVinicius Costa Gomes 		NL_SET_ERR_MSG(extack, "Delta must be positive");
6625db26a9SVinicius Costa Gomes 		return -EINVAL;
6725db26a9SVinicius Costa Gomes 	}
6825db26a9SVinicius Costa Gomes 
6925db26a9SVinicius Costa Gomes 	return 0;
7025db26a9SVinicius Costa Gomes }
7125db26a9SVinicius Costa Gomes 
7225db26a9SVinicius Costa Gomes static bool is_packet_valid(struct Qdisc *sch, struct sk_buff *nskb)
7325db26a9SVinicius Costa Gomes {
7425db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
7525db26a9SVinicius Costa Gomes 	ktime_t txtime = nskb->tstamp;
7625db26a9SVinicius Costa Gomes 	struct sock *sk = nskb->sk;
7725db26a9SVinicius Costa Gomes 	ktime_t now;
7825db26a9SVinicius Costa Gomes 
7925db26a9SVinicius Costa Gomes 	if (!sk)
8025db26a9SVinicius Costa Gomes 		return false;
8125db26a9SVinicius Costa Gomes 
8225db26a9SVinicius Costa Gomes 	if (!sock_flag(sk, SOCK_TXTIME))
8325db26a9SVinicius Costa Gomes 		return false;
8425db26a9SVinicius Costa Gomes 
8525db26a9SVinicius Costa Gomes 	/* We don't perform crosstimestamping.
8625db26a9SVinicius Costa Gomes 	 * Drop if packet's clockid differs from qdisc's.
8725db26a9SVinicius Costa Gomes 	 */
8825db26a9SVinicius Costa Gomes 	if (sk->sk_clockid != q->clockid)
8925db26a9SVinicius Costa Gomes 		return false;
9025db26a9SVinicius Costa Gomes 
9125db26a9SVinicius Costa Gomes 	if (sk->sk_txtime_deadline_mode != q->deadline_mode)
9225db26a9SVinicius Costa Gomes 		return false;
9325db26a9SVinicius Costa Gomes 
9425db26a9SVinicius Costa Gomes 	now = q->get_time();
9525db26a9SVinicius Costa Gomes 	if (ktime_before(txtime, now) || ktime_before(txtime, q->last))
9625db26a9SVinicius Costa Gomes 		return false;
9725db26a9SVinicius Costa Gomes 
9825db26a9SVinicius Costa Gomes 	return true;
9925db26a9SVinicius Costa Gomes }
10025db26a9SVinicius Costa Gomes 
10125db26a9SVinicius Costa Gomes static struct sk_buff *etf_peek_timesortedlist(struct Qdisc *sch)
10225db26a9SVinicius Costa Gomes {
10325db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
10425db26a9SVinicius Costa Gomes 	struct rb_node *p;
10525db26a9SVinicius Costa Gomes 
10625db26a9SVinicius Costa Gomes 	p = rb_first(&q->head);
10725db26a9SVinicius Costa Gomes 	if (!p)
10825db26a9SVinicius Costa Gomes 		return NULL;
10925db26a9SVinicius Costa Gomes 
11025db26a9SVinicius Costa Gomes 	return rb_to_skb(p);
11125db26a9SVinicius Costa Gomes }
11225db26a9SVinicius Costa Gomes 
11325db26a9SVinicius Costa Gomes static void reset_watchdog(struct Qdisc *sch)
11425db26a9SVinicius Costa Gomes {
11525db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
11625db26a9SVinicius Costa Gomes 	struct sk_buff *skb = etf_peek_timesortedlist(sch);
11725db26a9SVinicius Costa Gomes 	ktime_t next;
11825db26a9SVinicius Costa Gomes 
11925db26a9SVinicius Costa Gomes 	if (!skb)
12025db26a9SVinicius Costa Gomes 		return;
12125db26a9SVinicius Costa Gomes 
12225db26a9SVinicius Costa Gomes 	next = ktime_sub_ns(skb->tstamp, q->delta);
12325db26a9SVinicius Costa Gomes 	qdisc_watchdog_schedule_ns(&q->watchdog, ktime_to_ns(next));
12425db26a9SVinicius Costa Gomes }
12525db26a9SVinicius Costa Gomes 
12625db26a9SVinicius Costa Gomes static int etf_enqueue_timesortedlist(struct sk_buff *nskb, struct Qdisc *sch,
12725db26a9SVinicius Costa Gomes 				      struct sk_buff **to_free)
12825db26a9SVinicius Costa Gomes {
12925db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
13025db26a9SVinicius Costa Gomes 	struct rb_node **p = &q->head.rb_node, *parent = NULL;
13125db26a9SVinicius Costa Gomes 	ktime_t txtime = nskb->tstamp;
13225db26a9SVinicius Costa Gomes 
13325db26a9SVinicius Costa Gomes 	if (!is_packet_valid(sch, nskb))
13425db26a9SVinicius Costa Gomes 		return qdisc_drop(nskb, sch, to_free);
13525db26a9SVinicius Costa Gomes 
13625db26a9SVinicius Costa Gomes 	while (*p) {
13725db26a9SVinicius Costa Gomes 		struct sk_buff *skb;
13825db26a9SVinicius Costa Gomes 
13925db26a9SVinicius Costa Gomes 		parent = *p;
14025db26a9SVinicius Costa Gomes 		skb = rb_to_skb(parent);
14125db26a9SVinicius Costa Gomes 		if (ktime_after(txtime, skb->tstamp))
14225db26a9SVinicius Costa Gomes 			p = &parent->rb_right;
14325db26a9SVinicius Costa Gomes 		else
14425db26a9SVinicius Costa Gomes 			p = &parent->rb_left;
14525db26a9SVinicius Costa Gomes 	}
14625db26a9SVinicius Costa Gomes 	rb_link_node(&nskb->rbnode, parent, p);
14725db26a9SVinicius Costa Gomes 	rb_insert_color(&nskb->rbnode, &q->head);
14825db26a9SVinicius Costa Gomes 
14925db26a9SVinicius Costa Gomes 	qdisc_qstats_backlog_inc(sch, nskb);
15025db26a9SVinicius Costa Gomes 	sch->q.qlen++;
15125db26a9SVinicius Costa Gomes 
15225db26a9SVinicius Costa Gomes 	/* Now we may need to re-arm the qdisc watchdog for the next packet. */
15325db26a9SVinicius Costa Gomes 	reset_watchdog(sch);
15425db26a9SVinicius Costa Gomes 
15525db26a9SVinicius Costa Gomes 	return NET_XMIT_SUCCESS;
15625db26a9SVinicius Costa Gomes }
15725db26a9SVinicius Costa Gomes 
15825db26a9SVinicius Costa Gomes static void timesortedlist_erase(struct Qdisc *sch, struct sk_buff *skb,
15925db26a9SVinicius Costa Gomes 				 bool drop)
16025db26a9SVinicius Costa Gomes {
16125db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
16225db26a9SVinicius Costa Gomes 
16325db26a9SVinicius Costa Gomes 	rb_erase(&skb->rbnode, &q->head);
16425db26a9SVinicius Costa Gomes 
16525db26a9SVinicius Costa Gomes 	/* The rbnode field in the skb re-uses these fields, now that
16625db26a9SVinicius Costa Gomes 	 * we are done with the rbnode, reset them.
16725db26a9SVinicius Costa Gomes 	 */
16825db26a9SVinicius Costa Gomes 	skb->next = NULL;
16925db26a9SVinicius Costa Gomes 	skb->prev = NULL;
17025db26a9SVinicius Costa Gomes 	skb->dev = qdisc_dev(sch);
17125db26a9SVinicius Costa Gomes 
17225db26a9SVinicius Costa Gomes 	qdisc_qstats_backlog_dec(sch, skb);
17325db26a9SVinicius Costa Gomes 
17425db26a9SVinicius Costa Gomes 	if (drop) {
17525db26a9SVinicius Costa Gomes 		struct sk_buff *to_free = NULL;
17625db26a9SVinicius Costa Gomes 
17725db26a9SVinicius Costa Gomes 		qdisc_drop(skb, sch, &to_free);
17825db26a9SVinicius Costa Gomes 		kfree_skb_list(to_free);
17925db26a9SVinicius Costa Gomes 		qdisc_qstats_overlimit(sch);
18025db26a9SVinicius Costa Gomes 	} else {
18125db26a9SVinicius Costa Gomes 		qdisc_bstats_update(sch, skb);
18225db26a9SVinicius Costa Gomes 
18325db26a9SVinicius Costa Gomes 		q->last = skb->tstamp;
18425db26a9SVinicius Costa Gomes 	}
18525db26a9SVinicius Costa Gomes 
18625db26a9SVinicius Costa Gomes 	sch->q.qlen--;
18725db26a9SVinicius Costa Gomes }
18825db26a9SVinicius Costa Gomes 
18925db26a9SVinicius Costa Gomes static struct sk_buff *etf_dequeue_timesortedlist(struct Qdisc *sch)
19025db26a9SVinicius Costa Gomes {
19125db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
19225db26a9SVinicius Costa Gomes 	struct sk_buff *skb;
19325db26a9SVinicius Costa Gomes 	ktime_t now, next;
19425db26a9SVinicius Costa Gomes 
19525db26a9SVinicius Costa Gomes 	skb = etf_peek_timesortedlist(sch);
19625db26a9SVinicius Costa Gomes 	if (!skb)
19725db26a9SVinicius Costa Gomes 		return NULL;
19825db26a9SVinicius Costa Gomes 
19925db26a9SVinicius Costa Gomes 	now = q->get_time();
20025db26a9SVinicius Costa Gomes 
20125db26a9SVinicius Costa Gomes 	/* Drop if packet has expired while in queue. */
20225db26a9SVinicius Costa Gomes 	/* FIXME: Must return error on the socket's error queue */
20325db26a9SVinicius Costa Gomes 	if (ktime_before(skb->tstamp, now)) {
20425db26a9SVinicius Costa Gomes 		timesortedlist_erase(sch, skb, true);
20525db26a9SVinicius Costa Gomes 		skb = NULL;
20625db26a9SVinicius Costa Gomes 		goto out;
20725db26a9SVinicius Costa Gomes 	}
20825db26a9SVinicius Costa Gomes 
20925db26a9SVinicius Costa Gomes 	/* When in deadline mode, dequeue as soon as possible and change the
21025db26a9SVinicius Costa Gomes 	 * txtime from deadline to (now + delta).
21125db26a9SVinicius Costa Gomes 	 */
21225db26a9SVinicius Costa Gomes 	if (q->deadline_mode) {
21325db26a9SVinicius Costa Gomes 		timesortedlist_erase(sch, skb, false);
21425db26a9SVinicius Costa Gomes 		skb->tstamp = now;
21525db26a9SVinicius Costa Gomes 		goto out;
21625db26a9SVinicius Costa Gomes 	}
21725db26a9SVinicius Costa Gomes 
21825db26a9SVinicius Costa Gomes 	next = ktime_sub_ns(skb->tstamp, q->delta);
21925db26a9SVinicius Costa Gomes 
22025db26a9SVinicius Costa Gomes 	/* Dequeue only if now is within the [txtime - delta, txtime] range. */
22125db26a9SVinicius Costa Gomes 	if (ktime_after(now, next))
22225db26a9SVinicius Costa Gomes 		timesortedlist_erase(sch, skb, false);
22325db26a9SVinicius Costa Gomes 	else
22425db26a9SVinicius Costa Gomes 		skb = NULL;
22525db26a9SVinicius Costa Gomes 
22625db26a9SVinicius Costa Gomes out:
22725db26a9SVinicius Costa Gomes 	/* Now we may need to re-arm the qdisc watchdog for the next packet. */
22825db26a9SVinicius Costa Gomes 	reset_watchdog(sch);
22925db26a9SVinicius Costa Gomes 
23025db26a9SVinicius Costa Gomes 	return skb;
23125db26a9SVinicius Costa Gomes }
23225db26a9SVinicius Costa Gomes 
23388cab771SJesus Sanchez-Palencia static void etf_disable_offload(struct net_device *dev,
23488cab771SJesus Sanchez-Palencia 				struct etf_sched_data *q)
23588cab771SJesus Sanchez-Palencia {
23688cab771SJesus Sanchez-Palencia 	struct tc_etf_qopt_offload etf = { };
23788cab771SJesus Sanchez-Palencia 	const struct net_device_ops *ops;
23888cab771SJesus Sanchez-Palencia 	int err;
23988cab771SJesus Sanchez-Palencia 
24088cab771SJesus Sanchez-Palencia 	if (!q->offload)
24188cab771SJesus Sanchez-Palencia 		return;
24288cab771SJesus Sanchez-Palencia 
24388cab771SJesus Sanchez-Palencia 	ops = dev->netdev_ops;
24488cab771SJesus Sanchez-Palencia 	if (!ops->ndo_setup_tc)
24588cab771SJesus Sanchez-Palencia 		return;
24688cab771SJesus Sanchez-Palencia 
24788cab771SJesus Sanchez-Palencia 	etf.queue = q->queue;
24888cab771SJesus Sanchez-Palencia 	etf.enable = 0;
24988cab771SJesus Sanchez-Palencia 
25088cab771SJesus Sanchez-Palencia 	err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_ETF, &etf);
25188cab771SJesus Sanchez-Palencia 	if (err < 0)
25288cab771SJesus Sanchez-Palencia 		pr_warn("Couldn't disable ETF offload for queue %d\n",
25388cab771SJesus Sanchez-Palencia 			etf.queue);
25488cab771SJesus Sanchez-Palencia }
25588cab771SJesus Sanchez-Palencia 
25688cab771SJesus Sanchez-Palencia static int etf_enable_offload(struct net_device *dev, struct etf_sched_data *q,
25788cab771SJesus Sanchez-Palencia 			      struct netlink_ext_ack *extack)
25888cab771SJesus Sanchez-Palencia {
25988cab771SJesus Sanchez-Palencia 	const struct net_device_ops *ops = dev->netdev_ops;
26088cab771SJesus Sanchez-Palencia 	struct tc_etf_qopt_offload etf = { };
26188cab771SJesus Sanchez-Palencia 	int err;
26288cab771SJesus Sanchez-Palencia 
26388cab771SJesus Sanchez-Palencia 	if (q->offload)
26488cab771SJesus Sanchez-Palencia 		return 0;
26588cab771SJesus Sanchez-Palencia 
26688cab771SJesus Sanchez-Palencia 	if (!ops->ndo_setup_tc) {
26788cab771SJesus Sanchez-Palencia 		NL_SET_ERR_MSG(extack, "Specified device does not support ETF offload");
26888cab771SJesus Sanchez-Palencia 		return -EOPNOTSUPP;
26988cab771SJesus Sanchez-Palencia 	}
27088cab771SJesus Sanchez-Palencia 
27188cab771SJesus Sanchez-Palencia 	etf.queue = q->queue;
27288cab771SJesus Sanchez-Palencia 	etf.enable = 1;
27388cab771SJesus Sanchez-Palencia 
27488cab771SJesus Sanchez-Palencia 	err = ops->ndo_setup_tc(dev, TC_SETUP_QDISC_ETF, &etf);
27588cab771SJesus Sanchez-Palencia 	if (err < 0) {
27688cab771SJesus Sanchez-Palencia 		NL_SET_ERR_MSG(extack, "Specified device failed to setup ETF hardware offload");
27788cab771SJesus Sanchez-Palencia 		return err;
27888cab771SJesus Sanchez-Palencia 	}
27988cab771SJesus Sanchez-Palencia 
28088cab771SJesus Sanchez-Palencia 	return 0;
28188cab771SJesus Sanchez-Palencia }
28288cab771SJesus Sanchez-Palencia 
28325db26a9SVinicius Costa Gomes static int etf_init(struct Qdisc *sch, struct nlattr *opt,
28425db26a9SVinicius Costa Gomes 		    struct netlink_ext_ack *extack)
28525db26a9SVinicius Costa Gomes {
28625db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
28725db26a9SVinicius Costa Gomes 	struct net_device *dev = qdisc_dev(sch);
28825db26a9SVinicius Costa Gomes 	struct nlattr *tb[TCA_ETF_MAX + 1];
28925db26a9SVinicius Costa Gomes 	struct tc_etf_qopt *qopt;
29025db26a9SVinicius Costa Gomes 	int err;
29125db26a9SVinicius Costa Gomes 
29225db26a9SVinicius Costa Gomes 	if (!opt) {
29325db26a9SVinicius Costa Gomes 		NL_SET_ERR_MSG(extack,
29425db26a9SVinicius Costa Gomes 			       "Missing ETF qdisc options which are mandatory");
29525db26a9SVinicius Costa Gomes 		return -EINVAL;
29625db26a9SVinicius Costa Gomes 	}
29725db26a9SVinicius Costa Gomes 
29825db26a9SVinicius Costa Gomes 	err = nla_parse_nested(tb, TCA_ETF_MAX, opt, etf_policy, extack);
29925db26a9SVinicius Costa Gomes 	if (err < 0)
30025db26a9SVinicius Costa Gomes 		return err;
30125db26a9SVinicius Costa Gomes 
30225db26a9SVinicius Costa Gomes 	if (!tb[TCA_ETF_PARMS]) {
30325db26a9SVinicius Costa Gomes 		NL_SET_ERR_MSG(extack, "Missing mandatory ETF parameters");
30425db26a9SVinicius Costa Gomes 		return -EINVAL;
30525db26a9SVinicius Costa Gomes 	}
30625db26a9SVinicius Costa Gomes 
30725db26a9SVinicius Costa Gomes 	qopt = nla_data(tb[TCA_ETF_PARMS]);
30825db26a9SVinicius Costa Gomes 
30988cab771SJesus Sanchez-Palencia 	pr_debug("delta %d clockid %d offload %s deadline %s\n",
31025db26a9SVinicius Costa Gomes 		 qopt->delta, qopt->clockid,
31188cab771SJesus Sanchez-Palencia 		 OFFLOAD_IS_ON(qopt) ? "on" : "off",
31225db26a9SVinicius Costa Gomes 		 DEADLINE_MODE_IS_ON(qopt) ? "on" : "off");
31325db26a9SVinicius Costa Gomes 
31425db26a9SVinicius Costa Gomes 	err = validate_input_params(qopt, extack);
31525db26a9SVinicius Costa Gomes 	if (err < 0)
31625db26a9SVinicius Costa Gomes 		return err;
31725db26a9SVinicius Costa Gomes 
31825db26a9SVinicius Costa Gomes 	q->queue = sch->dev_queue - netdev_get_tx_queue(dev, 0);
31925db26a9SVinicius Costa Gomes 
32088cab771SJesus Sanchez-Palencia 	if (OFFLOAD_IS_ON(qopt)) {
32188cab771SJesus Sanchez-Palencia 		err = etf_enable_offload(dev, q, extack);
32288cab771SJesus Sanchez-Palencia 		if (err < 0)
32388cab771SJesus Sanchez-Palencia 			return err;
32488cab771SJesus Sanchez-Palencia 	}
32588cab771SJesus Sanchez-Palencia 
32625db26a9SVinicius Costa Gomes 	/* Everything went OK, save the parameters used. */
32725db26a9SVinicius Costa Gomes 	q->delta = qopt->delta;
32825db26a9SVinicius Costa Gomes 	q->clockid = qopt->clockid;
32988cab771SJesus Sanchez-Palencia 	q->offload = OFFLOAD_IS_ON(qopt);
33025db26a9SVinicius Costa Gomes 	q->deadline_mode = DEADLINE_MODE_IS_ON(qopt);
33125db26a9SVinicius Costa Gomes 
33225db26a9SVinicius Costa Gomes 	switch (q->clockid) {
33325db26a9SVinicius Costa Gomes 	case CLOCK_REALTIME:
33425db26a9SVinicius Costa Gomes 		q->get_time = ktime_get_real;
33525db26a9SVinicius Costa Gomes 		break;
33625db26a9SVinicius Costa Gomes 	case CLOCK_MONOTONIC:
33725db26a9SVinicius Costa Gomes 		q->get_time = ktime_get;
33825db26a9SVinicius Costa Gomes 		break;
33925db26a9SVinicius Costa Gomes 	case CLOCK_BOOTTIME:
34025db26a9SVinicius Costa Gomes 		q->get_time = ktime_get_boottime;
34125db26a9SVinicius Costa Gomes 		break;
34225db26a9SVinicius Costa Gomes 	case CLOCK_TAI:
34325db26a9SVinicius Costa Gomes 		q->get_time = ktime_get_clocktai;
34425db26a9SVinicius Costa Gomes 		break;
34525db26a9SVinicius Costa Gomes 	default:
34625db26a9SVinicius Costa Gomes 		NL_SET_ERR_MSG(extack, "Clockid is not supported");
34725db26a9SVinicius Costa Gomes 		return -ENOTSUPP;
34825db26a9SVinicius Costa Gomes 	}
34925db26a9SVinicius Costa Gomes 
35025db26a9SVinicius Costa Gomes 	qdisc_watchdog_init_clockid(&q->watchdog, sch, q->clockid);
35125db26a9SVinicius Costa Gomes 
35225db26a9SVinicius Costa Gomes 	return 0;
35325db26a9SVinicius Costa Gomes }
35425db26a9SVinicius Costa Gomes 
35525db26a9SVinicius Costa Gomes static void timesortedlist_clear(struct Qdisc *sch)
35625db26a9SVinicius Costa Gomes {
35725db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
35825db26a9SVinicius Costa Gomes 	struct rb_node *p = rb_first(&q->head);
35925db26a9SVinicius Costa Gomes 
36025db26a9SVinicius Costa Gomes 	while (p) {
36125db26a9SVinicius Costa Gomes 		struct sk_buff *skb = rb_to_skb(p);
36225db26a9SVinicius Costa Gomes 
36325db26a9SVinicius Costa Gomes 		p = rb_next(p);
36425db26a9SVinicius Costa Gomes 
36525db26a9SVinicius Costa Gomes 		rb_erase(&skb->rbnode, &q->head);
36625db26a9SVinicius Costa Gomes 		rtnl_kfree_skbs(skb, skb);
36725db26a9SVinicius Costa Gomes 		sch->q.qlen--;
36825db26a9SVinicius Costa Gomes 	}
36925db26a9SVinicius Costa Gomes }
37025db26a9SVinicius Costa Gomes 
37125db26a9SVinicius Costa Gomes static void etf_reset(struct Qdisc *sch)
37225db26a9SVinicius Costa Gomes {
37325db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
37425db26a9SVinicius Costa Gomes 
37525db26a9SVinicius Costa Gomes 	/* Only cancel watchdog if it's been initialized. */
37625db26a9SVinicius Costa Gomes 	if (q->watchdog.qdisc == sch)
37725db26a9SVinicius Costa Gomes 		qdisc_watchdog_cancel(&q->watchdog);
37825db26a9SVinicius Costa Gomes 
37925db26a9SVinicius Costa Gomes 	/* No matter which mode we are on, it's safe to clear both lists. */
38025db26a9SVinicius Costa Gomes 	timesortedlist_clear(sch);
38125db26a9SVinicius Costa Gomes 	__qdisc_reset_queue(&sch->q);
38225db26a9SVinicius Costa Gomes 
38325db26a9SVinicius Costa Gomes 	sch->qstats.backlog = 0;
38425db26a9SVinicius Costa Gomes 	sch->q.qlen = 0;
38525db26a9SVinicius Costa Gomes 
38625db26a9SVinicius Costa Gomes 	q->last = 0;
38725db26a9SVinicius Costa Gomes }
38825db26a9SVinicius Costa Gomes 
38925db26a9SVinicius Costa Gomes static void etf_destroy(struct Qdisc *sch)
39025db26a9SVinicius Costa Gomes {
39125db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
39288cab771SJesus Sanchez-Palencia 	struct net_device *dev = qdisc_dev(sch);
39325db26a9SVinicius Costa Gomes 
39425db26a9SVinicius Costa Gomes 	/* Only cancel watchdog if it's been initialized. */
39525db26a9SVinicius Costa Gomes 	if (q->watchdog.qdisc == sch)
39625db26a9SVinicius Costa Gomes 		qdisc_watchdog_cancel(&q->watchdog);
39788cab771SJesus Sanchez-Palencia 
39888cab771SJesus Sanchez-Palencia 	etf_disable_offload(dev, q);
39925db26a9SVinicius Costa Gomes }
40025db26a9SVinicius Costa Gomes 
40125db26a9SVinicius Costa Gomes static int etf_dump(struct Qdisc *sch, struct sk_buff *skb)
40225db26a9SVinicius Costa Gomes {
40325db26a9SVinicius Costa Gomes 	struct etf_sched_data *q = qdisc_priv(sch);
40425db26a9SVinicius Costa Gomes 	struct tc_etf_qopt opt = { };
40525db26a9SVinicius Costa Gomes 	struct nlattr *nest;
40625db26a9SVinicius Costa Gomes 
40725db26a9SVinicius Costa Gomes 	nest = nla_nest_start(skb, TCA_OPTIONS);
40825db26a9SVinicius Costa Gomes 	if (!nest)
40925db26a9SVinicius Costa Gomes 		goto nla_put_failure;
41025db26a9SVinicius Costa Gomes 
41125db26a9SVinicius Costa Gomes 	opt.delta = q->delta;
41225db26a9SVinicius Costa Gomes 	opt.clockid = q->clockid;
41388cab771SJesus Sanchez-Palencia 	if (q->offload)
41488cab771SJesus Sanchez-Palencia 		opt.flags |= TC_ETF_OFFLOAD_ON;
41588cab771SJesus Sanchez-Palencia 
41625db26a9SVinicius Costa Gomes 	if (q->deadline_mode)
41725db26a9SVinicius Costa Gomes 		opt.flags |= TC_ETF_DEADLINE_MODE_ON;
41825db26a9SVinicius Costa Gomes 
41925db26a9SVinicius Costa Gomes 	if (nla_put(skb, TCA_ETF_PARMS, sizeof(opt), &opt))
42025db26a9SVinicius Costa Gomes 		goto nla_put_failure;
42125db26a9SVinicius Costa Gomes 
42225db26a9SVinicius Costa Gomes 	return nla_nest_end(skb, nest);
42325db26a9SVinicius Costa Gomes 
42425db26a9SVinicius Costa Gomes nla_put_failure:
42525db26a9SVinicius Costa Gomes 	nla_nest_cancel(skb, nest);
42625db26a9SVinicius Costa Gomes 	return -1;
42725db26a9SVinicius Costa Gomes }
42825db26a9SVinicius Costa Gomes 
42925db26a9SVinicius Costa Gomes static struct Qdisc_ops etf_qdisc_ops __read_mostly = {
43025db26a9SVinicius Costa Gomes 	.id		=	"etf",
43125db26a9SVinicius Costa Gomes 	.priv_size	=	sizeof(struct etf_sched_data),
43225db26a9SVinicius Costa Gomes 	.enqueue	=	etf_enqueue_timesortedlist,
43325db26a9SVinicius Costa Gomes 	.dequeue	=	etf_dequeue_timesortedlist,
43425db26a9SVinicius Costa Gomes 	.peek		=	etf_peek_timesortedlist,
43525db26a9SVinicius Costa Gomes 	.init		=	etf_init,
43625db26a9SVinicius Costa Gomes 	.reset		=	etf_reset,
43725db26a9SVinicius Costa Gomes 	.destroy	=	etf_destroy,
43825db26a9SVinicius Costa Gomes 	.dump		=	etf_dump,
43925db26a9SVinicius Costa Gomes 	.owner		=	THIS_MODULE,
44025db26a9SVinicius Costa Gomes };
44125db26a9SVinicius Costa Gomes 
44225db26a9SVinicius Costa Gomes static int __init etf_module_init(void)
44325db26a9SVinicius Costa Gomes {
44425db26a9SVinicius Costa Gomes 	return register_qdisc(&etf_qdisc_ops);
44525db26a9SVinicius Costa Gomes }
44625db26a9SVinicius Costa Gomes 
44725db26a9SVinicius Costa Gomes static void __exit etf_module_exit(void)
44825db26a9SVinicius Costa Gomes {
44925db26a9SVinicius Costa Gomes 	unregister_qdisc(&etf_qdisc_ops);
45025db26a9SVinicius Costa Gomes }
45125db26a9SVinicius Costa Gomes module_init(etf_module_init)
45225db26a9SVinicius Costa Gomes module_exit(etf_module_exit)
45325db26a9SVinicius Costa Gomes MODULE_LICENSE("GPL");
454