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 */
15static LIST_HEAD(sparx5_block_cb_list);
16
17static 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
33static 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
40static 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
47static 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
63static 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
76static 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
87static 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
111static 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
157int 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