| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Management Complex (MC) userspace support | |
| * | |
| * Copyright 2021 NXP | |
| * | |
| */ | |
| #include <linux/slab.h> | |
| #include <linux/fs.h> | |
| #include <linux/uaccess.h> | |
| #include <linux/miscdevice.h> | |
| #include "fsl-mc-private.h" | |
| struct uapi_priv_data { | |
| struct fsl_mc_uapi *uapi; | |
| struct fsl_mc_io *mc_io; | |
| }; | |
| struct fsl_mc_cmd_desc { | |
| u16 cmdid_value; | |
| u16 cmdid_mask; | |
| int size; | |
| bool token; | |
| int flags; | |
| }; | |
| #define FSL_MC_CHECK_MODULE_ID BIT(0) | |
| #define FSL_MC_CAP_NET_ADMIN_NEEDED BIT(1) | |
| enum fsl_mc_cmd_index { | |
| DPDBG_DUMP = 0, | |
| DPDBG_SET, | |
| DPRC_GET_CONTAINER_ID, | |
| DPRC_CREATE_CONT, | |
| DPRC_DESTROY_CONT, | |
| DPRC_ASSIGN, | |
| DPRC_UNASSIGN, | |
| DPRC_GET_OBJ_COUNT, | |
| DPRC_GET_OBJ, | |
| DPRC_GET_RES_COUNT, | |
| DPRC_GET_RES_IDS, | |
| DPRC_SET_OBJ_LABEL, | |
| DPRC_SET_LOCKED, | |
| DPRC_CONNECT, | |
| DPRC_DISCONNECT, | |
| DPRC_GET_POOL, | |
| DPRC_GET_POOL_COUNT, | |
| DPRC_GET_CONNECTION, | |
| DPCI_GET_LINK_STATE, | |
| DPCI_GET_PEER_ATTR, | |
| DPAIOP_GET_SL_VERSION, | |
| DPAIOP_GET_STATE, | |
| DPMNG_GET_VERSION, | |
| DPSECI_GET_TX_QUEUE, | |
| DPMAC_GET_COUNTER, | |
| DPMAC_GET_MAC_ADDR, | |
| DPNI_SET_PRIM_MAC, | |
| DPNI_GET_PRIM_MAC, | |
| DPNI_GET_STATISTICS, | |
| DPNI_GET_LINK_STATE, | |
| DPNI_GET_MAX_FRAME_LENGTH, | |
| DPSW_GET_TAILDROP, | |
| DPSW_SET_TAILDROP, | |
| DPSW_IF_GET_COUNTER, | |
| DPSW_IF_GET_MAX_FRAME_LENGTH, | |
| DPDMUX_GET_COUNTER, | |
| DPDMUX_IF_GET_MAX_FRAME_LENGTH, | |
| GET_ATTR, | |
| GET_IRQ_MASK, | |
| GET_IRQ_STATUS, | |
| CLOSE, | |
| OPEN, | |
| GET_API_VERSION, | |
| DESTROY, | |
| CREATE, | |
| }; | |
| static struct fsl_mc_cmd_desc fsl_mc_accepted_cmds[] = { | |
| [DPDBG_DUMP] = { | |
| .cmdid_value = 0x1300, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 28, | |
| }, | |
| [DPDBG_SET] = { | |
| .cmdid_value = 0x1400, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 28, | |
| }, | |
| [DPRC_GET_CONTAINER_ID] = { | |
| .cmdid_value = 0x8300, | |
| .cmdid_mask = 0xFFF0, | |
| .token = false, | |
| .size = 8, | |
| }, | |
| [DPRC_CREATE_CONT] = { | |
| .cmdid_value = 0x1510, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 40, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPRC_DESTROY_CONT] = { | |
| .cmdid_value = 0x1520, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 12, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPRC_ASSIGN] = { | |
| .cmdid_value = 0x1570, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 40, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPRC_UNASSIGN] = { | |
| .cmdid_value = 0x1580, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 40, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPRC_GET_OBJ_COUNT] = { | |
| .cmdid_value = 0x1590, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 16, | |
| }, | |
| [DPRC_GET_OBJ] = { | |
| .cmdid_value = 0x15A0, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 12, | |
| }, | |
| [DPRC_GET_RES_COUNT] = { | |
| .cmdid_value = 0x15B0, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 32, | |
| }, | |
| [DPRC_GET_RES_IDS] = { | |
| .cmdid_value = 0x15C0, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 40, | |
| }, | |
| [DPRC_SET_OBJ_LABEL] = { | |
| .cmdid_value = 0x1610, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 48, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPRC_SET_LOCKED] = { | |
| .cmdid_value = 0x16B0, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 16, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPRC_CONNECT] = { | |
| .cmdid_value = 0x1670, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 56, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPRC_DISCONNECT] = { | |
| .cmdid_value = 0x1680, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 32, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPRC_GET_POOL] = { | |
| .cmdid_value = 0x1690, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 12, | |
| }, | |
| [DPRC_GET_POOL_COUNT] = { | |
| .cmdid_value = 0x16A0, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [DPRC_GET_CONNECTION] = { | |
| .cmdid_value = 0x16C0, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 32, | |
| }, | |
| [DPCI_GET_LINK_STATE] = { | |
| .cmdid_value = 0x0E10, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [DPCI_GET_PEER_ATTR] = { | |
| .cmdid_value = 0x0E20, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [DPAIOP_GET_SL_VERSION] = { | |
| .cmdid_value = 0x2820, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [DPAIOP_GET_STATE] = { | |
| .cmdid_value = 0x2830, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [DPMNG_GET_VERSION] = { | |
| .cmdid_value = 0x8310, | |
| .cmdid_mask = 0xFFF0, | |
| .token = false, | |
| .size = 8, | |
| }, | |
| [DPSECI_GET_TX_QUEUE] = { | |
| .cmdid_value = 0x1970, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 14, | |
| }, | |
| [DPMAC_GET_COUNTER] = { | |
| .cmdid_value = 0x0c40, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 9, | |
| }, | |
| [DPMAC_GET_MAC_ADDR] = { | |
| .cmdid_value = 0x0c50, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [DPNI_SET_PRIM_MAC] = { | |
| .cmdid_value = 0x2240, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 16, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPNI_GET_PRIM_MAC] = { | |
| .cmdid_value = 0x2250, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [DPNI_GET_STATISTICS] = { | |
| .cmdid_value = 0x25D0, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 10, | |
| }, | |
| [DPNI_GET_LINK_STATE] = { | |
| .cmdid_value = 0x2150, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [DPNI_GET_MAX_FRAME_LENGTH] = { | |
| .cmdid_value = 0x2170, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [DPSW_GET_TAILDROP] = { | |
| .cmdid_value = 0x0A80, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 14, | |
| }, | |
| [DPSW_SET_TAILDROP] = { | |
| .cmdid_value = 0x0A90, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 24, | |
| .flags = FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [DPSW_IF_GET_COUNTER] = { | |
| .cmdid_value = 0x0340, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 11, | |
| }, | |
| [DPSW_IF_GET_MAX_FRAME_LENGTH] = { | |
| .cmdid_value = 0x0450, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 10, | |
| }, | |
| [DPDMUX_GET_COUNTER] = { | |
| .cmdid_value = 0x0b20, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 11, | |
| }, | |
| [DPDMUX_IF_GET_MAX_FRAME_LENGTH] = { | |
| .cmdid_value = 0x0a20, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 10, | |
| }, | |
| [GET_ATTR] = { | |
| .cmdid_value = 0x0040, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| [GET_IRQ_MASK] = { | |
| .cmdid_value = 0x0150, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 13, | |
| }, | |
| [GET_IRQ_STATUS] = { | |
| .cmdid_value = 0x0160, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 13, | |
| }, | |
| [CLOSE] = { | |
| .cmdid_value = 0x8000, | |
| .cmdid_mask = 0xFFF0, | |
| .token = true, | |
| .size = 8, | |
| }, | |
| /* Common commands amongst all types of objects. Must be checked last. */ | |
| [OPEN] = { | |
| .cmdid_value = 0x8000, | |
| .cmdid_mask = 0xFC00, | |
| .token = false, | |
| .size = 12, | |
| .flags = FSL_MC_CHECK_MODULE_ID, | |
| }, | |
| [GET_API_VERSION] = { | |
| .cmdid_value = 0xA000, | |
| .cmdid_mask = 0xFC00, | |
| .token = false, | |
| .size = 8, | |
| .flags = FSL_MC_CHECK_MODULE_ID, | |
| }, | |
| [DESTROY] = { | |
| .cmdid_value = 0x9800, | |
| .cmdid_mask = 0xFC00, | |
| .token = true, | |
| .size = 12, | |
| .flags = FSL_MC_CHECK_MODULE_ID | FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| [CREATE] = { | |
| .cmdid_value = 0x9000, | |
| .cmdid_mask = 0xFC00, | |
| .token = true, | |
| .size = 64, | |
| .flags = FSL_MC_CHECK_MODULE_ID | FSL_MC_CAP_NET_ADMIN_NEEDED, | |
| }, | |
| }; | |
| #define FSL_MC_NUM_ACCEPTED_CMDS ARRAY_SIZE(fsl_mc_accepted_cmds) | |
| #define FSL_MC_MAX_MODULE_ID 0x10 | |
| static int fsl_mc_command_check(struct fsl_mc_device *mc_dev, | |
| struct fsl_mc_command *mc_cmd) | |
| { | |
| struct fsl_mc_cmd_desc *desc = NULL; | |
| int mc_cmd_max_size, i; | |
| bool token_provided; | |
| u16 cmdid, module_id; | |
| char *mc_cmd_end; | |
| char sum = 0; | |
| /* Check if this is an accepted MC command */ | |
| cmdid = mc_cmd_hdr_read_cmdid(mc_cmd); | |
| for (i = 0; i < FSL_MC_NUM_ACCEPTED_CMDS; i++) { | |
| desc = &fsl_mc_accepted_cmds[i]; | |
| if ((cmdid & desc->cmdid_mask) == desc->cmdid_value) | |
| break; | |
| } | |
| if (i == FSL_MC_NUM_ACCEPTED_CMDS) { | |
| dev_err(&mc_dev->dev, "MC command 0x%04x: cmdid not accepted\n", cmdid); | |
| return -EACCES; | |
| } | |
| /* Check if the size of the command is honored. Anything beyond the | |
| * last valid byte of the command should be zeroed. | |
| */ | |
| mc_cmd_max_size = sizeof(*mc_cmd); | |
| mc_cmd_end = ((char *)mc_cmd) + desc->size; | |
| for (i = desc->size; i < mc_cmd_max_size; i++) | |
| sum |= *mc_cmd_end++; | |
| if (sum) { | |
| dev_err(&mc_dev->dev, "MC command 0x%04x: garbage beyond max size of %d bytes!\n", | |
| cmdid, desc->size); | |
| return -EACCES; | |
| } | |
| /* Some MC commands request a token to be passed so that object | |
| * identification is possible. Check if the token passed in the command | |
| * is as expected. | |
| */ | |
| token_provided = mc_cmd_hdr_read_token(mc_cmd) ? true : false; | |
| if (token_provided != desc->token) { | |
| dev_err(&mc_dev->dev, "MC command 0x%04x: token 0x%04x is invalid!\n", | |
| cmdid, mc_cmd_hdr_read_token(mc_cmd)); | |
| return -EACCES; | |
| } | |
| /* If needed, check if the module ID passed is valid */ | |
| if (desc->flags & FSL_MC_CHECK_MODULE_ID) { | |
| /* The module ID is represented by bits [4:9] from the cmdid */ | |
| module_id = (cmdid & GENMASK(9, 4)) >> 4; | |
| if (module_id == 0 || module_id > FSL_MC_MAX_MODULE_ID) { | |
| dev_err(&mc_dev->dev, "MC command 0x%04x: unknown module ID 0x%x\n", | |
| cmdid, module_id); | |
| return -EACCES; | |
| } | |
| } | |
| /* Some commands alter how hardware resources are managed. For these | |
| * commands, check for CAP_NET_ADMIN. | |
| */ | |
| if (desc->flags & FSL_MC_CAP_NET_ADMIN_NEEDED) { | |
| if (!capable(CAP_NET_ADMIN)) { | |
| dev_err(&mc_dev->dev, "MC command 0x%04x: needs CAP_NET_ADMIN!\n", | |
| cmdid); | |
| return -EPERM; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int fsl_mc_uapi_send_command(struct fsl_mc_device *mc_dev, unsigned long arg, | |
| struct fsl_mc_io *mc_io) | |
| { | |
| struct fsl_mc_command mc_cmd; | |
| int error; | |
| error = copy_from_user(&mc_cmd, (void __user *)arg, sizeof(mc_cmd)); | |
| if (error) | |
| return -EFAULT; | |
| error = fsl_mc_command_check(mc_dev, &mc_cmd); | |
| if (error) | |
| return error; | |
| error = mc_send_command(mc_io, &mc_cmd); | |
| if (error) | |
| return error; | |
| error = copy_to_user((void __user *)arg, &mc_cmd, sizeof(mc_cmd)); | |
| if (error) | |
| return -EFAULT; | |
| return 0; | |
| } | |
| static int fsl_mc_uapi_dev_open(struct inode *inode, struct file *filep) | |
| { | |
| struct fsl_mc_device *root_mc_device; | |
| struct uapi_priv_data *priv_data; | |
| struct fsl_mc_io *dynamic_mc_io; | |
| struct fsl_mc_uapi *mc_uapi; | |
| struct fsl_mc_bus *mc_bus; | |
| int error; | |
| priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL); | |
| if (!priv_data) | |
| return -ENOMEM; | |
| mc_uapi = container_of(filep->private_data, struct fsl_mc_uapi, misc); | |
| mc_bus = container_of(mc_uapi, struct fsl_mc_bus, uapi_misc); | |
| root_mc_device = &mc_bus->mc_dev; | |
| mutex_lock(&mc_uapi->mutex); | |
| if (!mc_uapi->local_instance_in_use) { | |
| priv_data->mc_io = mc_uapi->static_mc_io; | |
| mc_uapi->local_instance_in_use = 1; | |
| } else { | |
| error = fsl_mc_portal_allocate(root_mc_device, 0, | |
| &dynamic_mc_io); | |
| if (error) { | |
| dev_dbg(&root_mc_device->dev, | |
| "Could not allocate MC portal\n"); | |
| goto error_portal_allocate; | |
| } | |
| priv_data->mc_io = dynamic_mc_io; | |
| } | |
| priv_data->uapi = mc_uapi; | |
| filep->private_data = priv_data; | |
| mutex_unlock(&mc_uapi->mutex); | |
| return 0; | |
| error_portal_allocate: | |
| mutex_unlock(&mc_uapi->mutex); | |
| kfree(priv_data); | |
| return error; | |
| } | |
| static int fsl_mc_uapi_dev_release(struct inode *inode, struct file *filep) | |
| { | |
| struct uapi_priv_data *priv_data; | |
| struct fsl_mc_uapi *mc_uapi; | |
| struct fsl_mc_io *mc_io; | |
| priv_data = filep->private_data; | |
| mc_uapi = priv_data->uapi; | |
| mc_io = priv_data->mc_io; | |
| mutex_lock(&mc_uapi->mutex); | |
| if (mc_io == mc_uapi->static_mc_io) | |
| mc_uapi->local_instance_in_use = 0; | |
| else | |
| fsl_mc_portal_free(mc_io); | |
| kfree(filep->private_data); | |
| filep->private_data = NULL; | |
| mutex_unlock(&mc_uapi->mutex); | |
| return 0; | |
| } | |
| static long fsl_mc_uapi_dev_ioctl(struct file *file, | |
| unsigned int cmd, | |
| unsigned long arg) | |
| { | |
| struct uapi_priv_data *priv_data = file->private_data; | |
| struct fsl_mc_device *root_mc_device; | |
| struct fsl_mc_bus *mc_bus; | |
| int error; | |
| mc_bus = container_of(priv_data->uapi, struct fsl_mc_bus, uapi_misc); | |
| root_mc_device = &mc_bus->mc_dev; | |
| switch (cmd) { | |
| case FSL_MC_SEND_MC_COMMAND: | |
| error = fsl_mc_uapi_send_command(root_mc_device, arg, priv_data->mc_io); | |
| break; | |
| default: | |
| dev_dbg(&root_mc_device->dev, "unexpected ioctl call number\n"); | |
| error = -EINVAL; | |
| } | |
| return error; | |
| } | |
| static const struct file_operations fsl_mc_uapi_dev_fops = { | |
| .owner = THIS_MODULE, | |
| .open = fsl_mc_uapi_dev_open, | |
| .release = fsl_mc_uapi_dev_release, | |
| .unlocked_ioctl = fsl_mc_uapi_dev_ioctl, | |
| }; | |
| int fsl_mc_uapi_create_device_file(struct fsl_mc_bus *mc_bus) | |
| { | |
| struct fsl_mc_device *mc_dev = &mc_bus->mc_dev; | |
| struct fsl_mc_uapi *mc_uapi = &mc_bus->uapi_misc; | |
| int error; | |
| mc_uapi->misc.minor = MISC_DYNAMIC_MINOR; | |
| mc_uapi->misc.name = dev_name(&mc_dev->dev); | |
| mc_uapi->misc.fops = &fsl_mc_uapi_dev_fops; | |
| error = misc_register(&mc_uapi->misc); | |
| if (error) | |
| return error; | |
| mc_uapi->static_mc_io = mc_bus->mc_dev.mc_io; | |
| mutex_init(&mc_uapi->mutex); | |
| return 0; | |
| } | |
| void fsl_mc_uapi_remove_device_file(struct fsl_mc_bus *mc_bus) | |
| { | |
| misc_deregister(&mc_bus->uapi_misc.misc); | |
| } |