Index: arch/arm/mach-tegra/nv/nvos_user.c |
diff --git a/arch/arm/mach-tegra/nv/nvos_user.c b/arch/arm/mach-tegra/nv/nvos_user.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5532d5b99c22d9d06e670f305eb4f9d1c50ba327 |
--- /dev/null |
+++ b/arch/arm/mach-tegra/nv/nvos_user.c |
@@ -0,0 +1,552 @@ |
+/* |
+ * arch/arm/mach-tegra/nvos_user.c |
+ * |
+ * User-land access to NvOs APIs |
+ * |
+ * Copyright (c) 2008-2009, NVIDIA Corporation. |
+ * |
+ * This program is free software; you can redistribute it and/or modify |
+ * it under the terms of the GNU General Public License as published by |
+ * the Free Software Foundation; either version 2 of the License, or |
+ * (at your option) any later version. |
+ * |
+ * 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. |
+ * |
+ * You should have received a copy of the GNU General Public License along |
+ * with this program; if not, write to the Free Software Foundation, Inc., |
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
+ */ |
+ |
+#include <linux/interrupt.h> |
+#include <linux/io.h> |
+#include <linux/miscdevice.h> |
+#include <linux/module.h> |
+#include <linux/mm.h> |
+#include <linux/mutex.h> |
+#include <linux/proc_fs.h> |
+#include <linux/spinlock.h> |
+#include <linux/uaccess.h> |
+#include <linux/rwsem.h> |
+#include <mach/irqs.h> |
+#include "nvos.h" |
+#include "linux/nvos_ioctl.h" |
+#include "nvassert.h" |
+ |
+int nvos_open(struct inode *inode, struct file *file); |
+int nvos_close(struct inode *inode, struct file *file); |
+static long nvos_ioctl(struct file *file, unsigned int cmd, unsigned long arg); |
+int nvos_mmap(struct file *file, struct vm_area_struct *vma); |
+int NvOsSemaphoreWaitInterruptible(NvOsSemaphoreHandle semaphore); |
+ |
+#define DEVICE_NAME "nvos" |
+ |
+static const struct file_operations nvos_fops = |
+{ |
+ .owner = THIS_MODULE, |
+ .open = nvos_open, |
+ .release = nvos_close, |
+ .unlocked_ioctl = nvos_ioctl, |
+ .mmap = nvos_mmap |
+}; |
+ |
+static struct miscdevice nvosDevice = |
+{ |
+ .name = DEVICE_NAME, |
+ .fops = &nvos_fops, |
+ .minor = MISC_DYNAMIC_MINOR, |
+}; |
+ |
+typedef struct NvOsIrqListNodeRec |
+{ |
+ struct list_head list; |
+ NvOsInterruptHandle h; |
+} NvOsIrqListNode; |
+ |
+typedef struct NvOsInstanceRec |
+{ |
+ struct rw_semaphore RwLock; |
+ struct vm_area_struct *Vma; |
+ NvOsMemRangeParams *MemRange; |
+ struct task_struct *tsk; |
+ spinlock_t Lock; |
+ struct list_head IrqHandles; |
+ int pid; |
+} NvOsInstance; |
+ |
+static int __init nvos_init( void ) |
+{ |
+ int retVal = 0; |
+ |
+ retVal = misc_register(&nvosDevice); |
+ |
+ if (retVal < 0) |
+ { |
+ printk("nvos init failure\n" ); |
+ } |
+ |
+ return retVal; |
+} |
+ |
+static void __exit nvos_deinit( void ) |
+{ |
+ misc_deregister (&nvosDevice); |
+} |
+ |
+int nvos_open(struct inode *inode, struct file *filp) |
+{ |
+ NvOsInstance *Instance = NULL; |
+ |
+ filp->private_data = NULL; |
+ |
+ Instance = NvOsAlloc(sizeof(NvOsInstance)); |
+ if (!Instance) |
+ { |
+ printk(KERN_INFO __FILE__ ": nvos_open failed\n"); |
+ return -ENOMEM; |
+ } |
+ init_rwsem(&Instance->RwLock); |
+ Instance->tsk = current; |
+ Instance->pid = current->group_leader->pid; |
+ Instance->MemRange = NULL; |
+ spin_lock_init(&Instance->Lock); |
+ INIT_LIST_HEAD(&Instance->IrqHandles); |
+ filp->private_data = (void*)Instance; |
+ |
+ return 0; |
+} |
+ |
+int nvos_close(struct inode *inode, struct file *filp) |
+{ |
+ NvOsIrqListNode *LeakedIrq; |
+ |
+ if (filp->private_data) |
+ { |
+ NvOsInstance *Instance = (NvOsInstance *)filp->private_data; |
+ filp->private_data = NULL; |
+ while (!list_empty(&Instance->IrqHandles)) |
+ { |
+ LeakedIrq = list_first_entry(&Instance->IrqHandles, |
+ NvOsIrqListNode, list); |
+ list_del_init(&LeakedIrq->list); |
+ printk(__FILE__": leaked NvOsInterruptHandle %p\n", |
+ LeakedIrq->h); |
+ NvOsInterruptUnregister(LeakedIrq->h); |
+ NvOsFree(LeakedIrq); |
+ } |
+ |
+ if (Instance->MemRange) |
+ NvOsFree(Instance->MemRange); |
+ NvOsFree(Instance); |
+ } |
+ |
+ return 0; |
+} |
+ |
+extern NvError NvOsInterruptRegisterInternal( |
+ NvU32 IrqListSize, |
+ const NvU32 *pIrqList, |
+ const void *pIrqHandlerList, |
+ void* context, |
+ NvOsInterruptHandle *handle, |
+ NvBool InterruptEnable, |
+ NvBool IsUser); |
+ |
+static int interrupt_op( |
+ NvOsInstance *Instance, |
+ unsigned int cmd, |
+ unsigned long arg) |
+{ |
+ NvOsInterruptOpParams p; |
+ NvOsInterruptOpParams *user = (NvOsInterruptOpParams*)arg; |
+ NvError e; |
+ |
+ e = NvOsCopyIn(&p, user, sizeof(NvOsInterruptOpParams)); |
+ if (e != NvSuccess) |
+ return -EINVAL; |
+ |
+ switch(cmd) { |
+ case NV_IOCTL_INTERRUPT_ENABLE: |
+ e = NvOsInterruptEnable((NvOsInterruptHandle)p.handle); |
+ break; |
+ case NV_IOCTL_INTERRUPT_DONE: |
+ NvOsInterruptDone((NvOsInterruptHandle)p.handle); |
+ e = NvSuccess; |
+ break; |
+ case NV_IOCTL_INTERRUPT_UNREGISTER: |
+ { |
+ NvOsIrqListNode *IrqNode; |
+ if (Instance) |
+ { |
+ e = NvError_CountMismatch; |
+ spin_lock(&Instance->Lock); |
+ list_for_each_entry(IrqNode, &Instance->IrqHandles, list) |
+ { |
+ if (IrqNode->h == (NvOsInterruptHandle)p.handle) |
+ { |
+ list_del(&IrqNode->list); |
+ NvOsInterruptUnregister(IrqNode->h); |
+ NvOsFree(IrqNode); |
+ e = NvSuccess; |
+ break; |
+ } |
+ } |
+ spin_unlock(&Instance->Lock); |
+ } |
+ else |
+ { |
+ NvOsInterruptUnregister((NvOsInterruptHandle)p.handle); |
+ } |
+ e = NvSuccess; |
+ break; |
+ } |
+ case NV_IOCTL_INTERRUPT_MASK: |
+ NvOsInterruptMask((NvOsInterruptHandle)p.handle, |
+ p.arg ? NV_TRUE : NV_FALSE); |
+ e = NvSuccess; |
+ break; |
+ default: |
+ return -EINVAL; |
+ } |
+ |
+ if (NvOsCopyOut(&user->errCode, &e, sizeof(e))!=NvSuccess) |
+ return -EINVAL; |
+ return 0; |
+} |
+ |
+static int interrupt_register( |
+ NvOsInstance *Instance, |
+ unsigned long arg) |
+{ |
+ NvOsInterruptRegisterParams k; |
+ NvOsInterruptHandle h = NULL; |
+ NvError e; |
+ NvU32 *irqList = NULL; |
+ NvOsSemaphoreHandle *semList = NULL; |
+ NvOsIrqListNode *node = NULL; |
+ |
+ e = NvOsCopyIn(&k, (void *)arg, sizeof(NvOsInterruptRegisterParams)); |
+ if (e!=NvSuccess) |
+ return -EINVAL; |
+ |
+ irqList = NvOsAlloc(k.nIrqs * sizeof(NvU32)); |
+ semList = NvOsAlloc(k.nIrqs * sizeof(NvOsSemaphoreHandle)); |
+ node = NvOsAlloc(sizeof(NvOsIrqListNode)); |
+ if (!node) |
+ { |
+ e = NvError_InsufficientMemory; |
+ goto fail; |
+ } |
+ |
+ if (!irqList || !semList) |
+ { |
+ e = NvError_InsufficientMemory; |
+ goto fail; |
+ } |
+ NV_CHECK_ERROR_CLEANUP(NvOsCopyIn(irqList, k.Irqs, k.nIrqs*sizeof(NvU32))); |
+ |
+ NV_CHECK_ERROR_CLEANUP( |
+ NvOsCopyIn(semList, k.SemaphoreList, |
+ k.nIrqs*sizeof(NvOsSemaphoreHandle)) |
+ ); |
+ |
+ /* To ensure that the kernel handle is safely stored in the user-space |
+ * wrapper before any interrupts are processed, interrupts must be |
+ * registered and enabled in two separate ioctls. |
+ */ |
+ e = NvOsInterruptRegisterInternal(k.nIrqs, irqList, |
+ (const void*)semList, NULL, &h, NV_FALSE, NV_TRUE); |
+ |
+ if (e==NvSuccess && Instance) |
+ { |
+ spin_lock(&Instance->Lock); |
+ node->h = h; |
+ list_add_tail(&node->list, &Instance->IrqHandles); |
+ spin_unlock(&Instance->Lock); |
+ } |
+ |
+fail: |
+ |
+ NvOsFree(irqList); |
+ NvOsFree(semList); |
+ if (e!=NvSuccess) |
+ { |
+ NvOsFree(node); |
+ h = NULL; |
+ } |
+ |
+ k.errCode = e; |
+ k.kernelHandle = (NvUPtr)h; |
+ e = NvOsCopyOut((void*)arg, &k, sizeof(k)); |
+ |
+ return (e==NvSuccess) ? 0 : -EINVAL; |
+} |
+ |
+static int sem_unmarshal(unsigned long arg) |
+{ |
+ NvOsSemaphoreUnmarshalParams *p = (NvOsSemaphoreUnmarshalParams *)arg; |
+ NvOsSemaphoreUnmarshalParams l; |
+ NvError e; |
+ |
+ l.hNew = NULL; |
+ e = NvOsCopyIn(&l, p, sizeof(l)); |
+ if (e!=NvSuccess) |
+ return -EINVAL; |
+ |
+ e = NvOsSemaphoreUnmarshal(l.hOrig, &l.hNew); |
+ l.Error = e; |
+ |
+ e = NvOsCopyOut(p, &l, sizeof(l)); |
+ if (e!=NvSuccess) |
+ { |
+ if (l.hNew) |
+ NvOsSemaphoreDestroy(l.hNew); |
+ return -EINVAL; |
+ } |
+ return 0; |
+} |
+ |
+static int sem_clone(unsigned long arg) |
+{ |
+ NvOsSemaphoreCloneParams *p = (NvOsSemaphoreCloneParams *)arg; |
+ NvOsSemaphoreCloneParams l; |
+ NvError e; |
+ |
+ l.hNew = NULL; |
+ e = NvOsCopyIn(&l, p, sizeof(l)); |
+ if (e!=NvSuccess) |
+ return -EINVAL; |
+ |
+ e = NvOsSemaphoreClone(l.hOrig, &l.hNew); |
+ l.Error = e; |
+ e = NvOsCopyOut(p, &l, sizeof(l)); |
+ |
+ if (e!=NvSuccess) |
+ { |
+ if (l.hNew) |
+ NvOsSemaphoreDestroy(l.hNew); |
+ return -EINVAL; |
+ } |
+ |
+ return 0; |
+} |
+ |
+static int sem_create(unsigned long arg) |
+{ |
+ NvOsSemaphoreIoctlParams *p = (NvOsSemaphoreIoctlParams *)arg; |
+ NvOsSemaphoreIoctlParams l; |
+ |
+ if (NvOsCopyIn(&l, p, sizeof(l))!=NvSuccess) |
+ return -EINVAL; |
+ |
+ l.sem = NULL; |
+ l.error = NvOsSemaphoreCreate(&l.sem, l.value); |
+ |
+ if (NvOsCopyOut(p, &l, sizeof(l))!=NvSuccess) |
+ { |
+ if (l.sem) |
+ NvOsSemaphoreDestroy(l.sem); |
+ return -EINVAL; |
+ } |
+ |
+ return 0; |
+} |
+ |
+static long nvos_ioctl(struct file *filp, |
+ unsigned int cmd, unsigned long arg) { |
+ int e = 0; |
+ NvError err; |
+ NvOsSemaphoreHandle kernelSem; |
+ NvOsInstance *Instance = (NvOsInstance *)filp->private_data; |
+ |
+ #define DO_CLEANUP( code ) \ |
+ do { \ |
+ err = code; \ |
+ if( err != NvSuccess ) \ |
+ { \ |
+ e = -EINVAL; \ |
+ goto clean; \ |
+ } \ |
+ } while( 0 ) |
+ |
+ switch( cmd ) { |
+ case NV_IOCTL_SEMAPHORE_CREATE: |
+ return sem_create(arg); |
+ |
+ case NV_IOCTL_SEMAPHORE_DESTROY: |
+ DO_CLEANUP( |
+ NvOsCopyIn( &kernelSem, (void *)arg, sizeof(kernelSem) ) |
+ ); |
+ |
+ NvOsSemaphoreDestroy(kernelSem); |
+ break; |
+ case NV_IOCTL_SEMAPHORE_CLONE: |
+ return sem_clone(arg); |
+ |
+ case NV_IOCTL_SEMAPHORE_UNMARSHAL: |
+ return sem_unmarshal(arg); |
+ |
+ case NV_IOCTL_SEMAPHORE_SIGNAL: |
+ DO_CLEANUP( |
+ NvOsCopyIn( &kernelSem, (void *)arg, sizeof(kernelSem) ) |
+ ); |
+ |
+ NvOsSemaphoreSignal(kernelSem); |
+ break; |
+ case NV_IOCTL_SEMAPHORE_WAIT: |
+ DO_CLEANUP( |
+ NvOsCopyIn( &kernelSem, (void *)arg, sizeof(kernelSem) ) |
+ ); |
+ e = NvOsSemaphoreWaitInterruptible(kernelSem); |
+ break; |
+ case NV_IOCTL_SEMAPHORE_WAIT_TIMEOUT: |
+ { |
+ NvOsSemaphoreIoctlParams *p = (NvOsSemaphoreIoctlParams *)arg; |
+ NvOsSemaphoreIoctlParams k; |
+ |
+ DO_CLEANUP( |
+ NvOsCopyIn( &k, p, sizeof(k) ) |
+ ); |
+ |
+ if (k.value == NV_WAIT_INFINITE) |
+ { |
+ k.error = NvSuccess; |
+ e = NvOsSemaphoreWaitInterruptible(kernelSem); |
+ } |
+ else |
+ { |
+ k.error = NvOsSemaphoreWaitTimeout(k.sem, k.value); |
+ } |
+ |
+ DO_CLEANUP( |
+ NvOsCopyOut( &p->error, &k.error, sizeof(k.error) ) |
+ ); |
+ |
+ break; |
+ } |
+ case NV_IOCTL_INTERRUPT_REGISTER: |
+ lock_kernel(); |
+ e = interrupt_register(Instance, arg); |
+ unlock_kernel(); |
+ return e; |
+ |
+ case NV_IOCTL_INTERRUPT_UNREGISTER: |
+ case NV_IOCTL_INTERRUPT_DONE: |
+ case NV_IOCTL_INTERRUPT_ENABLE: |
+ case NV_IOCTL_INTERRUPT_MASK: |
+ lock_kernel(); |
+ e = interrupt_op(Instance, cmd, arg); |
+ unlock_kernel(); |
+ return (e) ? -EINVAL : 0; |
+ |
+ case NV_IOCTL_MEMORY_RANGE: |
+ { |
+ NvOsMemRangeParams *p; |
+ |
+ p = NvOsAlloc( sizeof(NvOsMemRangeParams) ); |
+ if( !p ) |
+ { |
+ e = -ENOMEM; |
+ goto clean; |
+ } |
+ |
+ DO_CLEANUP( |
+ NvOsCopyIn( p, (void *)arg, sizeof(NvOsMemRangeParams) ); |
+ ); |
+ |
+ if (!Instance) |
+ printk(KERN_INFO __FILE__"(%d): No instance!\n", __LINE__); |
+ |
+ if (Instance) |
+ { |
+ down_write(&Instance->RwLock); |
+ Instance->MemRange = p; |
+ up_write(&Instance->RwLock); |
+ } |
+ return 0; |
+ } |
+ default: |
+ pr_err("Unknown IOCTL: %x\n", _IOC_NR(cmd)); |
+ e = -1; |
+ } |
+ |
+ #undef DO_CLEANUP |
+ |
+clean: |
+ return e; |
+} |
+ |
+static void nvos_vma_open (struct vm_area_struct *vma) |
+{ |
+} |
+ |
+static void nvos_vma_close (struct vm_area_struct *vma) |
+{ |
+} |
+ |
+static struct vm_operations_struct nvos_vm_ops = |
+{ |
+ .open = nvos_vma_open, |
+ .close = nvos_vma_close, |
+}; |
+ |
+int nvos_mmap(struct file *filp, struct vm_area_struct *vma) |
+{ |
+ unsigned long addr; |
+ unsigned long size; |
+ unsigned long pfn; |
+ NvOsInstance *Instance = (NvOsInstance *)filp->private_data; |
+ |
+ size = vma->vm_end - vma->vm_start; |
+ pfn = vma->vm_pgoff; |
+ addr = pfn << PAGE_SHIFT; |
+ |
+ if (!Instance) |
+ printk(KERN_INFO __FILE__"(%d): No instance!\n", __LINE__); |
+ |
+ if (Instance) |
+ { |
+ down_read(&Instance->RwLock); |
+ if (Instance->MemRange) |
+ { |
+ /* addr is an offset */ |
+ if( size > Instance->MemRange->size ) |
+ { |
+ printk( "nvos_mmap: size too big for restricted mapping: %lu " |
+ "max %lu\n", size, |
+ (unsigned long)Instance->MemRange->size ); |
+ up_read(&Instance->RwLock); |
+ return -EAGAIN; |
+ } |
+ addr += Instance->MemRange->base; |
+ pfn = addr >> PAGE_SHIFT; |
+ } |
+ up_read(&Instance->RwLock); |
+ } |
+ |
+ vma->vm_flags |= (VM_IO | VM_DONTCOPY | VM_DONTEXPAND); |
+ |
+ // FIXME: This is a major hack |
+#ifdef CONFIG_ARCH_TEGRA_A9 |
+ if (addr < 0x40000000UL) |
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); |
+ else |
+#endif |
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
+ |
+ if (io_remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) |
+ { |
+ printk( "nvos_mmap failed\n" ); |
+ return -EAGAIN; |
+ } |
+ |
+ vma->vm_ops = &nvos_vm_ops; |
+ vma->vm_private_data = Instance; |
+ |
+ return 0; |
+} |
+ |
+module_init(nvos_init); |
+module_exit(nvos_deinit); |