forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
597 lines
12 KiB
597 lines
12 KiB
// 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); |
|
}
|
|
|