1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | /* Microsemi Ocelot Switch driver |
3 | * |
4 | * Copyright (c) 2019 Microsemi Corporation |
5 | */ |
6 | |
7 | #include <soc/mscc/ocelot.h> |
8 | #include "ocelot_police.h" |
9 | |
10 | /* Types for ANA:POL[0-192]:POL_MODE_CFG.FRM_MODE */ |
11 | #define POL_MODE_LINERATE 0 /* Incl IPG. Unit: 33 1/3 kbps, 4096 bytes */ |
12 | #define POL_MODE_DATARATE 1 /* Excl IPG. Unit: 33 1/3 kbps, 4096 bytes */ |
13 | #define POL_MODE_FRMRATE_HI 2 /* Unit: 33 1/3 fps, 32.8 frames */ |
14 | #define POL_MODE_FRMRATE_LO 3 /* Unit: 1/3 fps, 0.3 frames */ |
15 | |
16 | /* Policer indexes */ |
17 | #define POL_IX_PORT 0 /* 0-11 : Port policers */ |
18 | #define POL_IX_QUEUE 32 /* 32-127 : Queue policers */ |
19 | |
20 | /* Default policer order */ |
21 | #define POL_ORDER 0x1d3 /* Ocelot policer order: Serial (QoS -> Port -> VCAP) */ |
22 | |
23 | int qos_policer_conf_set(struct ocelot *ocelot, u32 pol_ix, |
24 | struct qos_policer_conf *conf) |
25 | { |
26 | u32 cf = 0, cir_ena = 0, frm_mode = POL_MODE_LINERATE; |
27 | u32 cir = 0, cbs = 0, pir = 0, pbs = 0; |
28 | bool cir_discard = 0, pir_discard = 0; |
29 | u32 pbs_max = 0, cbs_max = 0; |
30 | u8 ipg = 20; |
31 | u32 value; |
32 | |
33 | pir = conf->pir; |
34 | pbs = conf->pbs; |
35 | |
36 | switch (conf->mode) { |
37 | case MSCC_QOS_RATE_MODE_LINE: |
38 | case MSCC_QOS_RATE_MODE_DATA: |
39 | if (conf->mode == MSCC_QOS_RATE_MODE_LINE) { |
40 | frm_mode = POL_MODE_LINERATE; |
41 | ipg = min_t(u8, GENMASK(4, 0), conf->ipg); |
42 | } else { |
43 | frm_mode = POL_MODE_DATARATE; |
44 | } |
45 | if (conf->dlb) { |
46 | cir_ena = 1; |
47 | cir = conf->cir; |
48 | cbs = conf->cbs; |
49 | if (cir == 0 && cbs == 0) { |
50 | /* Discard cir frames */ |
51 | cir_discard = 1; |
52 | } else { |
53 | cir = DIV_ROUND_UP(cir, 100); |
54 | cir *= 3; /* 33 1/3 kbps */ |
55 | cbs = DIV_ROUND_UP(cbs, 4096); |
56 | cbs = (cbs ? cbs : 1); /* No zero burst size */ |
57 | cbs_max = 60; /* Limit burst size */ |
58 | cf = conf->cf; |
59 | if (cf) |
60 | pir += conf->cir; |
61 | } |
62 | } |
63 | if (pir == 0 && pbs == 0) { |
64 | /* Discard PIR frames */ |
65 | pir_discard = 1; |
66 | } else { |
67 | pir = DIV_ROUND_UP(pir, 100); |
68 | pir *= 3; /* 33 1/3 kbps */ |
69 | pbs = DIV_ROUND_UP(pbs, 4096); |
70 | pbs = (pbs ? pbs : 1); /* No zero burst size */ |
71 | pbs_max = 60; /* Limit burst size */ |
72 | } |
73 | break; |
74 | case MSCC_QOS_RATE_MODE_FRAME: |
75 | if (pir >= 100) { |
76 | frm_mode = POL_MODE_FRMRATE_HI; |
77 | pir = DIV_ROUND_UP(pir, 100); |
78 | pir *= 3; /* 33 1/3 fps */ |
79 | pbs = (pbs * 10) / 328; /* 32.8 frames */ |
80 | pbs = (pbs ? pbs : 1); /* No zero burst size */ |
81 | pbs_max = GENMASK(6, 0); /* Limit burst size */ |
82 | } else { |
83 | frm_mode = POL_MODE_FRMRATE_LO; |
84 | if (pir == 0 && pbs == 0) { |
85 | /* Discard all frames */ |
86 | pir_discard = 1; |
87 | cir_discard = 1; |
88 | } else { |
89 | pir *= 3; /* 1/3 fps */ |
90 | pbs = (pbs * 10) / 3; /* 0.3 frames */ |
91 | pbs = (pbs ? pbs : 1); /* No zero burst size */ |
92 | pbs_max = 61; /* Limit burst size */ |
93 | } |
94 | } |
95 | break; |
96 | default: /* MSCC_QOS_RATE_MODE_DISABLED */ |
97 | /* Disable policer using maximum rate and zero burst */ |
98 | pir = GENMASK(15, 0); |
99 | pbs = 0; |
100 | break; |
101 | } |
102 | |
103 | /* Check limits */ |
104 | if (pir > GENMASK(15, 0)) { |
105 | dev_err(ocelot->dev, |
106 | "Invalid pir for policer %u: %u (max %lu)\n" , |
107 | pol_ix, pir, GENMASK(15, 0)); |
108 | return -EINVAL; |
109 | } |
110 | |
111 | if (cir > GENMASK(15, 0)) { |
112 | dev_err(ocelot->dev, |
113 | "Invalid cir for policer %u: %u (max %lu)\n" , |
114 | pol_ix, cir, GENMASK(15, 0)); |
115 | return -EINVAL; |
116 | } |
117 | |
118 | if (pbs > pbs_max) { |
119 | dev_err(ocelot->dev, |
120 | "Invalid pbs for policer %u: %u (max %u)\n" , |
121 | pol_ix, pbs, pbs_max); |
122 | return -EINVAL; |
123 | } |
124 | |
125 | if (cbs > cbs_max) { |
126 | dev_err(ocelot->dev, |
127 | "Invalid cbs for policer %u: %u (max %u)\n" , |
128 | pol_ix, cbs, cbs_max); |
129 | return -EINVAL; |
130 | } |
131 | |
132 | value = (ANA_POL_MODE_CFG_IPG_SIZE(ipg) | |
133 | ANA_POL_MODE_CFG_FRM_MODE(frm_mode) | |
134 | (cf ? ANA_POL_MODE_CFG_DLB_COUPLED : 0) | |
135 | (cir_ena ? ANA_POL_MODE_CFG_CIR_ENA : 0) | |
136 | ANA_POL_MODE_CFG_OVERSHOOT_ENA); |
137 | |
138 | ocelot_write_gix(ocelot, value, ANA_POL_MODE_CFG, pol_ix); |
139 | |
140 | ocelot_write_gix(ocelot, |
141 | ANA_POL_PIR_CFG_PIR_RATE(pir) | |
142 | ANA_POL_PIR_CFG_PIR_BURST(pbs), |
143 | ANA_POL_PIR_CFG, pol_ix); |
144 | |
145 | ocelot_write_gix(ocelot, |
146 | (pir_discard ? GENMASK(22, 0) : 0), |
147 | ANA_POL_PIR_STATE, pol_ix); |
148 | |
149 | ocelot_write_gix(ocelot, |
150 | ANA_POL_CIR_CFG_CIR_RATE(cir) | |
151 | ANA_POL_CIR_CFG_CIR_BURST(cbs), |
152 | ANA_POL_CIR_CFG, pol_ix); |
153 | |
154 | ocelot_write_gix(ocelot, |
155 | (cir_discard ? GENMASK(22, 0) : 0), |
156 | ANA_POL_CIR_STATE, pol_ix); |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | int ocelot_policer_validate(const struct flow_action *action, |
162 | const struct flow_action_entry *a, |
163 | struct netlink_ext_ack *extack) |
164 | { |
165 | if (a->police.exceed.act_id != FLOW_ACTION_DROP) { |
166 | NL_SET_ERR_MSG_MOD(extack, |
167 | "Offload not supported when exceed action is not drop" ); |
168 | return -EOPNOTSUPP; |
169 | } |
170 | |
171 | if (a->police.notexceed.act_id != FLOW_ACTION_PIPE && |
172 | a->police.notexceed.act_id != FLOW_ACTION_ACCEPT) { |
173 | NL_SET_ERR_MSG_MOD(extack, |
174 | "Offload not supported when conform action is not pipe or ok" ); |
175 | return -EOPNOTSUPP; |
176 | } |
177 | |
178 | if (a->police.notexceed.act_id == FLOW_ACTION_ACCEPT && |
179 | !flow_action_is_last_entry(action, entry: a)) { |
180 | NL_SET_ERR_MSG_MOD(extack, |
181 | "Offload not supported when conform action is ok, but police action is not last" ); |
182 | return -EOPNOTSUPP; |
183 | } |
184 | |
185 | if (a->police.peakrate_bytes_ps || |
186 | a->police.avrate || a->police.overhead) { |
187 | NL_SET_ERR_MSG_MOD(extack, |
188 | "Offload not supported when peakrate/avrate/overhead is configured" ); |
189 | return -EOPNOTSUPP; |
190 | } |
191 | |
192 | if (a->police.rate_pkt_ps) { |
193 | NL_SET_ERR_MSG_MOD(extack, |
194 | "Offload does not support packets per second" ); |
195 | return -EOPNOTSUPP; |
196 | } |
197 | |
198 | return 0; |
199 | } |
200 | EXPORT_SYMBOL(ocelot_policer_validate); |
201 | |
202 | int ocelot_port_policer_add(struct ocelot *ocelot, int port, |
203 | struct ocelot_policer *pol) |
204 | { |
205 | struct qos_policer_conf pp = { 0 }; |
206 | int err; |
207 | |
208 | if (!pol) |
209 | return -EINVAL; |
210 | |
211 | pp.mode = MSCC_QOS_RATE_MODE_DATA; |
212 | pp.pir = pol->rate; |
213 | pp.pbs = pol->burst; |
214 | |
215 | dev_dbg(ocelot->dev, "%s: port %u pir %u kbps, pbs %u bytes\n" , |
216 | __func__, port, pp.pir, pp.pbs); |
217 | |
218 | err = qos_policer_conf_set(ocelot, POL_IX_PORT + port, conf: &pp); |
219 | if (err) |
220 | return err; |
221 | |
222 | ocelot_rmw_gix(ocelot, |
223 | ANA_PORT_POL_CFG_PORT_POL_ENA | |
224 | ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER), |
225 | ANA_PORT_POL_CFG_PORT_POL_ENA | |
226 | ANA_PORT_POL_CFG_POL_ORDER_M, |
227 | ANA_PORT_POL_CFG, port); |
228 | |
229 | return 0; |
230 | } |
231 | EXPORT_SYMBOL(ocelot_port_policer_add); |
232 | |
233 | int ocelot_port_policer_del(struct ocelot *ocelot, int port) |
234 | { |
235 | struct qos_policer_conf pp = { 0 }; |
236 | int err; |
237 | |
238 | dev_dbg(ocelot->dev, "%s: port %u\n" , __func__, port); |
239 | |
240 | pp.mode = MSCC_QOS_RATE_MODE_DISABLED; |
241 | |
242 | err = qos_policer_conf_set(ocelot, POL_IX_PORT + port, conf: &pp); |
243 | if (err) |
244 | return err; |
245 | |
246 | ocelot_rmw_gix(ocelot, |
247 | ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER), |
248 | ANA_PORT_POL_CFG_PORT_POL_ENA | |
249 | ANA_PORT_POL_CFG_POL_ORDER_M, |
250 | ANA_PORT_POL_CFG, port); |
251 | |
252 | return 0; |
253 | } |
254 | EXPORT_SYMBOL(ocelot_port_policer_del); |
255 | |