1 // SPDX-License-Identifier: GPL-2.0+
2 /* Microchip Sparx5 Switch driver
3  *
4  * Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
5  */
6 
7 #include <net/pkt_cls.h>
8 #include <net/pkt_sched.h>
9 
10 #include "sparx5_tc.h"
11 #include "sparx5_main.h"
12 #include "sparx5_qos.h"
13 
14 /* tc block handling */
15 static LIST_HEAD(sparx5_block_cb_list);
16 
sparx5_tc_block_cb(enum tc_setup_type type,void * type_data,void * cb_priv,bool ingress)17 static int sparx5_tc_block_cb(enum tc_setup_type type,
18 			      void *type_data,
19 			      void *cb_priv, bool ingress)
20 {
21 	struct net_device *ndev = cb_priv;
22 
23 	switch (type) {
24 	case TC_SETUP_CLSMATCHALL:
25 		return sparx5_tc_matchall(ndev, type_data, ingress);
26 	case TC_SETUP_CLSFLOWER:
27 		return sparx5_tc_flower(ndev, type_data, ingress);
28 	default:
29 		return -EOPNOTSUPP;
30 	}
31 }
32 
sparx5_tc_block_cb_ingress(enum tc_setup_type type,void * type_data,void * cb_priv)33 static int sparx5_tc_block_cb_ingress(enum tc_setup_type type,
34 				      void *type_data,
35 				      void *cb_priv)
36 {
37 	return sparx5_tc_block_cb(type, type_data, cb_priv, true);
38 }
39 
sparx5_tc_block_cb_egress(enum tc_setup_type type,void * type_data,void * cb_priv)40 static int sparx5_tc_block_cb_egress(enum tc_setup_type type,
41 				     void *type_data,
42 				     void *cb_priv)
43 {
44 	return sparx5_tc_block_cb(type, type_data, cb_priv, false);
45 }
46 
sparx5_tc_setup_block(struct net_device * ndev,struct flow_block_offload * fbo)47 static int sparx5_tc_setup_block(struct net_device *ndev,
48 				 struct flow_block_offload *fbo)
49 {
50 	flow_setup_cb_t *cb;
51 
52 	if (fbo->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
53 		cb = sparx5_tc_block_cb_ingress;
54 	else if (fbo->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS)
55 		cb = sparx5_tc_block_cb_egress;
56 	else
57 		return -EOPNOTSUPP;
58 
59 	return flow_block_cb_setup_simple(fbo, &sparx5_block_cb_list,
60 					  cb, ndev, ndev, false);
61 }
62 
sparx5_tc_get_layer_and_idx(u32 parent,u32 portno,u32 * layer,u32 * idx)63 static void sparx5_tc_get_layer_and_idx(u32 parent, u32 portno, u32 *layer,
64 					u32 *idx)
65 {
66 	if (parent == TC_H_ROOT) {
67 		*layer = 2;
68 		*idx = portno;
69 	} else {
70 		u32 queue = TC_H_MIN(parent) - 1;
71 		*layer = 0;
72 		*idx = SPX5_HSCH_L0_GET_IDX(portno, queue);
73 	}
74 }
75 
sparx5_tc_setup_qdisc_mqprio(struct net_device * ndev,struct tc_mqprio_qopt_offload * m)76 static int sparx5_tc_setup_qdisc_mqprio(struct net_device *ndev,
77 					struct tc_mqprio_qopt_offload *m)
78 {
79 	m->qopt.hw = TC_MQPRIO_HW_OFFLOAD_TCS;
80 
81 	if (m->qopt.num_tc == 0)
82 		return sparx5_tc_mqprio_del(ndev);
83 	else
84 		return sparx5_tc_mqprio_add(ndev, m->qopt.num_tc);
85 }
86 
sparx5_tc_setup_qdisc_tbf(struct net_device * ndev,struct tc_tbf_qopt_offload * qopt)87 static int sparx5_tc_setup_qdisc_tbf(struct net_device *ndev,
88 				     struct tc_tbf_qopt_offload *qopt)
89 {
90 	struct sparx5_port *port = netdev_priv(ndev);
91 	u32 layer, se_idx;
92 
93 	sparx5_tc_get_layer_and_idx(qopt->parent, port->portno, &layer,
94 				    &se_idx);
95 
96 	switch (qopt->command) {
97 	case TC_TBF_REPLACE:
98 		return sparx5_tc_tbf_add(port, &qopt->replace_params, layer,
99 					 se_idx);
100 	case TC_TBF_DESTROY:
101 		return sparx5_tc_tbf_del(port, layer, se_idx);
102 	case TC_TBF_STATS:
103 		return -EOPNOTSUPP;
104 	default:
105 		return -EOPNOTSUPP;
106 	}
107 
108 	return -EOPNOTSUPP;
109 }
110 
sparx5_tc_setup_qdisc_ets(struct net_device * ndev,struct tc_ets_qopt_offload * qopt)111 static int sparx5_tc_setup_qdisc_ets(struct net_device *ndev,
112 				     struct tc_ets_qopt_offload *qopt)
113 {
114 	struct tc_ets_qopt_offload_replace_params *params =
115 		&qopt->replace_params;
116 	struct sparx5_port *port = netdev_priv(ndev);
117 	int i;
118 
119 	/* Only allow ets on ports  */
120 	if (qopt->parent != TC_H_ROOT)
121 		return -EOPNOTSUPP;
122 
123 	switch (qopt->command) {
124 	case TC_ETS_REPLACE:
125 
126 		/* We support eight priorities */
127 		if (params->bands != SPX5_PRIOS)
128 			return -EOPNOTSUPP;
129 
130 		/* Sanity checks */
131 		for (i = 0; i < SPX5_PRIOS; ++i) {
132 			/* Priority map is *always* reverse e.g: 7 6 5 .. 0 */
133 			if (params->priomap[i] != (7 - i))
134 				return -EOPNOTSUPP;
135 			/* Throw an error if we receive zero weights by tc */
136 			if (params->quanta[i] && params->weights[i] == 0) {
137 				pr_err("Invalid ets configuration; band %d has weight zero",
138 				       i);
139 				return -EINVAL;
140 			}
141 		}
142 
143 		return sparx5_tc_ets_add(port, params);
144 	case TC_ETS_DESTROY:
145 
146 		return sparx5_tc_ets_del(port);
147 	case TC_ETS_GRAFT:
148 		return -EOPNOTSUPP;
149 
150 	default:
151 		return -EOPNOTSUPP;
152 	}
153 
154 	return -EOPNOTSUPP;
155 }
156 
sparx5_port_setup_tc(struct net_device * ndev,enum tc_setup_type type,void * type_data)157 int sparx5_port_setup_tc(struct net_device *ndev, enum tc_setup_type type,
158 			 void *type_data)
159 {
160 	switch (type) {
161 	case TC_SETUP_BLOCK:
162 		return sparx5_tc_setup_block(ndev, type_data);
163 	case TC_SETUP_QDISC_MQPRIO:
164 		return sparx5_tc_setup_qdisc_mqprio(ndev, type_data);
165 	case TC_SETUP_QDISC_TBF:
166 		return sparx5_tc_setup_qdisc_tbf(ndev, type_data);
167 	case TC_SETUP_QDISC_ETS:
168 		return sparx5_tc_setup_qdisc_ets(ndev, type_data);
169 	default:
170 		return -EOPNOTSUPP;
171 	}
172 
173 	return 0;
174 }
175