OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * arch/arm/mach-tegra/nvos_user.c |
| 3 * |
| 4 * User-land access to NvOs APIs |
| 5 * |
| 6 * Copyright (c) 2008-2009, NVIDIA Corporation. |
| 7 * |
| 8 * This program is free software; you can redistribute it and/or modify |
| 9 * it under the terms of the GNU General Public License as published by |
| 10 * the Free Software Foundation; either version 2 of the License, or |
| 11 * (at your option) any later version. |
| 12 * |
| 13 * This program is distributed in the hope that it will be useful, but WITHOUT |
| 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| 16 * more details. |
| 17 * |
| 18 * You should have received a copy of the GNU General Public License along |
| 19 * with this program; if not, write to the Free Software Foundation, Inc., |
| 20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 21 */ |
| 22 |
| 23 #include <linux/interrupt.h> |
| 24 #include <linux/io.h> |
| 25 #include <linux/miscdevice.h> |
| 26 #include <linux/module.h> |
| 27 #include <linux/mm.h> |
| 28 #include <linux/mutex.h> |
| 29 #include <linux/proc_fs.h> |
| 30 #include <linux/spinlock.h> |
| 31 #include <linux/uaccess.h> |
| 32 #include <linux/rwsem.h> |
| 33 #include <mach/irqs.h> |
| 34 #include "nvos.h" |
| 35 #include "linux/nvos_ioctl.h" |
| 36 #include "nvassert.h" |
| 37 |
| 38 int nvos_open(struct inode *inode, struct file *file); |
| 39 int nvos_close(struct inode *inode, struct file *file); |
| 40 static long nvos_ioctl(struct file *file, unsigned int cmd, unsigned long arg); |
| 41 int nvos_mmap(struct file *file, struct vm_area_struct *vma); |
| 42 int NvOsSemaphoreWaitInterruptible(NvOsSemaphoreHandle semaphore); |
| 43 |
| 44 #define DEVICE_NAME "nvos" |
| 45 |
| 46 static const struct file_operations nvos_fops = |
| 47 { |
| 48 .owner = THIS_MODULE, |
| 49 .open = nvos_open, |
| 50 .release = nvos_close, |
| 51 .unlocked_ioctl = nvos_ioctl, |
| 52 .mmap = nvos_mmap |
| 53 }; |
| 54 |
| 55 static struct miscdevice nvosDevice = |
| 56 { |
| 57 .name = DEVICE_NAME, |
| 58 .fops = &nvos_fops, |
| 59 .minor = MISC_DYNAMIC_MINOR, |
| 60 }; |
| 61 |
| 62 typedef struct NvOsIrqListNodeRec |
| 63 { |
| 64 struct list_head list; |
| 65 NvOsInterruptHandle h; |
| 66 } NvOsIrqListNode; |
| 67 |
| 68 typedef struct NvOsInstanceRec |
| 69 { |
| 70 struct rw_semaphore RwLock; |
| 71 struct vm_area_struct *Vma; |
| 72 NvOsMemRangeParams *MemRange; |
| 73 struct task_struct *tsk; |
| 74 spinlock_t Lock; |
| 75 struct list_head IrqHandles; |
| 76 int pid; |
| 77 } NvOsInstance; |
| 78 |
| 79 static int __init nvos_init( void ) |
| 80 { |
| 81 int retVal = 0; |
| 82 |
| 83 retVal = misc_register(&nvosDevice); |
| 84 |
| 85 if (retVal < 0) |
| 86 { |
| 87 printk("nvos init failure\n" ); |
| 88 } |
| 89 |
| 90 return retVal; |
| 91 } |
| 92 |
| 93 static void __exit nvos_deinit( void ) |
| 94 { |
| 95 misc_deregister (&nvosDevice); |
| 96 } |
| 97 |
| 98 int nvos_open(struct inode *inode, struct file *filp) |
| 99 { |
| 100 NvOsInstance *Instance = NULL; |
| 101 |
| 102 filp->private_data = NULL; |
| 103 |
| 104 Instance = NvOsAlloc(sizeof(NvOsInstance)); |
| 105 if (!Instance) |
| 106 { |
| 107 printk(KERN_INFO __FILE__ ": nvos_open failed\n"); |
| 108 return -ENOMEM; |
| 109 } |
| 110 init_rwsem(&Instance->RwLock); |
| 111 Instance->tsk = current; |
| 112 Instance->pid = current->group_leader->pid; |
| 113 Instance->MemRange = NULL; |
| 114 spin_lock_init(&Instance->Lock); |
| 115 INIT_LIST_HEAD(&Instance->IrqHandles); |
| 116 filp->private_data = (void*)Instance; |
| 117 |
| 118 return 0; |
| 119 } |
| 120 |
| 121 int nvos_close(struct inode *inode, struct file *filp) |
| 122 { |
| 123 NvOsIrqListNode *LeakedIrq; |
| 124 |
| 125 if (filp->private_data) |
| 126 { |
| 127 NvOsInstance *Instance = (NvOsInstance *)filp->private_data; |
| 128 filp->private_data = NULL; |
| 129 while (!list_empty(&Instance->IrqHandles)) |
| 130 { |
| 131 LeakedIrq = list_first_entry(&Instance->IrqHandles, |
| 132 NvOsIrqListNode, list); |
| 133 list_del_init(&LeakedIrq->list); |
| 134 printk(__FILE__": leaked NvOsInterruptHandle %p\n", |
| 135 LeakedIrq->h); |
| 136 NvOsInterruptUnregister(LeakedIrq->h); |
| 137 NvOsFree(LeakedIrq); |
| 138 } |
| 139 |
| 140 if (Instance->MemRange) |
| 141 NvOsFree(Instance->MemRange); |
| 142 NvOsFree(Instance); |
| 143 } |
| 144 |
| 145 return 0; |
| 146 } |
| 147 |
| 148 extern NvError NvOsInterruptRegisterInternal( |
| 149 NvU32 IrqListSize, |
| 150 const NvU32 *pIrqList, |
| 151 const void *pIrqHandlerList, |
| 152 void* context, |
| 153 NvOsInterruptHandle *handle, |
| 154 NvBool InterruptEnable, |
| 155 NvBool IsUser); |
| 156 |
| 157 static int interrupt_op( |
| 158 NvOsInstance *Instance, |
| 159 unsigned int cmd, |
| 160 unsigned long arg) |
| 161 { |
| 162 NvOsInterruptOpParams p; |
| 163 NvOsInterruptOpParams *user = (NvOsInterruptOpParams*)arg; |
| 164 NvError e; |
| 165 |
| 166 e = NvOsCopyIn(&p, user, sizeof(NvOsInterruptOpParams)); |
| 167 if (e != NvSuccess) |
| 168 return -EINVAL; |
| 169 |
| 170 switch(cmd) { |
| 171 case NV_IOCTL_INTERRUPT_ENABLE: |
| 172 e = NvOsInterruptEnable((NvOsInterruptHandle)p.handle); |
| 173 break; |
| 174 case NV_IOCTL_INTERRUPT_DONE: |
| 175 NvOsInterruptDone((NvOsInterruptHandle)p.handle); |
| 176 e = NvSuccess; |
| 177 break; |
| 178 case NV_IOCTL_INTERRUPT_UNREGISTER: |
| 179 { |
| 180 NvOsIrqListNode *IrqNode; |
| 181 if (Instance) |
| 182 { |
| 183 e = NvError_CountMismatch; |
| 184 spin_lock(&Instance->Lock); |
| 185 list_for_each_entry(IrqNode, &Instance->IrqHandles, list) |
| 186 { |
| 187 if (IrqNode->h == (NvOsInterruptHandle)p.handle) |
| 188 { |
| 189 list_del(&IrqNode->list); |
| 190 NvOsInterruptUnregister(IrqNode->h); |
| 191 NvOsFree(IrqNode); |
| 192 e = NvSuccess; |
| 193 break; |
| 194 } |
| 195 } |
| 196 spin_unlock(&Instance->Lock); |
| 197 } |
| 198 else |
| 199 { |
| 200 NvOsInterruptUnregister((NvOsInterruptHandle)p.handle); |
| 201 } |
| 202 e = NvSuccess; |
| 203 break; |
| 204 } |
| 205 case NV_IOCTL_INTERRUPT_MASK: |
| 206 NvOsInterruptMask((NvOsInterruptHandle)p.handle, |
| 207 p.arg ? NV_TRUE : NV_FALSE); |
| 208 e = NvSuccess; |
| 209 break; |
| 210 default: |
| 211 return -EINVAL; |
| 212 } |
| 213 |
| 214 if (NvOsCopyOut(&user->errCode, &e, sizeof(e))!=NvSuccess) |
| 215 return -EINVAL; |
| 216 return 0; |
| 217 } |
| 218 |
| 219 static int interrupt_register( |
| 220 NvOsInstance *Instance, |
| 221 unsigned long arg) |
| 222 { |
| 223 NvOsInterruptRegisterParams k; |
| 224 NvOsInterruptHandle h = NULL; |
| 225 NvError e; |
| 226 NvU32 *irqList = NULL; |
| 227 NvOsSemaphoreHandle *semList = NULL; |
| 228 NvOsIrqListNode *node = NULL; |
| 229 |
| 230 e = NvOsCopyIn(&k, (void *)arg, sizeof(NvOsInterruptRegisterParams)); |
| 231 if (e!=NvSuccess) |
| 232 return -EINVAL; |
| 233 |
| 234 irqList = NvOsAlloc(k.nIrqs * sizeof(NvU32)); |
| 235 semList = NvOsAlloc(k.nIrqs * sizeof(NvOsSemaphoreHandle)); |
| 236 node = NvOsAlloc(sizeof(NvOsIrqListNode)); |
| 237 if (!node) |
| 238 { |
| 239 e = NvError_InsufficientMemory; |
| 240 goto fail; |
| 241 } |
| 242 |
| 243 if (!irqList || !semList) |
| 244 { |
| 245 e = NvError_InsufficientMemory; |
| 246 goto fail; |
| 247 } |
| 248 NV_CHECK_ERROR_CLEANUP(NvOsCopyIn(irqList, k.Irqs, k.nIrqs*sizeof(NvU32))); |
| 249 |
| 250 NV_CHECK_ERROR_CLEANUP( |
| 251 NvOsCopyIn(semList, k.SemaphoreList, |
| 252 k.nIrqs*sizeof(NvOsSemaphoreHandle)) |
| 253 ); |
| 254 |
| 255 /* To ensure that the kernel handle is safely stored in the user-space |
| 256 * wrapper before any interrupts are processed, interrupts must be |
| 257 * registered and enabled in two separate ioctls. |
| 258 */ |
| 259 e = NvOsInterruptRegisterInternal(k.nIrqs, irqList, |
| 260 (const void*)semList, NULL, &h, NV_FALSE, NV_TRUE); |
| 261 |
| 262 if (e==NvSuccess && Instance) |
| 263 { |
| 264 spin_lock(&Instance->Lock); |
| 265 node->h = h; |
| 266 list_add_tail(&node->list, &Instance->IrqHandles); |
| 267 spin_unlock(&Instance->Lock); |
| 268 } |
| 269 |
| 270 fail: |
| 271 |
| 272 NvOsFree(irqList); |
| 273 NvOsFree(semList); |
| 274 if (e!=NvSuccess) |
| 275 { |
| 276 NvOsFree(node); |
| 277 h = NULL; |
| 278 } |
| 279 |
| 280 k.errCode = e; |
| 281 k.kernelHandle = (NvUPtr)h; |
| 282 e = NvOsCopyOut((void*)arg, &k, sizeof(k)); |
| 283 |
| 284 return (e==NvSuccess) ? 0 : -EINVAL; |
| 285 } |
| 286 |
| 287 static int sem_unmarshal(unsigned long arg) |
| 288 { |
| 289 NvOsSemaphoreUnmarshalParams *p = (NvOsSemaphoreUnmarshalParams *)arg; |
| 290 NvOsSemaphoreUnmarshalParams l; |
| 291 NvError e; |
| 292 |
| 293 l.hNew = NULL; |
| 294 e = NvOsCopyIn(&l, p, sizeof(l)); |
| 295 if (e!=NvSuccess) |
| 296 return -EINVAL; |
| 297 |
| 298 e = NvOsSemaphoreUnmarshal(l.hOrig, &l.hNew); |
| 299 l.Error = e; |
| 300 |
| 301 e = NvOsCopyOut(p, &l, sizeof(l)); |
| 302 if (e!=NvSuccess) |
| 303 { |
| 304 if (l.hNew) |
| 305 NvOsSemaphoreDestroy(l.hNew); |
| 306 return -EINVAL; |
| 307 } |
| 308 return 0; |
| 309 } |
| 310 |
| 311 static int sem_clone(unsigned long arg) |
| 312 { |
| 313 NvOsSemaphoreCloneParams *p = (NvOsSemaphoreCloneParams *)arg; |
| 314 NvOsSemaphoreCloneParams l; |
| 315 NvError e; |
| 316 |
| 317 l.hNew = NULL; |
| 318 e = NvOsCopyIn(&l, p, sizeof(l)); |
| 319 if (e!=NvSuccess) |
| 320 return -EINVAL; |
| 321 |
| 322 e = NvOsSemaphoreClone(l.hOrig, &l.hNew); |
| 323 l.Error = e; |
| 324 e = NvOsCopyOut(p, &l, sizeof(l)); |
| 325 |
| 326 if (e!=NvSuccess) |
| 327 { |
| 328 if (l.hNew) |
| 329 NvOsSemaphoreDestroy(l.hNew); |
| 330 return -EINVAL; |
| 331 } |
| 332 |
| 333 return 0; |
| 334 } |
| 335 |
| 336 static int sem_create(unsigned long arg) |
| 337 { |
| 338 NvOsSemaphoreIoctlParams *p = (NvOsSemaphoreIoctlParams *)arg; |
| 339 NvOsSemaphoreIoctlParams l; |
| 340 |
| 341 if (NvOsCopyIn(&l, p, sizeof(l))!=NvSuccess) |
| 342 return -EINVAL; |
| 343 |
| 344 l.sem = NULL; |
| 345 l.error = NvOsSemaphoreCreate(&l.sem, l.value); |
| 346 |
| 347 if (NvOsCopyOut(p, &l, sizeof(l))!=NvSuccess) |
| 348 { |
| 349 if (l.sem) |
| 350 NvOsSemaphoreDestroy(l.sem); |
| 351 return -EINVAL; |
| 352 } |
| 353 |
| 354 return 0; |
| 355 } |
| 356 |
| 357 static long nvos_ioctl(struct file *filp, |
| 358 unsigned int cmd, unsigned long arg) { |
| 359 int e = 0; |
| 360 NvError err; |
| 361 NvOsSemaphoreHandle kernelSem; |
| 362 NvOsInstance *Instance = (NvOsInstance *)filp->private_data; |
| 363 |
| 364 #define DO_CLEANUP( code ) \ |
| 365 do { \ |
| 366 err = code; \ |
| 367 if( err != NvSuccess ) \ |
| 368 { \ |
| 369 e = -EINVAL; \ |
| 370 goto clean; \ |
| 371 } \ |
| 372 } while( 0 ) |
| 373 |
| 374 switch( cmd ) { |
| 375 case NV_IOCTL_SEMAPHORE_CREATE: |
| 376 return sem_create(arg); |
| 377 |
| 378 case NV_IOCTL_SEMAPHORE_DESTROY: |
| 379 DO_CLEANUP( |
| 380 NvOsCopyIn( &kernelSem, (void *)arg, sizeof(kernelSem) ) |
| 381 ); |
| 382 |
| 383 NvOsSemaphoreDestroy(kernelSem); |
| 384 break; |
| 385 case NV_IOCTL_SEMAPHORE_CLONE: |
| 386 return sem_clone(arg); |
| 387 |
| 388 case NV_IOCTL_SEMAPHORE_UNMARSHAL: |
| 389 return sem_unmarshal(arg); |
| 390 |
| 391 case NV_IOCTL_SEMAPHORE_SIGNAL: |
| 392 DO_CLEANUP( |
| 393 NvOsCopyIn( &kernelSem, (void *)arg, sizeof(kernelSem) ) |
| 394 ); |
| 395 |
| 396 NvOsSemaphoreSignal(kernelSem); |
| 397 break; |
| 398 case NV_IOCTL_SEMAPHORE_WAIT: |
| 399 DO_CLEANUP( |
| 400 NvOsCopyIn( &kernelSem, (void *)arg, sizeof(kernelSem) ) |
| 401 ); |
| 402 e = NvOsSemaphoreWaitInterruptible(kernelSem); |
| 403 break; |
| 404 case NV_IOCTL_SEMAPHORE_WAIT_TIMEOUT: |
| 405 { |
| 406 NvOsSemaphoreIoctlParams *p = (NvOsSemaphoreIoctlParams *)arg; |
| 407 NvOsSemaphoreIoctlParams k; |
| 408 |
| 409 DO_CLEANUP( |
| 410 NvOsCopyIn( &k, p, sizeof(k) ) |
| 411 ); |
| 412 |
| 413 if (k.value == NV_WAIT_INFINITE) |
| 414 { |
| 415 k.error = NvSuccess; |
| 416 e = NvOsSemaphoreWaitInterruptible(kernelSem); |
| 417 } |
| 418 else |
| 419 { |
| 420 k.error = NvOsSemaphoreWaitTimeout(k.sem, k.value); |
| 421 } |
| 422 |
| 423 DO_CLEANUP( |
| 424 NvOsCopyOut( &p->error, &k.error, sizeof(k.error) ) |
| 425 ); |
| 426 |
| 427 break; |
| 428 } |
| 429 case NV_IOCTL_INTERRUPT_REGISTER: |
| 430 lock_kernel(); |
| 431 e = interrupt_register(Instance, arg); |
| 432 unlock_kernel(); |
| 433 return e; |
| 434 |
| 435 case NV_IOCTL_INTERRUPT_UNREGISTER: |
| 436 case NV_IOCTL_INTERRUPT_DONE: |
| 437 case NV_IOCTL_INTERRUPT_ENABLE: |
| 438 case NV_IOCTL_INTERRUPT_MASK: |
| 439 lock_kernel(); |
| 440 e = interrupt_op(Instance, cmd, arg); |
| 441 unlock_kernel(); |
| 442 return (e) ? -EINVAL : 0; |
| 443 |
| 444 case NV_IOCTL_MEMORY_RANGE: |
| 445 { |
| 446 NvOsMemRangeParams *p; |
| 447 |
| 448 p = NvOsAlloc( sizeof(NvOsMemRangeParams) ); |
| 449 if( !p ) |
| 450 { |
| 451 e = -ENOMEM; |
| 452 goto clean; |
| 453 } |
| 454 |
| 455 DO_CLEANUP( |
| 456 NvOsCopyIn( p, (void *)arg, sizeof(NvOsMemRangeParams) ); |
| 457 ); |
| 458 |
| 459 if (!Instance) |
| 460 printk(KERN_INFO __FILE__"(%d): No instance!\n", __LINE__); |
| 461 |
| 462 if (Instance) |
| 463 { |
| 464 down_write(&Instance->RwLock); |
| 465 Instance->MemRange = p; |
| 466 up_write(&Instance->RwLock); |
| 467 } |
| 468 return 0; |
| 469 } |
| 470 default: |
| 471 pr_err("Unknown IOCTL: %x\n", _IOC_NR(cmd)); |
| 472 e = -1; |
| 473 } |
| 474 |
| 475 #undef DO_CLEANUP |
| 476 |
| 477 clean: |
| 478 return e; |
| 479 } |
| 480 |
| 481 static void nvos_vma_open (struct vm_area_struct *vma) |
| 482 { |
| 483 } |
| 484 |
| 485 static void nvos_vma_close (struct vm_area_struct *vma) |
| 486 { |
| 487 } |
| 488 |
| 489 static struct vm_operations_struct nvos_vm_ops = |
| 490 { |
| 491 .open = nvos_vma_open, |
| 492 .close = nvos_vma_close, |
| 493 }; |
| 494 |
| 495 int nvos_mmap(struct file *filp, struct vm_area_struct *vma) |
| 496 { |
| 497 unsigned long addr; |
| 498 unsigned long size; |
| 499 unsigned long pfn; |
| 500 NvOsInstance *Instance = (NvOsInstance *)filp->private_data; |
| 501 |
| 502 size = vma->vm_end - vma->vm_start; |
| 503 pfn = vma->vm_pgoff; |
| 504 addr = pfn << PAGE_SHIFT; |
| 505 |
| 506 if (!Instance) |
| 507 printk(KERN_INFO __FILE__"(%d): No instance!\n", __LINE__); |
| 508 |
| 509 if (Instance) |
| 510 { |
| 511 down_read(&Instance->RwLock); |
| 512 if (Instance->MemRange) |
| 513 { |
| 514 /* addr is an offset */ |
| 515 if( size > Instance->MemRange->size ) |
| 516 { |
| 517 printk( "nvos_mmap: size too big for restricted mapping: %lu " |
| 518 "max %lu\n", size, |
| 519 (unsigned long)Instance->MemRange->size ); |
| 520 up_read(&Instance->RwLock); |
| 521 return -EAGAIN; |
| 522 } |
| 523 addr += Instance->MemRange->base; |
| 524 pfn = addr >> PAGE_SHIFT; |
| 525 } |
| 526 up_read(&Instance->RwLock); |
| 527 } |
| 528 |
| 529 vma->vm_flags |= (VM_IO | VM_DONTCOPY | VM_DONTEXPAND); |
| 530 |
| 531 // FIXME: This is a major hack |
| 532 #ifdef CONFIG_ARCH_TEGRA_A9 |
| 533 if (addr < 0x40000000UL) |
| 534 vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); |
| 535 else |
| 536 #endif |
| 537 vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
| 538 |
| 539 if (io_remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot)) |
| 540 { |
| 541 printk( "nvos_mmap failed\n" ); |
| 542 return -EAGAIN; |
| 543 } |
| 544 |
| 545 vma->vm_ops = &nvos_vm_ops; |
| 546 vma->vm_private_data = Instance; |
| 547 |
| 548 return 0; |
| 549 } |
| 550 |
| 551 module_init(nvos_init); |
| 552 module_exit(nvos_deinit); |
OLD | NEW |