diff options
Diffstat (limited to 'drivers/soc/marvell/marvell_mac_mgmt.c')
-rw-r--r-- | drivers/soc/marvell/marvell_mac_mgmt.c | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/drivers/soc/marvell/marvell_mac_mgmt.c b/drivers/soc/marvell/marvell_mac_mgmt.c new file mode 100644 index 000000000000..5d38f3dd4ad3 --- /dev/null +++ b/drivers/soc/marvell/marvell_mac_mgmt.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Marvell + * + */ + +#include <linux/arm-smccc.h> +#include <soc/marvell/octeontx/octeontx_smc.h> +#include <linux/debugfs.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/uaccess.h> +#include <linux/slab.h> + +/* Maximum number of MAC addressess to pass */ +#define MAC_MGMT_MAX_MACS_NUM 32 + +/* Maximum mac data size */ +#define MAC_MGMT_MAX_MAC_TEXT_SIZE 2048 + +/* Single entry description */ +struct mac_info { + u32 index; + u32 reserved; /* Must be zero */ + union { + u64 mac_addr; + u8 bytes[8]; + } s; +}; + +/* SMC call number used to set MAC address */ +#define PLAT_OCTEONTX_MAC_MGMT_SET_ADDR 0xc2000e10 + +/** Set MAC address given by user + * + * The call passes MAC address information to ATF for further processing. + * Information contains index and MAC address itself. Data should be validated + * before this call. + * + * @param minfo - MAC address information (index, value) + * + * @return 0 for success, error code otherwise + * + */ +static int mac_mgmt_set_addr(struct mac_info *minfo) +{ + struct arm_smccc_res res; + + /* Pass validated data to ATF */ + arm_smccc_smc(PLAT_OCTEONTX_MAC_MGMT_SET_ADDR, + minfo->index, minfo->s.mac_addr, 0, 0, 0, 0, 0, + &res); + if (res.a0) + return -EINVAL; + + return 0; +} + +/** Parse user input in text for to MAC information structure + * + * @param buffer - ASCII string containing user's input + * @param n - size of the user's input + * @param minfo - input/output value, contains MAC information. Updated only when call succeeded + * + * @return bytes parsed for success, error code otherwise + * + */ +static ssize_t mac_mgmt_parse_buffer(const char *buffer, size_t n, + struct mac_info *minfo) +{ + u32 index; + u64 mac_addr; + int processed, ret; + + /* Data are in buffer, parse it */ + ret = sscanf(buffer, "%u %llx %n", &index, &mac_addr, &processed); + if (ret <= 0) + return -EINVAL; + + if (processed < 2) /* Expect at least two characters in input */ + return -EINVAL; + + if (index > MAC_MGMT_MAX_MACS_NUM) + return -EINVAL; + + if (!mac_addr) + return -EINVAL; + + /* Store validated data */ + minfo->index = index; + minfo->s.mac_addr = mac_addr & 0xffffffffffff; + + pr_debug("%s: Idx: %u, addr: %llx\n", __func__, + minfo->index, minfo->s.mac_addr); + + return n; +} + +/** Process the write operations to debugfs. + * + * The call is supported by seq_file API form kernel + * + * @param filep - file pointer + * @param buffer - user's input buffer + * @param count - user's input buffer size + * @param ppos - position in file + * + * @return bytes written for success, error code otherwise + * + */ +static ssize_t mac_mgmt_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct mac_info minfo = { 0 }; + char *mac_text_data = NULL; + size_t cnt; + int ret, bytes; + + /* User should fit into MAC_MGMT_MAX_MAC_TEXT_SIZE - 1, otherwise truncate */ + cnt = (count >= MAC_MGMT_MAX_MAC_TEXT_SIZE - 1) ? + (MAC_MGMT_MAX_MAC_TEXT_SIZE - 1) : count; + + /* Leave one byte for NULL termination */ + mac_text_data = kzalloc(cnt + 1, GFP_KERNEL); + if (!mac_text_data) + return -ENOMEM; + + if (copy_from_user(mac_text_data, buffer, cnt)) { + ret = -EFAULT; + goto done; + } + + bytes = mac_mgmt_parse_buffer(mac_text_data, cnt, &minfo); + if (bytes < 0) { + pr_warn("%s: Invalid text format!\n", __func__); + ret = bytes; + goto done; + } + + ret = mac_mgmt_set_addr(&minfo); + if (!ret) + pr_info("%s: MAC addresses has been updated, change takes effect after reboot\n", + __func__); +done: + kfree(mac_text_data); + return ret ? ret : bytes; +} + +/** Process the read operations from debugfs. + * + * The call is supported by seq_file API form kernel. + * It provides usage information to user. + * + * @param s - seq_file file handle + * @param unused - unused parameter + * + * @return 0 for success, error code otherwise + * + */ +static int mac_mgmt_read(struct seq_file *s, void *unused) +{ + seq_printf(s, "Sets MAC address for available interface.\nFormat:\n" + "ID BOARD-MAC-ADDRESS\n\n"); + return 0; +} + +/** Process the open call on debugfs. + * + * The call is supported by seq_file API form kernel. + * + * @param inode - inode representing debugfs entry + * @param file - file structure related to debugfs entry + * + * @return 0 for success, error code otherwise + * + */ +static int mac_mgmt_open(struct inode *inode, struct file *file) +{ + return single_open(file, mac_mgmt_read, inode->i_private); +} + +static const struct file_operations mac_mgmt_fops = { + .owner = THIS_MODULE, + .open = mac_mgmt_open, + .read = seq_read, + .write = mac_mgmt_write, + .llseek = seq_lseek, + .release = single_release, +}; + +/* Handle to debugfs root directory created by the driver */ +static struct dentry *mac_dbgfs_root; + +/** Initialize debugfs entries for the driver + * + * @return 0 for success, error code otherwise + * + */ +static int mac_mgmt_setup_debugfs(void) +{ + struct dentry *dbg_file; + + mac_dbgfs_root = debugfs_create_dir("mac_mgmt", NULL); + if (IS_ERR(mac_dbgfs_root)) + return PTR_ERR(mac_dbgfs_root); + + dbg_file = debugfs_create_file("set_mac_addr", 0600, mac_dbgfs_root, + NULL, &mac_mgmt_fops); + if (IS_ERR(dbg_file)) { + debugfs_remove(mac_dbgfs_root); + mac_dbgfs_root = NULL; + return PTR_ERR(dbg_file); + } + + return 0; +} + +static int __init cn10k_mac_mgmt_init(void) +{ + int ret; + + ret = octeontx_soc_check_smc(); + if (ret != 2) { + pr_debug("%s: Not supported\n", __func__); + return -EPERM; + } + + ret = mac_mgmt_setup_debugfs(); + if (ret) { + pr_err("%s: Can't create debugfs entries! (%d)\n", + __func__, ret); + return ret; + } + + pr_info("Marvell CN10K MAC management\n"); + + return 0; +} + +static void __exit cn10k_mac_mgmt_exit(void) +{ + debugfs_remove_recursive(mac_dbgfs_root); +} + +module_init(cn10k_mac_mgmt_init); +module_exit(cn10k_mac_mgmt_exit); + +MODULE_AUTHOR("Wojciech Bartczak <wbartczak@marvell.com>"); +MODULE_DESCRIPTION("MAC address management for Marvell CN10K"); +MODULE_LICENSE("GPL"); |