aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/soc/marvell/hw-access/hw_rw_access.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/marvell/hw-access/hw_rw_access.c')
-rw-r--r--drivers/soc/marvell/hw-access/hw_rw_access.c391
1 files changed, 391 insertions, 0 deletions
diff --git a/drivers/soc/marvell/hw-access/hw_rw_access.c b/drivers/soc/marvell/hw-access/hw_rw_access.c
new file mode 100644
index 000000000000..b0a1d656b01f
--- /dev/null
+++ b/drivers/soc/marvell/hw-access/hw_rw_access.c
@@ -0,0 +1,391 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Hardware device CSR Access driver
+ * Copyright (C) 2021 Marvell International Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* This driver supports Read/Write of only OcteonTx2/OcteonTx3 HW device
+ * config registers. Read/Write of System Registers are not supported.
+ */
+
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/pci.h>
+#include <linux/stddef.h>
+
+#include "rvu_struct.h"
+#include "rvu.h"
+#include "mbox.h"
+
+#define DEVICE_NAME "hw_access"
+#define CLASS_NAME "hw_access_class"
+
+/* PCI device IDs */
+#define PCI_DEVID_OCTEONTX2_RVU_AF 0xA065
+
+/* First physical address is the smallest start physical address of all HW
+ * devices.
+ * Smallest expected start physical address of all HW devices is based on
+ * datasheet 'Figure 4-1 Physical Address Regions' with lowest I/O start address
+ * being 0x800000000000 as:
+ * - bits <51:47> = 0x1 define I/O range,
+ * - bits <43:36> = 0x0 constitute NCB DID,
+ * - bits <36:0> = 0x0 assume zero-offset.
+ * In practice the lowest observed register address is for GIC = 0x801000000000
+ * which will be used for access.
+ */
+#define REG_PHYS_BASEADDR 0x801000000000
+
+/* The calculation does not take into consideration Armv8.2's 52bit extended
+ * addressing used for PEM which has bits<51:49> set to {0x1, 0x2, 0x3}.
+ *
+ * Maximum I/O address bits<43:36> are assumed ti be 0xFF with no limits on NCB
+ * offset addesses forwarded to NCB device. Such assumtion leads to maximum
+ * addressable HW address being 0x8FFFFFFFFFFF.
+ * In practice the highest observed address is for PEM(5)_MSIX_MBA(0) as below:
+ */
+#define REG_PHYS_ENDADDR 0x8E5F000F0000
+
+#define REG_SPACE_MAPSIZE (REG_PHYS_ENDADDR - REG_PHYS_BASEADDR + 1)
+
+struct hw_reg_cfg {
+ u64 regaddr; /* Register physical address within a hw device */
+ u64 regval; /* Register value to be read or to write */
+};
+
+struct hw_ctx_cfg {
+ u16 blkaddr;
+ u16 pcifunc;
+ union {
+ u16 qidx;
+ u16 aura;
+ };
+ u8 ctype;
+ u8 op;
+};
+
+struct hw_cgx_info {
+ u8 pf;
+ u8 cgx_id;
+ u8 lmac_id;
+ u8 nix_idx;
+};
+
+#define HW_ACCESS_TYPE 120
+
+#define HW_ACCESS_CSR_READ_IOCTL _IO(HW_ACCESS_TYPE, 1)
+#define HW_ACCESS_CSR_WRITE_IOCTL _IO(HW_ACCESS_TYPE, 2)
+#define HW_ACCESS_CTX_READ_IOCTL _IO(HW_ACCESS_TYPE, 3)
+#define HW_ACCESS_CGX_INFO_IOCTL _IO(HW_ACCESS_TYPE, 4)
+
+struct hw_priv_data {
+ void __iomem *reg_base;
+ struct rvu *rvu;
+};
+
+static struct class *hw_reg_class;
+static int major_no;
+
+static int hw_access_open(struct inode *inode, struct file *filp)
+{
+ struct hw_priv_data *priv_data = NULL;
+ struct pci_dev *pdev;
+ int err;
+
+ priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL);
+ if (!priv_data)
+ return -ENOMEM;
+
+ priv_data->reg_base = ioremap(REG_PHYS_BASEADDR, REG_SPACE_MAPSIZE);
+ if (!priv_data->reg_base) {
+ pr_err("Unable to map Physical Base Address\n");
+ err = -ENOMEM;
+ return err;
+ }
+
+ pdev = pci_get_device(PCI_VENDOR_ID_CAVIUM, PCI_DEVID_OCTEONTX2_RVU_AF,
+ NULL);
+ priv_data->rvu = pci_get_drvdata(pdev);
+
+ filp->private_data = priv_data;
+
+ return 0;
+}
+
+static int
+hw_access_csr_read(void __iomem *regbase, unsigned long arg)
+{
+ struct hw_reg_cfg reg_cfg;
+ u64 regoff;
+
+ if (copy_from_user(&reg_cfg, (void __user *)arg,
+ sizeof(struct hw_reg_cfg))) {
+ pr_err("Read Fault copy from user\n");
+
+ return -EFAULT;
+ }
+
+ if (reg_cfg.regaddr < REG_PHYS_BASEADDR ||
+ reg_cfg.regaddr >= REG_PHYS_BASEADDR + REG_SPACE_MAPSIZE) {
+ pr_err("Address [0x%llx] out of range [0x%lx - 0x%lx]\n",
+ reg_cfg.regaddr, REG_PHYS_BASEADDR,
+ REG_PHYS_BASEADDR + REG_SPACE_MAPSIZE);
+
+ return -EFAULT;
+ }
+
+ /* Only 64 bit reads/writes are allowed */
+ reg_cfg.regaddr &= ~0x07ULL;
+ regoff = reg_cfg.regaddr - REG_PHYS_BASEADDR;
+ reg_cfg.regval = readq(regbase + regoff);
+
+ if (copy_to_user((void __user *)(unsigned long)arg,
+ &reg_cfg,
+ sizeof(struct hw_reg_cfg))) {
+ pr_err("Fault in copy to user\n");
+
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static int
+hw_access_csr_write(void __iomem *regbase, unsigned long arg)
+{
+ struct hw_reg_cfg reg_cfg;
+ u64 regoff;
+
+ if (copy_from_user(&reg_cfg, (void __user *)arg,
+ sizeof(struct hw_reg_cfg))) {
+ pr_err("Write Fault in copy from user\n");
+
+ return -EFAULT;
+ }
+
+ if (reg_cfg.regaddr < REG_PHYS_BASEADDR ||
+ reg_cfg.regaddr >= REG_PHYS_BASEADDR + REG_SPACE_MAPSIZE) {
+ pr_err("Address [0x%llx] out of range [0x%lx - 0x%lx]\n",
+ reg_cfg.regaddr, REG_PHYS_BASEADDR,
+ REG_PHYS_BASEADDR + REG_SPACE_MAPSIZE);
+
+ return -EFAULT;
+ }
+
+ /* Only 64 bit reads/writes are allowed */
+ reg_cfg.regaddr &= ~0x07ULL;
+ regoff = reg_cfg.regaddr - REG_PHYS_BASEADDR;
+ writeq(reg_cfg.regval, regbase + regoff);
+
+ return 0;
+}
+
+static int
+hw_access_nix_ctx_read(struct rvu *rvu, struct hw_ctx_cfg *ctx_cfg,
+ unsigned long arg)
+{
+ struct nix_aq_enq_req aq_req;
+ struct nix_aq_enq_rsp rsp;
+
+ memset(&aq_req, 0, sizeof(struct nix_aq_enq_req));
+ aq_req.hdr.pcifunc = ctx_cfg->pcifunc;
+ aq_req.ctype = ctx_cfg->ctype;
+ aq_req.op = ctx_cfg->op;
+ aq_req.qidx = ctx_cfg->qidx;
+
+ if (rvu_mbox_handler_nix_aq_enq(rvu, &aq_req, &rsp)) {
+ pr_err("Failed to read the context\n");
+ return -EINVAL;
+ }
+
+ if (copy_to_user((struct nix_aq_enq_rsp *)arg,
+ &rsp, sizeof(struct nix_aq_enq_rsp))) {
+ pr_err("Fault in copy to user\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int
+hw_access_npa_ctx_read(struct rvu *rvu, struct hw_ctx_cfg *ctx_cfg,
+ unsigned long arg)
+{
+ struct npa_aq_enq_req aq_req;
+ struct npa_aq_enq_rsp rsp;
+
+ memset(&aq_req, 0, sizeof(struct npa_aq_enq_req));
+ aq_req.hdr.pcifunc = ctx_cfg->pcifunc;
+ aq_req.ctype = ctx_cfg->ctype;
+ aq_req.op = ctx_cfg->op;
+ aq_req.aura_id = ctx_cfg->aura;
+
+ if (rvu_mbox_handler_npa_aq_enq(rvu, &aq_req, &rsp)) {
+ pr_err("Failed to read the npa context\n");
+ return -EINVAL;
+ }
+
+ if (copy_to_user((struct npa_aq_enq_rsp *)arg,
+ &rsp, sizeof(struct npa_aq_enq_rsp))) {
+ pr_err("Fault in copy to user\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int
+hw_access_ctx_read(struct rvu *rvu, unsigned long arg)
+{
+ struct hw_ctx_cfg ctx_cfg;
+ int rc;
+
+ if (copy_from_user(&ctx_cfg, (struct hw_ctx_cfg *)arg,
+ sizeof(struct hw_ctx_cfg))) {
+ pr_err("Write Fault in copy from user\n");
+ return -EFAULT;
+ }
+
+ switch (ctx_cfg.blkaddr) {
+ case BLKADDR_NIX0:
+ case BLKADDR_NIX1:
+ rc = hw_access_nix_ctx_read(rvu, &ctx_cfg, arg);
+ break;
+ case BLKADDR_NPA:
+ rc = hw_access_npa_ctx_read(rvu, &ctx_cfg, arg);
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+ return rc;
+}
+
+static int
+hw_access_cgx_info(struct rvu *rvu, unsigned long arg)
+{
+ struct hw_cgx_info cgx_info;
+ struct rvu_pfvf *pfvf;
+ u8 cgx_id, lmac_id, pf;
+ u16 pcifunc;
+
+ if (copy_from_user(&cgx_info, (void __user *)arg, sizeof(struct hw_cgx_info))) {
+ pr_err("Reading PF value failed: copy from user\n");
+ return -EFAULT;
+ }
+
+ pf = cgx_info.pf;
+ if (!(pf >= PF_CGXMAP_BASE && pf <= rvu->cgx_mapped_pfs)) {
+ pr_err("Invalid PF value %d\n", pf);
+ return -EFAULT;
+ }
+
+ pcifunc = pf << 10;
+ pfvf = &rvu->pf[pf];
+ rvu_get_cgx_lmac_id(rvu->pf2cgxlmac_map[pf], &cgx_id,
+ &lmac_id);
+ cgx_info.cgx_id = cgx_id;
+ cgx_info.lmac_id = lmac_id;
+ cgx_info.nix_idx = (pfvf->nix_blkaddr == BLKADDR_NIX0) ? 0 : 1;
+
+ if (copy_to_user((void __user *)(unsigned long)arg,
+ &cgx_info,
+ sizeof(struct hw_cgx_info))) {
+ pr_err("Fault in copy to user\n");
+
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static long hw_access_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct hw_priv_data *priv_data = filp->private_data;
+ void __iomem *regbase = priv_data->reg_base;
+ struct rvu *rvu = priv_data->rvu;
+
+ switch (cmd) {
+ case HW_ACCESS_CSR_READ_IOCTL:
+ return hw_access_csr_read(regbase, arg);
+
+ case HW_ACCESS_CSR_WRITE_IOCTL:
+ return hw_access_csr_write(regbase, arg);
+
+ case HW_ACCESS_CTX_READ_IOCTL:
+ return hw_access_ctx_read(rvu, arg);
+
+ case HW_ACCESS_CGX_INFO_IOCTL:
+ return hw_access_cgx_info(rvu, arg);
+
+ default:
+ pr_info("Invalid IOCTL: %d\n", cmd);
+
+ return -EINVAL;
+ }
+}
+
+static int hw_access_release(struct inode *inode, struct file *filp)
+{
+ struct hw_priv_data *priv_data = filp->private_data;
+ void __iomem *regbase = priv_data->reg_base;
+
+ iounmap(regbase);
+ filp->private_data = NULL;
+ kfree(priv_data);
+ priv_data = NULL;
+
+ return 0;
+}
+
+static const struct file_operations mmap_fops = {
+ .open = hw_access_open,
+ .unlocked_ioctl = hw_access_ioctl,
+ .release = hw_access_release,
+};
+
+static int __init hw_access_module_init(void)
+{
+ static struct device *hw_reg_device;
+
+ major_no = register_chrdev(0, DEVICE_NAME, &mmap_fops);
+ if (major_no < 0) {
+ pr_err("failed to register a major number for %s\n",
+ DEVICE_NAME);
+ return major_no;
+ }
+
+ hw_reg_class = class_create(THIS_MODULE, CLASS_NAME);
+ if (IS_ERR(hw_reg_class)) {
+ unregister_chrdev(major_no, DEVICE_NAME);
+ return PTR_ERR(hw_reg_class);
+ }
+
+ hw_reg_device = device_create(hw_reg_class, NULL,
+ MKDEV(major_no, 0), NULL,
+ DEVICE_NAME);
+ if (IS_ERR(hw_reg_device)) {
+ class_destroy(hw_reg_class);
+ unregister_chrdev(major_no, DEVICE_NAME);
+ return PTR_ERR(hw_reg_device);
+ }
+
+ return 0;
+}
+
+static void __exit hw_access_module_exit(void)
+{
+ device_destroy(hw_reg_class, MKDEV(major_no, 0));
+ class_destroy(hw_reg_class);
+ unregister_chrdev(major_no, DEVICE_NAME);
+}
+
+module_init(hw_access_module_init);
+module_exit(hw_access_module_exit);
+MODULE_AUTHOR("Marvell International Ltd.");
+MODULE_LICENSE("GPL v2");