1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Management Complex (MC) userspace support |
4 | * |
5 | * Copyright 2021 NXP |
6 | * |
7 | */ |
8 | |
9 | #include <linux/slab.h> |
10 | #include <linux/fs.h> |
11 | #include <linux/uaccess.h> |
12 | #include <linux/miscdevice.h> |
13 | |
14 | #include "fsl-mc-private.h" |
15 | |
16 | struct uapi_priv_data { |
17 | struct fsl_mc_uapi *uapi; |
18 | struct fsl_mc_io *mc_io; |
19 | }; |
20 | |
21 | struct fsl_mc_cmd_desc { |
22 | u16 cmdid_value; |
23 | u16 cmdid_mask; |
24 | int size; |
25 | bool token; |
26 | int flags; |
27 | }; |
28 | |
29 | #define FSL_MC_CHECK_MODULE_ID BIT(0) |
30 | #define FSL_MC_CAP_NET_ADMIN_NEEDED BIT(1) |
31 | |
32 | enum fsl_mc_cmd_index { |
33 | DPDBG_DUMP = 0, |
34 | DPDBG_SET, |
35 | DPRC_GET_CONTAINER_ID, |
36 | DPRC_CREATE_CONT, |
37 | DPRC_DESTROY_CONT, |
38 | DPRC_ASSIGN, |
39 | DPRC_UNASSIGN, |
40 | DPRC_GET_OBJ_COUNT, |
41 | DPRC_GET_OBJ, |
42 | DPRC_GET_RES_COUNT, |
43 | DPRC_GET_RES_IDS, |
44 | DPRC_SET_OBJ_LABEL, |
45 | DPRC_SET_LOCKED, |
46 | DPRC_CONNECT, |
47 | DPRC_DISCONNECT, |
48 | DPRC_GET_POOL, |
49 | DPRC_GET_POOL_COUNT, |
50 | DPRC_GET_CONNECTION, |
51 | DPCI_GET_LINK_STATE, |
52 | DPCI_GET_PEER_ATTR, |
53 | DPAIOP_GET_SL_VERSION, |
54 | DPAIOP_GET_STATE, |
55 | DPMNG_GET_VERSION, |
56 | DPSECI_GET_TX_QUEUE, |
57 | DPMAC_GET_COUNTER, |
58 | DPMAC_GET_MAC_ADDR, |
59 | DPNI_SET_PRIM_MAC, |
60 | DPNI_GET_PRIM_MAC, |
61 | DPNI_GET_STATISTICS, |
62 | DPNI_GET_LINK_STATE, |
63 | DPNI_GET_MAX_FRAME_LENGTH, |
64 | DPSW_GET_TAILDROP, |
65 | DPSW_SET_TAILDROP, |
66 | DPSW_IF_GET_COUNTER, |
67 | DPSW_IF_GET_MAX_FRAME_LENGTH, |
68 | DPDMUX_GET_COUNTER, |
69 | DPDMUX_IF_GET_MAX_FRAME_LENGTH, |
70 | GET_ATTR, |
71 | GET_IRQ_MASK, |
72 | GET_IRQ_STATUS, |
73 | CLOSE, |
74 | OPEN, |
75 | GET_API_VERSION, |
76 | DESTROY, |
77 | CREATE, |
78 | }; |
79 | |
80 | static struct fsl_mc_cmd_desc fsl_mc_accepted_cmds[] = { |
81 | [DPDBG_DUMP] = { |
82 | .cmdid_value = 0x1300, |
83 | .cmdid_mask = 0xFFF0, |
84 | .token = true, |
85 | .size = 28, |
86 | }, |
87 | [DPDBG_SET] = { |
88 | .cmdid_value = 0x1400, |
89 | .cmdid_mask = 0xFFF0, |
90 | .token = true, |
91 | .size = 28, |
92 | }, |
93 | [DPRC_GET_CONTAINER_ID] = { |
94 | .cmdid_value = 0x8300, |
95 | .cmdid_mask = 0xFFF0, |
96 | .token = false, |
97 | .size = 8, |
98 | }, |
99 | [DPRC_CREATE_CONT] = { |
100 | .cmdid_value = 0x1510, |
101 | .cmdid_mask = 0xFFF0, |
102 | .token = true, |
103 | .size = 40, |
104 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
105 | }, |
106 | [DPRC_DESTROY_CONT] = { |
107 | .cmdid_value = 0x1520, |
108 | .cmdid_mask = 0xFFF0, |
109 | .token = true, |
110 | .size = 12, |
111 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
112 | }, |
113 | [DPRC_ASSIGN] = { |
114 | .cmdid_value = 0x1570, |
115 | .cmdid_mask = 0xFFF0, |
116 | .token = true, |
117 | .size = 40, |
118 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
119 | }, |
120 | [DPRC_UNASSIGN] = { |
121 | .cmdid_value = 0x1580, |
122 | .cmdid_mask = 0xFFF0, |
123 | .token = true, |
124 | .size = 40, |
125 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
126 | }, |
127 | [DPRC_GET_OBJ_COUNT] = { |
128 | .cmdid_value = 0x1590, |
129 | .cmdid_mask = 0xFFF0, |
130 | .token = true, |
131 | .size = 16, |
132 | }, |
133 | [DPRC_GET_OBJ] = { |
134 | .cmdid_value = 0x15A0, |
135 | .cmdid_mask = 0xFFF0, |
136 | .token = true, |
137 | .size = 12, |
138 | }, |
139 | [DPRC_GET_RES_COUNT] = { |
140 | .cmdid_value = 0x15B0, |
141 | .cmdid_mask = 0xFFF0, |
142 | .token = true, |
143 | .size = 32, |
144 | }, |
145 | [DPRC_GET_RES_IDS] = { |
146 | .cmdid_value = 0x15C0, |
147 | .cmdid_mask = 0xFFF0, |
148 | .token = true, |
149 | .size = 40, |
150 | }, |
151 | [DPRC_SET_OBJ_LABEL] = { |
152 | .cmdid_value = 0x1610, |
153 | .cmdid_mask = 0xFFF0, |
154 | .token = true, |
155 | .size = 48, |
156 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
157 | }, |
158 | [DPRC_SET_LOCKED] = { |
159 | .cmdid_value = 0x16B0, |
160 | .cmdid_mask = 0xFFF0, |
161 | .token = true, |
162 | .size = 16, |
163 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
164 | }, |
165 | [DPRC_CONNECT] = { |
166 | .cmdid_value = 0x1670, |
167 | .cmdid_mask = 0xFFF0, |
168 | .token = true, |
169 | .size = 56, |
170 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
171 | }, |
172 | [DPRC_DISCONNECT] = { |
173 | .cmdid_value = 0x1680, |
174 | .cmdid_mask = 0xFFF0, |
175 | .token = true, |
176 | .size = 32, |
177 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
178 | }, |
179 | [DPRC_GET_POOL] = { |
180 | .cmdid_value = 0x1690, |
181 | .cmdid_mask = 0xFFF0, |
182 | .token = true, |
183 | .size = 12, |
184 | }, |
185 | [DPRC_GET_POOL_COUNT] = { |
186 | .cmdid_value = 0x16A0, |
187 | .cmdid_mask = 0xFFF0, |
188 | .token = true, |
189 | .size = 8, |
190 | }, |
191 | [DPRC_GET_CONNECTION] = { |
192 | .cmdid_value = 0x16C0, |
193 | .cmdid_mask = 0xFFF0, |
194 | .token = true, |
195 | .size = 32, |
196 | }, |
197 | |
198 | [DPCI_GET_LINK_STATE] = { |
199 | .cmdid_value = 0x0E10, |
200 | .cmdid_mask = 0xFFF0, |
201 | .token = true, |
202 | .size = 8, |
203 | }, |
204 | [DPCI_GET_PEER_ATTR] = { |
205 | .cmdid_value = 0x0E20, |
206 | .cmdid_mask = 0xFFF0, |
207 | .token = true, |
208 | .size = 8, |
209 | }, |
210 | [DPAIOP_GET_SL_VERSION] = { |
211 | .cmdid_value = 0x2820, |
212 | .cmdid_mask = 0xFFF0, |
213 | .token = true, |
214 | .size = 8, |
215 | }, |
216 | [DPAIOP_GET_STATE] = { |
217 | .cmdid_value = 0x2830, |
218 | .cmdid_mask = 0xFFF0, |
219 | .token = true, |
220 | .size = 8, |
221 | }, |
222 | [DPMNG_GET_VERSION] = { |
223 | .cmdid_value = 0x8310, |
224 | .cmdid_mask = 0xFFF0, |
225 | .token = false, |
226 | .size = 8, |
227 | }, |
228 | [DPSECI_GET_TX_QUEUE] = { |
229 | .cmdid_value = 0x1970, |
230 | .cmdid_mask = 0xFFF0, |
231 | .token = true, |
232 | .size = 14, |
233 | }, |
234 | [DPMAC_GET_COUNTER] = { |
235 | .cmdid_value = 0x0c40, |
236 | .cmdid_mask = 0xFFF0, |
237 | .token = true, |
238 | .size = 9, |
239 | }, |
240 | [DPMAC_GET_MAC_ADDR] = { |
241 | .cmdid_value = 0x0c50, |
242 | .cmdid_mask = 0xFFF0, |
243 | .token = true, |
244 | .size = 8, |
245 | }, |
246 | [DPNI_SET_PRIM_MAC] = { |
247 | .cmdid_value = 0x2240, |
248 | .cmdid_mask = 0xFFF0, |
249 | .token = true, |
250 | .size = 16, |
251 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
252 | }, |
253 | [DPNI_GET_PRIM_MAC] = { |
254 | .cmdid_value = 0x2250, |
255 | .cmdid_mask = 0xFFF0, |
256 | .token = true, |
257 | .size = 8, |
258 | }, |
259 | [DPNI_GET_STATISTICS] = { |
260 | .cmdid_value = 0x25D0, |
261 | .cmdid_mask = 0xFFF0, |
262 | .token = true, |
263 | .size = 10, |
264 | }, |
265 | [DPNI_GET_LINK_STATE] = { |
266 | .cmdid_value = 0x2150, |
267 | .cmdid_mask = 0xFFF0, |
268 | .token = true, |
269 | .size = 8, |
270 | }, |
271 | [DPNI_GET_MAX_FRAME_LENGTH] = { |
272 | .cmdid_value = 0x2170, |
273 | .cmdid_mask = 0xFFF0, |
274 | .token = true, |
275 | .size = 8, |
276 | }, |
277 | [DPSW_GET_TAILDROP] = { |
278 | .cmdid_value = 0x0A80, |
279 | .cmdid_mask = 0xFFF0, |
280 | .token = true, |
281 | .size = 14, |
282 | }, |
283 | [DPSW_SET_TAILDROP] = { |
284 | .cmdid_value = 0x0A90, |
285 | .cmdid_mask = 0xFFF0, |
286 | .token = true, |
287 | .size = 24, |
288 | .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, |
289 | }, |
290 | [DPSW_IF_GET_COUNTER] = { |
291 | .cmdid_value = 0x0340, |
292 | .cmdid_mask = 0xFFF0, |
293 | .token = true, |
294 | .size = 11, |
295 | }, |
296 | [DPSW_IF_GET_MAX_FRAME_LENGTH] = { |
297 | .cmdid_value = 0x0450, |
298 | .cmdid_mask = 0xFFF0, |
299 | .token = true, |
300 | .size = 10, |
301 | }, |
302 | [DPDMUX_GET_COUNTER] = { |
303 | .cmdid_value = 0x0b20, |
304 | .cmdid_mask = 0xFFF0, |
305 | .token = true, |
306 | .size = 11, |
307 | }, |
308 | [DPDMUX_IF_GET_MAX_FRAME_LENGTH] = { |
309 | .cmdid_value = 0x0a20, |
310 | .cmdid_mask = 0xFFF0, |
311 | .token = true, |
312 | .size = 10, |
313 | }, |
314 | [GET_ATTR] = { |
315 | .cmdid_value = 0x0040, |
316 | .cmdid_mask = 0xFFF0, |
317 | .token = true, |
318 | .size = 8, |
319 | }, |
320 | [GET_IRQ_MASK] = { |
321 | .cmdid_value = 0x0150, |
322 | .cmdid_mask = 0xFFF0, |
323 | .token = true, |
324 | .size = 13, |
325 | }, |
326 | [GET_IRQ_STATUS] = { |
327 | .cmdid_value = 0x0160, |
328 | .cmdid_mask = 0xFFF0, |
329 | .token = true, |
330 | .size = 13, |
331 | }, |
332 | [CLOSE] = { |
333 | .cmdid_value = 0x8000, |
334 | .cmdid_mask = 0xFFF0, |
335 | .token = true, |
336 | .size = 8, |
337 | }, |
338 | |
339 | /* Common commands amongst all types of objects. Must be checked last. */ |
340 | [OPEN] = { |
341 | .cmdid_value = 0x8000, |
342 | .cmdid_mask = 0xFC00, |
343 | .token = false, |
344 | .size = 12, |
345 | .flags = FSL_MC_CHECK_MODULE_ID, |
346 | }, |
347 | [GET_API_VERSION] = { |
348 | .cmdid_value = 0xA000, |
349 | .cmdid_mask = 0xFC00, |
350 | .token = false, |
351 | .size = 8, |
352 | .flags = FSL_MC_CHECK_MODULE_ID, |
353 | }, |
354 | [DESTROY] = { |
355 | .cmdid_value = 0x9800, |
356 | .cmdid_mask = 0xFC00, |
357 | .token = true, |
358 | .size = 12, |
359 | .flags = FSL_MC_CHECK_MODULE_ID | FSL_MC_CAP_NET_ADMIN_NEEDED, |
360 | }, |
361 | [CREATE] = { |
362 | .cmdid_value = 0x9000, |
363 | .cmdid_mask = 0xFC00, |
364 | .token = true, |
365 | .size = 64, |
366 | .flags = FSL_MC_CHECK_MODULE_ID | FSL_MC_CAP_NET_ADMIN_NEEDED, |
367 | }, |
368 | }; |
369 | |
370 | #define FSL_MC_NUM_ACCEPTED_CMDS ARRAY_SIZE(fsl_mc_accepted_cmds) |
371 | |
372 | #define FSL_MC_MAX_MODULE_ID 0x10 |
373 | |
374 | static int fsl_mc_command_check(struct fsl_mc_device *mc_dev, |
375 | struct fsl_mc_command *mc_cmd) |
376 | { |
377 | struct fsl_mc_cmd_desc *desc = NULL; |
378 | int mc_cmd_max_size, i; |
379 | bool token_provided; |
380 | u16 cmdid, module_id; |
381 | char *mc_cmd_end; |
382 | char sum = 0; |
383 | |
384 | /* Check if this is an accepted MC command */ |
385 | cmdid = mc_cmd_hdr_read_cmdid(cmd: mc_cmd); |
386 | for (i = 0; i < FSL_MC_NUM_ACCEPTED_CMDS; i++) { |
387 | desc = &fsl_mc_accepted_cmds[i]; |
388 | if ((cmdid & desc->cmdid_mask) == desc->cmdid_value) |
389 | break; |
390 | } |
391 | if (i == FSL_MC_NUM_ACCEPTED_CMDS) { |
392 | dev_err(&mc_dev->dev, "MC command 0x%04x: cmdid not accepted\n" , cmdid); |
393 | return -EACCES; |
394 | } |
395 | |
396 | /* Check if the size of the command is honored. Anything beyond the |
397 | * last valid byte of the command should be zeroed. |
398 | */ |
399 | mc_cmd_max_size = sizeof(*mc_cmd); |
400 | mc_cmd_end = ((char *)mc_cmd) + desc->size; |
401 | for (i = desc->size; i < mc_cmd_max_size; i++) |
402 | sum |= *mc_cmd_end++; |
403 | if (sum) { |
404 | dev_err(&mc_dev->dev, "MC command 0x%04x: garbage beyond max size of %d bytes!\n" , |
405 | cmdid, desc->size); |
406 | return -EACCES; |
407 | } |
408 | |
409 | /* Some MC commands request a token to be passed so that object |
410 | * identification is possible. Check if the token passed in the command |
411 | * is as expected. |
412 | */ |
413 | token_provided = mc_cmd_hdr_read_token(cmd: mc_cmd) ? true : false; |
414 | if (token_provided != desc->token) { |
415 | dev_err(&mc_dev->dev, "MC command 0x%04x: token 0x%04x is invalid!\n" , |
416 | cmdid, mc_cmd_hdr_read_token(mc_cmd)); |
417 | return -EACCES; |
418 | } |
419 | |
420 | /* If needed, check if the module ID passed is valid */ |
421 | if (desc->flags & FSL_MC_CHECK_MODULE_ID) { |
422 | /* The module ID is represented by bits [4:9] from the cmdid */ |
423 | module_id = (cmdid & GENMASK(9, 4)) >> 4; |
424 | if (module_id == 0 || module_id > FSL_MC_MAX_MODULE_ID) { |
425 | dev_err(&mc_dev->dev, "MC command 0x%04x: unknown module ID 0x%x\n" , |
426 | cmdid, module_id); |
427 | return -EACCES; |
428 | } |
429 | } |
430 | |
431 | /* Some commands alter how hardware resources are managed. For these |
432 | * commands, check for CAP_NET_ADMIN. |
433 | */ |
434 | if (desc->flags & FSL_MC_CAP_NET_ADMIN_NEEDED) { |
435 | if (!capable(CAP_NET_ADMIN)) { |
436 | dev_err(&mc_dev->dev, "MC command 0x%04x: needs CAP_NET_ADMIN!\n" , |
437 | cmdid); |
438 | return -EPERM; |
439 | } |
440 | } |
441 | |
442 | return 0; |
443 | } |
444 | |
445 | static int fsl_mc_uapi_send_command(struct fsl_mc_device *mc_dev, unsigned long arg, |
446 | struct fsl_mc_io *mc_io) |
447 | { |
448 | struct fsl_mc_command mc_cmd; |
449 | int error; |
450 | |
451 | error = copy_from_user(to: &mc_cmd, from: (void __user *)arg, n: sizeof(mc_cmd)); |
452 | if (error) |
453 | return -EFAULT; |
454 | |
455 | error = fsl_mc_command_check(mc_dev, mc_cmd: &mc_cmd); |
456 | if (error) |
457 | return error; |
458 | |
459 | error = mc_send_command(mc_io, cmd: &mc_cmd); |
460 | if (error) |
461 | return error; |
462 | |
463 | error = copy_to_user(to: (void __user *)arg, from: &mc_cmd, n: sizeof(mc_cmd)); |
464 | if (error) |
465 | return -EFAULT; |
466 | |
467 | return 0; |
468 | } |
469 | |
470 | static int fsl_mc_uapi_dev_open(struct inode *inode, struct file *filep) |
471 | { |
472 | struct fsl_mc_device *root_mc_device; |
473 | struct uapi_priv_data *priv_data; |
474 | struct fsl_mc_io *dynamic_mc_io; |
475 | struct fsl_mc_uapi *mc_uapi; |
476 | struct fsl_mc_bus *mc_bus; |
477 | int error; |
478 | |
479 | priv_data = kzalloc(size: sizeof(*priv_data), GFP_KERNEL); |
480 | if (!priv_data) |
481 | return -ENOMEM; |
482 | |
483 | mc_uapi = container_of(filep->private_data, struct fsl_mc_uapi, misc); |
484 | mc_bus = container_of(mc_uapi, struct fsl_mc_bus, uapi_misc); |
485 | root_mc_device = &mc_bus->mc_dev; |
486 | |
487 | mutex_lock(&mc_uapi->mutex); |
488 | |
489 | if (!mc_uapi->local_instance_in_use) { |
490 | priv_data->mc_io = mc_uapi->static_mc_io; |
491 | mc_uapi->local_instance_in_use = 1; |
492 | } else { |
493 | error = fsl_mc_portal_allocate(mc_dev: root_mc_device, mc_io_flags: 0, |
494 | new_mc_io: &dynamic_mc_io); |
495 | if (error) { |
496 | dev_dbg(&root_mc_device->dev, |
497 | "Could not allocate MC portal\n" ); |
498 | goto error_portal_allocate; |
499 | } |
500 | |
501 | priv_data->mc_io = dynamic_mc_io; |
502 | } |
503 | priv_data->uapi = mc_uapi; |
504 | filep->private_data = priv_data; |
505 | |
506 | mutex_unlock(lock: &mc_uapi->mutex); |
507 | |
508 | return 0; |
509 | |
510 | error_portal_allocate: |
511 | mutex_unlock(lock: &mc_uapi->mutex); |
512 | kfree(objp: priv_data); |
513 | |
514 | return error; |
515 | } |
516 | |
517 | static int fsl_mc_uapi_dev_release(struct inode *inode, struct file *filep) |
518 | { |
519 | struct uapi_priv_data *priv_data; |
520 | struct fsl_mc_uapi *mc_uapi; |
521 | struct fsl_mc_io *mc_io; |
522 | |
523 | priv_data = filep->private_data; |
524 | mc_uapi = priv_data->uapi; |
525 | mc_io = priv_data->mc_io; |
526 | |
527 | mutex_lock(&mc_uapi->mutex); |
528 | |
529 | if (mc_io == mc_uapi->static_mc_io) |
530 | mc_uapi->local_instance_in_use = 0; |
531 | else |
532 | fsl_mc_portal_free(mc_io); |
533 | |
534 | kfree(objp: filep->private_data); |
535 | filep->private_data = NULL; |
536 | |
537 | mutex_unlock(lock: &mc_uapi->mutex); |
538 | |
539 | return 0; |
540 | } |
541 | |
542 | static long fsl_mc_uapi_dev_ioctl(struct file *file, |
543 | unsigned int cmd, |
544 | unsigned long arg) |
545 | { |
546 | struct uapi_priv_data *priv_data = file->private_data; |
547 | struct fsl_mc_device *root_mc_device; |
548 | struct fsl_mc_bus *mc_bus; |
549 | int error; |
550 | |
551 | mc_bus = container_of(priv_data->uapi, struct fsl_mc_bus, uapi_misc); |
552 | root_mc_device = &mc_bus->mc_dev; |
553 | |
554 | switch (cmd) { |
555 | case FSL_MC_SEND_MC_COMMAND: |
556 | error = fsl_mc_uapi_send_command(mc_dev: root_mc_device, arg, mc_io: priv_data->mc_io); |
557 | break; |
558 | default: |
559 | dev_dbg(&root_mc_device->dev, "unexpected ioctl call number\n" ); |
560 | error = -EINVAL; |
561 | } |
562 | |
563 | return error; |
564 | } |
565 | |
566 | static const struct file_operations fsl_mc_uapi_dev_fops = { |
567 | .owner = THIS_MODULE, |
568 | .open = fsl_mc_uapi_dev_open, |
569 | .release = fsl_mc_uapi_dev_release, |
570 | .unlocked_ioctl = fsl_mc_uapi_dev_ioctl, |
571 | }; |
572 | |
573 | int fsl_mc_uapi_create_device_file(struct fsl_mc_bus *mc_bus) |
574 | { |
575 | struct fsl_mc_device *mc_dev = &mc_bus->mc_dev; |
576 | struct fsl_mc_uapi *mc_uapi = &mc_bus->uapi_misc; |
577 | int error; |
578 | |
579 | mc_uapi->misc.minor = MISC_DYNAMIC_MINOR; |
580 | mc_uapi->misc.name = dev_name(dev: &mc_dev->dev); |
581 | mc_uapi->misc.fops = &fsl_mc_uapi_dev_fops; |
582 | |
583 | error = misc_register(misc: &mc_uapi->misc); |
584 | if (error) |
585 | return error; |
586 | |
587 | mc_uapi->static_mc_io = mc_bus->mc_dev.mc_io; |
588 | |
589 | mutex_init(&mc_uapi->mutex); |
590 | |
591 | return 0; |
592 | } |
593 | |
594 | void fsl_mc_uapi_remove_device_file(struct fsl_mc_bus *mc_bus) |
595 | { |
596 | misc_deregister(misc: &mc_bus->uapi_misc.misc); |
597 | } |
598 | |