aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/misc/otx_bphy_ctr.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/otx_bphy_ctr.c')
-rw-r--r--drivers/misc/otx_bphy_ctr.c282
1 files changed, 282 insertions, 0 deletions
diff --git a/drivers/misc/otx_bphy_ctr.c b/drivers/misc/otx_bphy_ctr.c
new file mode 100644
index 000000000000..7818ca69d345
--- /dev/null
+++ b/drivers/misc/otx_bphy_ctr.c
@@ -0,0 +1,282 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2016, 2018 Cavium Inc.
+ */
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/arm-smccc.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/moduleparam.h>
+#include <linux/uaccess.h>
+#include <linux/mmu_context.h>
+#include <linux/ioctl.h>
+#include <linux/fs.h>
+
+#define DEVICE_NAME "otx-bphy-ctr"
+#define OTX_IOC_MAGIC 0xF3
+#define MAX_IRQ 27
+
+static struct device *otx_device;
+static struct class *otx_class;
+static struct cdev *otx_cdev;
+static dev_t otx_dev;
+static DEFINE_SPINLOCK(el3_inthandler_lock);
+static int in_use;
+static int irq_installed[MAX_IRQ];
+static struct thread_info *irq_installed_threads[MAX_IRQ];
+static struct task_struct *irq_installed_tasks[MAX_IRQ];
+
+/* SMC definitons */
+/* X1 - irq_num, X2 - sp, X3 - cpu, X4 - ttbr0 */
+#define OCTEONTX_INSTALL_BPHY_PSM_ERRINT 0xc2000803
+/* X1 - irq_num */
+#define OCTEONTX_REMOVE_BPHY_PSM_ERRINT 0xc2000804
+
+struct otx_irq_usr_data {
+ u64 isr_base;
+ u64 sp;
+ u64 cpu;
+ u64 irq_num;
+};
+
+
+#define OTX_IOC_SET_BPHY_HANDLER \
+ _IOW(OTX_IOC_MAGIC, 1, struct otx_irq_usr_data)
+
+#define OTX_IOC_CLR_BPHY_HANDLER \
+ _IO(OTX_IOC_MAGIC, 2)
+
+static inline int __install_el3_inthandler(unsigned long irq_num,
+ unsigned long sp,
+ unsigned long cpu,
+ unsigned long ttbr0)
+{
+ struct arm_smccc_res res;
+ unsigned long flags;
+ int retval = -1;
+
+ spin_lock_irqsave(&el3_inthandler_lock, flags);
+
+ if (!irq_installed[irq_num]) {
+ lock_context(current->group_leader->mm, irq_num);
+ arm_smccc_smc(OCTEONTX_INSTALL_BPHY_PSM_ERRINT, irq_num,
+ sp, cpu, ttbr0, 0, 0, 0, &res);
+ if (res.a0 == 0) {
+ irq_installed[irq_num] = 1;
+ irq_installed_threads[irq_num]
+ = current_thread_info();
+ irq_installed_tasks[irq_num]
+ = current->group_leader;
+ retval = 0;
+ } else {
+ unlock_context_by_index(irq_num);
+ }
+ }
+ spin_unlock_irqrestore(&el3_inthandler_lock, flags);
+ return retval;
+}
+
+static inline int __remove_el3_inthandler(unsigned long irq_num)
+{
+ struct arm_smccc_res res;
+ unsigned long flags;
+ unsigned int retval;
+
+ spin_lock_irqsave(&el3_inthandler_lock, flags);
+
+ if (irq_installed[irq_num]) {
+ arm_smccc_smc(OCTEONTX_REMOVE_BPHY_PSM_ERRINT, irq_num,
+ 0, 0, 0, 0, 0, 0, &res);
+ irq_installed[irq_num] = 0;
+ irq_installed_threads[irq_num] = NULL;
+ irq_installed_tasks[irq_num] = NULL;
+ unlock_context_by_index(irq_num);
+ retval = 0;
+ } else {
+ retval = -1;
+ }
+ spin_unlock_irqrestore(&el3_inthandler_lock, flags);
+ return retval;
+}
+
+static long otx_dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ struct otx_irq_usr_data irq_usr;
+ u64 irq_ttbr, irq_isr_base, irq_sp, irq_cpu, irq_num;
+ int ret;
+ //struct task_struct *task = current;
+
+ if (!in_use)
+ return -EINVAL;
+
+ if (_IOC_TYPE(cmd) != OTX_IOC_MAGIC)
+ return -ENOTTY;
+
+ if (_IOC_DIR(cmd) & _IOC_READ)
+ err = !access_ok((void __user *)arg, _IOC_SIZE(cmd));
+ else if (_IOC_TYPE(cmd) & _IOC_WRITE)
+ err = !access_ok((void __user *)arg, _IOC_SIZE(cmd));
+
+ if (err)
+ return -EFAULT;
+
+ switch (cmd) {
+ case OTX_IOC_SET_BPHY_HANDLER: /*Install ISR handler*/
+ ret = copy_from_user(&irq_usr, (void *)arg, _IOC_SIZE(cmd));
+ if (irq_usr.irq_num >= MAX_IRQ)
+ return -EINVAL;
+ if (ret)
+ return -EFAULT;
+ irq_ttbr = 0;
+ //TODO: reserve a asid to avoid asid rollovers
+ asm volatile("mrs %0, ttbr0_el1\n\t" : "=r"(irq_ttbr));
+ irq_isr_base = irq_usr.isr_base;
+ irq_sp = irq_usr.sp;
+ irq_cpu = irq_usr.cpu;
+ irq_num = irq_usr.irq_num;
+ ret = __install_el3_inthandler(irq_num, irq_sp,
+ irq_cpu, irq_isr_base);
+ if (ret != 0)
+ return -EEXIST;
+ break;
+ case OTX_IOC_CLR_BPHY_HANDLER: /*Clear ISR handler*/
+ irq_usr.irq_num = arg;
+ if (irq_usr.irq_num >= MAX_IRQ)
+ return -EINVAL;
+ ret = __remove_el3_inthandler(irq_usr.irq_num);
+ if (ret != 0)
+ return -ENOENT;
+ break;
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static void cleanup_el3_irqs(struct task_struct *task)
+{
+ int i;
+
+ for (i = 0; i < MAX_IRQ; i++) {
+ if (irq_installed[i] &&
+ irq_installed_tasks[i] &&
+ ((irq_installed_tasks[i] == task) ||
+ (irq_installed_tasks[i] == task->group_leader))) {
+ pr_alert("Exiting, removing handler for BPHY IRQ %d\n",
+ i);
+ __remove_el3_inthandler(i);
+ pr_alert("Exited, removed handler for BPHY IRQ %d\n",
+ i);
+ } else {
+ if (irq_installed[i] &&
+ (irq_installed_threads[i]
+ == current_thread_info()))
+ pr_alert("Exiting, thread info matches, not removing handler for BPHY IRQ %d\n", i);
+ }
+ }
+}
+
+static int otx_dev_open(struct inode *inode, struct file *fp)
+{
+ in_use = 1;
+ return 0;
+}
+
+static int otx_dev_release(struct inode *inode, struct file *fp)
+{
+ if (in_use == 0)
+ return -EINVAL;
+ in_use = 0;
+ return 0;
+}
+
+static const struct file_operations fops = {
+ .owner = THIS_MODULE,
+ .open = otx_dev_open,
+ .release = otx_dev_release,
+ .unlocked_ioctl = otx_dev_ioctl
+};
+
+static int __init otx_ctr_dev_init(void)
+{
+ int err = 0;
+
+ /* create a character device */
+ err = alloc_chrdev_region(&otx_dev, 1, 1, DEVICE_NAME);
+ if (err != 0) {
+ pr_err("Failed to create device: %d\n", err);
+ goto alloc_chrdev_err;
+ }
+
+ otx_cdev = cdev_alloc();
+ if (!otx_cdev) {
+ err = -ENODEV;
+ goto cdev_alloc_err;
+ }
+
+ cdev_init(otx_cdev, &fops);
+ err = cdev_add(otx_cdev, otx_dev, 1);
+ if (err < 0) {
+ err = -ENODEV;
+ goto cdev_add_err;
+ }
+
+ /* create new class for sysfs*/
+ otx_class = class_create(THIS_MODULE, DEVICE_NAME);
+ if (IS_ERR(otx_class)) {
+ err = -ENODEV;
+ goto class_create_err;
+ }
+
+ otx_device = device_create(otx_class, NULL, otx_dev, NULL,
+ DEVICE_NAME);
+ if (IS_ERR(otx_device)) {
+ err = -ENODEV;
+ goto device_create_err;
+ }
+
+ /* Register task cleanup handler */
+ err = task_cleanup_handler_add(cleanup_el3_irqs);
+ if (err != 0) {
+ dev_err(otx_device, "Failed to register cleanup handler: %d\n", err);
+ goto cleanup_handler_err;
+ }
+
+ return err;
+
+device_create_err:
+ class_destroy(otx_class);
+
+class_create_err:
+cdev_add_err:
+ cdev_del(otx_cdev);
+cdev_alloc_err:
+ unregister_chrdev_region(otx_dev, 1);
+alloc_chrdev_err:
+ task_cleanup_handler_remove(cleanup_el3_irqs);
+cleanup_handler_err:
+ return err;
+}
+
+static void __exit otx_ctr_dev_exit(void)
+{
+ device_destroy(otx_class, otx_dev);
+ class_destroy(otx_class);
+ cdev_del(otx_cdev);
+ unregister_chrdev_region(otx_dev, 1);
+
+ task_cleanup_handler_remove(cleanup_el3_irqs);
+}
+
+module_init(otx_ctr_dev_init);
+module_exit(otx_ctr_dev_exit);
+
+MODULE_DESCRIPTION("Marvell OTX Control Device Driver");
+MODULE_LICENSE("GPL");