| // 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); |
| } |