/* 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 #include #include #include #include #include #include #include #include #include #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);