mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-03 17:40:04 +00:00
![Bruno Moreira-Guedes](/assets/img/avatar_default.png)
In <db3b9e990e75573402cda22faf933760f076c033> ("Staging: VME: move VME drivers out of staging") the vme code, board and bridge drivers were moved out of the staging tree, remaining only the VME user device driver. Since this driver is the only one remaining in staging, such multi-level structure confuses more than helps. The current structure is as follows: - drivers/staging/vme/ Makefile devices/ Kconfig Makefile vme_user.c vme_user.h The top-level Makefile has the only function of calling another Makefile into the devices/ subdirectory. This latter only compiles the vme_user driver, since there is no other in the staging tree. This patch removes the unnecessary Makefile from the 'vme/' dir, move the contents of 'vme/devices' into the 'vme/' dir, and renames it to 'vme_user/' (the driver name), allowing a straightforward understanding of this driver's contents. Furthermore, it updates the MAINTAINERS file to properly reflect the new paths. Signed-off-by: Bruno Moreira-Guedes <codeagain@codeagain.dev> Link: https://lore.kernel.org/r/2cd7de9a426c443a5ea618682d605ecfd751d798.1650544175.git.codeagain@codeagain.dev Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
780 lines
19 KiB
C
780 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* VMEbus User access driver
|
|
*
|
|
* Author: Martyn Welch <martyn.welch@ge.com>
|
|
* Copyright 2008 GE Intelligent Platforms Embedded Systems, Inc.
|
|
*
|
|
* Based on work by:
|
|
* Tom Armistead and Ajit Prem
|
|
* Copyright 2004 Motorola Inc.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/refcount.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/syscalls.h>
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/vme.h>
|
|
|
|
#include "vme_user.h"
|
|
|
|
static const char driver_name[] = "vme_user";
|
|
|
|
static int bus[VME_USER_BUS_MAX];
|
|
static unsigned int bus_num;
|
|
|
|
/* Currently Documentation/admin-guide/devices.rst defines the
|
|
* following for VME:
|
|
*
|
|
* 221 char VME bus
|
|
* 0 = /dev/bus/vme/m0 First master image
|
|
* 1 = /dev/bus/vme/m1 Second master image
|
|
* 2 = /dev/bus/vme/m2 Third master image
|
|
* 3 = /dev/bus/vme/m3 Fourth master image
|
|
* 4 = /dev/bus/vme/s0 First slave image
|
|
* 5 = /dev/bus/vme/s1 Second slave image
|
|
* 6 = /dev/bus/vme/s2 Third slave image
|
|
* 7 = /dev/bus/vme/s3 Fourth slave image
|
|
* 8 = /dev/bus/vme/ctl Control
|
|
*
|
|
* It is expected that all VME bus drivers will use the
|
|
* same interface. For interface documentation see
|
|
* http://www.vmelinux.org/.
|
|
*
|
|
* However the VME driver at http://www.vmelinux.org/ is rather old and doesn't
|
|
* even support the tsi148 chipset (which has 8 master and 8 slave windows).
|
|
* We'll run with this for now as far as possible, however it probably makes
|
|
* sense to get rid of the old mappings and just do everything dynamically.
|
|
*
|
|
* So for now, we'll restrict the driver to providing 4 masters and 4 slaves as
|
|
* defined above and try to support at least some of the interface from
|
|
* http://www.vmelinux.org/ as an alternative the driver can be written
|
|
* providing a saner interface later.
|
|
*
|
|
* The vmelinux.org driver never supported slave images, the devices reserved
|
|
* for slaves were repurposed to support all 8 master images on the UniverseII!
|
|
* We shall support 4 masters and 4 slaves with this driver.
|
|
*/
|
|
#define VME_MAJOR 221 /* VME Major Device Number */
|
|
#define VME_DEVS 9 /* Number of dev entries */
|
|
|
|
#define MASTER_MINOR 0
|
|
#define MASTER_MAX 3
|
|
#define SLAVE_MINOR 4
|
|
#define SLAVE_MAX 7
|
|
#define CONTROL_MINOR 8
|
|
|
|
#define PCI_BUF_SIZE 0x20000 /* Size of one slave image buffer */
|
|
|
|
/*
|
|
* Structure to handle image related parameters.
|
|
*/
|
|
struct image_desc {
|
|
void *kern_buf; /* Buffer address in kernel space */
|
|
dma_addr_t pci_buf; /* Buffer address in PCI address space */
|
|
unsigned long long size_buf; /* Buffer size */
|
|
struct mutex mutex; /* Mutex for locking image */
|
|
struct device *device; /* Sysfs device */
|
|
struct vme_resource *resource; /* VME resource */
|
|
int mmap_count; /* Number of current mmap's */
|
|
};
|
|
|
|
static struct image_desc image[VME_DEVS];
|
|
|
|
static struct cdev *vme_user_cdev; /* Character device */
|
|
static struct class *vme_user_sysfs_class; /* Sysfs class */
|
|
static struct vme_dev *vme_user_bridge; /* Pointer to user device */
|
|
|
|
static const int type[VME_DEVS] = { MASTER_MINOR, MASTER_MINOR,
|
|
MASTER_MINOR, MASTER_MINOR,
|
|
SLAVE_MINOR, SLAVE_MINOR,
|
|
SLAVE_MINOR, SLAVE_MINOR,
|
|
CONTROL_MINOR
|
|
};
|
|
|
|
struct vme_user_vma_priv {
|
|
unsigned int minor;
|
|
refcount_t refcnt;
|
|
};
|
|
|
|
static ssize_t resource_to_user(int minor, char __user *buf, size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
ssize_t copied = 0;
|
|
|
|
if (count > image[minor].size_buf)
|
|
count = image[minor].size_buf;
|
|
|
|
copied = vme_master_read(image[minor].resource, image[minor].kern_buf,
|
|
count, *ppos);
|
|
if (copied < 0)
|
|
return (int)copied;
|
|
|
|
if (copy_to_user(buf, image[minor].kern_buf, (unsigned long)copied))
|
|
return -EFAULT;
|
|
|
|
return copied;
|
|
}
|
|
|
|
static ssize_t resource_from_user(unsigned int minor, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
if (count > image[minor].size_buf)
|
|
count = image[minor].size_buf;
|
|
|
|
if (copy_from_user(image[minor].kern_buf, buf, (unsigned long)count))
|
|
return -EFAULT;
|
|
|
|
return vme_master_write(image[minor].resource, image[minor].kern_buf,
|
|
count, *ppos);
|
|
}
|
|
|
|
static ssize_t buffer_to_user(unsigned int minor, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
void *image_ptr;
|
|
|
|
image_ptr = image[minor].kern_buf + *ppos;
|
|
if (copy_to_user(buf, image_ptr, (unsigned long)count))
|
|
return -EFAULT;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t buffer_from_user(unsigned int minor, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
void *image_ptr;
|
|
|
|
image_ptr = image[minor].kern_buf + *ppos;
|
|
if (copy_from_user(image_ptr, buf, (unsigned long)count))
|
|
return -EFAULT;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t vme_user_read(struct file *file, char __user *buf, size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
unsigned int minor = iminor(file_inode(file));
|
|
ssize_t retval;
|
|
size_t image_size;
|
|
|
|
if (minor == CONTROL_MINOR)
|
|
return 0;
|
|
|
|
mutex_lock(&image[minor].mutex);
|
|
|
|
/* XXX Do we *really* want this helper - we can use vme_*_get ? */
|
|
image_size = vme_get_size(image[minor].resource);
|
|
|
|
/* Ensure we are starting at a valid location */
|
|
if ((*ppos < 0) || (*ppos > (image_size - 1))) {
|
|
mutex_unlock(&image[minor].mutex);
|
|
return 0;
|
|
}
|
|
|
|
/* Ensure not reading past end of the image */
|
|
if (*ppos + count > image_size)
|
|
count = image_size - *ppos;
|
|
|
|
switch (type[minor]) {
|
|
case MASTER_MINOR:
|
|
retval = resource_to_user(minor, buf, count, ppos);
|
|
break;
|
|
case SLAVE_MINOR:
|
|
retval = buffer_to_user(minor, buf, count, ppos);
|
|
break;
|
|
default:
|
|
retval = -EINVAL;
|
|
}
|
|
|
|
mutex_unlock(&image[minor].mutex);
|
|
if (retval > 0)
|
|
*ppos += retval;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static ssize_t vme_user_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
unsigned int minor = iminor(file_inode(file));
|
|
ssize_t retval;
|
|
size_t image_size;
|
|
|
|
if (minor == CONTROL_MINOR)
|
|
return 0;
|
|
|
|
mutex_lock(&image[minor].mutex);
|
|
|
|
image_size = vme_get_size(image[minor].resource);
|
|
|
|
/* Ensure we are starting at a valid location */
|
|
if ((*ppos < 0) || (*ppos > (image_size - 1))) {
|
|
mutex_unlock(&image[minor].mutex);
|
|
return 0;
|
|
}
|
|
|
|
/* Ensure not reading past end of the image */
|
|
if (*ppos + count > image_size)
|
|
count = image_size - *ppos;
|
|
|
|
switch (type[minor]) {
|
|
case MASTER_MINOR:
|
|
retval = resource_from_user(minor, buf, count, ppos);
|
|
break;
|
|
case SLAVE_MINOR:
|
|
retval = buffer_from_user(minor, buf, count, ppos);
|
|
break;
|
|
default:
|
|
retval = -EINVAL;
|
|
}
|
|
|
|
mutex_unlock(&image[minor].mutex);
|
|
|
|
if (retval > 0)
|
|
*ppos += retval;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static loff_t vme_user_llseek(struct file *file, loff_t off, int whence)
|
|
{
|
|
unsigned int minor = iminor(file_inode(file));
|
|
size_t image_size;
|
|
loff_t res;
|
|
|
|
switch (type[minor]) {
|
|
case MASTER_MINOR:
|
|
case SLAVE_MINOR:
|
|
mutex_lock(&image[minor].mutex);
|
|
image_size = vme_get_size(image[minor].resource);
|
|
res = fixed_size_llseek(file, off, whence, image_size);
|
|
mutex_unlock(&image[minor].mutex);
|
|
return res;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* The ioctls provided by the old VME access method (the one at vmelinux.org)
|
|
* are most certainly wrong as the effectively push the registers layout
|
|
* through to user space. Given that the VME core can handle multiple bridges,
|
|
* with different register layouts this is most certainly not the way to go.
|
|
*
|
|
* We aren't using the structures defined in the Motorola driver either - these
|
|
* are also quite low level, however we should use the definitions that have
|
|
* already been defined.
|
|
*/
|
|
static int vme_user_ioctl(struct inode *inode, struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct vme_master master;
|
|
struct vme_slave slave;
|
|
struct vme_irq_id irq_req;
|
|
unsigned long copied;
|
|
unsigned int minor = iminor(inode);
|
|
int retval;
|
|
dma_addr_t pci_addr;
|
|
void __user *argp = (void __user *)arg;
|
|
|
|
switch (type[minor]) {
|
|
case CONTROL_MINOR:
|
|
switch (cmd) {
|
|
case VME_IRQ_GEN:
|
|
copied = copy_from_user(&irq_req, argp,
|
|
sizeof(irq_req));
|
|
if (copied) {
|
|
pr_warn("Partial copy from userspace\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
return vme_irq_generate(vme_user_bridge,
|
|
irq_req.level,
|
|
irq_req.statid);
|
|
}
|
|
break;
|
|
case MASTER_MINOR:
|
|
switch (cmd) {
|
|
case VME_GET_MASTER:
|
|
memset(&master, 0, sizeof(master));
|
|
|
|
/* XXX We do not want to push aspace, cycle and width
|
|
* to userspace as they are
|
|
*/
|
|
retval = vme_master_get(image[minor].resource,
|
|
&master.enable,
|
|
&master.vme_addr,
|
|
&master.size, &master.aspace,
|
|
&master.cycle, &master.dwidth);
|
|
|
|
copied = copy_to_user(argp, &master,
|
|
sizeof(master));
|
|
if (copied) {
|
|
pr_warn("Partial copy to userspace\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
return retval;
|
|
|
|
case VME_SET_MASTER:
|
|
|
|
if (image[minor].mmap_count != 0) {
|
|
pr_warn("Can't adjust mapped window\n");
|
|
return -EPERM;
|
|
}
|
|
|
|
copied = copy_from_user(&master, argp, sizeof(master));
|
|
if (copied) {
|
|
pr_warn("Partial copy from userspace\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* XXX We do not want to push aspace, cycle and width
|
|
* to userspace as they are
|
|
*/
|
|
return vme_master_set(image[minor].resource,
|
|
master.enable, master.vme_addr, master.size,
|
|
master.aspace, master.cycle, master.dwidth);
|
|
|
|
break;
|
|
}
|
|
break;
|
|
case SLAVE_MINOR:
|
|
switch (cmd) {
|
|
case VME_GET_SLAVE:
|
|
memset(&slave, 0, sizeof(slave));
|
|
|
|
/* XXX We do not want to push aspace, cycle and width
|
|
* to userspace as they are
|
|
*/
|
|
retval = vme_slave_get(image[minor].resource,
|
|
&slave.enable, &slave.vme_addr,
|
|
&slave.size, &pci_addr,
|
|
&slave.aspace, &slave.cycle);
|
|
|
|
copied = copy_to_user(argp, &slave,
|
|
sizeof(slave));
|
|
if (copied) {
|
|
pr_warn("Partial copy to userspace\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
return retval;
|
|
|
|
case VME_SET_SLAVE:
|
|
|
|
copied = copy_from_user(&slave, argp, sizeof(slave));
|
|
if (copied) {
|
|
pr_warn("Partial copy from userspace\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* XXX We do not want to push aspace, cycle and width
|
|
* to userspace as they are
|
|
*/
|
|
return vme_slave_set(image[minor].resource,
|
|
slave.enable, slave.vme_addr, slave.size,
|
|
image[minor].pci_buf, slave.aspace,
|
|
slave.cycle);
|
|
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static long
|
|
vme_user_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret;
|
|
struct inode *inode = file_inode(file);
|
|
unsigned int minor = iminor(inode);
|
|
|
|
mutex_lock(&image[minor].mutex);
|
|
ret = vme_user_ioctl(inode, file, cmd, arg);
|
|
mutex_unlock(&image[minor].mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void vme_user_vm_open(struct vm_area_struct *vma)
|
|
{
|
|
struct vme_user_vma_priv *vma_priv = vma->vm_private_data;
|
|
|
|
refcount_inc(&vma_priv->refcnt);
|
|
}
|
|
|
|
static void vme_user_vm_close(struct vm_area_struct *vma)
|
|
{
|
|
struct vme_user_vma_priv *vma_priv = vma->vm_private_data;
|
|
unsigned int minor = vma_priv->minor;
|
|
|
|
if (!refcount_dec_and_test(&vma_priv->refcnt))
|
|
return;
|
|
|
|
mutex_lock(&image[minor].mutex);
|
|
image[minor].mmap_count--;
|
|
mutex_unlock(&image[minor].mutex);
|
|
|
|
kfree(vma_priv);
|
|
}
|
|
|
|
static const struct vm_operations_struct vme_user_vm_ops = {
|
|
.open = vme_user_vm_open,
|
|
.close = vme_user_vm_close,
|
|
};
|
|
|
|
static int vme_user_master_mmap(unsigned int minor, struct vm_area_struct *vma)
|
|
{
|
|
int err;
|
|
struct vme_user_vma_priv *vma_priv;
|
|
|
|
mutex_lock(&image[minor].mutex);
|
|
|
|
err = vme_master_mmap(image[minor].resource, vma);
|
|
if (err) {
|
|
mutex_unlock(&image[minor].mutex);
|
|
return err;
|
|
}
|
|
|
|
vma_priv = kmalloc(sizeof(*vma_priv), GFP_KERNEL);
|
|
if (!vma_priv) {
|
|
mutex_unlock(&image[minor].mutex);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
vma_priv->minor = minor;
|
|
refcount_set(&vma_priv->refcnt, 1);
|
|
vma->vm_ops = &vme_user_vm_ops;
|
|
vma->vm_private_data = vma_priv;
|
|
|
|
image[minor].mmap_count++;
|
|
|
|
mutex_unlock(&image[minor].mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vme_user_mmap(struct file *file, struct vm_area_struct *vma)
|
|
{
|
|
unsigned int minor = iminor(file_inode(file));
|
|
|
|
if (type[minor] == MASTER_MINOR)
|
|
return vme_user_master_mmap(minor, vma);
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static const struct file_operations vme_user_fops = {
|
|
.read = vme_user_read,
|
|
.write = vme_user_write,
|
|
.llseek = vme_user_llseek,
|
|
.unlocked_ioctl = vme_user_unlocked_ioctl,
|
|
.compat_ioctl = compat_ptr_ioctl,
|
|
.mmap = vme_user_mmap,
|
|
};
|
|
|
|
static int vme_user_match(struct vme_dev *vdev)
|
|
{
|
|
int i;
|
|
|
|
int cur_bus = vme_bus_num(vdev);
|
|
int cur_slot = vme_slot_num(vdev);
|
|
|
|
for (i = 0; i < bus_num; i++)
|
|
if ((cur_bus == bus[i]) && (cur_slot == vdev->num))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* In this simple access driver, the old behaviour is being preserved as much
|
|
* as practical. We will therefore reserve the buffers and request the images
|
|
* here so that we don't have to do it later.
|
|
*/
|
|
static int vme_user_probe(struct vme_dev *vdev)
|
|
{
|
|
int i, err;
|
|
char *name;
|
|
|
|
/* Save pointer to the bridge device */
|
|
if (vme_user_bridge) {
|
|
dev_err(&vdev->dev, "Driver can only be loaded for 1 device\n");
|
|
err = -EINVAL;
|
|
goto err_dev;
|
|
}
|
|
vme_user_bridge = vdev;
|
|
|
|
/* Initialise descriptors */
|
|
for (i = 0; i < VME_DEVS; i++) {
|
|
image[i].kern_buf = NULL;
|
|
image[i].pci_buf = 0;
|
|
mutex_init(&image[i].mutex);
|
|
image[i].device = NULL;
|
|
image[i].resource = NULL;
|
|
}
|
|
|
|
/* Assign major and minor numbers for the driver */
|
|
err = register_chrdev_region(MKDEV(VME_MAJOR, 0), VME_DEVS,
|
|
driver_name);
|
|
if (err) {
|
|
dev_warn(&vdev->dev, "Error getting Major Number %d for driver.\n",
|
|
VME_MAJOR);
|
|
goto err_region;
|
|
}
|
|
|
|
/* Register the driver as a char device */
|
|
vme_user_cdev = cdev_alloc();
|
|
if (!vme_user_cdev) {
|
|
err = -ENOMEM;
|
|
goto err_char;
|
|
}
|
|
vme_user_cdev->ops = &vme_user_fops;
|
|
vme_user_cdev->owner = THIS_MODULE;
|
|
err = cdev_add(vme_user_cdev, MKDEV(VME_MAJOR, 0), VME_DEVS);
|
|
if (err)
|
|
goto err_class;
|
|
|
|
/* Request slave resources and allocate buffers (128kB wide) */
|
|
for (i = SLAVE_MINOR; i < (SLAVE_MAX + 1); i++) {
|
|
/* XXX Need to properly request attributes */
|
|
/* For ca91cx42 bridge there are only two slave windows
|
|
* supporting A16 addressing, so we request A24 supported
|
|
* by all windows.
|
|
*/
|
|
image[i].resource = vme_slave_request(vme_user_bridge,
|
|
VME_A24, VME_SCT);
|
|
if (!image[i].resource) {
|
|
dev_warn(&vdev->dev,
|
|
"Unable to allocate slave resource\n");
|
|
err = -ENOMEM;
|
|
goto err_slave;
|
|
}
|
|
image[i].size_buf = PCI_BUF_SIZE;
|
|
image[i].kern_buf = vme_alloc_consistent(image[i].resource,
|
|
image[i].size_buf,
|
|
&image[i].pci_buf);
|
|
if (!image[i].kern_buf) {
|
|
dev_warn(&vdev->dev,
|
|
"Unable to allocate memory for buffer\n");
|
|
image[i].pci_buf = 0;
|
|
vme_slave_free(image[i].resource);
|
|
err = -ENOMEM;
|
|
goto err_slave;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Request master resources allocate page sized buffers for small
|
|
* reads and writes
|
|
*/
|
|
for (i = MASTER_MINOR; i < (MASTER_MAX + 1); i++) {
|
|
/* XXX Need to properly request attributes */
|
|
image[i].resource = vme_master_request(vme_user_bridge,
|
|
VME_A32, VME_SCT,
|
|
VME_D32);
|
|
if (!image[i].resource) {
|
|
dev_warn(&vdev->dev,
|
|
"Unable to allocate master resource\n");
|
|
err = -ENOMEM;
|
|
goto err_master;
|
|
}
|
|
image[i].size_buf = PCI_BUF_SIZE;
|
|
image[i].kern_buf = kmalloc(image[i].size_buf, GFP_KERNEL);
|
|
if (!image[i].kern_buf) {
|
|
err = -ENOMEM;
|
|
vme_master_free(image[i].resource);
|
|
goto err_master;
|
|
}
|
|
}
|
|
|
|
/* Create sysfs entries - on udev systems this creates the dev files */
|
|
vme_user_sysfs_class = class_create(THIS_MODULE, driver_name);
|
|
if (IS_ERR(vme_user_sysfs_class)) {
|
|
dev_err(&vdev->dev, "Error creating vme_user class.\n");
|
|
err = PTR_ERR(vme_user_sysfs_class);
|
|
goto err_master;
|
|
}
|
|
|
|
/* Add sysfs Entries */
|
|
for (i = 0; i < VME_DEVS; i++) {
|
|
int num;
|
|
|
|
switch (type[i]) {
|
|
case MASTER_MINOR:
|
|
name = "bus/vme/m%d";
|
|
break;
|
|
case CONTROL_MINOR:
|
|
name = "bus/vme/ctl";
|
|
break;
|
|
case SLAVE_MINOR:
|
|
name = "bus/vme/s%d";
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
goto err_sysfs;
|
|
}
|
|
|
|
num = (type[i] == SLAVE_MINOR) ? i - (MASTER_MAX + 1) : i;
|
|
image[i].device = device_create(vme_user_sysfs_class, NULL,
|
|
MKDEV(VME_MAJOR, i), NULL,
|
|
name, num);
|
|
if (IS_ERR(image[i].device)) {
|
|
dev_info(&vdev->dev, "Error creating sysfs device\n");
|
|
err = PTR_ERR(image[i].device);
|
|
goto err_sysfs;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_sysfs:
|
|
while (i > 0) {
|
|
i--;
|
|
device_destroy(vme_user_sysfs_class, MKDEV(VME_MAJOR, i));
|
|
}
|
|
class_destroy(vme_user_sysfs_class);
|
|
|
|
/* Ensure counter set correctly to unalloc all master windows */
|
|
i = MASTER_MAX + 1;
|
|
err_master:
|
|
while (i > MASTER_MINOR) {
|
|
i--;
|
|
kfree(image[i].kern_buf);
|
|
vme_master_free(image[i].resource);
|
|
}
|
|
|
|
/*
|
|
* Ensure counter set correctly to unalloc all slave windows and buffers
|
|
*/
|
|
i = SLAVE_MAX + 1;
|
|
err_slave:
|
|
while (i > SLAVE_MINOR) {
|
|
i--;
|
|
vme_free_consistent(image[i].resource, image[i].size_buf,
|
|
image[i].kern_buf, image[i].pci_buf);
|
|
vme_slave_free(image[i].resource);
|
|
}
|
|
err_class:
|
|
cdev_del(vme_user_cdev);
|
|
err_char:
|
|
unregister_chrdev_region(MKDEV(VME_MAJOR, 0), VME_DEVS);
|
|
err_region:
|
|
err_dev:
|
|
return err;
|
|
}
|
|
|
|
static void vme_user_remove(struct vme_dev *dev)
|
|
{
|
|
int i;
|
|
|
|
/* Remove sysfs Entries */
|
|
for (i = 0; i < VME_DEVS; i++) {
|
|
mutex_destroy(&image[i].mutex);
|
|
device_destroy(vme_user_sysfs_class, MKDEV(VME_MAJOR, i));
|
|
}
|
|
class_destroy(vme_user_sysfs_class);
|
|
|
|
for (i = MASTER_MINOR; i < (MASTER_MAX + 1); i++) {
|
|
kfree(image[i].kern_buf);
|
|
vme_master_free(image[i].resource);
|
|
}
|
|
|
|
for (i = SLAVE_MINOR; i < (SLAVE_MAX + 1); i++) {
|
|
vme_slave_set(image[i].resource, 0, 0, 0, 0, VME_A32, 0);
|
|
vme_free_consistent(image[i].resource, image[i].size_buf,
|
|
image[i].kern_buf, image[i].pci_buf);
|
|
vme_slave_free(image[i].resource);
|
|
}
|
|
|
|
/* Unregister device driver */
|
|
cdev_del(vme_user_cdev);
|
|
|
|
/* Unregister the major and minor device numbers */
|
|
unregister_chrdev_region(MKDEV(VME_MAJOR, 0), VME_DEVS);
|
|
}
|
|
|
|
static struct vme_driver vme_user_driver = {
|
|
.name = driver_name,
|
|
.match = vme_user_match,
|
|
.probe = vme_user_probe,
|
|
.remove = vme_user_remove,
|
|
};
|
|
|
|
static int __init vme_user_init(void)
|
|
{
|
|
int retval = 0;
|
|
|
|
pr_info("VME User Space Access Driver\n");
|
|
|
|
if (bus_num == 0) {
|
|
pr_err("No cards, skipping registration\n");
|
|
retval = -ENODEV;
|
|
goto err_nocard;
|
|
}
|
|
|
|
/* Let's start by supporting one bus, we can support more than one
|
|
* in future revisions if that ever becomes necessary.
|
|
*/
|
|
if (bus_num > VME_USER_BUS_MAX) {
|
|
pr_err("Driver only able to handle %d buses\n",
|
|
VME_USER_BUS_MAX);
|
|
bus_num = VME_USER_BUS_MAX;
|
|
}
|
|
|
|
/*
|
|
* Here we just register the maximum number of devices we can and
|
|
* leave vme_user_match() to allow only 1 to go through to probe().
|
|
* This way, if we later want to allow multiple user access devices,
|
|
* we just change the code in vme_user_match().
|
|
*/
|
|
retval = vme_register_driver(&vme_user_driver, VME_MAX_SLOTS);
|
|
if (retval)
|
|
goto err_reg;
|
|
|
|
return retval;
|
|
|
|
err_reg:
|
|
err_nocard:
|
|
return retval;
|
|
}
|
|
|
|
static void __exit vme_user_exit(void)
|
|
{
|
|
vme_unregister_driver(&vme_user_driver);
|
|
}
|
|
|
|
MODULE_PARM_DESC(bus, "Enumeration of VMEbus to which the driver is connected");
|
|
module_param_array(bus, int, &bus_num, 0000);
|
|
|
|
MODULE_DESCRIPTION("VME User Space Access Driver");
|
|
MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com>");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(vme_user_init);
|
|
module_exit(vme_user_exit);
|