162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * net/sched/sch_gred.c	Generic Random Early Detection queue.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:    J Hadi Salim (hadi@cyberus.ca) 1998-2002
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *             991129: -  Bug fix with grio mode
862306a36Sopenharmony_ci *		       - a better sing. AvgQ mode with Grio(WRED)
962306a36Sopenharmony_ci *		       - A finer grained VQ dequeue based on suggestion
1062306a36Sopenharmony_ci *		         from Ren Liu
1162306a36Sopenharmony_ci *		       - More error checks
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci *  For all the glorious comments look at include/net/red.h
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/slab.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/types.h>
1962306a36Sopenharmony_ci#include <linux/kernel.h>
2062306a36Sopenharmony_ci#include <linux/skbuff.h>
2162306a36Sopenharmony_ci#include <net/pkt_cls.h>
2262306a36Sopenharmony_ci#include <net/pkt_sched.h>
2362306a36Sopenharmony_ci#include <net/red.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define GRED_DEF_PRIO (MAX_DPs / 2)
2662306a36Sopenharmony_ci#define GRED_VQ_MASK (MAX_DPs - 1)
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define GRED_VQ_RED_FLAGS	(TC_RED_ECN | TC_RED_HARDDROP)
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct gred_sched_data;
3162306a36Sopenharmony_cistruct gred_sched;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistruct gred_sched_data {
3462306a36Sopenharmony_ci	u32		limit;		/* HARD maximal queue length	*/
3562306a36Sopenharmony_ci	u32		DP;		/* the drop parameters */
3662306a36Sopenharmony_ci	u32		red_flags;	/* virtualQ version of red_flags */
3762306a36Sopenharmony_ci	u64		bytesin;	/* bytes seen on virtualQ so far*/
3862306a36Sopenharmony_ci	u32		packetsin;	/* packets seen on virtualQ so far*/
3962306a36Sopenharmony_ci	u32		backlog;	/* bytes on the virtualQ */
4062306a36Sopenharmony_ci	u8		prio;		/* the prio of this vq */
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	struct red_parms parms;
4362306a36Sopenharmony_ci	struct red_vars  vars;
4462306a36Sopenharmony_ci	struct red_stats stats;
4562306a36Sopenharmony_ci};
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cienum {
4862306a36Sopenharmony_ci	GRED_WRED_MODE = 1,
4962306a36Sopenharmony_ci	GRED_RIO_MODE,
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistruct gred_sched {
5362306a36Sopenharmony_ci	struct gred_sched_data *tab[MAX_DPs];
5462306a36Sopenharmony_ci	unsigned long	flags;
5562306a36Sopenharmony_ci	u32		red_flags;
5662306a36Sopenharmony_ci	u32 		DPs;
5762306a36Sopenharmony_ci	u32 		def;
5862306a36Sopenharmony_ci	struct red_vars wred_set;
5962306a36Sopenharmony_ci	struct tc_gred_qopt_offload *opt;
6062306a36Sopenharmony_ci};
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic inline int gred_wred_mode(struct gred_sched *table)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	return test_bit(GRED_WRED_MODE, &table->flags);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic inline void gred_enable_wred_mode(struct gred_sched *table)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	__set_bit(GRED_WRED_MODE, &table->flags);
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic inline void gred_disable_wred_mode(struct gred_sched *table)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	__clear_bit(GRED_WRED_MODE, &table->flags);
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic inline int gred_rio_mode(struct gred_sched *table)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	return test_bit(GRED_RIO_MODE, &table->flags);
8062306a36Sopenharmony_ci}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_cistatic inline void gred_enable_rio_mode(struct gred_sched *table)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	__set_bit(GRED_RIO_MODE, &table->flags);
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic inline void gred_disable_rio_mode(struct gred_sched *table)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	__clear_bit(GRED_RIO_MODE, &table->flags);
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic inline int gred_wred_mode_check(struct Qdisc *sch)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	struct gred_sched *table = qdisc_priv(sch);
9562306a36Sopenharmony_ci	int i;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	/* Really ugly O(n^2) but shouldn't be necessary too frequent. */
9862306a36Sopenharmony_ci	for (i = 0; i < table->DPs; i++) {
9962306a36Sopenharmony_ci		struct gred_sched_data *q = table->tab[i];
10062306a36Sopenharmony_ci		int n;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		if (q == NULL)
10362306a36Sopenharmony_ci			continue;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci		for (n = i + 1; n < table->DPs; n++)
10662306a36Sopenharmony_ci			if (table->tab[n] && table->tab[n]->prio == q->prio)
10762306a36Sopenharmony_ci				return 1;
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	return 0;
11162306a36Sopenharmony_ci}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic inline unsigned int gred_backlog(struct gred_sched *table,
11462306a36Sopenharmony_ci					struct gred_sched_data *q,
11562306a36Sopenharmony_ci					struct Qdisc *sch)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	if (gred_wred_mode(table))
11862306a36Sopenharmony_ci		return sch->qstats.backlog;
11962306a36Sopenharmony_ci	else
12062306a36Sopenharmony_ci		return q->backlog;
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic inline u16 tc_index_to_dp(struct sk_buff *skb)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	return skb->tc_index & GRED_VQ_MASK;
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic inline void gred_load_wred_set(const struct gred_sched *table,
12962306a36Sopenharmony_ci				      struct gred_sched_data *q)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	q->vars.qavg = table->wred_set.qavg;
13262306a36Sopenharmony_ci	q->vars.qidlestart = table->wred_set.qidlestart;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic inline void gred_store_wred_set(struct gred_sched *table,
13662306a36Sopenharmony_ci				       struct gred_sched_data *q)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	table->wred_set.qavg = q->vars.qavg;
13962306a36Sopenharmony_ci	table->wred_set.qidlestart = q->vars.qidlestart;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic int gred_use_ecn(struct gred_sched_data *q)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	return q->red_flags & TC_RED_ECN;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic int gred_use_harddrop(struct gred_sched_data *q)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	return q->red_flags & TC_RED_HARDDROP;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic bool gred_per_vq_red_flags_used(struct gred_sched *table)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	unsigned int i;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/* Local per-vq flags couldn't have been set unless global are 0 */
15762306a36Sopenharmony_ci	if (table->red_flags)
15862306a36Sopenharmony_ci		return false;
15962306a36Sopenharmony_ci	for (i = 0; i < MAX_DPs; i++)
16062306a36Sopenharmony_ci		if (table->tab[i] && table->tab[i]->red_flags)
16162306a36Sopenharmony_ci			return true;
16262306a36Sopenharmony_ci	return false;
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic int gred_enqueue(struct sk_buff *skb, struct Qdisc *sch,
16662306a36Sopenharmony_ci			struct sk_buff **to_free)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	struct gred_sched_data *q = NULL;
16962306a36Sopenharmony_ci	struct gred_sched *t = qdisc_priv(sch);
17062306a36Sopenharmony_ci	unsigned long qavg = 0;
17162306a36Sopenharmony_ci	u16 dp = tc_index_to_dp(skb);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (dp >= t->DPs || (q = t->tab[dp]) == NULL) {
17462306a36Sopenharmony_ci		dp = t->def;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci		q = t->tab[dp];
17762306a36Sopenharmony_ci		if (!q) {
17862306a36Sopenharmony_ci			/* Pass through packets not assigned to a DP
17962306a36Sopenharmony_ci			 * if no default DP has been configured. This
18062306a36Sopenharmony_ci			 * allows for DP flows to be left untouched.
18162306a36Sopenharmony_ci			 */
18262306a36Sopenharmony_ci			if (likely(sch->qstats.backlog + qdisc_pkt_len(skb) <=
18362306a36Sopenharmony_ci					sch->limit))
18462306a36Sopenharmony_ci				return qdisc_enqueue_tail(skb, sch);
18562306a36Sopenharmony_ci			else
18662306a36Sopenharmony_ci				goto drop;
18762306a36Sopenharmony_ci		}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci		/* fix tc_index? --could be controversial but needed for
19062306a36Sopenharmony_ci		   requeueing */
19162306a36Sopenharmony_ci		skb->tc_index = (skb->tc_index & ~GRED_VQ_MASK) | dp;
19262306a36Sopenharmony_ci	}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	/* sum up all the qaves of prios < ours to get the new qave */
19562306a36Sopenharmony_ci	if (!gred_wred_mode(t) && gred_rio_mode(t)) {
19662306a36Sopenharmony_ci		int i;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci		for (i = 0; i < t->DPs; i++) {
19962306a36Sopenharmony_ci			if (t->tab[i] && t->tab[i]->prio < q->prio &&
20062306a36Sopenharmony_ci			    !red_is_idling(&t->tab[i]->vars))
20162306a36Sopenharmony_ci				qavg += t->tab[i]->vars.qavg;
20262306a36Sopenharmony_ci		}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	q->packetsin++;
20762306a36Sopenharmony_ci	q->bytesin += qdisc_pkt_len(skb);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	if (gred_wred_mode(t))
21062306a36Sopenharmony_ci		gred_load_wred_set(t, q);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	q->vars.qavg = red_calc_qavg(&q->parms,
21362306a36Sopenharmony_ci				     &q->vars,
21462306a36Sopenharmony_ci				     gred_backlog(t, q, sch));
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	if (red_is_idling(&q->vars))
21762306a36Sopenharmony_ci		red_end_of_idle_period(&q->vars);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	if (gred_wred_mode(t))
22062306a36Sopenharmony_ci		gred_store_wred_set(t, q);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	switch (red_action(&q->parms, &q->vars, q->vars.qavg + qavg)) {
22362306a36Sopenharmony_ci	case RED_DONT_MARK:
22462306a36Sopenharmony_ci		break;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	case RED_PROB_MARK:
22762306a36Sopenharmony_ci		qdisc_qstats_overlimit(sch);
22862306a36Sopenharmony_ci		if (!gred_use_ecn(q) || !INET_ECN_set_ce(skb)) {
22962306a36Sopenharmony_ci			q->stats.prob_drop++;
23062306a36Sopenharmony_ci			goto congestion_drop;
23162306a36Sopenharmony_ci		}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci		q->stats.prob_mark++;
23462306a36Sopenharmony_ci		break;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	case RED_HARD_MARK:
23762306a36Sopenharmony_ci		qdisc_qstats_overlimit(sch);
23862306a36Sopenharmony_ci		if (gred_use_harddrop(q) || !gred_use_ecn(q) ||
23962306a36Sopenharmony_ci		    !INET_ECN_set_ce(skb)) {
24062306a36Sopenharmony_ci			q->stats.forced_drop++;
24162306a36Sopenharmony_ci			goto congestion_drop;
24262306a36Sopenharmony_ci		}
24362306a36Sopenharmony_ci		q->stats.forced_mark++;
24462306a36Sopenharmony_ci		break;
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	if (gred_backlog(t, q, sch) + qdisc_pkt_len(skb) <= q->limit) {
24862306a36Sopenharmony_ci		q->backlog += qdisc_pkt_len(skb);
24962306a36Sopenharmony_ci		return qdisc_enqueue_tail(skb, sch);
25062306a36Sopenharmony_ci	}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	q->stats.pdrop++;
25362306a36Sopenharmony_cidrop:
25462306a36Sopenharmony_ci	return qdisc_drop(skb, sch, to_free);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cicongestion_drop:
25762306a36Sopenharmony_ci	qdisc_drop(skb, sch, to_free);
25862306a36Sopenharmony_ci	return NET_XMIT_CN;
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic struct sk_buff *gred_dequeue(struct Qdisc *sch)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	struct sk_buff *skb;
26462306a36Sopenharmony_ci	struct gred_sched *t = qdisc_priv(sch);
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	skb = qdisc_dequeue_head(sch);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	if (skb) {
26962306a36Sopenharmony_ci		struct gred_sched_data *q;
27062306a36Sopenharmony_ci		u16 dp = tc_index_to_dp(skb);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci		if (dp >= t->DPs || (q = t->tab[dp]) == NULL) {
27362306a36Sopenharmony_ci			net_warn_ratelimited("GRED: Unable to relocate VQ 0x%x after dequeue, screwing up backlog\n",
27462306a36Sopenharmony_ci					     tc_index_to_dp(skb));
27562306a36Sopenharmony_ci		} else {
27662306a36Sopenharmony_ci			q->backlog -= qdisc_pkt_len(skb);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci			if (gred_wred_mode(t)) {
27962306a36Sopenharmony_ci				if (!sch->qstats.backlog)
28062306a36Sopenharmony_ci					red_start_of_idle_period(&t->wred_set);
28162306a36Sopenharmony_ci			} else {
28262306a36Sopenharmony_ci				if (!q->backlog)
28362306a36Sopenharmony_ci					red_start_of_idle_period(&q->vars);
28462306a36Sopenharmony_ci			}
28562306a36Sopenharmony_ci		}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci		return skb;
28862306a36Sopenharmony_ci	}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	return NULL;
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic void gred_reset(struct Qdisc *sch)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	int i;
29662306a36Sopenharmony_ci	struct gred_sched *t = qdisc_priv(sch);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	qdisc_reset_queue(sch);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	for (i = 0; i < t->DPs; i++) {
30162306a36Sopenharmony_ci		struct gred_sched_data *q = t->tab[i];
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci		if (!q)
30462306a36Sopenharmony_ci			continue;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci		red_restart(&q->vars);
30762306a36Sopenharmony_ci		q->backlog = 0;
30862306a36Sopenharmony_ci	}
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_cistatic void gred_offload(struct Qdisc *sch, enum tc_gred_command command)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	struct gred_sched *table = qdisc_priv(sch);
31462306a36Sopenharmony_ci	struct net_device *dev = qdisc_dev(sch);
31562306a36Sopenharmony_ci	struct tc_gred_qopt_offload *opt = table->opt;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc)
31862306a36Sopenharmony_ci		return;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	memset(opt, 0, sizeof(*opt));
32162306a36Sopenharmony_ci	opt->command = command;
32262306a36Sopenharmony_ci	opt->handle = sch->handle;
32362306a36Sopenharmony_ci	opt->parent = sch->parent;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	if (command == TC_GRED_REPLACE) {
32662306a36Sopenharmony_ci		unsigned int i;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci		opt->set.grio_on = gred_rio_mode(table);
32962306a36Sopenharmony_ci		opt->set.wred_on = gred_wred_mode(table);
33062306a36Sopenharmony_ci		opt->set.dp_cnt = table->DPs;
33162306a36Sopenharmony_ci		opt->set.dp_def = table->def;
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci		for (i = 0; i < table->DPs; i++) {
33462306a36Sopenharmony_ci			struct gred_sched_data *q = table->tab[i];
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci			if (!q)
33762306a36Sopenharmony_ci				continue;
33862306a36Sopenharmony_ci			opt->set.tab[i].present = true;
33962306a36Sopenharmony_ci			opt->set.tab[i].limit = q->limit;
34062306a36Sopenharmony_ci			opt->set.tab[i].prio = q->prio;
34162306a36Sopenharmony_ci			opt->set.tab[i].min = q->parms.qth_min >> q->parms.Wlog;
34262306a36Sopenharmony_ci			opt->set.tab[i].max = q->parms.qth_max >> q->parms.Wlog;
34362306a36Sopenharmony_ci			opt->set.tab[i].is_ecn = gred_use_ecn(q);
34462306a36Sopenharmony_ci			opt->set.tab[i].is_harddrop = gred_use_harddrop(q);
34562306a36Sopenharmony_ci			opt->set.tab[i].probability = q->parms.max_P;
34662306a36Sopenharmony_ci			opt->set.tab[i].backlog = &q->backlog;
34762306a36Sopenharmony_ci		}
34862306a36Sopenharmony_ci		opt->set.qstats = &sch->qstats;
34962306a36Sopenharmony_ci	}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_QDISC_GRED, opt);
35262306a36Sopenharmony_ci}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_cistatic int gred_offload_dump_stats(struct Qdisc *sch)
35562306a36Sopenharmony_ci{
35662306a36Sopenharmony_ci	struct gred_sched *table = qdisc_priv(sch);
35762306a36Sopenharmony_ci	struct tc_gred_qopt_offload *hw_stats;
35862306a36Sopenharmony_ci	u64 bytes = 0, packets = 0;
35962306a36Sopenharmony_ci	unsigned int i;
36062306a36Sopenharmony_ci	int ret;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci	hw_stats = kzalloc(sizeof(*hw_stats), GFP_KERNEL);
36362306a36Sopenharmony_ci	if (!hw_stats)
36462306a36Sopenharmony_ci		return -ENOMEM;
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci	hw_stats->command = TC_GRED_STATS;
36762306a36Sopenharmony_ci	hw_stats->handle = sch->handle;
36862306a36Sopenharmony_ci	hw_stats->parent = sch->parent;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	for (i = 0; i < MAX_DPs; i++) {
37162306a36Sopenharmony_ci		gnet_stats_basic_sync_init(&hw_stats->stats.bstats[i]);
37262306a36Sopenharmony_ci		if (table->tab[i])
37362306a36Sopenharmony_ci			hw_stats->stats.xstats[i] = &table->tab[i]->stats;
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	ret = qdisc_offload_dump_helper(sch, TC_SETUP_QDISC_GRED, hw_stats);
37762306a36Sopenharmony_ci	/* Even if driver returns failure adjust the stats - in case offload
37862306a36Sopenharmony_ci	 * ended but driver still wants to adjust the values.
37962306a36Sopenharmony_ci	 */
38062306a36Sopenharmony_ci	sch_tree_lock(sch);
38162306a36Sopenharmony_ci	for (i = 0; i < MAX_DPs; i++) {
38262306a36Sopenharmony_ci		if (!table->tab[i])
38362306a36Sopenharmony_ci			continue;
38462306a36Sopenharmony_ci		table->tab[i]->packetsin += u64_stats_read(&hw_stats->stats.bstats[i].packets);
38562306a36Sopenharmony_ci		table->tab[i]->bytesin += u64_stats_read(&hw_stats->stats.bstats[i].bytes);
38662306a36Sopenharmony_ci		table->tab[i]->backlog += hw_stats->stats.qstats[i].backlog;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci		bytes += u64_stats_read(&hw_stats->stats.bstats[i].bytes);
38962306a36Sopenharmony_ci		packets += u64_stats_read(&hw_stats->stats.bstats[i].packets);
39062306a36Sopenharmony_ci		sch->qstats.qlen += hw_stats->stats.qstats[i].qlen;
39162306a36Sopenharmony_ci		sch->qstats.backlog += hw_stats->stats.qstats[i].backlog;
39262306a36Sopenharmony_ci		sch->qstats.drops += hw_stats->stats.qstats[i].drops;
39362306a36Sopenharmony_ci		sch->qstats.requeues += hw_stats->stats.qstats[i].requeues;
39462306a36Sopenharmony_ci		sch->qstats.overlimits += hw_stats->stats.qstats[i].overlimits;
39562306a36Sopenharmony_ci	}
39662306a36Sopenharmony_ci	_bstats_update(&sch->bstats, bytes, packets);
39762306a36Sopenharmony_ci	sch_tree_unlock(sch);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	kfree(hw_stats);
40062306a36Sopenharmony_ci	return ret;
40162306a36Sopenharmony_ci}
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_cistatic inline void gred_destroy_vq(struct gred_sched_data *q)
40462306a36Sopenharmony_ci{
40562306a36Sopenharmony_ci	kfree(q);
40662306a36Sopenharmony_ci}
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_cistatic int gred_change_table_def(struct Qdisc *sch, struct nlattr *dps,
40962306a36Sopenharmony_ci				 struct netlink_ext_ack *extack)
41062306a36Sopenharmony_ci{
41162306a36Sopenharmony_ci	struct gred_sched *table = qdisc_priv(sch);
41262306a36Sopenharmony_ci	struct tc_gred_sopt *sopt;
41362306a36Sopenharmony_ci	bool red_flags_changed;
41462306a36Sopenharmony_ci	int i;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	if (!dps)
41762306a36Sopenharmony_ci		return -EINVAL;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	sopt = nla_data(dps);
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	if (sopt->DPs > MAX_DPs) {
42262306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "number of virtual queues too high");
42362306a36Sopenharmony_ci		return -EINVAL;
42462306a36Sopenharmony_ci	}
42562306a36Sopenharmony_ci	if (sopt->DPs == 0) {
42662306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
42762306a36Sopenharmony_ci				   "number of virtual queues can't be 0");
42862306a36Sopenharmony_ci		return -EINVAL;
42962306a36Sopenharmony_ci	}
43062306a36Sopenharmony_ci	if (sopt->def_DP >= sopt->DPs) {
43162306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "default virtual queue above virtual queue count");
43262306a36Sopenharmony_ci		return -EINVAL;
43362306a36Sopenharmony_ci	}
43462306a36Sopenharmony_ci	if (sopt->flags && gred_per_vq_red_flags_used(table)) {
43562306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "can't set per-Qdisc RED flags when per-virtual queue flags are used");
43662306a36Sopenharmony_ci		return -EINVAL;
43762306a36Sopenharmony_ci	}
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	sch_tree_lock(sch);
44062306a36Sopenharmony_ci	table->DPs = sopt->DPs;
44162306a36Sopenharmony_ci	table->def = sopt->def_DP;
44262306a36Sopenharmony_ci	red_flags_changed = table->red_flags != sopt->flags;
44362306a36Sopenharmony_ci	table->red_flags = sopt->flags;
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	/*
44662306a36Sopenharmony_ci	 * Every entry point to GRED is synchronized with the above code
44762306a36Sopenharmony_ci	 * and the DP is checked against DPs, i.e. shadowed VQs can no
44862306a36Sopenharmony_ci	 * longer be found so we can unlock right here.
44962306a36Sopenharmony_ci	 */
45062306a36Sopenharmony_ci	sch_tree_unlock(sch);
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	if (sopt->grio) {
45362306a36Sopenharmony_ci		gred_enable_rio_mode(table);
45462306a36Sopenharmony_ci		gred_disable_wred_mode(table);
45562306a36Sopenharmony_ci		if (gred_wred_mode_check(sch))
45662306a36Sopenharmony_ci			gred_enable_wred_mode(table);
45762306a36Sopenharmony_ci	} else {
45862306a36Sopenharmony_ci		gred_disable_rio_mode(table);
45962306a36Sopenharmony_ci		gred_disable_wred_mode(table);
46062306a36Sopenharmony_ci	}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	if (red_flags_changed)
46362306a36Sopenharmony_ci		for (i = 0; i < table->DPs; i++)
46462306a36Sopenharmony_ci			if (table->tab[i])
46562306a36Sopenharmony_ci				table->tab[i]->red_flags =
46662306a36Sopenharmony_ci					table->red_flags & GRED_VQ_RED_FLAGS;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	for (i = table->DPs; i < MAX_DPs; i++) {
46962306a36Sopenharmony_ci		if (table->tab[i]) {
47062306a36Sopenharmony_ci			pr_warn("GRED: Warning: Destroying shadowed VQ 0x%x\n",
47162306a36Sopenharmony_ci				i);
47262306a36Sopenharmony_ci			gred_destroy_vq(table->tab[i]);
47362306a36Sopenharmony_ci			table->tab[i] = NULL;
47462306a36Sopenharmony_ci		}
47562306a36Sopenharmony_ci	}
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	gred_offload(sch, TC_GRED_REPLACE);
47862306a36Sopenharmony_ci	return 0;
47962306a36Sopenharmony_ci}
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_cistatic inline int gred_change_vq(struct Qdisc *sch, int dp,
48262306a36Sopenharmony_ci				 struct tc_gred_qopt *ctl, int prio,
48362306a36Sopenharmony_ci				 u8 *stab, u32 max_P,
48462306a36Sopenharmony_ci				 struct gred_sched_data **prealloc,
48562306a36Sopenharmony_ci				 struct netlink_ext_ack *extack)
48662306a36Sopenharmony_ci{
48762306a36Sopenharmony_ci	struct gred_sched *table = qdisc_priv(sch);
48862306a36Sopenharmony_ci	struct gred_sched_data *q = table->tab[dp];
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	if (!red_check_params(ctl->qth_min, ctl->qth_max, ctl->Wlog, ctl->Scell_log, stab)) {
49162306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "invalid RED parameters");
49262306a36Sopenharmony_ci		return -EINVAL;
49362306a36Sopenharmony_ci	}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	if (!q) {
49662306a36Sopenharmony_ci		table->tab[dp] = q = *prealloc;
49762306a36Sopenharmony_ci		*prealloc = NULL;
49862306a36Sopenharmony_ci		if (!q)
49962306a36Sopenharmony_ci			return -ENOMEM;
50062306a36Sopenharmony_ci		q->red_flags = table->red_flags & GRED_VQ_RED_FLAGS;
50162306a36Sopenharmony_ci	}
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	q->DP = dp;
50462306a36Sopenharmony_ci	q->prio = prio;
50562306a36Sopenharmony_ci	if (ctl->limit > sch->limit)
50662306a36Sopenharmony_ci		q->limit = sch->limit;
50762306a36Sopenharmony_ci	else
50862306a36Sopenharmony_ci		q->limit = ctl->limit;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	if (q->backlog == 0)
51162306a36Sopenharmony_ci		red_end_of_idle_period(&q->vars);
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	red_set_parms(&q->parms,
51462306a36Sopenharmony_ci		      ctl->qth_min, ctl->qth_max, ctl->Wlog, ctl->Plog,
51562306a36Sopenharmony_ci		      ctl->Scell_log, stab, max_P);
51662306a36Sopenharmony_ci	red_set_vars(&q->vars);
51762306a36Sopenharmony_ci	return 0;
51862306a36Sopenharmony_ci}
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_cistatic const struct nla_policy gred_vq_policy[TCA_GRED_VQ_MAX + 1] = {
52162306a36Sopenharmony_ci	[TCA_GRED_VQ_DP]	= { .type = NLA_U32 },
52262306a36Sopenharmony_ci	[TCA_GRED_VQ_FLAGS]	= { .type = NLA_U32 },
52362306a36Sopenharmony_ci};
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cistatic const struct nla_policy gred_vqe_policy[TCA_GRED_VQ_ENTRY_MAX + 1] = {
52662306a36Sopenharmony_ci	[TCA_GRED_VQ_ENTRY]	= { .type = NLA_NESTED },
52762306a36Sopenharmony_ci};
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_cistatic const struct nla_policy gred_policy[TCA_GRED_MAX + 1] = {
53062306a36Sopenharmony_ci	[TCA_GRED_PARMS]	= { .len = sizeof(struct tc_gred_qopt) },
53162306a36Sopenharmony_ci	[TCA_GRED_STAB]		= { .len = 256 },
53262306a36Sopenharmony_ci	[TCA_GRED_DPS]		= { .len = sizeof(struct tc_gred_sopt) },
53362306a36Sopenharmony_ci	[TCA_GRED_MAX_P]	= { .type = NLA_U32 },
53462306a36Sopenharmony_ci	[TCA_GRED_LIMIT]	= { .type = NLA_U32 },
53562306a36Sopenharmony_ci	[TCA_GRED_VQ_LIST]	= { .type = NLA_NESTED },
53662306a36Sopenharmony_ci};
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_cistatic void gred_vq_apply(struct gred_sched *table, const struct nlattr *entry)
53962306a36Sopenharmony_ci{
54062306a36Sopenharmony_ci	struct nlattr *tb[TCA_GRED_VQ_MAX + 1];
54162306a36Sopenharmony_ci	u32 dp;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	nla_parse_nested_deprecated(tb, TCA_GRED_VQ_MAX, entry,
54462306a36Sopenharmony_ci				    gred_vq_policy, NULL);
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	dp = nla_get_u32(tb[TCA_GRED_VQ_DP]);
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	if (tb[TCA_GRED_VQ_FLAGS])
54962306a36Sopenharmony_ci		table->tab[dp]->red_flags = nla_get_u32(tb[TCA_GRED_VQ_FLAGS]);
55062306a36Sopenharmony_ci}
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_cistatic void gred_vqs_apply(struct gred_sched *table, struct nlattr *vqs)
55362306a36Sopenharmony_ci{
55462306a36Sopenharmony_ci	const struct nlattr *attr;
55562306a36Sopenharmony_ci	int rem;
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	nla_for_each_nested(attr, vqs, rem) {
55862306a36Sopenharmony_ci		switch (nla_type(attr)) {
55962306a36Sopenharmony_ci		case TCA_GRED_VQ_ENTRY:
56062306a36Sopenharmony_ci			gred_vq_apply(table, attr);
56162306a36Sopenharmony_ci			break;
56262306a36Sopenharmony_ci		}
56362306a36Sopenharmony_ci	}
56462306a36Sopenharmony_ci}
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_cistatic int gred_vq_validate(struct gred_sched *table, u32 cdp,
56762306a36Sopenharmony_ci			    const struct nlattr *entry,
56862306a36Sopenharmony_ci			    struct netlink_ext_ack *extack)
56962306a36Sopenharmony_ci{
57062306a36Sopenharmony_ci	struct nlattr *tb[TCA_GRED_VQ_MAX + 1];
57162306a36Sopenharmony_ci	int err;
57262306a36Sopenharmony_ci	u32 dp;
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	err = nla_parse_nested_deprecated(tb, TCA_GRED_VQ_MAX, entry,
57562306a36Sopenharmony_ci					  gred_vq_policy, extack);
57662306a36Sopenharmony_ci	if (err < 0)
57762306a36Sopenharmony_ci		return err;
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	if (!tb[TCA_GRED_VQ_DP]) {
58062306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Virtual queue with no index specified");
58162306a36Sopenharmony_ci		return -EINVAL;
58262306a36Sopenharmony_ci	}
58362306a36Sopenharmony_ci	dp = nla_get_u32(tb[TCA_GRED_VQ_DP]);
58462306a36Sopenharmony_ci	if (dp >= table->DPs) {
58562306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Virtual queue with index out of bounds");
58662306a36Sopenharmony_ci		return -EINVAL;
58762306a36Sopenharmony_ci	}
58862306a36Sopenharmony_ci	if (dp != cdp && !table->tab[dp]) {
58962306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Virtual queue not yet instantiated");
59062306a36Sopenharmony_ci		return -EINVAL;
59162306a36Sopenharmony_ci	}
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	if (tb[TCA_GRED_VQ_FLAGS]) {
59462306a36Sopenharmony_ci		u32 red_flags = nla_get_u32(tb[TCA_GRED_VQ_FLAGS]);
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci		if (table->red_flags && table->red_flags != red_flags) {
59762306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "can't change per-virtual queue RED flags when per-Qdisc flags are used");
59862306a36Sopenharmony_ci			return -EINVAL;
59962306a36Sopenharmony_ci		}
60062306a36Sopenharmony_ci		if (red_flags & ~GRED_VQ_RED_FLAGS) {
60162306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack,
60262306a36Sopenharmony_ci					   "invalid RED flags specified");
60362306a36Sopenharmony_ci			return -EINVAL;
60462306a36Sopenharmony_ci		}
60562306a36Sopenharmony_ci	}
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci	return 0;
60862306a36Sopenharmony_ci}
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_cistatic int gred_vqs_validate(struct gred_sched *table, u32 cdp,
61162306a36Sopenharmony_ci			     struct nlattr *vqs, struct netlink_ext_ack *extack)
61262306a36Sopenharmony_ci{
61362306a36Sopenharmony_ci	const struct nlattr *attr;
61462306a36Sopenharmony_ci	int rem, err;
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	err = nla_validate_nested_deprecated(vqs, TCA_GRED_VQ_ENTRY_MAX,
61762306a36Sopenharmony_ci					     gred_vqe_policy, extack);
61862306a36Sopenharmony_ci	if (err < 0)
61962306a36Sopenharmony_ci		return err;
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	nla_for_each_nested(attr, vqs, rem) {
62262306a36Sopenharmony_ci		switch (nla_type(attr)) {
62362306a36Sopenharmony_ci		case TCA_GRED_VQ_ENTRY:
62462306a36Sopenharmony_ci			err = gred_vq_validate(table, cdp, attr, extack);
62562306a36Sopenharmony_ci			if (err)
62662306a36Sopenharmony_ci				return err;
62762306a36Sopenharmony_ci			break;
62862306a36Sopenharmony_ci		default:
62962306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "GRED_VQ_LIST can contain only entry attributes");
63062306a36Sopenharmony_ci			return -EINVAL;
63162306a36Sopenharmony_ci		}
63262306a36Sopenharmony_ci	}
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_ci	if (rem > 0) {
63562306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Trailing data after parsing virtual queue list");
63662306a36Sopenharmony_ci		return -EINVAL;
63762306a36Sopenharmony_ci	}
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	return 0;
64062306a36Sopenharmony_ci}
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_cistatic int gred_change(struct Qdisc *sch, struct nlattr *opt,
64362306a36Sopenharmony_ci		       struct netlink_ext_ack *extack)
64462306a36Sopenharmony_ci{
64562306a36Sopenharmony_ci	struct gred_sched *table = qdisc_priv(sch);
64662306a36Sopenharmony_ci	struct tc_gred_qopt *ctl;
64762306a36Sopenharmony_ci	struct nlattr *tb[TCA_GRED_MAX + 1];
64862306a36Sopenharmony_ci	int err, prio = GRED_DEF_PRIO;
64962306a36Sopenharmony_ci	u8 *stab;
65062306a36Sopenharmony_ci	u32 max_P;
65162306a36Sopenharmony_ci	struct gred_sched_data *prealloc;
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	err = nla_parse_nested_deprecated(tb, TCA_GRED_MAX, opt, gred_policy,
65462306a36Sopenharmony_ci					  extack);
65562306a36Sopenharmony_ci	if (err < 0)
65662306a36Sopenharmony_ci		return err;
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	if (tb[TCA_GRED_PARMS] == NULL && tb[TCA_GRED_STAB] == NULL) {
65962306a36Sopenharmony_ci		if (tb[TCA_GRED_LIMIT] != NULL)
66062306a36Sopenharmony_ci			sch->limit = nla_get_u32(tb[TCA_GRED_LIMIT]);
66162306a36Sopenharmony_ci		return gred_change_table_def(sch, tb[TCA_GRED_DPS], extack);
66262306a36Sopenharmony_ci	}
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ci	if (tb[TCA_GRED_PARMS] == NULL ||
66562306a36Sopenharmony_ci	    tb[TCA_GRED_STAB] == NULL ||
66662306a36Sopenharmony_ci	    tb[TCA_GRED_LIMIT] != NULL) {
66762306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "can't configure Qdisc and virtual queue at the same time");
66862306a36Sopenharmony_ci		return -EINVAL;
66962306a36Sopenharmony_ci	}
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci	max_P = tb[TCA_GRED_MAX_P] ? nla_get_u32(tb[TCA_GRED_MAX_P]) : 0;
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	ctl = nla_data(tb[TCA_GRED_PARMS]);
67462306a36Sopenharmony_ci	stab = nla_data(tb[TCA_GRED_STAB]);
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	if (ctl->DP >= table->DPs) {
67762306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "virtual queue index above virtual queue count");
67862306a36Sopenharmony_ci		return -EINVAL;
67962306a36Sopenharmony_ci	}
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_ci	if (tb[TCA_GRED_VQ_LIST]) {
68262306a36Sopenharmony_ci		err = gred_vqs_validate(table, ctl->DP, tb[TCA_GRED_VQ_LIST],
68362306a36Sopenharmony_ci					extack);
68462306a36Sopenharmony_ci		if (err)
68562306a36Sopenharmony_ci			return err;
68662306a36Sopenharmony_ci	}
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	if (gred_rio_mode(table)) {
68962306a36Sopenharmony_ci		if (ctl->prio == 0) {
69062306a36Sopenharmony_ci			int def_prio = GRED_DEF_PRIO;
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ci			if (table->tab[table->def])
69362306a36Sopenharmony_ci				def_prio = table->tab[table->def]->prio;
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci			printk(KERN_DEBUG "GRED: DP %u does not have a prio "
69662306a36Sopenharmony_ci			       "setting default to %d\n", ctl->DP, def_prio);
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ci			prio = def_prio;
69962306a36Sopenharmony_ci		} else
70062306a36Sopenharmony_ci			prio = ctl->prio;
70162306a36Sopenharmony_ci	}
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci	prealloc = kzalloc(sizeof(*prealloc), GFP_KERNEL);
70462306a36Sopenharmony_ci	sch_tree_lock(sch);
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci	err = gred_change_vq(sch, ctl->DP, ctl, prio, stab, max_P, &prealloc,
70762306a36Sopenharmony_ci			     extack);
70862306a36Sopenharmony_ci	if (err < 0)
70962306a36Sopenharmony_ci		goto err_unlock_free;
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	if (tb[TCA_GRED_VQ_LIST])
71262306a36Sopenharmony_ci		gred_vqs_apply(table, tb[TCA_GRED_VQ_LIST]);
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci	if (gred_rio_mode(table)) {
71562306a36Sopenharmony_ci		gred_disable_wred_mode(table);
71662306a36Sopenharmony_ci		if (gred_wred_mode_check(sch))
71762306a36Sopenharmony_ci			gred_enable_wred_mode(table);
71862306a36Sopenharmony_ci	}
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci	sch_tree_unlock(sch);
72162306a36Sopenharmony_ci	kfree(prealloc);
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci	gred_offload(sch, TC_GRED_REPLACE);
72462306a36Sopenharmony_ci	return 0;
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_cierr_unlock_free:
72762306a36Sopenharmony_ci	sch_tree_unlock(sch);
72862306a36Sopenharmony_ci	kfree(prealloc);
72962306a36Sopenharmony_ci	return err;
73062306a36Sopenharmony_ci}
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_cistatic int gred_init(struct Qdisc *sch, struct nlattr *opt,
73362306a36Sopenharmony_ci		     struct netlink_ext_ack *extack)
73462306a36Sopenharmony_ci{
73562306a36Sopenharmony_ci	struct gred_sched *table = qdisc_priv(sch);
73662306a36Sopenharmony_ci	struct nlattr *tb[TCA_GRED_MAX + 1];
73762306a36Sopenharmony_ci	int err;
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_ci	if (!opt)
74062306a36Sopenharmony_ci		return -EINVAL;
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci	err = nla_parse_nested_deprecated(tb, TCA_GRED_MAX, opt, gred_policy,
74362306a36Sopenharmony_ci					  extack);
74462306a36Sopenharmony_ci	if (err < 0)
74562306a36Sopenharmony_ci		return err;
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	if (tb[TCA_GRED_PARMS] || tb[TCA_GRED_STAB]) {
74862306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
74962306a36Sopenharmony_ci				   "virtual queue configuration can't be specified at initialization time");
75062306a36Sopenharmony_ci		return -EINVAL;
75162306a36Sopenharmony_ci	}
75262306a36Sopenharmony_ci
75362306a36Sopenharmony_ci	if (tb[TCA_GRED_LIMIT])
75462306a36Sopenharmony_ci		sch->limit = nla_get_u32(tb[TCA_GRED_LIMIT]);
75562306a36Sopenharmony_ci	else
75662306a36Sopenharmony_ci		sch->limit = qdisc_dev(sch)->tx_queue_len
75762306a36Sopenharmony_ci		             * psched_mtu(qdisc_dev(sch));
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_ci	if (qdisc_dev(sch)->netdev_ops->ndo_setup_tc) {
76062306a36Sopenharmony_ci		table->opt = kzalloc(sizeof(*table->opt), GFP_KERNEL);
76162306a36Sopenharmony_ci		if (!table->opt)
76262306a36Sopenharmony_ci			return -ENOMEM;
76362306a36Sopenharmony_ci	}
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci	return gred_change_table_def(sch, tb[TCA_GRED_DPS], extack);
76662306a36Sopenharmony_ci}
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_cistatic int gred_dump(struct Qdisc *sch, struct sk_buff *skb)
76962306a36Sopenharmony_ci{
77062306a36Sopenharmony_ci	struct gred_sched *table = qdisc_priv(sch);
77162306a36Sopenharmony_ci	struct nlattr *parms, *vqs, *opts = NULL;
77262306a36Sopenharmony_ci	int i;
77362306a36Sopenharmony_ci	u32 max_p[MAX_DPs];
77462306a36Sopenharmony_ci	struct tc_gred_sopt sopt = {
77562306a36Sopenharmony_ci		.DPs	= table->DPs,
77662306a36Sopenharmony_ci		.def_DP	= table->def,
77762306a36Sopenharmony_ci		.grio	= gred_rio_mode(table),
77862306a36Sopenharmony_ci		.flags	= table->red_flags,
77962306a36Sopenharmony_ci	};
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	if (gred_offload_dump_stats(sch))
78262306a36Sopenharmony_ci		goto nla_put_failure;
78362306a36Sopenharmony_ci
78462306a36Sopenharmony_ci	opts = nla_nest_start_noflag(skb, TCA_OPTIONS);
78562306a36Sopenharmony_ci	if (opts == NULL)
78662306a36Sopenharmony_ci		goto nla_put_failure;
78762306a36Sopenharmony_ci	if (nla_put(skb, TCA_GRED_DPS, sizeof(sopt), &sopt))
78862306a36Sopenharmony_ci		goto nla_put_failure;
78962306a36Sopenharmony_ci
79062306a36Sopenharmony_ci	for (i = 0; i < MAX_DPs; i++) {
79162306a36Sopenharmony_ci		struct gred_sched_data *q = table->tab[i];
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci		max_p[i] = q ? q->parms.max_P : 0;
79462306a36Sopenharmony_ci	}
79562306a36Sopenharmony_ci	if (nla_put(skb, TCA_GRED_MAX_P, sizeof(max_p), max_p))
79662306a36Sopenharmony_ci		goto nla_put_failure;
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	if (nla_put_u32(skb, TCA_GRED_LIMIT, sch->limit))
79962306a36Sopenharmony_ci		goto nla_put_failure;
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci	/* Old style all-in-one dump of VQs */
80262306a36Sopenharmony_ci	parms = nla_nest_start_noflag(skb, TCA_GRED_PARMS);
80362306a36Sopenharmony_ci	if (parms == NULL)
80462306a36Sopenharmony_ci		goto nla_put_failure;
80562306a36Sopenharmony_ci
80662306a36Sopenharmony_ci	for (i = 0; i < MAX_DPs; i++) {
80762306a36Sopenharmony_ci		struct gred_sched_data *q = table->tab[i];
80862306a36Sopenharmony_ci		struct tc_gred_qopt opt;
80962306a36Sopenharmony_ci		unsigned long qavg;
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_ci		memset(&opt, 0, sizeof(opt));
81262306a36Sopenharmony_ci
81362306a36Sopenharmony_ci		if (!q) {
81462306a36Sopenharmony_ci			/* hack -- fix at some point with proper message
81562306a36Sopenharmony_ci			   This is how we indicate to tc that there is no VQ
81662306a36Sopenharmony_ci			   at this DP */
81762306a36Sopenharmony_ci
81862306a36Sopenharmony_ci			opt.DP = MAX_DPs + i;
81962306a36Sopenharmony_ci			goto append_opt;
82062306a36Sopenharmony_ci		}
82162306a36Sopenharmony_ci
82262306a36Sopenharmony_ci		opt.limit	= q->limit;
82362306a36Sopenharmony_ci		opt.DP		= q->DP;
82462306a36Sopenharmony_ci		opt.backlog	= gred_backlog(table, q, sch);
82562306a36Sopenharmony_ci		opt.prio	= q->prio;
82662306a36Sopenharmony_ci		opt.qth_min	= q->parms.qth_min >> q->parms.Wlog;
82762306a36Sopenharmony_ci		opt.qth_max	= q->parms.qth_max >> q->parms.Wlog;
82862306a36Sopenharmony_ci		opt.Wlog	= q->parms.Wlog;
82962306a36Sopenharmony_ci		opt.Plog	= q->parms.Plog;
83062306a36Sopenharmony_ci		opt.Scell_log	= q->parms.Scell_log;
83162306a36Sopenharmony_ci		opt.early	= q->stats.prob_drop;
83262306a36Sopenharmony_ci		opt.forced	= q->stats.forced_drop;
83362306a36Sopenharmony_ci		opt.pdrop	= q->stats.pdrop;
83462306a36Sopenharmony_ci		opt.packets	= q->packetsin;
83562306a36Sopenharmony_ci		opt.bytesin	= q->bytesin;
83662306a36Sopenharmony_ci
83762306a36Sopenharmony_ci		if (gred_wred_mode(table))
83862306a36Sopenharmony_ci			gred_load_wred_set(table, q);
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci		qavg = red_calc_qavg(&q->parms, &q->vars,
84162306a36Sopenharmony_ci				     q->vars.qavg >> q->parms.Wlog);
84262306a36Sopenharmony_ci		opt.qave = qavg >> q->parms.Wlog;
84362306a36Sopenharmony_ci
84462306a36Sopenharmony_ciappend_opt:
84562306a36Sopenharmony_ci		if (nla_append(skb, sizeof(opt), &opt) < 0)
84662306a36Sopenharmony_ci			goto nla_put_failure;
84762306a36Sopenharmony_ci	}
84862306a36Sopenharmony_ci
84962306a36Sopenharmony_ci	nla_nest_end(skb, parms);
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci	/* Dump the VQs again, in more structured way */
85262306a36Sopenharmony_ci	vqs = nla_nest_start_noflag(skb, TCA_GRED_VQ_LIST);
85362306a36Sopenharmony_ci	if (!vqs)
85462306a36Sopenharmony_ci		goto nla_put_failure;
85562306a36Sopenharmony_ci
85662306a36Sopenharmony_ci	for (i = 0; i < MAX_DPs; i++) {
85762306a36Sopenharmony_ci		struct gred_sched_data *q = table->tab[i];
85862306a36Sopenharmony_ci		struct nlattr *vq;
85962306a36Sopenharmony_ci
86062306a36Sopenharmony_ci		if (!q)
86162306a36Sopenharmony_ci			continue;
86262306a36Sopenharmony_ci
86362306a36Sopenharmony_ci		vq = nla_nest_start_noflag(skb, TCA_GRED_VQ_ENTRY);
86462306a36Sopenharmony_ci		if (!vq)
86562306a36Sopenharmony_ci			goto nla_put_failure;
86662306a36Sopenharmony_ci
86762306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_GRED_VQ_DP, q->DP))
86862306a36Sopenharmony_ci			goto nla_put_failure;
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_GRED_VQ_FLAGS, q->red_flags))
87162306a36Sopenharmony_ci			goto nla_put_failure;
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci		/* Stats */
87462306a36Sopenharmony_ci		if (nla_put_u64_64bit(skb, TCA_GRED_VQ_STAT_BYTES, q->bytesin,
87562306a36Sopenharmony_ci				      TCA_GRED_VQ_PAD))
87662306a36Sopenharmony_ci			goto nla_put_failure;
87762306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_GRED_VQ_STAT_PACKETS, q->packetsin))
87862306a36Sopenharmony_ci			goto nla_put_failure;
87962306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_GRED_VQ_STAT_BACKLOG,
88062306a36Sopenharmony_ci				gred_backlog(table, q, sch)))
88162306a36Sopenharmony_ci			goto nla_put_failure;
88262306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_GRED_VQ_STAT_PROB_DROP,
88362306a36Sopenharmony_ci				q->stats.prob_drop))
88462306a36Sopenharmony_ci			goto nla_put_failure;
88562306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_GRED_VQ_STAT_PROB_MARK,
88662306a36Sopenharmony_ci				q->stats.prob_mark))
88762306a36Sopenharmony_ci			goto nla_put_failure;
88862306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_GRED_VQ_STAT_FORCED_DROP,
88962306a36Sopenharmony_ci				q->stats.forced_drop))
89062306a36Sopenharmony_ci			goto nla_put_failure;
89162306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_GRED_VQ_STAT_FORCED_MARK,
89262306a36Sopenharmony_ci				q->stats.forced_mark))
89362306a36Sopenharmony_ci			goto nla_put_failure;
89462306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_GRED_VQ_STAT_PDROP, q->stats.pdrop))
89562306a36Sopenharmony_ci			goto nla_put_failure;
89662306a36Sopenharmony_ci
89762306a36Sopenharmony_ci		nla_nest_end(skb, vq);
89862306a36Sopenharmony_ci	}
89962306a36Sopenharmony_ci	nla_nest_end(skb, vqs);
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_ci	return nla_nest_end(skb, opts);
90262306a36Sopenharmony_ci
90362306a36Sopenharmony_cinla_put_failure:
90462306a36Sopenharmony_ci	nla_nest_cancel(skb, opts);
90562306a36Sopenharmony_ci	return -EMSGSIZE;
90662306a36Sopenharmony_ci}
90762306a36Sopenharmony_ci
90862306a36Sopenharmony_cistatic void gred_destroy(struct Qdisc *sch)
90962306a36Sopenharmony_ci{
91062306a36Sopenharmony_ci	struct gred_sched *table = qdisc_priv(sch);
91162306a36Sopenharmony_ci	int i;
91262306a36Sopenharmony_ci
91362306a36Sopenharmony_ci	for (i = 0; i < table->DPs; i++)
91462306a36Sopenharmony_ci		gred_destroy_vq(table->tab[i]);
91562306a36Sopenharmony_ci
91662306a36Sopenharmony_ci	gred_offload(sch, TC_GRED_DESTROY);
91762306a36Sopenharmony_ci	kfree(table->opt);
91862306a36Sopenharmony_ci}
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_cistatic struct Qdisc_ops gred_qdisc_ops __read_mostly = {
92162306a36Sopenharmony_ci	.id		=	"gred",
92262306a36Sopenharmony_ci	.priv_size	=	sizeof(struct gred_sched),
92362306a36Sopenharmony_ci	.enqueue	=	gred_enqueue,
92462306a36Sopenharmony_ci	.dequeue	=	gred_dequeue,
92562306a36Sopenharmony_ci	.peek		=	qdisc_peek_head,
92662306a36Sopenharmony_ci	.init		=	gred_init,
92762306a36Sopenharmony_ci	.reset		=	gred_reset,
92862306a36Sopenharmony_ci	.destroy	=	gred_destroy,
92962306a36Sopenharmony_ci	.change		=	gred_change,
93062306a36Sopenharmony_ci	.dump		=	gred_dump,
93162306a36Sopenharmony_ci	.owner		=	THIS_MODULE,
93262306a36Sopenharmony_ci};
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_cistatic int __init gred_module_init(void)
93562306a36Sopenharmony_ci{
93662306a36Sopenharmony_ci	return register_qdisc(&gred_qdisc_ops);
93762306a36Sopenharmony_ci}
93862306a36Sopenharmony_ci
93962306a36Sopenharmony_cistatic void __exit gred_module_exit(void)
94062306a36Sopenharmony_ci{
94162306a36Sopenharmony_ci	unregister_qdisc(&gred_qdisc_ops);
94262306a36Sopenharmony_ci}
94362306a36Sopenharmony_ci
94462306a36Sopenharmony_cimodule_init(gred_module_init)
94562306a36Sopenharmony_cimodule_exit(gred_module_exit)
94662306a36Sopenharmony_ci
94762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
948