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
16struct uapi_priv_data {
17	struct fsl_mc_uapi *uapi;
18	struct fsl_mc_io *mc_io;
19};
20
21struct 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
32enum 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
80static 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
374static 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(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(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
445static 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(&mc_cmd, (void __user *)arg, sizeof(mc_cmd));
452	if (error)
453		return -EFAULT;
454
455	error = fsl_mc_command_check(mc_dev, &mc_cmd);
456	if (error)
457		return error;
458
459	error = mc_send_command(mc_io, &mc_cmd);
460	if (error)
461		return error;
462
463	error = copy_to_user((void __user *)arg, &mc_cmd, sizeof(mc_cmd));
464	if (error)
465		return -EFAULT;
466
467	return 0;
468}
469
470static 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(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(root_mc_device, 0,
494					       &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(&mc_uapi->mutex);
507
508	return 0;
509
510error_portal_allocate:
511	mutex_unlock(&mc_uapi->mutex);
512	kfree(priv_data);
513
514	return error;
515}
516
517static 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(filep->private_data);
535	filep->private_data =  NULL;
536
537	mutex_unlock(&mc_uapi->mutex);
538
539	return 0;
540}
541
542static 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(root_mc_device, arg, 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
566static 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
573int 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(&mc_dev->dev);
581	mc_uapi->misc.fops = &fsl_mc_uapi_dev_fops;
582
583	error = misc_register(&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
594void fsl_mc_uapi_remove_device_file(struct fsl_mc_bus *mc_bus)
595{
596	misc_deregister(&mc_bus->uapi_misc.misc);
597}
598