]>
Commit | Line | Data |
---|---|---|
7318166c FF |
1 | /* |
2 | * Broadcom Starfighter 2 DSA switch CFP support | |
3 | * | |
4 | * Copyright (C) 2016, Broadcom | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | */ | |
11 | ||
12 | #include <linux/list.h> | |
7318166c FF |
13 | #include <linux/ethtool.h> |
14 | #include <linux/if_ether.h> | |
15 | #include <linux/in.h> | |
c6e970a0 AL |
16 | #include <linux/netdevice.h> |
17 | #include <net/dsa.h> | |
7318166c FF |
18 | #include <linux/bitmap.h> |
19 | ||
20 | #include "bcm_sf2.h" | |
21 | #include "bcm_sf2_regs.h" | |
22 | ||
23 | struct cfp_udf_layout { | |
24 | u8 slices[UDF_NUM_SLICES]; | |
25 | u32 mask_value; | |
26 | ||
27 | }; | |
28 | ||
29 | /* UDF slices layout for a TCPv4/UDPv4 specification */ | |
30 | static const struct cfp_udf_layout udf_tcpip4_layout = { | |
31 | .slices = { | |
32 | /* End of L2, byte offset 12, src IP[0:15] */ | |
33 | CFG_UDF_EOL2 | 6, | |
34 | /* End of L2, byte offset 14, src IP[16:31] */ | |
35 | CFG_UDF_EOL2 | 7, | |
36 | /* End of L2, byte offset 16, dst IP[0:15] */ | |
37 | CFG_UDF_EOL2 | 8, | |
38 | /* End of L2, byte offset 18, dst IP[16:31] */ | |
39 | CFG_UDF_EOL2 | 9, | |
40 | /* End of L3, byte offset 0, src port */ | |
41 | CFG_UDF_EOL3 | 0, | |
42 | /* End of L3, byte offset 2, dst port */ | |
43 | CFG_UDF_EOL3 | 1, | |
44 | 0, 0, 0 | |
45 | }, | |
46 | .mask_value = L3_FRAMING_MASK | IPPROTO_MASK | IP_FRAG, | |
47 | }; | |
48 | ||
49 | static inline unsigned int bcm_sf2_get_num_udf_slices(const u8 *layout) | |
50 | { | |
51 | unsigned int i, count = 0; | |
52 | ||
53 | for (i = 0; i < UDF_NUM_SLICES; i++) { | |
54 | if (layout[i] != 0) | |
55 | count++; | |
56 | } | |
57 | ||
58 | return count; | |
59 | } | |
60 | ||
61 | static void bcm_sf2_cfp_udf_set(struct bcm_sf2_priv *priv, | |
62 | unsigned int slice_num, | |
63 | const u8 *layout) | |
64 | { | |
65 | u32 offset = CORE_UDF_0_A_0_8_PORT_0 + slice_num * UDF_SLICE_OFFSET; | |
66 | unsigned int i; | |
67 | ||
68 | for (i = 0; i < UDF_NUM_SLICES; i++) | |
69 | core_writel(priv, layout[i], offset + i * 4); | |
70 | } | |
71 | ||
72 | static int bcm_sf2_cfp_op(struct bcm_sf2_priv *priv, unsigned int op) | |
73 | { | |
74 | unsigned int timeout = 1000; | |
75 | u32 reg; | |
76 | ||
77 | reg = core_readl(priv, CORE_CFP_ACC); | |
78 | reg &= ~(OP_SEL_MASK | RAM_SEL_MASK); | |
79 | reg |= OP_STR_DONE | op; | |
80 | core_writel(priv, reg, CORE_CFP_ACC); | |
81 | ||
82 | do { | |
83 | reg = core_readl(priv, CORE_CFP_ACC); | |
84 | if (!(reg & OP_STR_DONE)) | |
85 | break; | |
86 | ||
87 | cpu_relax(); | |
88 | } while (timeout--); | |
89 | ||
90 | if (!timeout) | |
91 | return -ETIMEDOUT; | |
92 | ||
93 | return 0; | |
94 | } | |
95 | ||
96 | static inline void bcm_sf2_cfp_rule_addr_set(struct bcm_sf2_priv *priv, | |
97 | unsigned int addr) | |
98 | { | |
99 | u32 reg; | |
100 | ||
df191632 | 101 | WARN_ON(addr >= priv->num_cfp_rules); |
7318166c FF |
102 | |
103 | reg = core_readl(priv, CORE_CFP_ACC); | |
104 | reg &= ~(XCESS_ADDR_MASK << XCESS_ADDR_SHIFT); | |
105 | reg |= addr << XCESS_ADDR_SHIFT; | |
106 | core_writel(priv, reg, CORE_CFP_ACC); | |
107 | } | |
108 | ||
109 | static inline unsigned int bcm_sf2_cfp_rule_size(struct bcm_sf2_priv *priv) | |
110 | { | |
111 | /* Entry #0 is reserved */ | |
df191632 | 112 | return priv->num_cfp_rules - 1; |
7318166c FF |
113 | } |
114 | ||
115 | static int bcm_sf2_cfp_rule_set(struct dsa_switch *ds, int port, | |
116 | struct ethtool_rx_flow_spec *fs) | |
117 | { | |
118 | struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); | |
119 | struct ethtool_tcpip4_spec *v4_spec; | |
120 | const struct cfp_udf_layout *layout; | |
121 | unsigned int slice_num, rule_index; | |
122 | unsigned int queue_num, port_num; | |
123 | u8 ip_proto, ip_frag; | |
124 | u8 num_udf; | |
125 | u32 reg; | |
126 | int ret; | |
127 | ||
128 | /* Check for unsupported extensions */ | |
129 | if ((fs->flow_type & FLOW_EXT) && | |
130 | (fs->m_ext.vlan_etype || fs->m_ext.data[1])) | |
131 | return -EINVAL; | |
132 | ||
133 | if (fs->location != RX_CLS_LOC_ANY && | |
134 | test_bit(fs->location, priv->cfp.used)) | |
135 | return -EBUSY; | |
136 | ||
137 | if (fs->location != RX_CLS_LOC_ANY && | |
138 | fs->location > bcm_sf2_cfp_rule_size(priv)) | |
139 | return -EINVAL; | |
140 | ||
141 | ip_frag = be32_to_cpu(fs->m_ext.data[0]); | |
142 | ||
143 | /* We do not support discarding packets, check that the | |
144 | * destination port is enabled and that we are within the | |
145 | * number of ports supported by the switch | |
146 | */ | |
152b6fd6 | 147 | port_num = fs->ring_cookie / SF2_NUM_EGRESS_QUEUES; |
7318166c FF |
148 | |
149 | if (fs->ring_cookie == RX_CLS_FLOW_DISC || | |
150 | !(BIT(port_num) & ds->enabled_port_mask) || | |
151 | port_num >= priv->hw_params.num_ports) | |
152 | return -EINVAL; | |
153 | ||
154 | switch (fs->flow_type & ~FLOW_EXT) { | |
155 | case TCP_V4_FLOW: | |
156 | ip_proto = IPPROTO_TCP; | |
157 | v4_spec = &fs->h_u.tcp_ip4_spec; | |
158 | break; | |
159 | case UDP_V4_FLOW: | |
160 | ip_proto = IPPROTO_UDP; | |
161 | v4_spec = &fs->h_u.udp_ip4_spec; | |
162 | break; | |
163 | default: | |
164 | return -EINVAL; | |
165 | } | |
166 | ||
167 | /* We only use one UDF slice for now */ | |
168 | slice_num = 1; | |
169 | layout = &udf_tcpip4_layout; | |
170 | num_udf = bcm_sf2_get_num_udf_slices(layout->slices); | |
171 | ||
172 | /* Apply the UDF layout for this filter */ | |
173 | bcm_sf2_cfp_udf_set(priv, slice_num, layout->slices); | |
174 | ||
175 | /* Apply to all packets received through this port */ | |
176 | core_writel(priv, BIT(port), CORE_CFP_DATA_PORT(7)); | |
177 | ||
178 | /* S-Tag status [31:30] | |
179 | * C-Tag status [29:28] | |
180 | * L2 framing [27:26] | |
181 | * L3 framing [25:24] | |
182 | * IP ToS [23:16] | |
183 | * IP proto [15:08] | |
184 | * IP Fragm [7] | |
185 | * Non 1st frag [6] | |
186 | * IP Authen [5] | |
187 | * TTL range [4:3] | |
188 | * PPPoE session [2] | |
189 | * Reserved [1] | |
190 | * UDF_Valid[8] [0] | |
191 | */ | |
39cdd349 FF |
192 | core_writel(priv, v4_spec->tos << IPTOS_SHIFT | |
193 | ip_proto << IPPROTO_SHIFT | ip_frag << IP_FRAG_SHIFT, | |
7318166c FF |
194 | CORE_CFP_DATA_PORT(6)); |
195 | ||
196 | /* UDF_Valid[7:0] [31:24] | |
197 | * S-Tag [23:8] | |
198 | * C-Tag [7:0] | |
199 | */ | |
200 | core_writel(priv, GENMASK(num_udf - 1, 0) << 24, CORE_CFP_DATA_PORT(5)); | |
201 | ||
202 | /* C-Tag [31:24] | |
203 | * UDF_n_A8 [23:8] | |
204 | * UDF_n_A7 [7:0] | |
205 | */ | |
206 | core_writel(priv, 0, CORE_CFP_DATA_PORT(4)); | |
207 | ||
208 | /* UDF_n_A7 [31:24] | |
209 | * UDF_n_A6 [23:8] | |
210 | * UDF_n_A5 [7:0] | |
211 | */ | |
212 | core_writel(priv, be16_to_cpu(v4_spec->pdst) >> 8, | |
213 | CORE_CFP_DATA_PORT(3)); | |
214 | ||
215 | /* UDF_n_A5 [31:24] | |
216 | * UDF_n_A4 [23:8] | |
217 | * UDF_n_A3 [7:0] | |
218 | */ | |
219 | reg = (be16_to_cpu(v4_spec->pdst) & 0xff) << 24 | | |
220 | (u32)be16_to_cpu(v4_spec->psrc) << 8 | | |
221 | (be32_to_cpu(v4_spec->ip4dst) & 0x0000ff00) >> 8; | |
222 | core_writel(priv, reg, CORE_CFP_DATA_PORT(2)); | |
223 | ||
224 | /* UDF_n_A3 [31:24] | |
225 | * UDF_n_A2 [23:8] | |
226 | * UDF_n_A1 [7:0] | |
227 | */ | |
228 | reg = (u32)(be32_to_cpu(v4_spec->ip4dst) & 0xff) << 24 | | |
229 | (u32)(be32_to_cpu(v4_spec->ip4dst) >> 16) << 8 | | |
230 | (be32_to_cpu(v4_spec->ip4src) & 0x0000ff00) >> 8; | |
231 | core_writel(priv, reg, CORE_CFP_DATA_PORT(1)); | |
232 | ||
233 | /* UDF_n_A1 [31:24] | |
234 | * UDF_n_A0 [23:8] | |
235 | * Reserved [7:4] | |
236 | * Slice ID [3:2] | |
237 | * Slice valid [1:0] | |
238 | */ | |
239 | reg = (u32)(be32_to_cpu(v4_spec->ip4src) & 0xff) << 24 | | |
240 | (u32)(be32_to_cpu(v4_spec->ip4src) >> 16) << 8 | | |
241 | SLICE_NUM(slice_num) | SLICE_VALID; | |
242 | core_writel(priv, reg, CORE_CFP_DATA_PORT(0)); | |
243 | ||
244 | /* Source port map match */ | |
245 | core_writel(priv, 0xff, CORE_CFP_MASK_PORT(7)); | |
246 | ||
247 | /* Mask with the specific layout for IPv4 packets */ | |
248 | core_writel(priv, layout->mask_value, CORE_CFP_MASK_PORT(6)); | |
249 | ||
250 | /* Mask all but valid UDFs */ | |
251 | core_writel(priv, GENMASK(num_udf - 1, 0) << 24, CORE_CFP_MASK_PORT(5)); | |
252 | ||
253 | /* Mask all */ | |
254 | core_writel(priv, 0, CORE_CFP_MASK_PORT(4)); | |
255 | ||
256 | /* All other UDFs should be matched with the filter */ | |
257 | core_writel(priv, 0xff, CORE_CFP_MASK_PORT(3)); | |
258 | core_writel(priv, 0xffffffff, CORE_CFP_MASK_PORT(2)); | |
259 | core_writel(priv, 0xffffffff, CORE_CFP_MASK_PORT(1)); | |
260 | core_writel(priv, 0xffffff0f, CORE_CFP_MASK_PORT(0)); | |
261 | ||
262 | /* Locate the first rule available */ | |
263 | if (fs->location == RX_CLS_LOC_ANY) | |
264 | rule_index = find_first_zero_bit(priv->cfp.used, | |
265 | bcm_sf2_cfp_rule_size(priv)); | |
266 | else | |
267 | rule_index = fs->location; | |
268 | ||
269 | /* Insert into TCAM now */ | |
270 | bcm_sf2_cfp_rule_addr_set(priv, rule_index); | |
271 | ||
272 | ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | TCAM_SEL); | |
273 | if (ret) { | |
274 | pr_err("TCAM entry at addr %d failed\n", rule_index); | |
275 | return ret; | |
276 | } | |
277 | ||
278 | /* Replace ARL derived destination with DST_MAP derived, define | |
279 | * which port and queue this should be forwarded to. | |
280 | * | |
281 | * We have a small oddity where Port 6 just does not have a | |
282 | * valid bit here (so we subtract by one). | |
283 | */ | |
152b6fd6 | 284 | queue_num = fs->ring_cookie % SF2_NUM_EGRESS_QUEUES; |
7318166c FF |
285 | if (port_num >= 7) |
286 | port_num -= 1; | |
287 | ||
288 | reg = CHANGE_FWRD_MAP_IB_REP_ARL | BIT(port_num + DST_MAP_IB_SHIFT) | | |
289 | CHANGE_TC | queue_num << NEW_TC_SHIFT; | |
290 | ||
291 | core_writel(priv, reg, CORE_ACT_POL_DATA0); | |
292 | ||
293 | /* Set classification ID that needs to be put in Broadcom tag */ | |
294 | core_writel(priv, rule_index << CHAIN_ID_SHIFT, | |
295 | CORE_ACT_POL_DATA1); | |
296 | ||
297 | core_writel(priv, 0, CORE_ACT_POL_DATA2); | |
298 | ||
299 | /* Configure policer RAM now */ | |
300 | ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | ACT_POL_RAM); | |
301 | if (ret) { | |
302 | pr_err("Policer entry at %d failed\n", rule_index); | |
303 | return ret; | |
304 | } | |
305 | ||
306 | /* Disable the policer */ | |
307 | core_writel(priv, POLICER_MODE_DISABLE, CORE_RATE_METER0); | |
308 | ||
309 | /* Now the rate meter */ | |
310 | ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | RATE_METER_RAM); | |
311 | if (ret) { | |
312 | pr_err("Meter entry at %d failed\n", rule_index); | |
313 | return ret; | |
314 | } | |
315 | ||
316 | /* Turn on CFP for this rule now */ | |
317 | reg = core_readl(priv, CORE_CFP_CTL_REG); | |
318 | reg |= BIT(port); | |
319 | core_writel(priv, reg, CORE_CFP_CTL_REG); | |
320 | ||
321 | /* Flag the rule as being used and return it */ | |
322 | set_bit(rule_index, priv->cfp.used); | |
323 | fs->location = rule_index; | |
324 | ||
325 | return 0; | |
326 | } | |
327 | ||
328 | static int bcm_sf2_cfp_rule_del(struct bcm_sf2_priv *priv, int port, | |
329 | u32 loc) | |
330 | { | |
331 | int ret; | |
332 | u32 reg; | |
333 | ||
334 | /* Refuse deletion of unused rules, and the default reserved rule */ | |
335 | if (!test_bit(loc, priv->cfp.used) || loc == 0) | |
336 | return -EINVAL; | |
337 | ||
338 | /* Indicate which rule we want to read */ | |
339 | bcm_sf2_cfp_rule_addr_set(priv, loc); | |
340 | ||
341 | ret = bcm_sf2_cfp_op(priv, OP_SEL_READ | TCAM_SEL); | |
342 | if (ret) | |
343 | return ret; | |
344 | ||
345 | /* Clear its valid bits */ | |
346 | reg = core_readl(priv, CORE_CFP_DATA_PORT(0)); | |
347 | reg &= ~SLICE_VALID; | |
348 | core_writel(priv, reg, CORE_CFP_DATA_PORT(0)); | |
349 | ||
350 | /* Write back this entry into the TCAM now */ | |
351 | ret = bcm_sf2_cfp_op(priv, OP_SEL_WRITE | TCAM_SEL); | |
352 | if (ret) | |
353 | return ret; | |
354 | ||
355 | clear_bit(loc, priv->cfp.used); | |
356 | ||
357 | return 0; | |
358 | } | |
359 | ||
360 | static void bcm_sf2_invert_masks(struct ethtool_rx_flow_spec *flow) | |
361 | { | |
362 | unsigned int i; | |
363 | ||
364 | for (i = 0; i < sizeof(flow->m_u); i++) | |
365 | flow->m_u.hdata[i] ^= 0xff; | |
366 | ||
367 | flow->m_ext.vlan_etype ^= cpu_to_be16(~0); | |
368 | flow->m_ext.vlan_tci ^= cpu_to_be16(~0); | |
369 | flow->m_ext.data[0] ^= cpu_to_be32(~0); | |
370 | flow->m_ext.data[1] ^= cpu_to_be32(~0); | |
371 | } | |
372 | ||
373 | static int bcm_sf2_cfp_rule_get(struct bcm_sf2_priv *priv, int port, | |
374 | struct ethtool_rxnfc *nfc, bool search) | |
375 | { | |
376 | struct ethtool_tcpip4_spec *v4_spec; | |
377 | unsigned int queue_num; | |
378 | u16 src_dst_port; | |
379 | u32 reg, ipv4; | |
380 | int ret; | |
381 | ||
382 | if (!search) { | |
383 | bcm_sf2_cfp_rule_addr_set(priv, nfc->fs.location); | |
384 | ||
385 | ret = bcm_sf2_cfp_op(priv, OP_SEL_READ | ACT_POL_RAM); | |
386 | if (ret) | |
387 | return ret; | |
388 | ||
389 | reg = core_readl(priv, CORE_ACT_POL_DATA0); | |
390 | ||
391 | ret = bcm_sf2_cfp_op(priv, OP_SEL_READ | TCAM_SEL); | |
392 | if (ret) | |
393 | return ret; | |
394 | } else { | |
395 | reg = core_readl(priv, CORE_ACT_POL_DATA0); | |
396 | } | |
397 | ||
398 | /* Extract the destination port */ | |
399 | nfc->fs.ring_cookie = fls((reg >> DST_MAP_IB_SHIFT) & | |
400 | DST_MAP_IB_MASK) - 1; | |
401 | ||
402 | /* There is no Port 6, so we compensate for that here */ | |
403 | if (nfc->fs.ring_cookie >= 6) | |
404 | nfc->fs.ring_cookie++; | |
152b6fd6 | 405 | nfc->fs.ring_cookie *= SF2_NUM_EGRESS_QUEUES; |
7318166c FF |
406 | |
407 | /* Extract the destination queue */ | |
408 | queue_num = (reg >> NEW_TC_SHIFT) & NEW_TC_MASK; | |
409 | nfc->fs.ring_cookie += queue_num; | |
410 | ||
411 | /* Extract the IP protocol */ | |
412 | reg = core_readl(priv, CORE_CFP_DATA_PORT(6)); | |
413 | switch ((reg & IPPROTO_MASK) >> IPPROTO_SHIFT) { | |
414 | case IPPROTO_TCP: | |
415 | nfc->fs.flow_type = TCP_V4_FLOW; | |
416 | v4_spec = &nfc->fs.h_u.tcp_ip4_spec; | |
417 | break; | |
418 | case IPPROTO_UDP: | |
419 | nfc->fs.flow_type = UDP_V4_FLOW; | |
420 | v4_spec = &nfc->fs.h_u.udp_ip4_spec; | |
421 | break; | |
422 | default: | |
423 | /* Clear to exit the search process */ | |
424 | if (search) | |
425 | core_readl(priv, CORE_CFP_DATA_PORT(7)); | |
426 | return -EINVAL; | |
427 | } | |
428 | ||
39cdd349 FF |
429 | v4_spec->tos = (reg >> IPTOS_SHIFT) & IPTOS_MASK; |
430 | nfc->fs.m_ext.data[0] = cpu_to_be32((reg >> IP_FRAG_SHIFT) & 1); | |
7318166c FF |
431 | |
432 | reg = core_readl(priv, CORE_CFP_DATA_PORT(3)); | |
433 | /* src port [15:8] */ | |
434 | src_dst_port = reg << 8; | |
435 | ||
436 | reg = core_readl(priv, CORE_CFP_DATA_PORT(2)); | |
437 | /* src port [7:0] */ | |
438 | src_dst_port |= (reg >> 24); | |
439 | ||
440 | v4_spec->pdst = cpu_to_be16(src_dst_port); | |
441 | nfc->fs.m_u.tcp_ip4_spec.pdst = cpu_to_be16(~0); | |
442 | v4_spec->psrc = cpu_to_be16((u16)(reg >> 8)); | |
443 | nfc->fs.m_u.tcp_ip4_spec.psrc = cpu_to_be16(~0); | |
444 | ||
445 | /* IPv4 dst [15:8] */ | |
ff4cf0e5 | 446 | ipv4 = (reg & 0xff) << 8; |
7318166c FF |
447 | reg = core_readl(priv, CORE_CFP_DATA_PORT(1)); |
448 | /* IPv4 dst [31:16] */ | |
ff4cf0e5 | 449 | ipv4 |= ((reg >> 8) & 0xffff) << 16; |
7318166c FF |
450 | /* IPv4 dst [7:0] */ |
451 | ipv4 |= (reg >> 24) & 0xff; | |
452 | v4_spec->ip4dst = cpu_to_be32(ipv4); | |
453 | nfc->fs.m_u.tcp_ip4_spec.ip4dst = cpu_to_be32(~0); | |
454 | ||
455 | /* IPv4 src [15:8] */ | |
ff4cf0e5 | 456 | ipv4 = (reg & 0xff) << 8; |
7318166c FF |
457 | reg = core_readl(priv, CORE_CFP_DATA_PORT(0)); |
458 | ||
459 | if (!(reg & SLICE_VALID)) | |
460 | return -EINVAL; | |
461 | ||
462 | /* IPv4 src [7:0] */ | |
463 | ipv4 |= (reg >> 24) & 0xff; | |
464 | /* IPv4 src [31:16] */ | |
ff4cf0e5 | 465 | ipv4 |= ((reg >> 8) & 0xffff) << 16; |
7318166c FF |
466 | v4_spec->ip4src = cpu_to_be32(ipv4); |
467 | nfc->fs.m_u.tcp_ip4_spec.ip4src = cpu_to_be32(~0); | |
468 | ||
469 | /* Read last to avoid next entry clobbering the results during search | |
470 | * operations | |
471 | */ | |
472 | reg = core_readl(priv, CORE_CFP_DATA_PORT(7)); | |
473 | if (!(reg & 1 << port)) | |
474 | return -EINVAL; | |
475 | ||
476 | bcm_sf2_invert_masks(&nfc->fs); | |
477 | ||
478 | /* Put the TCAM size here */ | |
479 | nfc->data = bcm_sf2_cfp_rule_size(priv); | |
480 | ||
481 | return 0; | |
482 | } | |
483 | ||
484 | /* We implement the search doing a TCAM search operation */ | |
485 | static int bcm_sf2_cfp_rule_get_all(struct bcm_sf2_priv *priv, | |
486 | int port, struct ethtool_rxnfc *nfc, | |
487 | u32 *rule_locs) | |
488 | { | |
489 | unsigned int index = 1, rules_cnt = 0; | |
490 | int ret; | |
491 | u32 reg; | |
492 | ||
493 | /* Do not poll on OP_STR_DONE to be self-clearing for search | |
494 | * operations, we cannot use bcm_sf2_cfp_op here because it completes | |
495 | * on clearing OP_STR_DONE which won't clear until the entire search | |
496 | * operation is over. | |
497 | */ | |
498 | reg = core_readl(priv, CORE_CFP_ACC); | |
499 | reg &= ~(XCESS_ADDR_MASK << XCESS_ADDR_SHIFT); | |
500 | reg |= index << XCESS_ADDR_SHIFT; | |
501 | reg &= ~(OP_SEL_MASK | RAM_SEL_MASK); | |
502 | reg |= OP_SEL_SEARCH | TCAM_SEL | OP_STR_DONE; | |
503 | core_writel(priv, reg, CORE_CFP_ACC); | |
504 | ||
505 | do { | |
506 | /* Wait for results to be ready */ | |
507 | reg = core_readl(priv, CORE_CFP_ACC); | |
508 | ||
509 | /* Extract the address we are searching */ | |
510 | index = reg >> XCESS_ADDR_SHIFT; | |
511 | index &= XCESS_ADDR_MASK; | |
512 | ||
513 | /* We have a valid search result, so flag it accordingly */ | |
514 | if (reg & SEARCH_STS) { | |
515 | ret = bcm_sf2_cfp_rule_get(priv, port, nfc, true); | |
516 | if (ret) | |
517 | continue; | |
518 | ||
519 | rule_locs[rules_cnt] = index; | |
520 | rules_cnt++; | |
521 | } | |
522 | ||
523 | /* Search is over break out */ | |
524 | if (!(reg & OP_STR_DONE)) | |
525 | break; | |
526 | ||
df191632 | 527 | } while (index < priv->num_cfp_rules); |
7318166c FF |
528 | |
529 | /* Put the TCAM size here */ | |
530 | nfc->data = bcm_sf2_cfp_rule_size(priv); | |
531 | nfc->rule_cnt = rules_cnt; | |
532 | ||
533 | return 0; | |
534 | } | |
535 | ||
536 | int bcm_sf2_get_rxnfc(struct dsa_switch *ds, int port, | |
537 | struct ethtool_rxnfc *nfc, u32 *rule_locs) | |
538 | { | |
539 | struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); | |
540 | int ret = 0; | |
541 | ||
542 | mutex_lock(&priv->cfp.lock); | |
543 | ||
544 | switch (nfc->cmd) { | |
545 | case ETHTOOL_GRXCLSRLCNT: | |
546 | /* Subtract the default, unusable rule */ | |
547 | nfc->rule_cnt = bitmap_weight(priv->cfp.used, | |
df191632 | 548 | priv->num_cfp_rules) - 1; |
7318166c FF |
549 | /* We support specifying rule locations */ |
550 | nfc->data |= RX_CLS_LOC_SPECIAL; | |
551 | break; | |
552 | case ETHTOOL_GRXCLSRULE: | |
553 | ret = bcm_sf2_cfp_rule_get(priv, port, nfc, false); | |
554 | break; | |
555 | case ETHTOOL_GRXCLSRLALL: | |
556 | ret = bcm_sf2_cfp_rule_get_all(priv, port, nfc, rule_locs); | |
557 | break; | |
558 | default: | |
559 | ret = -EOPNOTSUPP; | |
560 | break; | |
561 | } | |
562 | ||
563 | mutex_unlock(&priv->cfp.lock); | |
564 | ||
565 | return ret; | |
566 | } | |
567 | ||
568 | int bcm_sf2_set_rxnfc(struct dsa_switch *ds, int port, | |
569 | struct ethtool_rxnfc *nfc) | |
570 | { | |
571 | struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds); | |
572 | int ret = 0; | |
573 | ||
574 | mutex_lock(&priv->cfp.lock); | |
575 | ||
576 | switch (nfc->cmd) { | |
577 | case ETHTOOL_SRXCLSRLINS: | |
578 | ret = bcm_sf2_cfp_rule_set(ds, port, &nfc->fs); | |
579 | break; | |
580 | ||
581 | case ETHTOOL_SRXCLSRLDEL: | |
582 | ret = bcm_sf2_cfp_rule_del(priv, port, nfc->fs.location); | |
583 | break; | |
584 | default: | |
585 | ret = -EOPNOTSUPP; | |
586 | break; | |
587 | } | |
588 | ||
589 | mutex_unlock(&priv->cfp.lock); | |
590 | ||
591 | return ret; | |
592 | } | |
593 | ||
594 | int bcm_sf2_cfp_rst(struct bcm_sf2_priv *priv) | |
595 | { | |
596 | unsigned int timeout = 1000; | |
597 | u32 reg; | |
598 | ||
599 | reg = core_readl(priv, CORE_CFP_ACC); | |
600 | reg |= TCAM_RESET; | |
601 | core_writel(priv, reg, CORE_CFP_ACC); | |
602 | ||
603 | do { | |
604 | reg = core_readl(priv, CORE_CFP_ACC); | |
605 | if (!(reg & TCAM_RESET)) | |
606 | break; | |
607 | ||
608 | cpu_relax(); | |
609 | } while (timeout--); | |
610 | ||
611 | if (!timeout) | |
612 | return -ETIMEDOUT; | |
613 | ||
614 | return 0; | |
615 | } |