1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | #include <net/dsa.h> |
3 | |
4 | #include "chip.h" |
5 | #include "devlink.h" |
6 | #include "global1.h" |
7 | #include "global2.h" |
8 | #include "port.h" |
9 | |
10 | static int mv88e6xxx_atu_get_hash(struct mv88e6xxx_chip *chip, u8 *hash) |
11 | { |
12 | if (chip->info->ops->atu_get_hash) |
13 | return chip->info->ops->atu_get_hash(chip, hash); |
14 | |
15 | return -EOPNOTSUPP; |
16 | } |
17 | |
18 | static int mv88e6xxx_atu_set_hash(struct mv88e6xxx_chip *chip, u8 hash) |
19 | { |
20 | if (chip->info->ops->atu_set_hash) |
21 | return chip->info->ops->atu_set_hash(chip, hash); |
22 | |
23 | return -EOPNOTSUPP; |
24 | } |
25 | |
26 | enum mv88e6xxx_devlink_param_id { |
27 | MV88E6XXX_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX, |
28 | MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH, |
29 | }; |
30 | |
31 | int mv88e6xxx_devlink_param_get(struct dsa_switch *ds, u32 id, |
32 | struct devlink_param_gset_ctx *ctx) |
33 | { |
34 | struct mv88e6xxx_chip *chip = ds->priv; |
35 | int err; |
36 | |
37 | mv88e6xxx_reg_lock(chip); |
38 | |
39 | switch (id) { |
40 | case MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH: |
41 | err = mv88e6xxx_atu_get_hash(chip, hash: &ctx->val.vu8); |
42 | break; |
43 | default: |
44 | err = -EOPNOTSUPP; |
45 | break; |
46 | } |
47 | |
48 | mv88e6xxx_reg_unlock(chip); |
49 | |
50 | return err; |
51 | } |
52 | |
53 | int mv88e6xxx_devlink_param_set(struct dsa_switch *ds, u32 id, |
54 | struct devlink_param_gset_ctx *ctx) |
55 | { |
56 | struct mv88e6xxx_chip *chip = ds->priv; |
57 | int err; |
58 | |
59 | mv88e6xxx_reg_lock(chip); |
60 | |
61 | switch (id) { |
62 | case MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH: |
63 | err = mv88e6xxx_atu_set_hash(chip, hash: ctx->val.vu8); |
64 | break; |
65 | default: |
66 | err = -EOPNOTSUPP; |
67 | break; |
68 | } |
69 | |
70 | mv88e6xxx_reg_unlock(chip); |
71 | |
72 | return err; |
73 | } |
74 | |
75 | static const struct devlink_param mv88e6xxx_devlink_params[] = { |
76 | DSA_DEVLINK_PARAM_DRIVER(MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH, |
77 | "ATU_hash" , DEVLINK_PARAM_TYPE_U8, |
78 | BIT(DEVLINK_PARAM_CMODE_RUNTIME)), |
79 | }; |
80 | |
81 | int mv88e6xxx_setup_devlink_params(struct dsa_switch *ds) |
82 | { |
83 | return dsa_devlink_params_register(ds, params: mv88e6xxx_devlink_params, |
84 | ARRAY_SIZE(mv88e6xxx_devlink_params)); |
85 | } |
86 | |
87 | void mv88e6xxx_teardown_devlink_params(struct dsa_switch *ds) |
88 | { |
89 | dsa_devlink_params_unregister(ds, params: mv88e6xxx_devlink_params, |
90 | ARRAY_SIZE(mv88e6xxx_devlink_params)); |
91 | } |
92 | |
93 | enum mv88e6xxx_devlink_resource_id { |
94 | MV88E6XXX_RESOURCE_ID_ATU, |
95 | MV88E6XXX_RESOURCE_ID_ATU_BIN_0, |
96 | MV88E6XXX_RESOURCE_ID_ATU_BIN_1, |
97 | MV88E6XXX_RESOURCE_ID_ATU_BIN_2, |
98 | MV88E6XXX_RESOURCE_ID_ATU_BIN_3, |
99 | }; |
100 | |
101 | static u64 mv88e6xxx_devlink_atu_bin_get(struct mv88e6xxx_chip *chip, |
102 | u16 bin) |
103 | { |
104 | u16 occupancy = 0; |
105 | int err; |
106 | |
107 | mv88e6xxx_reg_lock(chip); |
108 | |
109 | err = mv88e6xxx_g2_atu_stats_set(chip, MV88E6XXX_G2_ATU_STATS_MODE_ALL, |
110 | bin); |
111 | if (err) { |
112 | dev_err(chip->dev, "failed to set ATU stats kind/bin\n" ); |
113 | goto unlock; |
114 | } |
115 | |
116 | err = mv88e6xxx_g1_atu_get_next(chip, fid: 0); |
117 | if (err) { |
118 | dev_err(chip->dev, "failed to perform ATU get next\n" ); |
119 | goto unlock; |
120 | } |
121 | |
122 | err = mv88e6xxx_g2_atu_stats_get(chip, stats: &occupancy); |
123 | if (err) { |
124 | dev_err(chip->dev, "failed to get ATU stats\n" ); |
125 | goto unlock; |
126 | } |
127 | |
128 | occupancy &= MV88E6XXX_G2_ATU_STATS_MASK; |
129 | |
130 | unlock: |
131 | mv88e6xxx_reg_unlock(chip); |
132 | |
133 | return occupancy; |
134 | } |
135 | |
136 | static u64 mv88e6xxx_devlink_atu_bin_0_get(void *priv) |
137 | { |
138 | struct mv88e6xxx_chip *chip = priv; |
139 | |
140 | return mv88e6xxx_devlink_atu_bin_get(chip, |
141 | MV88E6XXX_G2_ATU_STATS_BIN_0); |
142 | } |
143 | |
144 | static u64 mv88e6xxx_devlink_atu_bin_1_get(void *priv) |
145 | { |
146 | struct mv88e6xxx_chip *chip = priv; |
147 | |
148 | return mv88e6xxx_devlink_atu_bin_get(chip, |
149 | MV88E6XXX_G2_ATU_STATS_BIN_1); |
150 | } |
151 | |
152 | static u64 mv88e6xxx_devlink_atu_bin_2_get(void *priv) |
153 | { |
154 | struct mv88e6xxx_chip *chip = priv; |
155 | |
156 | return mv88e6xxx_devlink_atu_bin_get(chip, |
157 | MV88E6XXX_G2_ATU_STATS_BIN_2); |
158 | } |
159 | |
160 | static u64 mv88e6xxx_devlink_atu_bin_3_get(void *priv) |
161 | { |
162 | struct mv88e6xxx_chip *chip = priv; |
163 | |
164 | return mv88e6xxx_devlink_atu_bin_get(chip, |
165 | MV88E6XXX_G2_ATU_STATS_BIN_3); |
166 | } |
167 | |
168 | static u64 mv88e6xxx_devlink_atu_get(void *priv) |
169 | { |
170 | return mv88e6xxx_devlink_atu_bin_0_get(priv) + |
171 | mv88e6xxx_devlink_atu_bin_1_get(priv) + |
172 | mv88e6xxx_devlink_atu_bin_2_get(priv) + |
173 | mv88e6xxx_devlink_atu_bin_3_get(priv); |
174 | } |
175 | |
176 | int mv88e6xxx_setup_devlink_resources(struct dsa_switch *ds) |
177 | { |
178 | struct devlink_resource_size_params size_params; |
179 | struct mv88e6xxx_chip *chip = ds->priv; |
180 | int err; |
181 | |
182 | devlink_resource_size_params_init(size_params: &size_params, |
183 | size_min: mv88e6xxx_num_macs(chip), |
184 | size_max: mv88e6xxx_num_macs(chip), |
185 | size_granularity: 1, unit: DEVLINK_RESOURCE_UNIT_ENTRY); |
186 | |
187 | err = dsa_devlink_resource_register(ds, resource_name: "ATU" , |
188 | resource_size: mv88e6xxx_num_macs(chip), |
189 | resource_id: MV88E6XXX_RESOURCE_ID_ATU, |
190 | DEVLINK_RESOURCE_ID_PARENT_TOP, |
191 | size_params: &size_params); |
192 | if (err) |
193 | goto out; |
194 | |
195 | devlink_resource_size_params_init(size_params: &size_params, |
196 | size_min: mv88e6xxx_num_macs(chip) / 4, |
197 | size_max: mv88e6xxx_num_macs(chip) / 4, |
198 | size_granularity: 1, unit: DEVLINK_RESOURCE_UNIT_ENTRY); |
199 | |
200 | err = dsa_devlink_resource_register(ds, resource_name: "ATU_bin_0" , |
201 | resource_size: mv88e6xxx_num_macs(chip) / 4, |
202 | resource_id: MV88E6XXX_RESOURCE_ID_ATU_BIN_0, |
203 | parent_resource_id: MV88E6XXX_RESOURCE_ID_ATU, |
204 | size_params: &size_params); |
205 | if (err) |
206 | goto out; |
207 | |
208 | err = dsa_devlink_resource_register(ds, resource_name: "ATU_bin_1" , |
209 | resource_size: mv88e6xxx_num_macs(chip) / 4, |
210 | resource_id: MV88E6XXX_RESOURCE_ID_ATU_BIN_1, |
211 | parent_resource_id: MV88E6XXX_RESOURCE_ID_ATU, |
212 | size_params: &size_params); |
213 | if (err) |
214 | goto out; |
215 | |
216 | err = dsa_devlink_resource_register(ds, resource_name: "ATU_bin_2" , |
217 | resource_size: mv88e6xxx_num_macs(chip) / 4, |
218 | resource_id: MV88E6XXX_RESOURCE_ID_ATU_BIN_2, |
219 | parent_resource_id: MV88E6XXX_RESOURCE_ID_ATU, |
220 | size_params: &size_params); |
221 | if (err) |
222 | goto out; |
223 | |
224 | err = dsa_devlink_resource_register(ds, resource_name: "ATU_bin_3" , |
225 | resource_size: mv88e6xxx_num_macs(chip) / 4, |
226 | resource_id: MV88E6XXX_RESOURCE_ID_ATU_BIN_3, |
227 | parent_resource_id: MV88E6XXX_RESOURCE_ID_ATU, |
228 | size_params: &size_params); |
229 | if (err) |
230 | goto out; |
231 | |
232 | dsa_devlink_resource_occ_get_register(ds, |
233 | resource_id: MV88E6XXX_RESOURCE_ID_ATU, |
234 | occ_get: mv88e6xxx_devlink_atu_get, |
235 | occ_get_priv: chip); |
236 | |
237 | dsa_devlink_resource_occ_get_register(ds, |
238 | resource_id: MV88E6XXX_RESOURCE_ID_ATU_BIN_0, |
239 | occ_get: mv88e6xxx_devlink_atu_bin_0_get, |
240 | occ_get_priv: chip); |
241 | |
242 | dsa_devlink_resource_occ_get_register(ds, |
243 | resource_id: MV88E6XXX_RESOURCE_ID_ATU_BIN_1, |
244 | occ_get: mv88e6xxx_devlink_atu_bin_1_get, |
245 | occ_get_priv: chip); |
246 | |
247 | dsa_devlink_resource_occ_get_register(ds, |
248 | resource_id: MV88E6XXX_RESOURCE_ID_ATU_BIN_2, |
249 | occ_get: mv88e6xxx_devlink_atu_bin_2_get, |
250 | occ_get_priv: chip); |
251 | |
252 | dsa_devlink_resource_occ_get_register(ds, |
253 | resource_id: MV88E6XXX_RESOURCE_ID_ATU_BIN_3, |
254 | occ_get: mv88e6xxx_devlink_atu_bin_3_get, |
255 | occ_get_priv: chip); |
256 | |
257 | return 0; |
258 | |
259 | out: |
260 | dsa_devlink_resources_unregister(ds); |
261 | return err; |
262 | } |
263 | |
264 | static int mv88e6xxx_region_global_snapshot(struct devlink *dl, |
265 | const struct devlink_region_ops *ops, |
266 | struct netlink_ext_ack *extack, |
267 | u8 **data) |
268 | { |
269 | struct mv88e6xxx_region_priv *region_priv = ops->priv; |
270 | struct dsa_switch *ds = dsa_devlink_to_ds(dl); |
271 | struct mv88e6xxx_chip *chip = ds->priv; |
272 | u16 *registers; |
273 | int i, err; |
274 | |
275 | registers = kmalloc_array(n: 32, size: sizeof(u16), GFP_KERNEL); |
276 | if (!registers) |
277 | return -ENOMEM; |
278 | |
279 | mv88e6xxx_reg_lock(chip); |
280 | for (i = 0; i < 32; i++) { |
281 | switch (region_priv->id) { |
282 | case MV88E6XXX_REGION_GLOBAL1: |
283 | err = mv88e6xxx_g1_read(chip, reg: i, val: ®isters[i]); |
284 | break; |
285 | case MV88E6XXX_REGION_GLOBAL2: |
286 | err = mv88e6xxx_g2_read(chip, reg: i, val: ®isters[i]); |
287 | break; |
288 | default: |
289 | err = -EOPNOTSUPP; |
290 | } |
291 | |
292 | if (err) { |
293 | kfree(objp: registers); |
294 | goto out; |
295 | } |
296 | } |
297 | *data = (u8 *)registers; |
298 | out: |
299 | mv88e6xxx_reg_unlock(chip); |
300 | |
301 | return err; |
302 | } |
303 | |
304 | /* The ATU entry varies between mv88e6xxx chipset generations. Define |
305 | * a generic format which covers all the current and hopefully future |
306 | * mv88e6xxx generations |
307 | */ |
308 | |
309 | struct mv88e6xxx_devlink_atu_entry { |
310 | /* The FID is scattered over multiple registers. */ |
311 | u16 fid; |
312 | u16 atu_op; |
313 | u16 atu_data; |
314 | u16 atu_01; |
315 | u16 atu_23; |
316 | u16 atu_45; |
317 | }; |
318 | |
319 | static int mv88e6xxx_region_atu_snapshot_fid(struct mv88e6xxx_chip *chip, |
320 | int fid, |
321 | struct mv88e6xxx_devlink_atu_entry *table, |
322 | int *count) |
323 | { |
324 | u16 atu_op, atu_data, atu_01, atu_23, atu_45; |
325 | struct mv88e6xxx_atu_entry addr; |
326 | int err; |
327 | |
328 | addr.state = 0; |
329 | eth_broadcast_addr(addr: addr.mac); |
330 | |
331 | do { |
332 | err = mv88e6xxx_g1_atu_getnext(chip, fid, entry: &addr); |
333 | if (err) |
334 | return err; |
335 | |
336 | if (!addr.state) |
337 | break; |
338 | |
339 | err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_OP, val: &atu_op); |
340 | if (err) |
341 | return err; |
342 | |
343 | err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_DATA, val: &atu_data); |
344 | if (err) |
345 | return err; |
346 | |
347 | err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC01, val: &atu_01); |
348 | if (err) |
349 | return err; |
350 | |
351 | err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC23, val: &atu_23); |
352 | if (err) |
353 | return err; |
354 | |
355 | err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC45, val: &atu_45); |
356 | if (err) |
357 | return err; |
358 | |
359 | table[*count].fid = fid; |
360 | table[*count].atu_op = atu_op; |
361 | table[*count].atu_data = atu_data; |
362 | table[*count].atu_01 = atu_01; |
363 | table[*count].atu_23 = atu_23; |
364 | table[*count].atu_45 = atu_45; |
365 | (*count)++; |
366 | } while (!is_broadcast_ether_addr(addr: addr.mac)); |
367 | |
368 | return 0; |
369 | } |
370 | |
371 | static int mv88e6xxx_region_atu_snapshot(struct devlink *dl, |
372 | const struct devlink_region_ops *ops, |
373 | struct netlink_ext_ack *extack, |
374 | u8 **data) |
375 | { |
376 | struct dsa_switch *ds = dsa_devlink_to_ds(dl); |
377 | DECLARE_BITMAP(fid_bitmap, MV88E6XXX_N_FID); |
378 | struct mv88e6xxx_devlink_atu_entry *table; |
379 | struct mv88e6xxx_chip *chip = ds->priv; |
380 | int fid = -1, count, err; |
381 | |
382 | table = kmalloc_array(n: mv88e6xxx_num_databases(chip), |
383 | size: sizeof(struct mv88e6xxx_devlink_atu_entry), |
384 | GFP_KERNEL); |
385 | if (!table) |
386 | return -ENOMEM; |
387 | |
388 | memset(table, 0, mv88e6xxx_num_databases(chip) * |
389 | sizeof(struct mv88e6xxx_devlink_atu_entry)); |
390 | |
391 | count = 0; |
392 | |
393 | mv88e6xxx_reg_lock(chip); |
394 | |
395 | err = mv88e6xxx_fid_map(chip, bitmap: fid_bitmap); |
396 | if (err) { |
397 | kfree(objp: table); |
398 | goto out; |
399 | } |
400 | |
401 | while (1) { |
402 | fid = find_next_bit(addr: fid_bitmap, MV88E6XXX_N_FID, offset: fid + 1); |
403 | if (fid == MV88E6XXX_N_FID) |
404 | break; |
405 | |
406 | err = mv88e6xxx_region_atu_snapshot_fid(chip, fid, table, |
407 | count: &count); |
408 | if (err) { |
409 | kfree(objp: table); |
410 | goto out; |
411 | } |
412 | } |
413 | *data = (u8 *)table; |
414 | out: |
415 | mv88e6xxx_reg_unlock(chip); |
416 | |
417 | return err; |
418 | } |
419 | |
420 | /** |
421 | * struct mv88e6xxx_devlink_vtu_entry - Devlink VTU entry |
422 | * @fid: Global1/2: FID and VLAN policy. |
423 | * @sid: Global1/3: SID, unknown filters and learning. |
424 | * @op: Global1/5: FID (old chipsets). |
425 | * @vid: Global1/6: VID, valid, and page. |
426 | * @data: Global1/7-9: Membership data and priority override. |
427 | * @resvd: Reserved. Also happens to align the size to 16B. |
428 | * |
429 | * The VTU entry format varies between chipset generations, the |
430 | * descriptions above represent the superset of all possible |
431 | * information, not all fields are valid on all devices. Since this is |
432 | * a low-level debug interface, copy all data verbatim and defer |
433 | * parsing to the consumer. |
434 | */ |
435 | struct mv88e6xxx_devlink_vtu_entry { |
436 | u16 fid; |
437 | u16 sid; |
438 | u16 op; |
439 | u16 vid; |
440 | u16 data[3]; |
441 | u16 resvd; |
442 | }; |
443 | |
444 | static int mv88e6xxx_region_vtu_snapshot(struct devlink *dl, |
445 | const struct devlink_region_ops *ops, |
446 | struct netlink_ext_ack *extack, |
447 | u8 **data) |
448 | { |
449 | struct mv88e6xxx_devlink_vtu_entry *table, *entry; |
450 | struct dsa_switch *ds = dsa_devlink_to_ds(dl); |
451 | struct mv88e6xxx_chip *chip = ds->priv; |
452 | struct mv88e6xxx_vtu_entry vlan; |
453 | int err; |
454 | |
455 | table = kcalloc(n: mv88e6xxx_max_vid(chip) + 1, |
456 | size: sizeof(struct mv88e6xxx_devlink_vtu_entry), |
457 | GFP_KERNEL); |
458 | if (!table) |
459 | return -ENOMEM; |
460 | |
461 | entry = table; |
462 | vlan.vid = mv88e6xxx_max_vid(chip); |
463 | vlan.valid = false; |
464 | |
465 | mv88e6xxx_reg_lock(chip); |
466 | |
467 | do { |
468 | err = mv88e6xxx_g1_vtu_getnext(chip, entry: &vlan); |
469 | if (err) |
470 | break; |
471 | |
472 | if (!vlan.valid) |
473 | break; |
474 | |
475 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_FID, |
476 | val: &entry->fid); |
477 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_SID, |
478 | val: &entry->sid); |
479 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_OP, |
480 | val: &entry->op); |
481 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_VID, |
482 | val: &entry->vid); |
483 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1, |
484 | val: &entry->data[0]); |
485 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA2, |
486 | val: &entry->data[1]); |
487 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA3, |
488 | val: &entry->data[2]); |
489 | if (err) |
490 | break; |
491 | |
492 | entry++; |
493 | } while (vlan.vid < mv88e6xxx_max_vid(chip)); |
494 | |
495 | mv88e6xxx_reg_unlock(chip); |
496 | |
497 | if (err) { |
498 | kfree(objp: table); |
499 | return err; |
500 | } |
501 | |
502 | *data = (u8 *)table; |
503 | return 0; |
504 | } |
505 | |
506 | /** |
507 | * struct mv88e6xxx_devlink_stu_entry - Devlink STU entry |
508 | * @sid: Global1/3: SID, unknown filters and learning. |
509 | * @vid: Global1/6: Valid bit. |
510 | * @data: Global1/7-9: Membership data and priority override. |
511 | * @resvd: Reserved. In case we forgot something. |
512 | * |
513 | * The STU entry format varies between chipset generations. Peridot |
514 | * and Amethyst packs the STU data into Global1/7-8. Older silicon |
515 | * spreads the information across all three VTU data registers - |
516 | * inheriting the layout of even older hardware that had no STU at |
517 | * all. Since this is a low-level debug interface, copy all data |
518 | * verbatim and defer parsing to the consumer. |
519 | */ |
520 | struct mv88e6xxx_devlink_stu_entry { |
521 | u16 sid; |
522 | u16 vid; |
523 | u16 data[3]; |
524 | u16 resvd; |
525 | }; |
526 | |
527 | static int mv88e6xxx_region_stu_snapshot(struct devlink *dl, |
528 | const struct devlink_region_ops *ops, |
529 | struct netlink_ext_ack *extack, |
530 | u8 **data) |
531 | { |
532 | struct mv88e6xxx_devlink_stu_entry *table, *entry; |
533 | struct dsa_switch *ds = dsa_devlink_to_ds(dl); |
534 | struct mv88e6xxx_chip *chip = ds->priv; |
535 | struct mv88e6xxx_stu_entry stu; |
536 | int err; |
537 | |
538 | table = kcalloc(n: mv88e6xxx_max_sid(chip) + 1, |
539 | size: sizeof(struct mv88e6xxx_devlink_stu_entry), |
540 | GFP_KERNEL); |
541 | if (!table) |
542 | return -ENOMEM; |
543 | |
544 | entry = table; |
545 | stu.sid = mv88e6xxx_max_sid(chip); |
546 | stu.valid = false; |
547 | |
548 | mv88e6xxx_reg_lock(chip); |
549 | |
550 | do { |
551 | err = mv88e6xxx_g1_stu_getnext(chip, entry: &stu); |
552 | if (err) |
553 | break; |
554 | |
555 | if (!stu.valid) |
556 | break; |
557 | |
558 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_SID, |
559 | val: &entry->sid); |
560 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_VID, |
561 | val: &entry->vid); |
562 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1, |
563 | val: &entry->data[0]); |
564 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA2, |
565 | val: &entry->data[1]); |
566 | err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA3, |
567 | val: &entry->data[2]); |
568 | if (err) |
569 | break; |
570 | |
571 | entry++; |
572 | } while (stu.sid < mv88e6xxx_max_sid(chip)); |
573 | |
574 | mv88e6xxx_reg_unlock(chip); |
575 | |
576 | if (err) { |
577 | kfree(objp: table); |
578 | return err; |
579 | } |
580 | |
581 | *data = (u8 *)table; |
582 | return 0; |
583 | } |
584 | |
585 | static int mv88e6xxx_region_pvt_snapshot(struct devlink *dl, |
586 | const struct devlink_region_ops *ops, |
587 | struct netlink_ext_ack *extack, |
588 | u8 **data) |
589 | { |
590 | struct dsa_switch *ds = dsa_devlink_to_ds(dl); |
591 | struct mv88e6xxx_chip *chip = ds->priv; |
592 | int dev, port, err; |
593 | u16 *pvt, *cur; |
594 | |
595 | pvt = kcalloc(MV88E6XXX_MAX_PVT_ENTRIES, size: sizeof(*pvt), GFP_KERNEL); |
596 | if (!pvt) |
597 | return -ENOMEM; |
598 | |
599 | mv88e6xxx_reg_lock(chip); |
600 | |
601 | cur = pvt; |
602 | for (dev = 0; dev < MV88E6XXX_MAX_PVT_SWITCHES; dev++) { |
603 | for (port = 0; port < MV88E6XXX_MAX_PVT_PORTS; port++) { |
604 | err = mv88e6xxx_g2_pvt_read(chip, src_dev: dev, src_port: port, data: cur); |
605 | if (err) |
606 | break; |
607 | |
608 | cur++; |
609 | } |
610 | } |
611 | |
612 | mv88e6xxx_reg_unlock(chip); |
613 | |
614 | if (err) { |
615 | kfree(objp: pvt); |
616 | return err; |
617 | } |
618 | |
619 | *data = (u8 *)pvt; |
620 | return 0; |
621 | } |
622 | |
623 | static int mv88e6xxx_region_port_snapshot(struct devlink_port *devlink_port, |
624 | const struct devlink_port_region_ops *ops, |
625 | struct netlink_ext_ack *extack, |
626 | u8 **data) |
627 | { |
628 | struct dsa_switch *ds = dsa_devlink_port_to_ds(port: devlink_port); |
629 | int port = dsa_devlink_port_to_port(port: devlink_port); |
630 | struct mv88e6xxx_chip *chip = ds->priv; |
631 | u16 *registers; |
632 | int i, err; |
633 | |
634 | registers = kmalloc_array(n: 32, size: sizeof(u16), GFP_KERNEL); |
635 | if (!registers) |
636 | return -ENOMEM; |
637 | |
638 | mv88e6xxx_reg_lock(chip); |
639 | for (i = 0; i < 32; i++) { |
640 | err = mv88e6xxx_port_read(chip, port, reg: i, val: ®isters[i]); |
641 | if (err) { |
642 | kfree(objp: registers); |
643 | goto out; |
644 | } |
645 | } |
646 | *data = (u8 *)registers; |
647 | out: |
648 | mv88e6xxx_reg_unlock(chip); |
649 | |
650 | return err; |
651 | } |
652 | |
653 | static struct mv88e6xxx_region_priv mv88e6xxx_region_global1_priv = { |
654 | .id = MV88E6XXX_REGION_GLOBAL1, |
655 | }; |
656 | |
657 | static struct devlink_region_ops mv88e6xxx_region_global1_ops = { |
658 | .name = "global1" , |
659 | .snapshot = mv88e6xxx_region_global_snapshot, |
660 | .destructor = kfree, |
661 | .priv = &mv88e6xxx_region_global1_priv, |
662 | }; |
663 | |
664 | static struct mv88e6xxx_region_priv mv88e6xxx_region_global2_priv = { |
665 | .id = MV88E6XXX_REGION_GLOBAL2, |
666 | }; |
667 | |
668 | static struct devlink_region_ops mv88e6xxx_region_global2_ops = { |
669 | .name = "global2" , |
670 | .snapshot = mv88e6xxx_region_global_snapshot, |
671 | .destructor = kfree, |
672 | .priv = &mv88e6xxx_region_global2_priv, |
673 | }; |
674 | |
675 | static struct devlink_region_ops mv88e6xxx_region_atu_ops = { |
676 | .name = "atu" , |
677 | .snapshot = mv88e6xxx_region_atu_snapshot, |
678 | .destructor = kfree, |
679 | }; |
680 | |
681 | static struct devlink_region_ops mv88e6xxx_region_vtu_ops = { |
682 | .name = "vtu" , |
683 | .snapshot = mv88e6xxx_region_vtu_snapshot, |
684 | .destructor = kfree, |
685 | }; |
686 | |
687 | static struct devlink_region_ops mv88e6xxx_region_stu_ops = { |
688 | .name = "stu" , |
689 | .snapshot = mv88e6xxx_region_stu_snapshot, |
690 | .destructor = kfree, |
691 | }; |
692 | |
693 | static struct devlink_region_ops mv88e6xxx_region_pvt_ops = { |
694 | .name = "pvt" , |
695 | .snapshot = mv88e6xxx_region_pvt_snapshot, |
696 | .destructor = kfree, |
697 | }; |
698 | |
699 | static const struct devlink_port_region_ops mv88e6xxx_region_port_ops = { |
700 | .name = "port" , |
701 | .snapshot = mv88e6xxx_region_port_snapshot, |
702 | .destructor = kfree, |
703 | }; |
704 | |
705 | struct mv88e6xxx_region { |
706 | struct devlink_region_ops *ops; |
707 | u64 size; |
708 | |
709 | bool (*cond)(struct mv88e6xxx_chip *chip); |
710 | }; |
711 | |
712 | static struct mv88e6xxx_region mv88e6xxx_regions[] = { |
713 | [MV88E6XXX_REGION_GLOBAL1] = { |
714 | .ops = &mv88e6xxx_region_global1_ops, |
715 | .size = 32 * sizeof(u16) |
716 | }, |
717 | [MV88E6XXX_REGION_GLOBAL2] = { |
718 | .ops = &mv88e6xxx_region_global2_ops, |
719 | .size = 32 * sizeof(u16) }, |
720 | [MV88E6XXX_REGION_ATU] = { |
721 | .ops = &mv88e6xxx_region_atu_ops |
722 | /* calculated at runtime */ |
723 | }, |
724 | [MV88E6XXX_REGION_VTU] = { |
725 | .ops = &mv88e6xxx_region_vtu_ops |
726 | /* calculated at runtime */ |
727 | }, |
728 | [MV88E6XXX_REGION_STU] = { |
729 | .ops = &mv88e6xxx_region_stu_ops, |
730 | .cond = mv88e6xxx_has_stu, |
731 | /* calculated at runtime */ |
732 | }, |
733 | [MV88E6XXX_REGION_PVT] = { |
734 | .ops = &mv88e6xxx_region_pvt_ops, |
735 | .size = MV88E6XXX_MAX_PVT_ENTRIES * sizeof(u16), |
736 | .cond = mv88e6xxx_has_pvt, |
737 | }, |
738 | }; |
739 | |
740 | void mv88e6xxx_teardown_devlink_regions_global(struct dsa_switch *ds) |
741 | { |
742 | struct mv88e6xxx_chip *chip = ds->priv; |
743 | int i; |
744 | |
745 | for (i = 0; i < ARRAY_SIZE(mv88e6xxx_regions); i++) |
746 | dsa_devlink_region_destroy(region: chip->regions[i]); |
747 | } |
748 | |
749 | void mv88e6xxx_teardown_devlink_regions_port(struct dsa_switch *ds, int port) |
750 | { |
751 | struct mv88e6xxx_chip *chip = ds->priv; |
752 | |
753 | dsa_devlink_region_destroy(region: chip->ports[port].region); |
754 | } |
755 | |
756 | int mv88e6xxx_setup_devlink_regions_port(struct dsa_switch *ds, int port) |
757 | { |
758 | struct mv88e6xxx_chip *chip = ds->priv; |
759 | struct devlink_region *region; |
760 | |
761 | region = dsa_devlink_port_region_create(ds, |
762 | port, |
763 | ops: &mv88e6xxx_region_port_ops, region_max_snapshots: 1, |
764 | region_size: 32 * sizeof(u16)); |
765 | if (IS_ERR(ptr: region)) |
766 | return PTR_ERR(ptr: region); |
767 | |
768 | chip->ports[port].region = region; |
769 | |
770 | return 0; |
771 | } |
772 | |
773 | int mv88e6xxx_setup_devlink_regions_global(struct dsa_switch *ds) |
774 | { |
775 | bool (*cond)(struct mv88e6xxx_chip *chip); |
776 | struct mv88e6xxx_chip *chip = ds->priv; |
777 | struct devlink_region_ops *ops; |
778 | struct devlink_region *region; |
779 | u64 size; |
780 | int i, j; |
781 | |
782 | for (i = 0; i < ARRAY_SIZE(mv88e6xxx_regions); i++) { |
783 | ops = mv88e6xxx_regions[i].ops; |
784 | size = mv88e6xxx_regions[i].size; |
785 | cond = mv88e6xxx_regions[i].cond; |
786 | |
787 | if (cond && !cond(chip)) |
788 | continue; |
789 | |
790 | switch (i) { |
791 | case MV88E6XXX_REGION_ATU: |
792 | size = mv88e6xxx_num_databases(chip) * |
793 | sizeof(struct mv88e6xxx_devlink_atu_entry); |
794 | break; |
795 | case MV88E6XXX_REGION_VTU: |
796 | size = (mv88e6xxx_max_vid(chip) + 1) * |
797 | sizeof(struct mv88e6xxx_devlink_vtu_entry); |
798 | break; |
799 | case MV88E6XXX_REGION_STU: |
800 | size = (mv88e6xxx_max_sid(chip) + 1) * |
801 | sizeof(struct mv88e6xxx_devlink_stu_entry); |
802 | break; |
803 | } |
804 | |
805 | region = dsa_devlink_region_create(ds, ops, region_max_snapshots: 1, region_size: size); |
806 | if (IS_ERR(ptr: region)) |
807 | goto out; |
808 | chip->regions[i] = region; |
809 | } |
810 | return 0; |
811 | |
812 | out: |
813 | for (j = 0; j < i; j++) |
814 | dsa_devlink_region_destroy(region: chip->regions[j]); |
815 | |
816 | return PTR_ERR(ptr: region); |
817 | } |
818 | |
819 | int mv88e6xxx_devlink_info_get(struct dsa_switch *ds, |
820 | struct devlink_info_req *req, |
821 | struct netlink_ext_ack *extack) |
822 | { |
823 | struct mv88e6xxx_chip *chip = ds->priv; |
824 | |
825 | return devlink_info_version_fixed_put(req, |
826 | DEVLINK_INFO_VERSION_GENERIC_ASIC_ID, |
827 | version_value: chip->info->name); |
828 | } |
829 | |