]>
Commit | Line | Data |
---|---|---|
96f17e07 NF |
1 | /* |
2 | * drivers/net/ethernet/mellanox/mlxsw/spectrum_qdisc.c | |
3 | * Copyright (c) 2017 Mellanox Technologies. All rights reserved. | |
4 | * Copyright (c) 2017 Nogah Frankel <nogahf@mellanox.com> | |
5 | * | |
6 | * Redistribution and use in source and binary forms, with or without | |
7 | * modification, are permitted provided that the following conditions are met: | |
8 | * | |
9 | * 1. Redistributions of source code must retain the above copyright | |
10 | * notice, this list of conditions and the following disclaimer. | |
11 | * 2. Redistributions in binary form must reproduce the above copyright | |
12 | * notice, this list of conditions and the following disclaimer in the | |
13 | * documentation and/or other materials provided with the distribution. | |
14 | * 3. Neither the names of the copyright holders nor the names of its | |
15 | * contributors may be used to endorse or promote products derived from | |
16 | * this software without specific prior written permission. | |
17 | * | |
18 | * Alternatively, this software may be distributed under the terms of the | |
19 | * GNU General Public License ("GPL") version 2 as published by the Free | |
20 | * Software Foundation. | |
21 | * | |
22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
23 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
26 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
27 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
28 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
29 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
31 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
32 | * POSSIBILITY OF SUCH DAMAGE. | |
33 | */ | |
34 | ||
35 | #include <linux/kernel.h> | |
36 | #include <linux/errno.h> | |
37 | #include <linux/netdevice.h> | |
38 | #include <net/pkt_cls.h> | |
861fb829 | 39 | #include <net/red.h> |
96f17e07 NF |
40 | |
41 | #include "spectrum.h" | |
42 | #include "reg.h" | |
43 | ||
44 | static int | |
45 | mlxsw_sp_tclass_congestion_enable(struct mlxsw_sp_port *mlxsw_sp_port, | |
46 | int tclass_num, u32 min, u32 max, | |
47 | u32 probability, bool is_ecn) | |
48 | { | |
db84924c JP |
49 | char cwtpm_cmd[MLXSW_REG_CWTPM_LEN]; |
50 | char cwtp_cmd[MLXSW_REG_CWTP_LEN]; | |
96f17e07 NF |
51 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; |
52 | int err; | |
53 | ||
54 | mlxsw_reg_cwtp_pack(cwtp_cmd, mlxsw_sp_port->local_port, tclass_num); | |
55 | mlxsw_reg_cwtp_profile_pack(cwtp_cmd, MLXSW_REG_CWTP_DEFAULT_PROFILE, | |
56 | roundup(min, MLXSW_REG_CWTP_MIN_VALUE), | |
57 | roundup(max, MLXSW_REG_CWTP_MIN_VALUE), | |
58 | probability); | |
59 | ||
60 | err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(cwtp), cwtp_cmd); | |
61 | if (err) | |
62 | return err; | |
63 | ||
db84924c | 64 | mlxsw_reg_cwtpm_pack(cwtpm_cmd, mlxsw_sp_port->local_port, tclass_num, |
96f17e07 NF |
65 | MLXSW_REG_CWTP_DEFAULT_PROFILE, true, is_ecn); |
66 | ||
db84924c | 67 | return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(cwtpm), cwtpm_cmd); |
96f17e07 NF |
68 | } |
69 | ||
70 | static int | |
71 | mlxsw_sp_tclass_congestion_disable(struct mlxsw_sp_port *mlxsw_sp_port, | |
72 | int tclass_num) | |
73 | { | |
74 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; | |
75 | char cwtpm_cmd[MLXSW_REG_CWTPM_LEN]; | |
76 | ||
77 | mlxsw_reg_cwtpm_pack(cwtpm_cmd, mlxsw_sp_port->local_port, tclass_num, | |
78 | MLXSW_REG_CWTPM_RESET_PROFILE, false, false); | |
79 | return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(cwtpm), cwtpm_cmd); | |
80 | } | |
81 | ||
861fb829 NF |
82 | static void |
83 | mlxsw_sp_setup_tc_qdisc_clean_stats(struct mlxsw_sp_port *mlxsw_sp_port, | |
84 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
85 | int tclass_num) | |
86 | { | |
87 | struct red_stats *xstats_base = &mlxsw_sp_qdisc->xstats_base; | |
88 | struct mlxsw_sp_port_xstats *xstats; | |
3670756f | 89 | struct rtnl_link_stats64 *stats; |
861fb829 NF |
90 | |
91 | xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; | |
3670756f NF |
92 | stats = &mlxsw_sp_port->periodic_hw_stats.stats; |
93 | ||
94 | mlxsw_sp_qdisc->tx_packets = stats->tx_packets; | |
95 | mlxsw_sp_qdisc->tx_bytes = stats->tx_bytes; | |
861fb829 NF |
96 | |
97 | switch (mlxsw_sp_qdisc->type) { | |
98 | case MLXSW_SP_QDISC_RED: | |
99 | xstats_base->prob_mark = xstats->ecn; | |
100 | xstats_base->prob_drop = xstats->wred_drop[tclass_num]; | |
101 | xstats_base->pdrop = xstats->tail_drop[tclass_num]; | |
3670756f NF |
102 | |
103 | mlxsw_sp_qdisc->overlimits = xstats_base->prob_drop + | |
104 | xstats_base->prob_mark; | |
105 | mlxsw_sp_qdisc->drops = xstats_base->prob_drop + | |
106 | xstats_base->pdrop; | |
861fb829 NF |
107 | break; |
108 | default: | |
109 | break; | |
110 | } | |
111 | } | |
112 | ||
96f17e07 NF |
113 | static int |
114 | mlxsw_sp_qdisc_red_destroy(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle, | |
115 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
116 | int tclass_num) | |
117 | { | |
118 | int err; | |
119 | ||
120 | if (mlxsw_sp_qdisc->handle != handle) | |
121 | return 0; | |
122 | ||
123 | err = mlxsw_sp_tclass_congestion_disable(mlxsw_sp_port, tclass_num); | |
124 | mlxsw_sp_qdisc->handle = TC_H_UNSPEC; | |
125 | mlxsw_sp_qdisc->type = MLXSW_SP_QDISC_NO_QDISC; | |
126 | ||
127 | return err; | |
128 | } | |
129 | ||
130 | static int | |
131 | mlxsw_sp_qdisc_red_replace(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle, | |
132 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
133 | int tclass_num, | |
134 | struct tc_red_qopt_offload_params *p) | |
135 | { | |
136 | struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; | |
137 | u32 min, max; | |
138 | u64 prob; | |
139 | int err = 0; | |
140 | ||
141 | if (p->min > p->max) { | |
142 | dev_err(mlxsw_sp->bus_info->dev, | |
143 | "spectrum: RED: min %u is bigger then max %u\n", p->min, | |
144 | p->max); | |
145 | goto err_bad_param; | |
146 | } | |
147 | if (p->max > MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_BUFFER_SIZE)) { | |
148 | dev_err(mlxsw_sp->bus_info->dev, | |
149 | "spectrum: RED: max value %u is too big\n", p->max); | |
150 | goto err_bad_param; | |
151 | } | |
152 | if (p->min == 0 || p->max == 0) { | |
153 | dev_err(mlxsw_sp->bus_info->dev, | |
154 | "spectrum: RED: 0 value is illegal for min and max\n"); | |
155 | goto err_bad_param; | |
156 | } | |
157 | ||
158 | /* calculate probability in percentage */ | |
159 | prob = p->probability; | |
160 | prob *= 100; | |
161 | prob = DIV_ROUND_UP(prob, 1 << 16); | |
162 | prob = DIV_ROUND_UP(prob, 1 << 16); | |
163 | min = mlxsw_sp_bytes_cells(mlxsw_sp, p->min); | |
164 | max = mlxsw_sp_bytes_cells(mlxsw_sp, p->max); | |
165 | err = mlxsw_sp_tclass_congestion_enable(mlxsw_sp_port, tclass_num, min, | |
166 | max, prob, p->is_ecn); | |
167 | if (err) | |
168 | goto err_config; | |
169 | ||
170 | mlxsw_sp_qdisc->type = MLXSW_SP_QDISC_RED; | |
861fb829 NF |
171 | if (mlxsw_sp_qdisc->handle != handle) |
172 | mlxsw_sp_setup_tc_qdisc_clean_stats(mlxsw_sp_port, | |
173 | mlxsw_sp_qdisc, | |
174 | tclass_num); | |
175 | ||
96f17e07 NF |
176 | mlxsw_sp_qdisc->handle = handle; |
177 | return 0; | |
178 | ||
179 | err_bad_param: | |
180 | err = -EINVAL; | |
181 | err_config: | |
182 | mlxsw_sp_qdisc_red_destroy(mlxsw_sp_port, mlxsw_sp_qdisc->handle, | |
183 | mlxsw_sp_qdisc, tclass_num); | |
184 | return err; | |
185 | } | |
186 | ||
861fb829 NF |
187 | static int |
188 | mlxsw_sp_qdisc_get_red_xstats(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle, | |
189 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
190 | int tclass_num, struct red_stats *res) | |
191 | { | |
192 | struct red_stats *xstats_base = &mlxsw_sp_qdisc->xstats_base; | |
193 | struct mlxsw_sp_port_xstats *xstats; | |
194 | ||
195 | if (mlxsw_sp_qdisc->handle != handle || | |
196 | mlxsw_sp_qdisc->type != MLXSW_SP_QDISC_RED) | |
197 | return -EOPNOTSUPP; | |
198 | ||
199 | xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; | |
200 | ||
201 | res->prob_drop = xstats->wred_drop[tclass_num] - xstats_base->prob_drop; | |
202 | res->prob_mark = xstats->ecn - xstats_base->prob_mark; | |
203 | res->pdrop = xstats->tail_drop[tclass_num] - xstats_base->pdrop; | |
204 | return 0; | |
205 | } | |
206 | ||
3670756f NF |
207 | static int |
208 | mlxsw_sp_qdisc_get_red_stats(struct mlxsw_sp_port *mlxsw_sp_port, u32 handle, | |
209 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc, | |
210 | int tclass_num, | |
211 | struct tc_red_qopt_offload_stats *res) | |
212 | { | |
213 | u64 tx_bytes, tx_packets, overlimits, drops; | |
214 | struct mlxsw_sp_port_xstats *xstats; | |
215 | struct rtnl_link_stats64 *stats; | |
216 | ||
217 | if (mlxsw_sp_qdisc->handle != handle || | |
218 | mlxsw_sp_qdisc->type != MLXSW_SP_QDISC_RED) | |
219 | return -EOPNOTSUPP; | |
220 | ||
221 | xstats = &mlxsw_sp_port->periodic_hw_stats.xstats; | |
222 | stats = &mlxsw_sp_port->periodic_hw_stats.stats; | |
223 | ||
224 | tx_bytes = stats->tx_bytes - mlxsw_sp_qdisc->tx_bytes; | |
225 | tx_packets = stats->tx_packets - mlxsw_sp_qdisc->tx_packets; | |
226 | overlimits = xstats->wred_drop[tclass_num] + xstats->ecn - | |
227 | mlxsw_sp_qdisc->overlimits; | |
228 | drops = xstats->wred_drop[tclass_num] + xstats->tail_drop[tclass_num] - | |
229 | mlxsw_sp_qdisc->drops; | |
230 | ||
231 | _bstats_update(res->bstats, tx_bytes, tx_packets); | |
232 | res->qstats->overlimits += overlimits; | |
233 | res->qstats->drops += drops; | |
234 | res->qstats->backlog += mlxsw_sp_cells_bytes(mlxsw_sp_port->mlxsw_sp, | |
235 | xstats->backlog[tclass_num]); | |
236 | ||
237 | mlxsw_sp_qdisc->drops += drops; | |
238 | mlxsw_sp_qdisc->overlimits += overlimits; | |
239 | mlxsw_sp_qdisc->tx_bytes += tx_bytes; | |
240 | mlxsw_sp_qdisc->tx_packets += tx_packets; | |
241 | return 0; | |
242 | } | |
243 | ||
96f17e07 NF |
244 | #define MLXSW_SP_PORT_DEFAULT_TCLASS 0 |
245 | ||
246 | int mlxsw_sp_setup_tc_red(struct mlxsw_sp_port *mlxsw_sp_port, | |
247 | struct tc_red_qopt_offload *p) | |
248 | { | |
249 | struct mlxsw_sp_qdisc *mlxsw_sp_qdisc; | |
250 | int tclass_num; | |
251 | ||
252 | if (p->parent != TC_H_ROOT) | |
253 | return -EOPNOTSUPP; | |
254 | ||
255 | mlxsw_sp_qdisc = &mlxsw_sp_port->root_qdisc; | |
256 | tclass_num = MLXSW_SP_PORT_DEFAULT_TCLASS; | |
257 | ||
258 | switch (p->command) { | |
259 | case TC_RED_REPLACE: | |
260 | return mlxsw_sp_qdisc_red_replace(mlxsw_sp_port, p->handle, | |
261 | mlxsw_sp_qdisc, tclass_num, | |
262 | &p->set); | |
263 | case TC_RED_DESTROY: | |
264 | return mlxsw_sp_qdisc_red_destroy(mlxsw_sp_port, p->handle, | |
265 | mlxsw_sp_qdisc, tclass_num); | |
861fb829 NF |
266 | case TC_RED_XSTATS: |
267 | return mlxsw_sp_qdisc_get_red_xstats(mlxsw_sp_port, p->handle, | |
268 | mlxsw_sp_qdisc, tclass_num, | |
269 | p->xstats); | |
3670756f NF |
270 | case TC_RED_STATS: |
271 | return mlxsw_sp_qdisc_get_red_stats(mlxsw_sp_port, p->handle, | |
272 | mlxsw_sp_qdisc, tclass_num, | |
273 | &p->stats); | |
96f17e07 NF |
274 | default: |
275 | return -EOPNOTSUPP; | |
276 | } | |
277 | } |