FILE
vme_user.c
File preview with syntax highlighting.
linux-6.19.14/drivers/staging/vme_user/vme_user.c
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * VMEbus User access driver 4 * 5 * Author: Martyn Welch <martyn.welch@ge.com> 6 * Copyright 2008 GE Intelligent Platforms Embedded Systems, Inc. 7 * 8 * Based on work by: 9 * Tom Armistead and Ajit Prem 10 * Copyright 2004 Motorola Inc. 11 */ 12 13#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 14 15#include <linux/refcount.h> 16#include <linux/cdev.h> 17#include <linux/delay.h> 18#include <linux/device.h> 19#include <linux/dma-mapping.h> 20#include <linux/errno.h> 21#include <linux/init.h> 22#include <linux/ioctl.h> 23#include <linux/kernel.h> 24#include <linux/mm.h> 25#include <linux/module.h> 26#include <linux/pagemap.h> 27#include <linux/pci.h> 28#include <linux/mutex.h> 29#include <linux/slab.h> 30#include <linux/spinlock.h> 31#include <linux/syscalls.h> 32#include <linux/types.h> 33 34#include <linux/io.h> 35#include <linux/uaccess.h> 36 37#include "vme.h" 38#include "vme_user.h" 39 40#define DRIVER_NAME "vme_user" 41 42static int bus[VME_USER_BUS_MAX]; 43static unsigned int bus_num; 44 45/* Currently Documentation/admin-guide/devices.rst defines the 46 * following for VME: 47 * 48 * 221 char VME bus 49 * 0 = /dev/bus/vme/m0 First master image 50 * 1 = /dev/bus/vme/m1 Second master image 51 * 2 = /dev/bus/vme/m2 Third master image 52 * 3 = /dev/bus/vme/m3 Fourth master image 53 * 4 = /dev/bus/vme/s0 First slave image 54 * 5 = /dev/bus/vme/s1 Second slave image 55 * 6 = /dev/bus/vme/s2 Third slave image 56 * 7 = /dev/bus/vme/s3 Fourth slave image 57 * 8 = /dev/bus/vme/ctl Control 58 * 59 * It is expected that all VME bus drivers will use the 60 * same interface. For interface documentation see 61 * http://www.vmelinux.org/. 62 * 63 * However the VME driver at http://www.vmelinux.org/ is rather old and doesn't 64 * even support the tsi148 chipset (which has 8 master and 8 slave windows). 65 * We'll run with this for now as far as possible, however it probably makes 66 * sense to get rid of the old mappings and just do everything dynamically. 67 * 68 * So for now, we'll restrict the driver to providing 4 masters and 4 slaves as 69 * defined above and try to support at least some of the interface from 70 * http://www.vmelinux.org/ as an alternative the driver can be written 71 * providing a saner interface later. 72 * 73 * The vmelinux.org driver never supported slave images, the devices reserved 74 * for slaves were repurposed to support all 8 master images on the UniverseII! 75 * We shall support 4 masters and 4 slaves with this driver. 76 */ 77#define VME_MAJOR 221 /* VME Major Device Number */ 78#define VME_DEVS 9 /* Number of dev entries */ 79 80#define MASTER_MINOR 0 81#define MASTER_MAX 3 82#define SLAVE_MINOR 4 83#define SLAVE_MAX 7 84#define CONTROL_MINOR 8 85 86#define PCI_BUF_SIZE 0x20000 /* Size of one slave image buffer */ 87 88/* 89 * Structure to handle image related parameters. 90 */ 91struct image_desc { 92 void *kern_buf; /* Buffer address in kernel space */ 93 dma_addr_t pci_buf; /* Buffer address in PCI address space */ 94 unsigned long long size_buf; /* Buffer size */ 95 struct mutex mutex; /* Mutex for locking image */ 96 struct device *device; /* Sysfs device */ 97 struct vme_resource *resource; /* VME resource */ 98 int mmap_count; /* Number of current mmap's */ 99}; 100 101static struct image_desc image[VME_DEVS]; 102 103static struct cdev *vme_user_cdev; /* Character device */ 104static struct vme_dev *vme_user_bridge; /* Pointer to user device */ 105 106static const struct class vme_user_sysfs_class = { 107 .name = DRIVER_NAME, 108}; 109 110static const int type[VME_DEVS] = { MASTER_MINOR, MASTER_MINOR, 111 MASTER_MINOR, MASTER_MINOR, 112 SLAVE_MINOR, SLAVE_MINOR, 113 SLAVE_MINOR, SLAVE_MINOR, 114 CONTROL_MINOR 115 }; 116 117struct vme_user_vma_priv { 118 unsigned int minor; 119 refcount_t refcnt; 120}; 121 122static ssize_t resource_to_user(int minor, char __user *buf, size_t count, 123 loff_t *ppos) 124{ 125 ssize_t copied = 0; 126 127 if (count > image[minor].size_buf) 128 count = image[minor].size_buf; 129 130 copied = vme_master_read(image[minor].resource, image[minor].kern_buf, 131 count, *ppos); 132 if (copied < 0) 133 return (int)copied; 134 135 if (copy_to_user(buf, image[minor].kern_buf, (unsigned long)copied)) 136 return -EFAULT; 137 138 return copied; 139} 140 141static ssize_t resource_from_user(unsigned int minor, const char __user *buf, 142 size_t count, loff_t *ppos) 143{ 144 if (count > image[minor].size_buf) 145 count = image[minor].size_buf; 146 147 if (copy_from_user(image[minor].kern_buf, buf, (unsigned long)count)) 148 return -EFAULT; 149 150 return vme_master_write(image[minor].resource, image[minor].kern_buf, 151 count, *ppos); 152} 153 154static ssize_t buffer_to_user(unsigned int minor, char __user *buf, 155 size_t count, loff_t *ppos) 156{ 157 void *image_ptr; 158 159 image_ptr = image[minor].kern_buf + *ppos; 160 if (copy_to_user(buf, image_ptr, (unsigned long)count)) 161 return -EFAULT; 162 163 return count; 164} 165 166static ssize_t buffer_from_user(unsigned int minor, const char __user *buf, 167 size_t count, loff_t *ppos) 168{ 169 void *image_ptr; 170 171 image_ptr = image[minor].kern_buf + *ppos; 172 if (copy_from_user(image_ptr, buf, (unsigned long)count)) 173 return -EFAULT; 174 175 return count; 176} 177 178static ssize_t vme_user_read(struct file *file, char __user *buf, size_t count, 179 loff_t *ppos) 180{ 181 unsigned int minor = iminor(file_inode(file)); 182 ssize_t retval; 183 size_t image_size; 184 185 if (minor == CONTROL_MINOR) 186 return 0; 187 188 mutex_lock(&image[minor].mutex); 189 190 /* XXX Do we *really* want this helper - we can use vme_*_get ? */ 191 image_size = vme_get_size(image[minor].resource); 192 193 /* Ensure we are starting at a valid location */ 194 if ((*ppos < 0) || (*ppos > (image_size - 1))) { 195 mutex_unlock(&image[minor].mutex); 196 return 0; 197 } 198 199 /* Ensure not reading past end of the image */ 200 if (*ppos + count > image_size) 201 count = image_size - *ppos; 202 203 switch (type[minor]) { 204 case MASTER_MINOR: 205 retval = resource_to_user(minor, buf, count, ppos); 206 break; 207 case SLAVE_MINOR: 208 retval = buffer_to_user(minor, buf, count, ppos); 209 break; 210 default: 211 retval = -EINVAL; 212 } 213 214 mutex_unlock(&image[minor].mutex); 215 if (retval > 0) 216 *ppos += retval; 217 218 return retval; 219} 220 221static ssize_t vme_user_write(struct file *file, const char __user *buf, 222 size_t count, loff_t *ppos) 223{ 224 unsigned int minor = iminor(file_inode(file)); 225 ssize_t retval; 226 size_t image_size; 227 228 if (minor == CONTROL_MINOR) 229 return 0; 230 231 mutex_lock(&image[minor].mutex); 232 233 image_size = vme_get_size(image[minor].resource); 234 235 /* Ensure we are starting at a valid location */ 236 if ((*ppos < 0) || (*ppos > (image_size - 1))) { 237 mutex_unlock(&image[minor].mutex); 238 return 0; 239 } 240 241 /* Ensure not reading past end of the image */ 242 if (*ppos + count > image_size) 243 count = image_size - *ppos; 244 245 switch (type[minor]) { 246 case MASTER_MINOR: 247 retval = resource_from_user(minor, buf, count, ppos); 248 break; 249 case SLAVE_MINOR: 250 retval = buffer_from_user(minor, buf, count, ppos); 251 break; 252 default: 253 retval = -EINVAL; 254 } 255 256 mutex_unlock(&image[minor].mutex); 257 258 if (retval > 0) 259 *ppos += retval; 260 261 return retval; 262} 263 264static loff_t vme_user_llseek(struct file *file, loff_t off, int whence) 265{ 266 unsigned int minor = iminor(file_inode(file)); 267 size_t image_size; 268 loff_t res; 269 270 switch (type[minor]) { 271 case MASTER_MINOR: 272 case SLAVE_MINOR: 273 mutex_lock(&image[minor].mutex); 274 image_size = vme_get_size(image[minor].resource); 275 res = fixed_size_llseek(file, off, whence, image_size); 276 mutex_unlock(&image[minor].mutex); 277 return res; 278 } 279 280 return -EINVAL; 281} 282 283/* 284 * The ioctls provided by the old VME access method (the one at vmelinux.org) 285 * are most certainly wrong as the effectively push the registers layout 286 * through to user space. Given that the VME core can handle multiple bridges, 287 * with different register layouts this is most certainly not the way to go. 288 * 289 * We aren't using the structures defined in the Motorola driver either - these 290 * are also quite low level, however we should use the definitions that have 291 * already been defined. 292 */ 293static int vme_user_ioctl(struct inode *inode, struct file *file, 294 unsigned int cmd, unsigned long arg) 295{ 296 struct vme_master master; 297 struct vme_slave slave; 298 struct vme_irq_id irq_req; 299 unsigned long copied; 300 unsigned int minor = iminor(inode); 301 int retval; 302 dma_addr_t pci_addr; 303 void __user *argp = (void __user *)arg; 304 305 switch (type[minor]) { 306 case CONTROL_MINOR: 307 switch (cmd) { 308 case VME_IRQ_GEN: 309 copied = copy_from_user(&irq_req, argp, 310 sizeof(irq_req)); 311 if (copied) { 312 pr_warn("Partial copy from userspace\n"); 313 return -EFAULT; 314 } 315 316 return vme_irq_generate(vme_user_bridge, 317 irq_req.level, 318 irq_req.statid); 319 } 320 break; 321 case MASTER_MINOR: 322 switch (cmd) { 323 case VME_GET_MASTER: 324 memset(&master, 0, sizeof(master)); 325 326 /* XXX We do not want to push aspace, cycle and width 327 * to userspace as they are 328 */ 329 retval = vme_master_get(image[minor].resource, 330 &master.enable, 331 &master.vme_addr, 332 &master.size, &master.aspace, 333 &master.cycle, &master.dwidth); 334 335 copied = copy_to_user(argp, &master, 336 sizeof(master)); 337 if (copied) { 338 pr_warn("Partial copy to userspace\n"); 339 return -EFAULT; 340 } 341 342 return retval; 343 344 case VME_SET_MASTER: 345 346 if (image[minor].mmap_count != 0) { 347 pr_warn("Can't adjust mapped window\n"); 348 return -EPERM; 349 } 350 351 copied = copy_from_user(&master, argp, sizeof(master)); 352 if (copied) { 353 pr_warn("Partial copy from userspace\n"); 354 return -EFAULT; 355 } 356 357 /* XXX We do not want to push aspace, cycle and width 358 * to userspace as they are 359 */ 360 return vme_master_set(image[minor].resource, 361 master.enable, master.vme_addr, master.size, 362 master.aspace, master.cycle, master.dwidth); 363 364 break; 365 } 366 break; 367 case SLAVE_MINOR: 368 switch (cmd) { 369 case VME_GET_SLAVE: 370 memset(&slave, 0, sizeof(slave)); 371 372 /* XXX We do not want to push aspace, cycle and width 373 * to userspace as they are 374 */ 375 retval = vme_slave_get(image[minor].resource, 376 &slave.enable, &slave.vme_addr, 377 &slave.size, &pci_addr, 378 &slave.aspace, &slave.cycle); 379 380 copied = copy_to_user(argp, &slave, 381 sizeof(slave)); 382 if (copied) { 383 pr_warn("Partial copy to userspace\n"); 384 return -EFAULT; 385 } 386 387 return retval; 388 389 case VME_SET_SLAVE: 390 391 copied = copy_from_user(&slave, argp, sizeof(slave)); 392 if (copied) { 393 pr_warn("Partial copy from userspace\n"); 394 return -EFAULT; 395 } 396 397 /* XXX We do not want to push aspace, cycle and width 398 * to userspace as they are 399 */ 400 return vme_slave_set(image[minor].resource, 401 slave.enable, slave.vme_addr, slave.size, 402 image[minor].pci_buf, slave.aspace, 403 slave.cycle); 404 405 break; 406 } 407 break; 408 } 409 410 return -EINVAL; 411} 412 413static long 414vme_user_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 415{ 416 int ret; 417 struct inode *inode = file_inode(file); 418 unsigned int minor = iminor(inode); 419 420 mutex_lock(&image[minor].mutex); 421 ret = vme_user_ioctl(inode, file, cmd, arg); 422 mutex_unlock(&image[minor].mutex); 423 424 return ret; 425} 426 427static void vme_user_vm_open(struct vm_area_struct *vma) 428{ 429 struct vme_user_vma_priv *vma_priv = vma->vm_private_data; 430 431 refcount_inc(&vma_priv->refcnt); 432} 433 434static void vme_user_vm_close(struct vm_area_struct *vma) 435{ 436 struct vme_user_vma_priv *vma_priv = vma->vm_private_data; 437 unsigned int minor = vma_priv->minor; 438 439 if (!refcount_dec_and_test(&vma_priv->refcnt)) 440 return; 441 442 mutex_lock(&image[minor].mutex); 443 image[minor].mmap_count--; 444 mutex_unlock(&image[minor].mutex); 445 446 kfree(vma_priv); 447} 448 449static const struct vm_operations_struct vme_user_vm_ops = { 450 .open = vme_user_vm_open, 451 .close = vme_user_vm_close, 452}; 453 454static int vme_user_master_mmap(unsigned int minor, struct vm_area_struct *vma) 455{ 456 int err; 457 struct vme_user_vma_priv *vma_priv; 458 459 mutex_lock(&image[minor].mutex); 460 461 err = vme_master_mmap(image[minor].resource, vma); 462 if (err) { 463 mutex_unlock(&image[minor].mutex); 464 return err; 465 } 466 467 vma_priv = kmalloc(sizeof(*vma_priv), GFP_KERNEL); 468 if (!vma_priv) { 469 mutex_unlock(&image[minor].mutex); 470 return -ENOMEM; 471 } 472 473 vma_priv->minor = minor; 474 refcount_set(&vma_priv->refcnt, 1); 475 vma->vm_ops = &vme_user_vm_ops; 476 vma->vm_private_data = vma_priv; 477 478 image[minor].mmap_count++; 479 480 mutex_unlock(&image[minor].mutex); 481 482 return 0; 483} 484 485static int vme_user_mmap(struct file *file, struct vm_area_struct *vma) 486{ 487 unsigned int minor = iminor(file_inode(file)); 488 489 if (type[minor] == MASTER_MINOR) 490 return vme_user_master_mmap(minor, vma); 491 492 return -ENODEV; 493} 494 495static const struct file_operations vme_user_fops = { 496 .read = vme_user_read, 497 .write = vme_user_write, 498 .llseek = vme_user_llseek, 499 .unlocked_ioctl = vme_user_unlocked_ioctl, 500 .compat_ioctl = compat_ptr_ioctl, 501 .mmap = vme_user_mmap, 502}; 503 504static int vme_user_match(struct vme_dev *vdev) 505{ 506 int i; 507 508 int cur_bus = vme_bus_num(vdev); 509 int cur_slot = vme_slot_num(vdev); 510 511 for (i = 0; i < bus_num; i++) 512 if ((cur_bus == bus[i]) && (cur_slot == vdev->num)) 513 return 1; 514 515 return 0; 516} 517 518/* 519 * In this simple access driver, the old behaviour is being preserved as much 520 * as practical. We will therefore reserve the buffers and request the images 521 * here so that we don't have to do it later. 522 */ 523static int vme_user_probe(struct vme_dev *vdev) 524{ 525 int i, err; 526 char *name; 527 528 /* Save pointer to the bridge device */ 529 if (vme_user_bridge) { 530 dev_err(&vdev->dev, "Driver can only be loaded for 1 device\n"); 531 err = -EINVAL; 532 goto err_dev; 533 } 534 vme_user_bridge = vdev; 535 536 /* Initialise descriptors */ 537 for (i = 0; i < VME_DEVS; i++) { 538 image[i].kern_buf = NULL; 539 image[i].pci_buf = 0; 540 mutex_init(&image[i].mutex); 541 image[i].device = NULL; 542 image[i].resource = NULL; 543 } 544 545 /* Assign major and minor numbers for the driver */ 546 err = register_chrdev_region(MKDEV(VME_MAJOR, 0), VME_DEVS, DRIVER_NAME); 547 if (err) { 548 dev_warn(&vdev->dev, "Error getting Major Number %d for driver.\n", 549 VME_MAJOR); 550 goto err_region; 551 } 552 553 /* Register the driver as a char device */ 554 vme_user_cdev = cdev_alloc(); 555 if (!vme_user_cdev) { 556 err = -ENOMEM; 557 goto err_char; 558 } 559 vme_user_cdev->ops = &vme_user_fops; 560 vme_user_cdev->owner = THIS_MODULE; 561 err = cdev_add(vme_user_cdev, MKDEV(VME_MAJOR, 0), VME_DEVS); 562 if (err) 563 goto err_class; 564 565 /* Request slave resources and allocate buffers (128kB wide) */ 566 for (i = SLAVE_MINOR; i < (SLAVE_MAX + 1); i++) { 567 /* XXX Need to properly request attributes */ 568 /* For ca91cx42 bridge there are only two slave windows 569 * supporting A16 addressing, so we request A24 supported 570 * by all windows. 571 */ 572 image[i].resource = vme_slave_request(vme_user_bridge, 573 VME_A24, VME_SCT); 574 if (!image[i].resource) { 575 dev_warn(&vdev->dev, 576 "Unable to allocate slave resource\n"); 577 err = -ENOMEM; 578 goto err_slave; 579 } 580 image[i].size_buf = PCI_BUF_SIZE; 581 image[i].kern_buf = vme_alloc_consistent(image[i].resource, 582 image[i].size_buf, 583 &image[i].pci_buf); 584 if (!image[i].kern_buf) { 585 dev_warn(&vdev->dev, 586 "Unable to allocate memory for buffer\n"); 587 image[i].pci_buf = 0; 588 vme_slave_free(image[i].resource); 589 err = -ENOMEM; 590 goto err_slave; 591 } 592 } 593 594 /* 595 * Request master resources allocate page sized buffers for small 596 * reads and writes 597 */ 598 for (i = MASTER_MINOR; i < (MASTER_MAX + 1); i++) { 599 /* XXX Need to properly request attributes */ 600 image[i].resource = vme_master_request(vme_user_bridge, 601 VME_A32, VME_SCT, 602 VME_D32); 603 if (!image[i].resource) { 604 dev_warn(&vdev->dev, 605 "Unable to allocate master resource\n"); 606 err = -ENOMEM; 607 goto err_master; 608 } 609 image[i].size_buf = PCI_BUF_SIZE; 610 image[i].kern_buf = kmalloc(image[i].size_buf, GFP_KERNEL); 611 if (!image[i].kern_buf) { 612 err = -ENOMEM; 613 vme_master_free(image[i].resource); 614 goto err_master; 615 } 616 } 617 618 /* Create sysfs entries - on udev systems this creates the dev files */ 619 err = class_register(&vme_user_sysfs_class); 620 if (err) { 621 dev_err(&vdev->dev, "Error creating vme_user class.\n"); 622 goto err_master; 623 } 624 625 /* Add sysfs Entries */ 626 for (i = 0; i < VME_DEVS; i++) { 627 int num; 628 629 switch (type[i]) { 630 case MASTER_MINOR: 631 name = "bus/vme/m%d"; 632 break; 633 case CONTROL_MINOR: 634 name = "bus/vme/ctl"; 635 break; 636 case SLAVE_MINOR: 637 name = "bus/vme/s%d"; 638 break; 639 default: 640 err = -EINVAL; 641 goto err_sysfs; 642 } 643 644 num = (type[i] == SLAVE_MINOR) ? i - (MASTER_MAX + 1) : i; 645 image[i].device = device_create(&vme_user_sysfs_class, NULL, 646 MKDEV(VME_MAJOR, i), NULL, 647 name, num); 648 if (IS_ERR(image[i].device)) { 649 dev_info(&vdev->dev, "Error creating sysfs device\n"); 650 err = PTR_ERR(image[i].device); 651 goto err_sysfs; 652 } 653 } 654 655 return 0; 656 657err_sysfs: 658 while (i > 0) { 659 i--; 660 device_destroy(&vme_user_sysfs_class, MKDEV(VME_MAJOR, i)); 661 } 662 class_unregister(&vme_user_sysfs_class); 663 664 /* Ensure counter set correctly to unalloc all master windows */ 665 i = MASTER_MAX + 1; 666err_master: 667 while (i > MASTER_MINOR) { 668 i--; 669 kfree(image[i].kern_buf); 670 vme_master_free(image[i].resource); 671 } 672 673 /* 674 * Ensure counter set correctly to unalloc all slave windows and buffers 675 */ 676 i = SLAVE_MAX + 1; 677err_slave: 678 while (i > SLAVE_MINOR) { 679 i--; 680 vme_free_consistent(image[i].resource, image[i].size_buf, 681 image[i].kern_buf, image[i].pci_buf); 682 vme_slave_free(image[i].resource); 683 } 684err_class: 685 cdev_del(vme_user_cdev); 686err_char: 687 unregister_chrdev_region(MKDEV(VME_MAJOR, 0), VME_DEVS); 688err_region: 689err_dev: 690 return err; 691} 692 693static void vme_user_remove(struct vme_dev *dev) 694{ 695 int i; 696 697 /* Remove sysfs Entries */ 698 for (i = 0; i < VME_DEVS; i++) { 699 mutex_destroy(&image[i].mutex); 700 device_destroy(&vme_user_sysfs_class, MKDEV(VME_MAJOR, i)); 701 } 702 class_unregister(&vme_user_sysfs_class); 703 704 for (i = MASTER_MINOR; i < (MASTER_MAX + 1); i++) { 705 kfree(image[i].kern_buf); 706 vme_master_free(image[i].resource); 707 } 708 709 for (i = SLAVE_MINOR; i < (SLAVE_MAX + 1); i++) { 710 vme_slave_set(image[i].resource, 0, 0, 0, 0, VME_A32, 0); 711 vme_free_consistent(image[i].resource, image[i].size_buf, 712 image[i].kern_buf, image[i].pci_buf); 713 vme_slave_free(image[i].resource); 714 } 715 716 /* Unregister device driver */ 717 cdev_del(vme_user_cdev); 718 719 /* Unregister the major and minor device numbers */ 720 unregister_chrdev_region(MKDEV(VME_MAJOR, 0), VME_DEVS); 721} 722 723static struct vme_driver vme_user_driver = { 724 .name = DRIVER_NAME, 725 .match = vme_user_match, 726 .probe = vme_user_probe, 727 .remove = vme_user_remove, 728}; 729 730static int __init vme_user_init(void) 731{ 732 int retval = 0; 733 734 pr_info("VME User Space Access Driver\n"); 735 736 if (bus_num == 0) { 737 pr_err("No cards, skipping registration\n"); 738 retval = -ENODEV; 739 goto err_nocard; 740 } 741 742 /* Let's start by supporting one bus, we can support more than one 743 * in future revisions if that ever becomes necessary. 744 */ 745 if (bus_num > VME_USER_BUS_MAX) { 746 pr_err("Driver only able to handle %d buses\n", 747 VME_USER_BUS_MAX); 748 bus_num = VME_USER_BUS_MAX; 749 } 750 751 /* 752 * Here we just register the maximum number of devices we can and 753 * leave vme_user_match() to allow only 1 to go through to probe(). 754 * This way, if we later want to allow multiple user access devices, 755 * we just change the code in vme_user_match(). 756 */ 757 retval = vme_register_driver(&vme_user_driver, VME_MAX_SLOTS); 758 if (retval) 759 goto err_reg; 760 761 return retval; 762 763err_reg: 764err_nocard: 765 return retval; 766} 767 768static void __exit vme_user_exit(void) 769{ 770 vme_unregister_driver(&vme_user_driver); 771} 772 773MODULE_PARM_DESC(bus, "Enumeration of VMEbus to which the driver is connected"); 774module_param_array(bus, int, &bus_num, 0000); 775 776MODULE_DESCRIPTION("VME User Space Access Driver"); 777MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com>"); 778MODULE_LICENSE("GPL"); 779 780module_init(vme_user_init); 781module_exit(vme_user_exit);
~/
Read-only file preview.