1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2023 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> |
3 | |
4 | /* Access Control List (ACL) structure: |
5 | * |
6 | * There are multiple groups of registers involved in ACL configuration: |
7 | * |
8 | * - Matching Rules: These registers define the criteria for matching incoming |
9 | * packets based on their header information (Layer 2 MAC, Layer 3 IP, or |
10 | * Layer 4 TCP/UDP). Different register settings are used depending on the |
11 | * matching rule mode (MD) and the Enable (ENB) settings. |
12 | * |
13 | * - Action Rules: These registers define how the ACL should modify the packet's |
14 | * priority, VLAN tag priority, and forwarding map once a matching rule has |
15 | * been triggered. The settings vary depending on whether the matching rule is |
16 | * in Count Mode (MD = 01 and ENB = 00) or not. |
17 | * |
18 | * - Processing Rules: These registers control the overall behavior of the ACL, |
19 | * such as selecting which matching rule to apply first, enabling/disabling |
20 | * specific rules, or specifying actions for matched packets. |
21 | * |
22 | * ACL Structure: |
23 | * +----------------------+ |
24 | * +----------------------+ | (optional) | |
25 | * | Matching Rules | | Matching Rules | |
26 | * | (Layer 2, 3, 4) | | (Layer 2, 3, 4) | |
27 | * +----------------------+ +----------------------+ |
28 | * | | |
29 | * \___________________________/ |
30 | * v |
31 | * +----------------------+ |
32 | * | Processing Rules | |
33 | * | (action idx, | |
34 | * | matching rule set) | |
35 | * +----------------------+ |
36 | * | |
37 | * v |
38 | * +----------------------+ |
39 | * | Action Rules | |
40 | * | (Modify Priority, | |
41 | * | Forwarding Map, | |
42 | * | VLAN tag, etc) | |
43 | * +----------------------+ |
44 | */ |
45 | |
46 | #include <linux/bitops.h> |
47 | |
48 | #include "ksz9477.h" |
49 | #include "ksz9477_reg.h" |
50 | #include "ksz_common.h" |
51 | |
52 | #define KSZ9477_PORT_ACL_0 0x600 |
53 | |
54 | enum ksz9477_acl_port_access { |
55 | KSZ9477_ACL_PORT_ACCESS_0 = 0x00, |
56 | KSZ9477_ACL_PORT_ACCESS_1 = 0x01, |
57 | KSZ9477_ACL_PORT_ACCESS_2 = 0x02, |
58 | KSZ9477_ACL_PORT_ACCESS_3 = 0x03, |
59 | KSZ9477_ACL_PORT_ACCESS_4 = 0x04, |
60 | KSZ9477_ACL_PORT_ACCESS_5 = 0x05, |
61 | KSZ9477_ACL_PORT_ACCESS_6 = 0x06, |
62 | KSZ9477_ACL_PORT_ACCESS_7 = 0x07, |
63 | KSZ9477_ACL_PORT_ACCESS_8 = 0x08, |
64 | KSZ9477_ACL_PORT_ACCESS_9 = 0x09, |
65 | KSZ9477_ACL_PORT_ACCESS_A = 0x0A, |
66 | KSZ9477_ACL_PORT_ACCESS_B = 0x0B, |
67 | KSZ9477_ACL_PORT_ACCESS_C = 0x0C, |
68 | KSZ9477_ACL_PORT_ACCESS_D = 0x0D, |
69 | KSZ9477_ACL_PORT_ACCESS_E = 0x0E, |
70 | KSZ9477_ACL_PORT_ACCESS_F = 0x0F, |
71 | KSZ9477_ACL_PORT_ACCESS_10 = 0x10, |
72 | KSZ9477_ACL_PORT_ACCESS_11 = 0x11 |
73 | }; |
74 | |
75 | #define KSZ9477_ACL_MD_MASK GENMASK(5, 4) |
76 | #define KSZ9477_ACL_MD_DISABLE 0 |
77 | #define KSZ9477_ACL_MD_L2_MAC 1 |
78 | #define KSZ9477_ACL_MD_L3_IP 2 |
79 | #define KSZ9477_ACL_MD_L4_TCP_UDP 3 |
80 | |
81 | #define KSZ9477_ACL_ENB_MASK GENMASK(3, 2) |
82 | #define KSZ9477_ACL_ENB_L2_COUNTER 0 |
83 | #define KSZ9477_ACL_ENB_L2_TYPE 1 |
84 | #define KSZ9477_ACL_ENB_L2_MAC 2 |
85 | #define KSZ9477_ACL_ENB_L2_MAC_TYPE 3 |
86 | |
87 | /* only IPv4 src or dst can be used with mask */ |
88 | #define KSZ9477_ACL_ENB_L3_IPV4_ADDR_MASK 1 |
89 | /* only IPv4 src and dst can be used without mask */ |
90 | #define KSZ9477_ACL_ENB_L3_IPV4_ADDR_SRC_DST 2 |
91 | |
92 | #define KSZ9477_ACL_ENB_L4_IP_PROTO 0 |
93 | #define KSZ9477_ACL_ENB_L4_TCP_SRC_DST_PORT 1 |
94 | #define KSZ9477_ACL_ENB_L4_UDP_SRC_DST_PORT 2 |
95 | #define KSZ9477_ACL_ENB_L4_TCP_SEQ_NUMBER 3 |
96 | |
97 | #define KSZ9477_ACL_SD_SRC BIT(1) |
98 | #define KSZ9477_ACL_SD_DST 0 |
99 | #define KSZ9477_ACL_EQ_EQUAL BIT(0) |
100 | #define KSZ9477_ACL_EQ_NOT_EQUAL 0 |
101 | |
102 | #define KSZ9477_ACL_PM_M GENMASK(7, 6) |
103 | #define KSZ9477_ACL_PM_DISABLE 0 |
104 | #define KSZ9477_ACL_PM_HIGHER 1 |
105 | #define KSZ9477_ACL_PM_LOWER 2 |
106 | #define KSZ9477_ACL_PM_REPLACE 3 |
107 | #define KSZ9477_ACL_P_M GENMASK(5, 3) |
108 | |
109 | #define KSZ9477_PORT_ACL_CTRL_0 0x0612 |
110 | |
111 | #define KSZ9477_ACL_WRITE_DONE BIT(6) |
112 | #define KSZ9477_ACL_READ_DONE BIT(5) |
113 | #define KSZ9477_ACL_WRITE BIT(4) |
114 | #define KSZ9477_ACL_INDEX_M GENMASK(3, 0) |
115 | |
116 | /** |
117 | * ksz9477_dump_acl_index - Print the ACL entry at the specified index |
118 | * |
119 | * @dev: Pointer to the ksz9477 device structure. |
120 | * @acle: Pointer to the ACL entry array. |
121 | * @index: The index of the ACL entry to print. |
122 | * |
123 | * This function prints the details of an ACL entry, located at a particular |
124 | * index within the ksz9477 device's ACL table. It omits printing entries that |
125 | * are empty. |
126 | * |
127 | * Return: 1 if the entry is non-empty and printed, 0 otherwise. |
128 | */ |
129 | static int ksz9477_dump_acl_index(struct ksz_device *dev, |
130 | struct ksz9477_acl_entry *acle, int index) |
131 | { |
132 | bool empty = true; |
133 | char buf[64]; |
134 | u8 *entry; |
135 | int i; |
136 | |
137 | entry = &acle[index].entry[0]; |
138 | for (i = 0; i <= KSZ9477_ACL_PORT_ACCESS_11; i++) { |
139 | if (entry[i]) |
140 | empty = false; |
141 | |
142 | sprintf(buf: buf + (i * 3), fmt: "%02x " , entry[i]); |
143 | } |
144 | |
145 | /* no need to print empty entries */ |
146 | if (empty) |
147 | return 0; |
148 | |
149 | dev_err(dev->dev, " Entry %02d, prio: %02d : %s" , index, |
150 | acle[index].prio, buf); |
151 | |
152 | return 1; |
153 | } |
154 | |
155 | /** |
156 | * ksz9477_dump_acl - Print ACL entries |
157 | * |
158 | * @dev: Pointer to the device structure. |
159 | * @acle: Pointer to the ACL entry array. |
160 | */ |
161 | static void ksz9477_dump_acl(struct ksz_device *dev, |
162 | struct ksz9477_acl_entry *acle) |
163 | { |
164 | int count = 0; |
165 | int i; |
166 | |
167 | for (i = 0; i < KSZ9477_ACL_MAX_ENTRIES; i++) |
168 | count += ksz9477_dump_acl_index(dev, acle, index: i); |
169 | |
170 | if (count != KSZ9477_ACL_MAX_ENTRIES - 1) |
171 | dev_err(dev->dev, " Empty ACL entries were skipped\n" ); |
172 | } |
173 | |
174 | /** |
175 | * ksz9477_acl_is_valid_matching_rule - Check if an ACL entry contains a valid |
176 | * matching rule. |
177 | * |
178 | * @entry: Pointer to ACL entry buffer |
179 | * |
180 | * This function checks if the given ACL entry buffer contains a valid |
181 | * matching rule by inspecting the Mode (MD) and Enable (ENB) fields. |
182 | * |
183 | * Returns: True if it's a valid matching rule, false otherwise. |
184 | */ |
185 | static bool ksz9477_acl_is_valid_matching_rule(u8 *entry) |
186 | { |
187 | u8 val1, md, enb; |
188 | |
189 | val1 = entry[KSZ9477_ACL_PORT_ACCESS_1]; |
190 | |
191 | md = FIELD_GET(KSZ9477_ACL_MD_MASK, val1); |
192 | if (md == KSZ9477_ACL_MD_DISABLE) |
193 | return false; |
194 | |
195 | if (md == KSZ9477_ACL_MD_L2_MAC) { |
196 | /* L2 counter is not support, so it is not valid rule for now */ |
197 | enb = FIELD_GET(KSZ9477_ACL_ENB_MASK, val1); |
198 | if (enb == KSZ9477_ACL_ENB_L2_COUNTER) |
199 | return false; |
200 | } |
201 | |
202 | return true; |
203 | } |
204 | |
205 | /** |
206 | * ksz9477_acl_get_cont_entr - Get count of contiguous ACL entries and validate |
207 | * the matching rules. |
208 | * @dev: Pointer to the KSZ9477 device structure. |
209 | * @port: Port number. |
210 | * @index: Index of the starting ACL entry. |
211 | * |
212 | * Based on the KSZ9477 switch's Access Control List (ACL) system, the RuleSet |
213 | * in an ACL entry indicates which entries contain Matching rules linked to it. |
214 | * This RuleSet is represented by two registers: KSZ9477_ACL_PORT_ACCESS_E and |
215 | * KSZ9477_ACL_PORT_ACCESS_F. Each bit set in these registers corresponds to |
216 | * an entry containing a Matching rule for this RuleSet. |
217 | * |
218 | * For a single Matching rule linked, only one bit is set. However, when an |
219 | * entry links multiple Matching rules, forming what's termed a 'complex rule', |
220 | * multiple bits are set in these registers. |
221 | * |
222 | * This function checks that, for complex rules, the entries containing the |
223 | * linked Matching rules are contiguous in terms of their indices. It calculates |
224 | * and returns the number of these contiguous entries. |
225 | * |
226 | * Returns: |
227 | * - 0 if the entry is empty and can be safely overwritten |
228 | * - 1 if the entry represents a simple rule |
229 | * - The number of contiguous entries if it is the root entry of a complex |
230 | * rule |
231 | * - -ENOTEMPTY if the entry is part of a complex rule but not the root |
232 | * entry |
233 | * - -EINVAL if the validation fails |
234 | */ |
235 | static int ksz9477_acl_get_cont_entr(struct ksz_device *dev, int port, |
236 | int index) |
237 | { |
238 | struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv; |
239 | struct ksz9477_acl_entries *acles = &acl->acles; |
240 | int start_idx, end_idx, contiguous_count; |
241 | unsigned long val; |
242 | u8 vale, valf; |
243 | u8 *entry; |
244 | int i; |
245 | |
246 | entry = &acles->entries[index].entry[0]; |
247 | vale = entry[KSZ9477_ACL_PORT_ACCESS_E]; |
248 | valf = entry[KSZ9477_ACL_PORT_ACCESS_F]; |
249 | |
250 | val = (vale << 8) | valf; |
251 | |
252 | /* If no bits are set, return an appropriate value or error */ |
253 | if (!val) { |
254 | if (ksz9477_acl_is_valid_matching_rule(entry)) { |
255 | /* Looks like we are about to corrupt some complex rule. |
256 | * Do not print an error here, as this is a normal case |
257 | * when we are trying to find a free or starting entry. |
258 | */ |
259 | dev_dbg(dev->dev, "ACL: entry %d starting with a valid matching rule, but no bits set in RuleSet\n" , |
260 | index); |
261 | return -ENOTEMPTY; |
262 | } |
263 | |
264 | /* This entry does not contain a valid matching rule */ |
265 | return 0; |
266 | } |
267 | |
268 | start_idx = find_first_bit(addr: (unsigned long *)&val, size: 16); |
269 | end_idx = find_last_bit(addr: (unsigned long *)&val, size: 16); |
270 | |
271 | /* Calculate the contiguous count */ |
272 | contiguous_count = end_idx - start_idx + 1; |
273 | |
274 | /* Check if the number of bits set in val matches our calculated count */ |
275 | if (contiguous_count != hweight16(val)) { |
276 | /* Probably we have a fragmented complex rule, which is not |
277 | * supported by this driver. |
278 | */ |
279 | dev_err(dev->dev, "ACL: number of bits set in RuleSet does not match calculated count\n" ); |
280 | return -EINVAL; |
281 | } |
282 | |
283 | /* loop over the contiguous entries and check for valid matching rules */ |
284 | for (i = start_idx; i <= end_idx; i++) { |
285 | u8 *current_entry = &acles->entries[i].entry[0]; |
286 | |
287 | if (!ksz9477_acl_is_valid_matching_rule(entry: current_entry)) { |
288 | /* we have something linked without a valid matching |
289 | * rule. ACL table? |
290 | */ |
291 | dev_err(dev->dev, "ACL: entry %d does not contain a valid matching rule\n" , |
292 | i); |
293 | return -EINVAL; |
294 | } |
295 | |
296 | if (i > start_idx) { |
297 | vale = current_entry[KSZ9477_ACL_PORT_ACCESS_E]; |
298 | valf = current_entry[KSZ9477_ACL_PORT_ACCESS_F]; |
299 | /* Following entry should have empty linkage list */ |
300 | if (vale || valf) { |
301 | dev_err(dev->dev, "ACL: entry %d has non-empty RuleSet linkage\n" , |
302 | i); |
303 | return -EINVAL; |
304 | } |
305 | } |
306 | } |
307 | |
308 | return contiguous_count; |
309 | } |
310 | |
311 | /** |
312 | * ksz9477_acl_update_linkage - Update the RuleSet linkage for an ACL entry |
313 | * after a move operation. |
314 | * |
315 | * @dev: Pointer to the ksz_device. |
316 | * @entry: Pointer to the ACL entry array. |
317 | * @old_idx: The original index of the ACL entry before moving. |
318 | * @new_idx: The new index of the ACL entry after moving. |
319 | * |
320 | * This function updates the RuleSet linkage bits for an ACL entry when |
321 | * it's moved from one position to another in the ACL table. The RuleSet |
322 | * linkage is represented by two 8-bit registers, which are combined |
323 | * into a 16-bit value for easier manipulation. The linkage bits are shifted |
324 | * based on the difference between the old and new index. If any bits are lost |
325 | * during the shift operation, an error is returned. |
326 | * |
327 | * Note: Fragmentation within a RuleSet is not supported. Hence, entries must |
328 | * be moved as complete blocks, maintaining the integrity of the RuleSet. |
329 | * |
330 | * Returns: 0 on success, or -EINVAL if any RuleSet linkage bits are lost |
331 | * during the move. |
332 | */ |
333 | static int ksz9477_acl_update_linkage(struct ksz_device *dev, u8 *entry, |
334 | u16 old_idx, u16 new_idx) |
335 | { |
336 | unsigned int original_bit_count; |
337 | unsigned long rule_linkage; |
338 | u8 vale, valf, val0; |
339 | int shift; |
340 | |
341 | val0 = entry[KSZ9477_ACL_PORT_ACCESS_0]; |
342 | vale = entry[KSZ9477_ACL_PORT_ACCESS_E]; |
343 | valf = entry[KSZ9477_ACL_PORT_ACCESS_F]; |
344 | |
345 | /* Combine the two u8 values into one u16 for easier manipulation */ |
346 | rule_linkage = (vale << 8) | valf; |
347 | original_bit_count = hweight16(rule_linkage); |
348 | |
349 | /* Even if HW is able to handle fragmented RuleSet, we don't support it. |
350 | * RuleSet is filled only for the first entry of the set. |
351 | */ |
352 | if (!rule_linkage) |
353 | return 0; |
354 | |
355 | if (val0 != old_idx) { |
356 | dev_err(dev->dev, "ACL: entry %d has unexpected ActionRule linkage: %d\n" , |
357 | old_idx, val0); |
358 | return -EINVAL; |
359 | } |
360 | |
361 | val0 = new_idx; |
362 | |
363 | /* Calculate the number of positions to shift */ |
364 | shift = new_idx - old_idx; |
365 | |
366 | /* Shift the RuleSet */ |
367 | if (shift > 0) |
368 | rule_linkage <<= shift; |
369 | else |
370 | rule_linkage >>= -shift; |
371 | |
372 | /* Check that no bits were lost in the process */ |
373 | if (original_bit_count != hweight16(rule_linkage)) { |
374 | dev_err(dev->dev, "ACL RuleSet linkage bits lost during move\n" ); |
375 | return -EINVAL; |
376 | } |
377 | |
378 | entry[KSZ9477_ACL_PORT_ACCESS_0] = val0; |
379 | |
380 | /* Update the RuleSet bitfields in the entry */ |
381 | entry[KSZ9477_ACL_PORT_ACCESS_E] = (rule_linkage >> 8) & 0xFF; |
382 | entry[KSZ9477_ACL_PORT_ACCESS_F] = rule_linkage & 0xFF; |
383 | |
384 | return 0; |
385 | } |
386 | |
387 | /** |
388 | * ksz9477_validate_and_get_src_count - Validate source and destination indices |
389 | * and determine the source entry count. |
390 | * @dev: Pointer to the KSZ device structure. |
391 | * @port: Port number on the KSZ device where the ACL entries reside. |
392 | * @src_idx: Index of the starting ACL entry that needs to be validated. |
393 | * @dst_idx: Index of the destination where the source entries are intended to |
394 | * be moved. |
395 | * @src_count: Pointer to the variable that will hold the number of contiguous |
396 | * source entries if the validation passes. |
397 | * @dst_count: Pointer to the variable that will hold the number of contiguous |
398 | * destination entries if the validation passes. |
399 | * |
400 | * This function performs validation on the source and destination indices |
401 | * provided for ACL entries. It checks if the indices are within the valid |
402 | * range, and if the source entries are contiguous. Additionally, the function |
403 | * ensures that there's adequate space at the destination for the source entries |
404 | * and that the destination index isn't in the middle of a RuleSet. If all |
405 | * validations pass, the function returns the number of contiguous source and |
406 | * destination entries. |
407 | * |
408 | * Return: 0 on success, otherwise returns a negative error code if any |
409 | * validation check fails. |
410 | */ |
411 | static int ksz9477_validate_and_get_src_count(struct ksz_device *dev, int port, |
412 | int src_idx, int dst_idx, |
413 | int *src_count, int *dst_count) |
414 | { |
415 | int ret; |
416 | |
417 | if (src_idx >= KSZ9477_ACL_MAX_ENTRIES || |
418 | dst_idx >= KSZ9477_ACL_MAX_ENTRIES) { |
419 | dev_err(dev->dev, "ACL: invalid entry index\n" ); |
420 | return -EINVAL; |
421 | } |
422 | |
423 | /* Validate if the source entries are contiguous */ |
424 | ret = ksz9477_acl_get_cont_entr(dev, port, index: src_idx); |
425 | if (ret < 0) |
426 | return ret; |
427 | *src_count = ret; |
428 | |
429 | if (!*src_count) { |
430 | dev_err(dev->dev, "ACL: source entry is empty\n" ); |
431 | return -EINVAL; |
432 | } |
433 | |
434 | if (dst_idx + *src_count >= KSZ9477_ACL_MAX_ENTRIES) { |
435 | dev_err(dev->dev, "ACL: Not enough space at the destination. Move operation will fail.\n" ); |
436 | return -EINVAL; |
437 | } |
438 | |
439 | /* Validate if the destination entry is empty or not in the middle of |
440 | * a RuleSet. |
441 | */ |
442 | ret = ksz9477_acl_get_cont_entr(dev, port, index: dst_idx); |
443 | if (ret < 0) |
444 | return ret; |
445 | *dst_count = ret; |
446 | |
447 | return 0; |
448 | } |
449 | |
450 | /** |
451 | * ksz9477_move_entries_downwards - Move a range of ACL entries downwards in |
452 | * the list. |
453 | * @dev: Pointer to the KSZ device structure. |
454 | * @acles: Pointer to the structure encapsulating all the ACL entries. |
455 | * @start_idx: Starting index of the entries to be relocated. |
456 | * @num_entries_to_move: Number of consecutive entries to be relocated. |
457 | * @end_idx: Destination index where the first entry should be situated post |
458 | * relocation. |
459 | * |
460 | * This function is responsible for rearranging a specific block of ACL entries |
461 | * by shifting them downwards in the list based on the supplied source and |
462 | * destination indices. It ensures that the linkage between the ACL entries is |
463 | * maintained accurately after the relocation. |
464 | * |
465 | * Return: 0 on successful relocation of entries, otherwise returns a negative |
466 | * error code. |
467 | */ |
468 | static int ksz9477_move_entries_downwards(struct ksz_device *dev, |
469 | struct ksz9477_acl_entries *acles, |
470 | u16 start_idx, |
471 | u16 num_entries_to_move, |
472 | u16 end_idx) |
473 | { |
474 | struct ksz9477_acl_entry *e; |
475 | int ret, i; |
476 | |
477 | for (i = start_idx; i < end_idx; i++) { |
478 | e = &acles->entries[i]; |
479 | *e = acles->entries[i + num_entries_to_move]; |
480 | |
481 | ret = ksz9477_acl_update_linkage(dev, entry: &e->entry[0], |
482 | old_idx: i + num_entries_to_move, new_idx: i); |
483 | if (ret < 0) |
484 | return ret; |
485 | } |
486 | |
487 | return 0; |
488 | } |
489 | |
490 | /** |
491 | * ksz9477_move_entries_upwards - Move a range of ACL entries upwards in the |
492 | * list. |
493 | * @dev: Pointer to the KSZ device structure. |
494 | * @acles: Pointer to the structure holding all the ACL entries. |
495 | * @start_idx: The starting index of the entries to be moved. |
496 | * @num_entries_to_move: Number of contiguous entries to be moved. |
497 | * @target_idx: The destination index where the first entry should be placed |
498 | * after moving. |
499 | * |
500 | * This function rearranges a chunk of ACL entries by moving them upwards |
501 | * in the list based on the given source and destination indices. The reordering |
502 | * process preserves the linkage between entries by updating it accordingly. |
503 | * |
504 | * Return: 0 if the entries were successfully moved, otherwise a negative error |
505 | * code. |
506 | */ |
507 | static int ksz9477_move_entries_upwards(struct ksz_device *dev, |
508 | struct ksz9477_acl_entries *acles, |
509 | u16 start_idx, u16 num_entries_to_move, |
510 | u16 target_idx) |
511 | { |
512 | struct ksz9477_acl_entry *e; |
513 | int ret, i, b; |
514 | |
515 | for (i = start_idx; i > target_idx; i--) { |
516 | b = i + num_entries_to_move - 1; |
517 | |
518 | e = &acles->entries[b]; |
519 | *e = acles->entries[i - 1]; |
520 | |
521 | ret = ksz9477_acl_update_linkage(dev, entry: &e->entry[0], old_idx: i - 1, new_idx: b); |
522 | if (ret < 0) |
523 | return ret; |
524 | } |
525 | |
526 | return 0; |
527 | } |
528 | |
529 | /** |
530 | * ksz9477_acl_move_entries - Move a block of contiguous ACL entries from a |
531 | * source to a destination index. |
532 | * @dev: Pointer to the KSZ9477 device structure. |
533 | * @port: Port number. |
534 | * @src_idx: Index of the starting source ACL entry. |
535 | * @dst_idx: Index of the starting destination ACL entry. |
536 | * |
537 | * This function aims to move a block of contiguous ACL entries from the source |
538 | * index to the destination index while ensuring the integrity and validity of |
539 | * the ACL table. |
540 | * |
541 | * In case of any errors during the adjustments or copying, the function will |
542 | * restore the ACL entries to their original state from the backup. |
543 | * |
544 | * Return: 0 if the move operation is successful. Returns -EINVAL for validation |
545 | * errors or other error codes based on specific failure conditions. |
546 | */ |
547 | static int ksz9477_acl_move_entries(struct ksz_device *dev, int port, |
548 | u16 src_idx, u16 dst_idx) |
549 | { |
550 | struct ksz9477_acl_entry buffer[KSZ9477_ACL_MAX_ENTRIES]; |
551 | struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv; |
552 | struct ksz9477_acl_entries *acles = &acl->acles; |
553 | int src_count, ret, dst_count; |
554 | |
555 | /* Nothing to do */ |
556 | if (src_idx == dst_idx) |
557 | return 0; |
558 | |
559 | ret = ksz9477_validate_and_get_src_count(dev, port, src_idx, dst_idx, |
560 | src_count: &src_count, dst_count: &dst_count); |
561 | if (ret) |
562 | return ret; |
563 | |
564 | /* In case dst_index is greater than src_index, we need to adjust the |
565 | * destination index to account for the entries that will be moved |
566 | * downwards and the size of the entry located at dst_idx. |
567 | */ |
568 | if (dst_idx > src_idx) |
569 | dst_idx = dst_idx + dst_count - src_count; |
570 | |
571 | /* Copy source block to buffer and update its linkage */ |
572 | for (int i = 0; i < src_count; i++) { |
573 | buffer[i] = acles->entries[src_idx + i]; |
574 | ret = ksz9477_acl_update_linkage(dev, entry: &buffer[i].entry[0], |
575 | old_idx: src_idx + i, new_idx: dst_idx + i); |
576 | if (ret < 0) |
577 | return ret; |
578 | } |
579 | |
580 | /* Adjust other entries and their linkage based on destination */ |
581 | if (dst_idx > src_idx) { |
582 | ret = ksz9477_move_entries_downwards(dev, acles, start_idx: src_idx, |
583 | num_entries_to_move: src_count, end_idx: dst_idx); |
584 | } else { |
585 | ret = ksz9477_move_entries_upwards(dev, acles, start_idx: src_idx, |
586 | num_entries_to_move: src_count, target_idx: dst_idx); |
587 | } |
588 | if (ret < 0) |
589 | return ret; |
590 | |
591 | /* Copy buffer to destination block */ |
592 | for (int i = 0; i < src_count; i++) |
593 | acles->entries[dst_idx + i] = buffer[i]; |
594 | |
595 | return 0; |
596 | } |
597 | |
598 | /** |
599 | * ksz9477_get_next_block_start - Identify the starting index of the next ACL |
600 | * block. |
601 | * @dev: Pointer to the device structure. |
602 | * @port: The port number on which the ACL entries are being checked. |
603 | * @start: The starting index from which the search begins. |
604 | * |
605 | * This function looks for the next valid ACL block starting from the provided |
606 | * 'start' index and returns the beginning index of that block. If the block is |
607 | * invalid or if it reaches the end of the ACL entries without finding another |
608 | * block, it returns the maximum ACL entries count. |
609 | * |
610 | * Returns: |
611 | * - The starting index of the next valid ACL block. |
612 | * - KSZ9477_ACL_MAX_ENTRIES if no other valid blocks are found after 'start'. |
613 | * - A negative error code if an error occurs while checking. |
614 | */ |
615 | static int ksz9477_get_next_block_start(struct ksz_device *dev, int port, |
616 | int start) |
617 | { |
618 | int block_size; |
619 | |
620 | for (int i = start; i < KSZ9477_ACL_MAX_ENTRIES;) { |
621 | block_size = ksz9477_acl_get_cont_entr(dev, port, index: i); |
622 | if (block_size < 0 && block_size != -ENOTEMPTY) |
623 | return block_size; |
624 | |
625 | if (block_size > 0) |
626 | return i; |
627 | |
628 | i++; |
629 | } |
630 | return KSZ9477_ACL_MAX_ENTRIES; |
631 | } |
632 | |
633 | /** |
634 | * ksz9477_swap_acl_blocks - Swap two ACL blocks |
635 | * @dev: Pointer to the device structure. |
636 | * @port: The port number on which the ACL blocks are to be swapped. |
637 | * @i: The starting index of the first ACL block. |
638 | * @j: The starting index of the second ACL block. |
639 | * |
640 | * This function is used to swap two ACL blocks present at given indices. The |
641 | * main purpose is to aid in the sorting and reordering of ACL blocks based on |
642 | * certain criteria, e.g., priority. It checks the validity of the block at |
643 | * index 'i', ensuring it's not an empty block, and then proceeds to swap it |
644 | * with the block at index 'j'. |
645 | * |
646 | * Returns: |
647 | * - 0 on successful swapping of blocks. |
648 | * - -EINVAL if the block at index 'i' is empty. |
649 | * - A negative error code if any other error occurs during the swap. |
650 | */ |
651 | static int ksz9477_swap_acl_blocks(struct ksz_device *dev, int port, int i, |
652 | int j) |
653 | { |
654 | int ret, current_block_size; |
655 | |
656 | current_block_size = ksz9477_acl_get_cont_entr(dev, port, index: i); |
657 | if (current_block_size < 0) |
658 | return current_block_size; |
659 | |
660 | if (!current_block_size) { |
661 | dev_err(dev->dev, "ACL: swapping empty entry %d\n" , i); |
662 | return -EINVAL; |
663 | } |
664 | |
665 | ret = ksz9477_acl_move_entries(dev, port, src_idx: i, dst_idx: j); |
666 | if (ret) |
667 | return ret; |
668 | |
669 | ret = ksz9477_acl_move_entries(dev, port, src_idx: j - current_block_size, dst_idx: i); |
670 | if (ret) |
671 | return ret; |
672 | |
673 | return 0; |
674 | } |
675 | |
676 | /** |
677 | * ksz9477_sort_acl_entr_no_back - Sort ACL entries for a given port based on |
678 | * priority without backing up entries. |
679 | * @dev: Pointer to the device structure. |
680 | * @port: The port number whose ACL entries need to be sorted. |
681 | * |
682 | * This function sorts ACL entries of the specified port using a variant of the |
683 | * bubble sort algorithm. It operates on blocks of ACL entries rather than |
684 | * individual entries. Each block's starting point is identified and then |
685 | * compared with subsequent blocks based on their priority. If the current |
686 | * block has a lower priority than the subsequent block, the two blocks are |
687 | * swapped. |
688 | * |
689 | * This is done in order to maintain an organized order of ACL entries based on |
690 | * priority, ensuring efficient and predictable ACL rule application. |
691 | * |
692 | * Returns: |
693 | * - 0 on successful sorting of entries. |
694 | * - A negative error code if any issue arises during sorting, e.g., |
695 | * if the function is unable to get the next block start. |
696 | */ |
697 | static int ksz9477_sort_acl_entr_no_back(struct ksz_device *dev, int port) |
698 | { |
699 | struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv; |
700 | struct ksz9477_acl_entries *acles = &acl->acles; |
701 | struct ksz9477_acl_entry *curr, *next; |
702 | int i, j, ret; |
703 | |
704 | /* Bubble sort */ |
705 | for (i = 0; i < KSZ9477_ACL_MAX_ENTRIES;) { |
706 | curr = &acles->entries[i]; |
707 | |
708 | j = ksz9477_get_next_block_start(dev, port, start: i + 1); |
709 | if (j < 0) |
710 | return j; |
711 | |
712 | while (j < KSZ9477_ACL_MAX_ENTRIES) { |
713 | next = &acles->entries[j]; |
714 | |
715 | if (curr->prio > next->prio) { |
716 | ret = ksz9477_swap_acl_blocks(dev, port, i, j); |
717 | if (ret) |
718 | return ret; |
719 | } |
720 | |
721 | j = ksz9477_get_next_block_start(dev, port, start: j + 1); |
722 | if (j < 0) |
723 | return j; |
724 | } |
725 | |
726 | i = ksz9477_get_next_block_start(dev, port, start: i + 1); |
727 | if (i < 0) |
728 | return i; |
729 | } |
730 | |
731 | return 0; |
732 | } |
733 | |
734 | /** |
735 | * ksz9477_sort_acl_entries - Sort the ACL entries for a given port. |
736 | * @dev: Pointer to the KSZ device. |
737 | * @port: Port number. |
738 | * |
739 | * This function sorts the Access Control List (ACL) entries for a specified |
740 | * port. Before sorting, a backup of the original entries is created. If the |
741 | * sorting process fails, the function will log error messages displaying both |
742 | * the original and attempted sorted entries, and then restore the original |
743 | * entries from the backup. |
744 | * |
745 | * Return: 0 if the sorting succeeds, otherwise a negative error code. |
746 | */ |
747 | int ksz9477_sort_acl_entries(struct ksz_device *dev, int port) |
748 | { |
749 | struct ksz9477_acl_entry backup[KSZ9477_ACL_MAX_ENTRIES]; |
750 | struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv; |
751 | struct ksz9477_acl_entries *acles = &acl->acles; |
752 | int ret; |
753 | |
754 | /* create a backup of the ACL entries, if something goes wrong |
755 | * we can restore the ACL entries. |
756 | */ |
757 | memcpy(backup, acles->entries, sizeof(backup)); |
758 | |
759 | ret = ksz9477_sort_acl_entr_no_back(dev, port); |
760 | if (ret) { |
761 | dev_err(dev->dev, "ACL: failed to sort entries for port %d\n" , |
762 | port); |
763 | dev_err(dev->dev, "ACL dump before sorting:\n" ); |
764 | ksz9477_dump_acl(dev, acle: backup); |
765 | dev_err(dev->dev, "ACL dump after sorting:\n" ); |
766 | ksz9477_dump_acl(dev, acle: acles->entries); |
767 | /* Restore the original entries */ |
768 | memcpy(acles->entries, backup, sizeof(backup)); |
769 | } |
770 | |
771 | return ret; |
772 | } |
773 | |
774 | /** |
775 | * ksz9477_acl_wait_ready - Waits for the ACL operation to complete on a given |
776 | * port. |
777 | * @dev: The ksz_device instance. |
778 | * @port: The port number to wait for. |
779 | * |
780 | * This function checks if the ACL write or read operation is completed by |
781 | * polling the specified register. |
782 | * |
783 | * Returns: 0 if the operation is successful, or a negative error code if an |
784 | * error occurs. |
785 | */ |
786 | static int ksz9477_acl_wait_ready(struct ksz_device *dev, int port) |
787 | { |
788 | unsigned int wr_mask = KSZ9477_ACL_WRITE_DONE | KSZ9477_ACL_READ_DONE; |
789 | unsigned int val, reg; |
790 | int ret; |
791 | |
792 | reg = dev->dev_ops->get_port_addr(port, KSZ9477_PORT_ACL_CTRL_0); |
793 | |
794 | ret = regmap_read_poll_timeout(dev->regmap[0], reg, val, |
795 | (val & wr_mask) == wr_mask, 1000, 10000); |
796 | if (ret) |
797 | dev_err(dev->dev, "Failed to read/write ACL table\n" ); |
798 | |
799 | return ret; |
800 | } |
801 | |
802 | /** |
803 | * ksz9477_acl_entry_write - Writes an ACL entry to a given port at the |
804 | * specified index. |
805 | * @dev: The ksz_device instance. |
806 | * @port: The port number to write the ACL entry to. |
807 | * @entry: A pointer to the ACL entry data. |
808 | * @idx: The index at which to write the ACL entry. |
809 | * |
810 | * This function writes the provided ACL entry to the specified port at the |
811 | * given index. |
812 | * |
813 | * Returns: 0 if the operation is successful, or a negative error code if an |
814 | * error occurs. |
815 | */ |
816 | static int ksz9477_acl_entry_write(struct ksz_device *dev, int port, u8 *entry, |
817 | int idx) |
818 | { |
819 | int ret, i; |
820 | u8 val; |
821 | |
822 | for (i = 0; i < KSZ9477_ACL_ENTRY_SIZE; i++) { |
823 | ret = ksz_pwrite8(dev, port, KSZ9477_PORT_ACL_0 + i, data: entry[i]); |
824 | if (ret) { |
825 | dev_err(dev->dev, "Failed to write ACL entry %d\n" , i); |
826 | return ret; |
827 | } |
828 | } |
829 | |
830 | /* write everything down */ |
831 | val = FIELD_PREP(KSZ9477_ACL_INDEX_M, idx) | KSZ9477_ACL_WRITE; |
832 | ret = ksz_pwrite8(dev, port, KSZ9477_PORT_ACL_CTRL_0, data: val); |
833 | if (ret) |
834 | return ret; |
835 | |
836 | /* wait until everything is written */ |
837 | return ksz9477_acl_wait_ready(dev, port); |
838 | } |
839 | |
840 | /** |
841 | * ksz9477_acl_port_enable - Enables ACL functionality on a given port. |
842 | * @dev: The ksz_device instance. |
843 | * @port: The port number on which to enable ACL functionality. |
844 | * |
845 | * This function enables ACL functionality on the specified port by configuring |
846 | * the appropriate control registers. It returns 0 if the operation is |
847 | * successful, or a negative error code if an error occurs. |
848 | * |
849 | * 0xn801 - KSZ9477S 5.2.8.2 Port Priority Control Register |
850 | * Bit 7 - Highest Priority |
851 | * Bit 6 - OR'ed Priority |
852 | * Bit 4 - MAC Address Priority Classification |
853 | * Bit 3 - VLAN Priority Classification |
854 | * Bit 2 - 802.1p Priority Classification |
855 | * Bit 1 - Diffserv Priority Classification |
856 | * Bit 0 - ACL Priority Classification |
857 | * |
858 | * Current driver implementation sets 802.1p priority classification by default. |
859 | * In this function we add ACL priority classification with OR'ed priority. |
860 | * According to testing, priority set by ACL will supersede the 802.1p priority. |
861 | * |
862 | * 0xn803 - KSZ9477S 5.2.8.4 Port Authentication Control Register |
863 | * Bit 2 - Access Control List (ACL) Enable |
864 | * Bits 1:0 - Authentication Mode |
865 | * 00 = Reserved |
866 | * 01 = Block Mode. Authentication is enabled. When ACL is |
867 | * enabled, all traffic that misses the ACL rules is |
868 | * blocked; otherwise ACL actions apply. |
869 | * 10 = Pass Mode. Authentication is disabled. When ACL is |
870 | * enabled, all traffic that misses the ACL rules is |
871 | * forwarded; otherwise ACL actions apply. |
872 | * 11 = Trap Mode. Authentication is enabled. All traffic is |
873 | * forwarded to the host port. When ACL is enabled, all |
874 | * traffic that misses the ACL rules is blocked; otherwise |
875 | * ACL actions apply. |
876 | * |
877 | * We are using Pass Mode int this function. |
878 | * |
879 | * Returns: 0 if the operation is successful, or a negative error code if an |
880 | * error occurs. |
881 | */ |
882 | static int ksz9477_acl_port_enable(struct ksz_device *dev, int port) |
883 | { |
884 | int ret; |
885 | |
886 | ret = ksz_prmw8(dev, port, P_PRIO_CTRL, mask: 0, PORT_ACL_PRIO_ENABLE | |
887 | PORT_OR_PRIO); |
888 | if (ret) |
889 | return ret; |
890 | |
891 | return ksz_pwrite8(dev, port, REG_PORT_MRI_AUTHEN_CTRL, |
892 | PORT_ACL_ENABLE | |
893 | FIELD_PREP(PORT_AUTHEN_MODE, PORT_AUTHEN_PASS)); |
894 | } |
895 | |
896 | /** |
897 | * ksz9477_acl_port_disable - Disables ACL functionality on a given port. |
898 | * @dev: The ksz_device instance. |
899 | * @port: The port number on which to disable ACL functionality. |
900 | * |
901 | * This function disables ACL functionality on the specified port by writing a |
902 | * value of 0 to the REG_PORT_MRI_AUTHEN_CTRL control register and remove |
903 | * PORT_ACL_PRIO_ENABLE bit from P_PRIO_CTRL register. |
904 | * |
905 | * Returns: 0 if the operation is successful, or a negative error code if an |
906 | * error occurs. |
907 | */ |
908 | static int ksz9477_acl_port_disable(struct ksz_device *dev, int port) |
909 | { |
910 | int ret; |
911 | |
912 | ret = ksz_prmw8(dev, port, P_PRIO_CTRL, PORT_ACL_PRIO_ENABLE, val: 0); |
913 | if (ret) |
914 | return ret; |
915 | |
916 | return ksz_pwrite8(dev, port, REG_PORT_MRI_AUTHEN_CTRL, data: 0); |
917 | } |
918 | |
919 | /** |
920 | * ksz9477_acl_write_list - Write a list of ACL entries to a given port. |
921 | * @dev: The ksz_device instance. |
922 | * @port: The port number on which to write ACL entries. |
923 | * |
924 | * This function enables ACL functionality on the specified port, writes a list |
925 | * of ACL entries to the port, and disables ACL functionality if there are no |
926 | * entries. |
927 | * |
928 | * Returns: 0 if the operation is successful, or a negative error code if an |
929 | * error occurs. |
930 | */ |
931 | int ksz9477_acl_write_list(struct ksz_device *dev, int port) |
932 | { |
933 | struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv; |
934 | struct ksz9477_acl_entries *acles = &acl->acles; |
935 | int ret, i; |
936 | |
937 | /* ACL should be enabled before writing entries */ |
938 | ret = ksz9477_acl_port_enable(dev, port); |
939 | if (ret) |
940 | return ret; |
941 | |
942 | /* write all entries */ |
943 | for (i = 0; i < ARRAY_SIZE(acles->entries); i++) { |
944 | u8 *entry = acles->entries[i].entry; |
945 | |
946 | /* Check if entry was removed and should be zeroed. |
947 | * If last fields of the entry are not zero, it means |
948 | * it is removed locally but currently not synced with the HW. |
949 | * So, we will write it down to the HW to remove it. |
950 | */ |
951 | if (i >= acles->entries_count && |
952 | entry[KSZ9477_ACL_PORT_ACCESS_10] == 0 && |
953 | entry[KSZ9477_ACL_PORT_ACCESS_11] == 0) |
954 | continue; |
955 | |
956 | ret = ksz9477_acl_entry_write(dev, port, entry, idx: i); |
957 | if (ret) |
958 | return ret; |
959 | |
960 | /* now removed entry is clean on HW side, so it can |
961 | * in the cache too |
962 | */ |
963 | if (i >= acles->entries_count && |
964 | entry[KSZ9477_ACL_PORT_ACCESS_10] != 0 && |
965 | entry[KSZ9477_ACL_PORT_ACCESS_11] != 0) { |
966 | entry[KSZ9477_ACL_PORT_ACCESS_10] = 0; |
967 | entry[KSZ9477_ACL_PORT_ACCESS_11] = 0; |
968 | } |
969 | } |
970 | |
971 | if (!acles->entries_count) |
972 | return ksz9477_acl_port_disable(dev, port); |
973 | |
974 | return 0; |
975 | } |
976 | |
977 | /** |
978 | * ksz9477_acl_remove_entries - Remove ACL entries with a given cookie from a |
979 | * specified ksz9477_acl_entries structure. |
980 | * @dev: The ksz_device instance. |
981 | * @port: The port number on which to remove ACL entries. |
982 | * @acles: The ksz9477_acl_entries instance. |
983 | * @cookie: The cookie value to match for entry removal. |
984 | * |
985 | * This function iterates through the entries array, removing any entries with |
986 | * a matching cookie value. The remaining entries are then shifted down to fill |
987 | * the gap. |
988 | */ |
989 | void ksz9477_acl_remove_entries(struct ksz_device *dev, int port, |
990 | struct ksz9477_acl_entries *acles, |
991 | unsigned long cookie) |
992 | { |
993 | int entries_count = acles->entries_count; |
994 | int ret, i, src_count; |
995 | int src_idx = -1; |
996 | |
997 | if (!entries_count) |
998 | return; |
999 | |
1000 | /* Search for the first position with the cookie */ |
1001 | for (i = 0; i < entries_count; i++) { |
1002 | if (acles->entries[i].cookie == cookie) { |
1003 | src_idx = i; |
1004 | break; |
1005 | } |
1006 | } |
1007 | |
1008 | /* No entries with the matching cookie found */ |
1009 | if (src_idx == -1) |
1010 | return; |
1011 | |
1012 | /* Get the size of the cookie entry. We may have complex entries. */ |
1013 | src_count = ksz9477_acl_get_cont_entr(dev, port, index: src_idx); |
1014 | if (src_count <= 0) |
1015 | return; |
1016 | |
1017 | /* Move all entries down to overwrite removed entry with the cookie */ |
1018 | ret = ksz9477_move_entries_downwards(dev, acles, start_idx: src_idx, |
1019 | num_entries_to_move: src_count, |
1020 | end_idx: entries_count - src_count); |
1021 | if (ret) { |
1022 | dev_err(dev->dev, "Failed to move ACL entries down\n" ); |
1023 | return; |
1024 | } |
1025 | |
1026 | /* Overwrite new empty places at the end of the list with zeros to make |
1027 | * sure not unexpected things will happen or no unexplored quirks will |
1028 | * come out. |
1029 | */ |
1030 | for (i = entries_count - src_count; i < entries_count; i++) { |
1031 | struct ksz9477_acl_entry *entry = &acles->entries[i]; |
1032 | |
1033 | memset(entry, 0, sizeof(*entry)); |
1034 | |
1035 | /* Set all access bits to be able to write zeroed entry to HW */ |
1036 | entry->entry[KSZ9477_ACL_PORT_ACCESS_10] = 0xff; |
1037 | entry->entry[KSZ9477_ACL_PORT_ACCESS_11] = 0xff; |
1038 | } |
1039 | |
1040 | /* Adjust the total entries count */ |
1041 | acles->entries_count -= src_count; |
1042 | } |
1043 | |
1044 | /** |
1045 | * ksz9477_port_acl_init - Initialize the ACL for a specified port on a ksz |
1046 | * device. |
1047 | * @dev: The ksz_device instance. |
1048 | * @port: The port number to initialize the ACL for. |
1049 | * |
1050 | * This function allocates memory for an acl structure, associates it with the |
1051 | * specified port, and initializes the ACL entries to a default state. The |
1052 | * entries are then written using the ksz9477_acl_write_list function, ensuring |
1053 | * the ACL has a predictable initial hardware state. |
1054 | * |
1055 | * Returns: 0 on success, or an error code on failure. |
1056 | */ |
1057 | int ksz9477_port_acl_init(struct ksz_device *dev, int port) |
1058 | { |
1059 | struct ksz9477_acl_entries *acles; |
1060 | struct ksz9477_acl_priv *acl; |
1061 | int ret, i; |
1062 | |
1063 | acl = kzalloc(size: sizeof(*acl), GFP_KERNEL); |
1064 | if (!acl) |
1065 | return -ENOMEM; |
1066 | |
1067 | dev->ports[port].acl_priv = acl; |
1068 | |
1069 | acles = &acl->acles; |
1070 | /* write all entries */ |
1071 | for (i = 0; i < ARRAY_SIZE(acles->entries); i++) { |
1072 | u8 *entry = acles->entries[i].entry; |
1073 | |
1074 | /* Set all access bits to be able to write zeroed |
1075 | * entry |
1076 | */ |
1077 | entry[KSZ9477_ACL_PORT_ACCESS_10] = 0xff; |
1078 | entry[KSZ9477_ACL_PORT_ACCESS_11] = 0xff; |
1079 | } |
1080 | |
1081 | ret = ksz9477_acl_write_list(dev, port); |
1082 | if (ret) |
1083 | goto free_acl; |
1084 | |
1085 | return 0; |
1086 | |
1087 | free_acl: |
1088 | kfree(objp: dev->ports[port].acl_priv); |
1089 | dev->ports[port].acl_priv = NULL; |
1090 | |
1091 | return ret; |
1092 | } |
1093 | |
1094 | /** |
1095 | * ksz9477_port_acl_free - Free the ACL resources for a specified port on a ksz |
1096 | * device. |
1097 | * @dev: The ksz_device instance. |
1098 | * @port: The port number to initialize the ACL for. |
1099 | * |
1100 | * This disables the ACL for the specified port and frees the associated memory, |
1101 | */ |
1102 | void ksz9477_port_acl_free(struct ksz_device *dev, int port) |
1103 | { |
1104 | if (!dev->ports[port].acl_priv) |
1105 | return; |
1106 | |
1107 | ksz9477_acl_port_disable(dev, port); |
1108 | |
1109 | kfree(objp: dev->ports[port].acl_priv); |
1110 | dev->ports[port].acl_priv = NULL; |
1111 | } |
1112 | |
1113 | /** |
1114 | * ksz9477_acl_set_reg - Set entry[16] and entry[17] depending on the updated |
1115 | * entry[] |
1116 | * @entry: An array containing the entries |
1117 | * @reg: The register of the entry that needs to be updated |
1118 | * @value: The value to be assigned to the updated entry |
1119 | * |
1120 | * This function updates the entry[] array based on the provided register and |
1121 | * value. It also sets entry[0x10] and entry[0x11] according to the ACL byte |
1122 | * enable rules. |
1123 | * |
1124 | * 0x10 - Byte Enable [15:8] |
1125 | * |
1126 | * Each bit enables accessing one of the ACL bytes when a read or write is |
1127 | * initiated by writing to the Port ACL Byte Enable LSB Register. |
1128 | * Bit 0 applies to the Port ACL Access 7 Register |
1129 | * Bit 1 applies to the Port ACL Access 6 Register, etc. |
1130 | * Bit 7 applies to the Port ACL Access 0 Register |
1131 | * 1 = Byte is selected for read/write |
1132 | * 0 = Byte is not selected |
1133 | * |
1134 | * 0x11 - Byte Enable [7:0] |
1135 | * |
1136 | * Each bit enables accessing one of the ACL bytes when a read or write is |
1137 | * initiated by writing to the Port ACL Byte Enable LSB Register. |
1138 | * Bit 0 applies to the Port ACL Access F Register |
1139 | * Bit 1 applies to the Port ACL Access E Register, etc. |
1140 | * Bit 7 applies to the Port ACL Access 8 Register |
1141 | * 1 = Byte is selected for read/write |
1142 | * 0 = Byte is not selected |
1143 | */ |
1144 | static void ksz9477_acl_set_reg(u8 *entry, enum ksz9477_acl_port_access reg, |
1145 | u8 value) |
1146 | { |
1147 | if (reg >= KSZ9477_ACL_PORT_ACCESS_0 && |
1148 | reg <= KSZ9477_ACL_PORT_ACCESS_7) { |
1149 | entry[KSZ9477_ACL_PORT_ACCESS_10] |= |
1150 | BIT(KSZ9477_ACL_PORT_ACCESS_7 - reg); |
1151 | } else if (reg >= KSZ9477_ACL_PORT_ACCESS_8 && |
1152 | reg <= KSZ9477_ACL_PORT_ACCESS_F) { |
1153 | entry[KSZ9477_ACL_PORT_ACCESS_11] |= |
1154 | BIT(KSZ9477_ACL_PORT_ACCESS_F - reg); |
1155 | } else { |
1156 | WARN_ON(1); |
1157 | return; |
1158 | } |
1159 | |
1160 | entry[reg] = value; |
1161 | } |
1162 | |
1163 | /** |
1164 | * ksz9477_acl_matching_rule_cfg_l2 - Configure an ACL filtering entry to match |
1165 | * L2 types of Ethernet frames |
1166 | * @entry: Pointer to ACL entry buffer |
1167 | * @ethertype: Ethertype value |
1168 | * @eth_addr: Pointer to Ethernet address |
1169 | * @is_src: If true, match the source MAC address; if false, match the |
1170 | * destination MAC address |
1171 | * |
1172 | * This function configures an Access Control List (ACL) filtering |
1173 | * entry to match Layer 2 types of Ethernet frames based on the provided |
1174 | * ethertype and Ethernet address. Additionally, it can match either the source |
1175 | * or destination MAC address depending on the value of the is_src parameter. |
1176 | * |
1177 | * Register Descriptions for MD = 01 and ENB != 00 (Layer 2 MAC header |
1178 | * filtering) |
1179 | * |
1180 | * 0x01 - Mode and Enable |
1181 | * Bits 5:4 - MD (Mode) |
1182 | * 01 = Layer 2 MAC header or counter filtering |
1183 | * Bits 3:2 - ENB (Enable) |
1184 | * 01 = Comparison is performed only on the TYPE value |
1185 | * 10 = Comparison is performed only on the MAC Address value |
1186 | * 11 = Both the MAC Address and TYPE are tested |
1187 | * Bit 1 - S/D (Source / Destination) |
1188 | * 0 = Destination address |
1189 | * 1 = Source address |
1190 | * Bit 0 - EQ (Equal / Not Equal) |
1191 | * 0 = Not Equal produces true result |
1192 | * 1 = Equal produces true result |
1193 | * |
1194 | * 0x02-0x07 - MAC Address |
1195 | * 0x02 - MAC Address [47:40] |
1196 | * 0x03 - MAC Address [39:32] |
1197 | * 0x04 - MAC Address [31:24] |
1198 | * 0x05 - MAC Address [23:16] |
1199 | * 0x06 - MAC Address [15:8] |
1200 | * 0x07 - MAC Address [7:0] |
1201 | * |
1202 | * 0x08-0x09 - EtherType |
1203 | * 0x08 - EtherType [15:8] |
1204 | * 0x09 - EtherType [7:0] |
1205 | */ |
1206 | static void ksz9477_acl_matching_rule_cfg_l2(u8 *entry, u16 ethertype, |
1207 | u8 *eth_addr, bool is_src) |
1208 | { |
1209 | u8 enb = 0; |
1210 | u8 val; |
1211 | |
1212 | if (ethertype) |
1213 | enb |= KSZ9477_ACL_ENB_L2_TYPE; |
1214 | if (eth_addr) |
1215 | enb |= KSZ9477_ACL_ENB_L2_MAC; |
1216 | |
1217 | val = FIELD_PREP(KSZ9477_ACL_MD_MASK, KSZ9477_ACL_MD_L2_MAC) | |
1218 | FIELD_PREP(KSZ9477_ACL_ENB_MASK, enb) | |
1219 | FIELD_PREP(KSZ9477_ACL_SD_SRC, is_src) | KSZ9477_ACL_EQ_EQUAL; |
1220 | ksz9477_acl_set_reg(entry, reg: KSZ9477_ACL_PORT_ACCESS_1, value: val); |
1221 | |
1222 | if (eth_addr) { |
1223 | int i; |
1224 | |
1225 | for (i = 0; i < ETH_ALEN; i++) { |
1226 | ksz9477_acl_set_reg(entry, |
1227 | reg: KSZ9477_ACL_PORT_ACCESS_2 + i, |
1228 | value: eth_addr[i]); |
1229 | } |
1230 | } |
1231 | |
1232 | ksz9477_acl_set_reg(entry, reg: KSZ9477_ACL_PORT_ACCESS_8, value: ethertype >> 8); |
1233 | ksz9477_acl_set_reg(entry, reg: KSZ9477_ACL_PORT_ACCESS_9, value: ethertype & 0xff); |
1234 | } |
1235 | |
1236 | /** |
1237 | * ksz9477_acl_action_rule_cfg - Set action for an ACL entry |
1238 | * @entry: Pointer to the ACL entry |
1239 | * @force_prio: If true, force the priority value |
1240 | * @prio_val: Priority value |
1241 | * |
1242 | * This function sets the action for the specified ACL entry. It prepares |
1243 | * the priority mode and traffic class values and updates the entry's |
1244 | * action registers accordingly. Currently, there is no port or VLAN PCP |
1245 | * remapping. |
1246 | * |
1247 | * ACL Action Rule Parameters for Non-Count Modes (MD ≠ 01 or ENB ≠ 00) |
1248 | * |
1249 | * 0x0A - PM, P, RPE, RP[2:1] |
1250 | * Bits 7:6 - PM[1:0] - Priority Mode |
1251 | * 00 = ACL does not specify the packet priority. Priority is |
1252 | * determined by standard QoS functions. |
1253 | * 01 = Change packet priority to P[2:0] if it is greater than QoS |
1254 | * result. |
1255 | * 10 = Change packet priority to P[2:0] if it is smaller than the |
1256 | * QoS result. |
1257 | * 11 = Always change packet priority to P[2:0]. |
1258 | * Bits 5:3 - P[2:0] - Priority value |
1259 | * Bit 2 - RPE - Remark Priority Enable |
1260 | * Bits 1:0 - RP[2:1] - Remarked Priority value (bits 2:1) |
1261 | * 0 = Disable priority remarking |
1262 | * 1 = Enable priority remarking. VLAN tag priority (PCP) bits are |
1263 | * replaced by RP[2:0]. |
1264 | * |
1265 | * 0x0B - RP[0], MM |
1266 | * Bit 7 - RP[0] - Remarked Priority value (bit 0) |
1267 | * Bits 6:5 - MM[1:0] - Map Mode |
1268 | * 00 = No forwarding remapping |
1269 | * 01 = The forwarding map in FORWARD is OR'ed with the forwarding |
1270 | * map from the Address Lookup Table. |
1271 | * 10 = The forwarding map in FORWARD is AND'ed with the forwarding |
1272 | * map from the Address Lookup Table. |
1273 | * 11 = The forwarding map in FORWARD replaces the forwarding map |
1274 | * from the Address Lookup Table. |
1275 | * 0x0D - FORWARD[n:0] |
1276 | * Bits 7:0 - FORWARD[n:0] - Forwarding map. Bit 0 = port 1, |
1277 | * bit 1 = port 2, etc. |
1278 | * 1 = enable forwarding to this port |
1279 | * 0 = do not forward to this port |
1280 | */ |
1281 | void ksz9477_acl_action_rule_cfg(u8 *entry, bool force_prio, u8 prio_val) |
1282 | { |
1283 | u8 prio_mode, val; |
1284 | |
1285 | if (force_prio) |
1286 | prio_mode = KSZ9477_ACL_PM_REPLACE; |
1287 | else |
1288 | prio_mode = KSZ9477_ACL_PM_DISABLE; |
1289 | |
1290 | val = FIELD_PREP(KSZ9477_ACL_PM_M, prio_mode) | |
1291 | FIELD_PREP(KSZ9477_ACL_P_M, prio_val); |
1292 | ksz9477_acl_set_reg(entry, reg: KSZ9477_ACL_PORT_ACCESS_A, value: val); |
1293 | |
1294 | /* no port or VLAN PCP remapping for now */ |
1295 | ksz9477_acl_set_reg(entry, reg: KSZ9477_ACL_PORT_ACCESS_B, value: 0); |
1296 | ksz9477_acl_set_reg(entry, reg: KSZ9477_ACL_PORT_ACCESS_D, value: 0); |
1297 | } |
1298 | |
1299 | /** |
1300 | * ksz9477_acl_processing_rule_set_action - Set the action for the processing |
1301 | * rule set. |
1302 | * @entry: Pointer to the ACL entry |
1303 | * @action_idx: Index of the action to be applied |
1304 | * |
1305 | * This function sets the action for the processing rule set by updating the |
1306 | * appropriate register in the entry. There can be only one action per |
1307 | * processing rule. |
1308 | * |
1309 | * Access Control List (ACL) Processing Rule Registers: |
1310 | * |
1311 | * 0x00 - First Rule Number (FRN) |
1312 | * Bits 3:0 - First Rule Number. Pointer to an Action rule entry. |
1313 | */ |
1314 | void ksz9477_acl_processing_rule_set_action(u8 *entry, u8 action_idx) |
1315 | { |
1316 | ksz9477_acl_set_reg(entry, reg: KSZ9477_ACL_PORT_ACCESS_0, value: action_idx); |
1317 | } |
1318 | |
1319 | /** |
1320 | * ksz9477_acl_processing_rule_add_match - Add a matching rule to the rule set |
1321 | * @entry: Pointer to the ACL entry |
1322 | * @match_idx: Index of the matching rule to be added |
1323 | * |
1324 | * This function adds a matching rule to the rule set by updating the |
1325 | * appropriate bits in the entry's rule set registers. |
1326 | * |
1327 | * Access Control List (ACL) Processing Rule Registers: |
1328 | * |
1329 | * 0x0E - RuleSet [15:8] |
1330 | * Bits 7:0 - RuleSet [15:8] Specifies a set of one or more Matching rule |
1331 | * entries. RuleSet has one bit for each of the 16 Matching rule entries. |
1332 | * If multiple Matching rules are selected, then all conditions will be |
1333 | * AND'ed to produce a final match result. |
1334 | * 0 = Matching rule not selected |
1335 | * 1 = Matching rule selected |
1336 | * |
1337 | * 0x0F - RuleSet [7:0] |
1338 | * Bits 7:0 - RuleSet [7:0] |
1339 | */ |
1340 | static void ksz9477_acl_processing_rule_add_match(u8 *entry, u8 match_idx) |
1341 | { |
1342 | u8 vale = entry[KSZ9477_ACL_PORT_ACCESS_E]; |
1343 | u8 valf = entry[KSZ9477_ACL_PORT_ACCESS_F]; |
1344 | |
1345 | if (match_idx < 8) |
1346 | valf |= BIT(match_idx); |
1347 | else |
1348 | vale |= BIT(match_idx - 8); |
1349 | |
1350 | ksz9477_acl_set_reg(entry, reg: KSZ9477_ACL_PORT_ACCESS_E, value: vale); |
1351 | ksz9477_acl_set_reg(entry, reg: KSZ9477_ACL_PORT_ACCESS_F, value: valf); |
1352 | } |
1353 | |
1354 | /** |
1355 | * ksz9477_acl_get_init_entry - Get a new uninitialized entry for a specified |
1356 | * port on a ksz_device. |
1357 | * @dev: The ksz_device instance. |
1358 | * @port: The port number to get the uninitialized entry for. |
1359 | * @cookie: The cookie to associate with the entry. |
1360 | * @prio: The priority to associate with the entry. |
1361 | * |
1362 | * This function retrieves the next available ACL entry for the specified port, |
1363 | * clears all access flags, and associates it with the current cookie. |
1364 | * |
1365 | * Returns: A pointer to the new uninitialized ACL entry. |
1366 | */ |
1367 | static struct ksz9477_acl_entry * |
1368 | ksz9477_acl_get_init_entry(struct ksz_device *dev, int port, |
1369 | unsigned long cookie, u32 prio) |
1370 | { |
1371 | struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv; |
1372 | struct ksz9477_acl_entries *acles = &acl->acles; |
1373 | struct ksz9477_acl_entry *entry; |
1374 | |
1375 | entry = &acles->entries[acles->entries_count]; |
1376 | entry->cookie = cookie; |
1377 | entry->prio = prio; |
1378 | |
1379 | /* clear all access flags */ |
1380 | entry->entry[KSZ9477_ACL_PORT_ACCESS_10] = 0; |
1381 | entry->entry[KSZ9477_ACL_PORT_ACCESS_11] = 0; |
1382 | |
1383 | return entry; |
1384 | } |
1385 | |
1386 | /** |
1387 | * ksz9477_acl_match_process_l2 - Configure Layer 2 ACL matching rules and |
1388 | * processing rules. |
1389 | * @dev: Pointer to the ksz_device. |
1390 | * @port: Port number. |
1391 | * @ethtype: Ethernet type. |
1392 | * @src_mac: Source MAC address. |
1393 | * @dst_mac: Destination MAC address. |
1394 | * @cookie: The cookie to associate with the entry. |
1395 | * @prio: The priority of the entry. |
1396 | * |
1397 | * This function sets up matching and processing rules for Layer 2 ACLs. |
1398 | * It takes into account that only one MAC per entry is supported. |
1399 | */ |
1400 | void ksz9477_acl_match_process_l2(struct ksz_device *dev, int port, |
1401 | u16 ethtype, u8 *src_mac, u8 *dst_mac, |
1402 | unsigned long cookie, u32 prio) |
1403 | { |
1404 | struct ksz9477_acl_priv *acl = dev->ports[port].acl_priv; |
1405 | struct ksz9477_acl_entries *acles = &acl->acles; |
1406 | struct ksz9477_acl_entry *entry; |
1407 | |
1408 | entry = ksz9477_acl_get_init_entry(dev, port, cookie, prio); |
1409 | |
1410 | /* ACL supports only one MAC per entry */ |
1411 | if (src_mac && dst_mac) { |
1412 | ksz9477_acl_matching_rule_cfg_l2(entry: entry->entry, ethertype: ethtype, eth_addr: src_mac, |
1413 | is_src: true); |
1414 | |
1415 | /* Add both match entries to first processing rule */ |
1416 | ksz9477_acl_processing_rule_add_match(entry: entry->entry, |
1417 | match_idx: acles->entries_count); |
1418 | acles->entries_count++; |
1419 | ksz9477_acl_processing_rule_add_match(entry: entry->entry, |
1420 | match_idx: acles->entries_count); |
1421 | |
1422 | entry = ksz9477_acl_get_init_entry(dev, port, cookie, prio); |
1423 | ksz9477_acl_matching_rule_cfg_l2(entry: entry->entry, ethertype: 0, eth_addr: dst_mac, |
1424 | is_src: false); |
1425 | acles->entries_count++; |
1426 | } else { |
1427 | u8 *mac = src_mac ? src_mac : dst_mac; |
1428 | bool is_src = src_mac ? true : false; |
1429 | |
1430 | ksz9477_acl_matching_rule_cfg_l2(entry: entry->entry, ethertype: ethtype, eth_addr: mac, |
1431 | is_src); |
1432 | ksz9477_acl_processing_rule_add_match(entry: entry->entry, |
1433 | match_idx: acles->entries_count); |
1434 | acles->entries_count++; |
1435 | } |
1436 | } |
1437 | |