msm: Kernel module to support bus performance monitoring applications

This kernel module is used to mmap() hardware registers for the
performance monitors, counters, etc. The module can also be used to
allocate physical memory which is used by bus performance hardware to
dump performance data.

Change-Id: I5309b84405f40812205f75e0accc9f3fb0c4839c
Signed-off-by: Kenneth Heitke <kheitke@codeaurora.org>
Signed-off-by: Harini Jayaraman <harinij@codeaurora.org>
diff --git a/arch/arm/mach-msm/msm-buspm-dev.c b/arch/arm/mach-msm/msm-buspm-dev.c
new file mode 100644
index 0000000..296418d
--- /dev/null
+++ b/arch/arm/mach-msm/msm-buspm-dev.c
@@ -0,0 +1,256 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/* #define DEBUG */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/memory_alloc.h>
+#include "msm-buspm-dev.h"
+
+#define MSM_BUSPM_DRV_NAME "msm-buspm-dev"
+
+/*
+ * Allocate kernel buffer.
+ * Currently limited to one buffer per file descriptor.  If alloc() is
+ * called twice for the same descriptor, the original buffer is freed.
+ * There is also no locking protection so the same descriptor can not be shared.
+ */
+
+static inline void *msm_buspm_dev_get_vaddr(struct file *filp)
+{
+	struct msm_buspm_map_dev *dev = filp->private_data;
+
+	return (dev) ? dev->vaddr : NULL;
+}
+
+static inline unsigned long msm_buspm_dev_get_paddr(struct file *filp)
+{
+	struct msm_buspm_map_dev *dev = filp->private_data;
+
+	return (dev) ? dev->paddr : 0L;
+}
+
+static void msm_buspm_dev_free(struct file *filp)
+{
+	struct msm_buspm_map_dev *dev = filp->private_data;
+
+	if (dev) {
+		pr_debug("freeing memory at 0x%p\n", dev->vaddr);
+		free_contiguous_memory(dev->vaddr);
+		dev->paddr = 0L;
+		dev->vaddr = NULL;
+	}
+}
+
+static int msm_buspm_dev_open(struct inode *inode, struct file *filp)
+{
+	struct msm_buspm_map_dev *dev;
+
+	if (capable(CAP_SYS_ADMIN)) {
+		dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+		if (dev)
+			filp->private_data = dev;
+		else
+			return -ENOMEM;
+	} else {
+		return -EPERM;
+	}
+
+	return 0;
+}
+
+static int
+msm_buspm_dev_alloc(struct file *filp, struct buspm_alloc_params data)
+{
+	unsigned long paddr;
+	void *vaddr;
+	struct msm_buspm_map_dev *dev = filp->private_data;
+
+	/* If buffer already allocated, then free it */
+	if (dev->vaddr)
+		msm_buspm_dev_free(filp);
+
+	/* Allocate uncached memory */
+	vaddr = allocate_contiguous_ebi(data.size, PAGE_SIZE, 0);
+	paddr = (vaddr) ? memory_pool_node_paddr(vaddr) : 0L;
+
+	if (vaddr == NULL) {
+		pr_err("allocation of 0x%x bytes failed", data.size);
+		return -ENOMEM;
+	}
+
+	dev->vaddr = vaddr;
+	dev->paddr = paddr;
+	dev->buflen = data.size;
+	filp->f_pos = 0;
+	pr_debug("virt addr = 0x%p\n", dev->vaddr);
+	pr_debug("phys addr = 0x%lx\n", dev->paddr);
+
+	return 0;
+}
+
+static long
+msm_buspm_dev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	struct buspm_xfer_req xfer;
+	struct buspm_alloc_params alloc_data;
+	unsigned long paddr;
+	int retval = 0;
+	void *buf = msm_buspm_dev_get_vaddr(filp);
+	unsigned char *dbgbuf = buf;
+
+	switch (cmd) {
+	case MSM_BUSPM_IOC_FREE:
+		pr_debug("cmd = 0x%x (FREE)\n", cmd);
+		msm_buspm_dev_free(filp);
+		break;
+
+	case MSM_BUSPM_IOC_ALLOC:
+		pr_debug("cmd = 0x%x (ALLOC)\n", cmd);
+		retval = __get_user(alloc_data.size, (size_t __user *)arg);
+
+		if (retval == 0)
+			retval = msm_buspm_dev_alloc(filp, alloc_data);
+		break;
+
+	case MSM_BUSPM_IOC_RD_PHYS_ADDR:
+		pr_debug("Read Physical Address\n");
+		paddr = msm_buspm_dev_get_paddr(filp);
+		if (paddr == 0L) {
+			retval = -EINVAL;
+		} else {
+			pr_debug("phys addr = 0x%lx\n", paddr);
+			retval = __put_user(paddr,
+				(unsigned long __user *)arg);
+		}
+		break;
+
+	case MSM_BUSPM_IOC_RDBUF:
+		pr_debug("Read Buffer: 0x%x%x%x%x\n",
+				dbgbuf[0], dbgbuf[1], dbgbuf[2], dbgbuf[3]);
+
+		if (!buf) {
+			retval = -EINVAL;
+			break;
+		}
+
+		if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer))) {
+			retval = -EFAULT;
+			break;
+		}
+
+		if ((xfer.size <= sizeof(buf)) &&
+			(copy_to_user((void __user *)xfer.data, buf,
+					xfer.size))) {
+			retval = -EFAULT;
+			break;
+		}
+		break;
+
+	case MSM_BUSPM_IOC_WRBUF:
+		pr_debug("Write Buffer\n");
+
+		if (!buf) {
+			retval = -EINVAL;
+			break;
+		}
+
+		if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer))) {
+			retval = -EFAULT;
+			break;
+		}
+
+		if ((sizeof(buf) <= xfer.size) &&
+			(copy_from_user(buf, (void __user *)xfer.data,
+			xfer.size))) {
+			retval = -EFAULT;
+			break;
+		}
+		break;
+
+	default:
+		pr_debug("Unknown command 0x%x\n", cmd);
+		retval = -EINVAL;
+		break;
+	}
+
+	return retval;
+}
+
+static int msm_buspm_dev_release(struct inode *inode, struct file *filp)
+{
+	struct msm_buspm_map_dev *dev = filp->private_data;
+
+	msm_buspm_dev_free(filp);
+	kfree(dev);
+	filp->private_data = NULL;
+
+	return 0;
+}
+
+static int msm_buspm_dev_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	pr_debug("vma = 0x%p\n", vma);
+
+	/* Mappings are uncached */
+	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+	if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+		vma->vm_end - vma->vm_start, vma->vm_page_prot))
+		return -EFAULT;
+
+	return 0;
+}
+
+static const struct file_operations msm_buspm_dev_fops = {
+	.owner		= THIS_MODULE,
+	.mmap		= msm_buspm_dev_mmap,
+	.open		= msm_buspm_dev_open,
+	.unlocked_ioctl	= msm_buspm_dev_ioctl,
+	.llseek		= noop_llseek,
+	.release	= msm_buspm_dev_release,
+};
+
+struct miscdevice msm_buspm_misc = {
+	.minor	= MISC_DYNAMIC_MINOR,
+	.name	= MSM_BUSPM_DRV_NAME,
+	.fops	= &msm_buspm_dev_fops,
+};
+
+static int __init msm_buspm_dev_init(void)
+{
+	int ret = 0;
+
+	ret = misc_register(&msm_buspm_misc);
+	if (ret < 0)
+		pr_err("%s: Cannot register misc device\n", __func__);
+
+	return ret;
+}
+
+static void __exit msm_buspm_dev_exit(void)
+{
+	misc_deregister(&msm_buspm_misc);
+}
+module_init(msm_buspm_dev_init);
+module_exit(msm_buspm_dev_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:"MSM_BUSPM_DRV_NAME);