summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/nvos_user.c
diff options
context:
space:
mode:
authorGary King <gking@nvidia.com>2010-05-12 20:51:28 -0700
committerGary King <gking@nvidia.com>2010-05-12 20:51:28 -0700
commit9e90f484f2c485ad11136b6948fe26d72113d068 (patch)
tree624d8e0ae4a612d9bf5e972813504489397ca391 /arch/arm/mach-tegra/nvos_user.c
parent2381fc59dbf1d4256a33dd4eefb094221a55f635 (diff)
[ARM/tegra] add NvOs
integrate files from android-tegra-2.6.29 branch to build NvOs changes from previous branch: * nvos_ioctl.h moved to top-level include/ directory * tegra ATAG value and structure declaration moved to nvos.c * cache and memory fence calls updated to match new kernel APIs * PhysicalMemMap and PhysicalMemUnmap just call ioremap, which handles static vs dynamic remapping internally. Change-Id: I13d7b7068c12d73a4cf3d810a0e3ea6b21e3805c
Diffstat (limited to 'arch/arm/mach-tegra/nvos_user.c')
-rw-r--r--arch/arm/mach-tegra/nvos_user.c552
1 files changed, 552 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/nvos_user.c b/arch/arm/mach-tegra/nvos_user.c
new file mode 100644
index 000000000000..19f151815a55
--- /dev/null
+++ b/arch/arm/mach-tegra/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 "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);