diff options
Diffstat (limited to 'drivers/uio')
-rw-r--r-- | drivers/uio/Kconfig | 185 | ||||
-rw-r--r-- | drivers/uio/Makefile | 14 | ||||
-rw-r--r-- | drivers/uio/uio.c | 1087 | ||||
-rw-r--r-- | drivers/uio/uio_aec.c | 147 | ||||
-rw-r--r-- | drivers/uio/uio_cif.c | 134 | ||||
-rw-r--r-- | drivers/uio/uio_dfl.c | 70 | ||||
-rw-r--r-- | drivers/uio/uio_dmem_genirq.c | 345 | ||||
-rw-r--r-- | drivers/uio/uio_fsl_elbc_gpcm.c | 464 | ||||
-rw-r--r-- | drivers/uio/uio_hv_generic.c | 399 | ||||
-rw-r--r-- | drivers/uio/uio_mf624.c | 225 | ||||
-rw-r--r-- | drivers/uio/uio_netx.c | 174 | ||||
-rw-r--r-- | drivers/uio/uio_pci_generic.c | 149 | ||||
-rw-r--r-- | drivers/uio/uio_pdrv_genirq.c | 301 | ||||
-rw-r--r-- | drivers/uio/uio_pruss.c | 248 | ||||
-rw-r--r-- | drivers/uio/uio_sercos3.c | 226 |
15 files changed, 4168 insertions, 0 deletions
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig new file mode 100644 index 000000000..2e16c5338 --- /dev/null +++ b/drivers/uio/Kconfig @@ -0,0 +1,185 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig UIO + tristate "Userspace I/O drivers" + depends on MMU + help + Enable this to allow the userspace driver core code to be + built. This code allows userspace programs easy access to + kernel interrupts and memory locations, allowing some drivers + to be written in userspace. Note that a small kernel driver + is also required for interrupt handling to work properly. + + If you don't know what to do here, say N. + +if UIO + +config UIO_CIF + tristate "generic Hilscher CIF Card driver" + depends on PCI + help + Driver for Hilscher CIF DeviceNet and Profibus cards. This + driver requires a userspace component called cif that handles + all of the heavy lifting and can be found at: + <http://www.osadl.org/projects/downloads/UIO/user/> + + To compile this driver as a module, choose M here: the module + will be called uio_cif. + +config UIO_PDRV_GENIRQ + tristate "Userspace I/O platform driver with generic IRQ handling" + help + Platform driver for Userspace I/O devices, including generic + interrupt handling code. Shared interrupts are not supported. + + This kernel driver requires that the matching userspace driver + handles interrupts in a special way. Userspace is responsible + for acknowledging the hardware device if needed, and re-enabling + interrupts in the interrupt controller using the write() syscall. + + If you don't know what to do here, say N. + +config UIO_DMEM_GENIRQ + tristate "Userspace platform driver with generic irq and dynamic memory" + depends on HAS_DMA + help + Platform driver for Userspace I/O devices, including generic + interrupt handling code. Shared interrupts are not supported. + + Memory regions can be specified with the same platform device + resources as the UIO_PDRV drivers, but dynamic regions can also + be specified. + The number and size of these regions is static, + but the memory allocation is not performed until + the associated device file is opened. The + memory is freed once the uio device is closed. + + If you don't know what to do here, say N. + +config UIO_AEC + tristate "AEC video timestamp device" + depends on PCI + help + + UIO driver for the Adrienne Electronics Corporation PCI time + code device. + + This device differs from other UIO devices since it uses I/O + ports instead of memory mapped I/O. In order to make it + possible for UIO to work with this device a utility, uioport, + can be used to read and write the ports: + + git clone git://ifup.org/philips/uioport.git + + If you compile this as a module, it will be called uio_aec. + +config UIO_SERCOS3 + tristate "Automata Sercos III PCI card driver" + depends on PCI + help + Userspace I/O interface for the Sercos III PCI card from + Automata GmbH. The userspace part of this driver will be + available for download from the Automata GmbH web site. + + Automata GmbH: http://www.automataweb.com + Sercos III interface: http://www.sercos.com + + If you compile this as a module, it will be called uio_sercos3. + +config UIO_PCI_GENERIC + tristate "Generic driver for PCI 2.3 and PCI Express cards" + depends on PCI + help + Generic driver that you can bind, dynamically, to any + PCI 2.3 compliant and PCI Express card. It is useful, + primarily, for virtualization scenarios. + If you compile this as a module, it will be called uio_pci_generic. + +config UIO_NETX + tristate "Hilscher NetX Card driver" + depends on PCI + help + Driver for Hilscher NetX based fieldbus cards (cifX, comX). + This driver requires a userspace component that comes with the card + or is available from Hilscher (http://www.hilscher.com). + + To compile this driver as a module, choose M here; the module + will be called uio_netx. + +config UIO_FSL_ELBC_GPCM + tristate "eLBC/GPCM driver" + depends on FSL_LBC + help + Generic driver for accessing a peripheral connected to an eLBC port + that is running in GPCM mode. GPCM is an interface for simple lower + performance memories and memory-mapped devices. For devices using + FCM or UPM eLBC modes, other device-specific drivers are available. + +config UIO_FSL_ELBC_GPCM_NETX5152 + bool "eLBC/GPCM netX 51/52 support" + depends on UIO_FSL_ELBC_GPCM + help + This will add support for netX 51/52 devices connected via eLBC/GPCM. + In particular, it implements interrupt handling. This can be used + together with the userspace netX stack from Hilscher. + + Information about this hardware can be found at: + http://www.hilscher.com/netx + +config UIO_PRUSS + tristate "Texas Instruments PRUSS driver" + select GENERIC_ALLOCATOR + depends on HAS_IOMEM && HAS_DMA + help + PRUSS driver for OMAPL138/DA850/AM18XX devices + PRUSS driver requires user space components, examples and user space + driver is available from below SVN repo - you may use anonymous login + + https://gforge.ti.com/gf/project/pru_sw/ + + More info on API is available at below wiki + + http://processors.wiki.ti.com/index.php/PRU_Linux_Application_Loader + + To compile this driver as a module, choose M here: the module + will be called uio_pruss. + +config UIO_MF624 + tristate "Humusoft MF624 DAQ PCI card driver" + depends on PCI + help + Userspace I/O interface for the Humusoft MF624 PCI card. + A sample userspace application using this driver is available + (among other MF624 related information and software components) + for download in a git repository: + + git clone git://rtime.felk.cvut.cz/mf6xx.git + + If you compile this as a module, it will be called uio_mf624. + +config UIO_HV_GENERIC + tristate "Generic driver for Hyper-V VMBus" + depends on HYPERV + help + Generic driver that you can bind, dynamically, to any + Hyper-V VMBus device. It is useful to provide direct access + to network and storage devices from userspace. + + If you compile this as a module, it will be called uio_hv_generic. + +config UIO_DFL + tristate "Generic driver for DFL (Device Feature List) bus" + depends on FPGA_DFL + help + Generic DFL (Device Feature List) driver for Userspace I/O devices. + It is useful to provide direct access to DFL devices from userspace. + A sample userspace application using this driver is available for + download in a git repository: + + git clone https://github.com/OPAE/opae-sdk.git + + It could be found at: + + opae-sdk/tools/libopaeuio/ + + If you compile this as a module, it will be called uio_dfl. +endif diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile new file mode 100644 index 000000000..f2f416a14 --- /dev/null +++ b/drivers/uio/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_UIO) += uio.o +obj-$(CONFIG_UIO_CIF) += uio_cif.o +obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o +obj-$(CONFIG_UIO_DMEM_GENIRQ) += uio_dmem_genirq.o +obj-$(CONFIG_UIO_AEC) += uio_aec.o +obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o +obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o +obj-$(CONFIG_UIO_NETX) += uio_netx.o +obj-$(CONFIG_UIO_PRUSS) += uio_pruss.o +obj-$(CONFIG_UIO_MF624) += uio_mf624.o +obj-$(CONFIG_UIO_FSL_ELBC_GPCM) += uio_fsl_elbc_gpcm.o +obj-$(CONFIG_UIO_HV_GENERIC) += uio_hv_generic.o +obj-$(CONFIG_UIO_DFL) += uio_dfl.o diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c new file mode 100644 index 000000000..e55e8cef8 --- /dev/null +++ b/drivers/uio/uio.c @@ -0,0 +1,1087 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/uio/uio.c + * + * Copyright(C) 2005, Benedikt Spranger <b.spranger@linutronix.de> + * Copyright(C) 2005, Thomas Gleixner <tglx@linutronix.de> + * Copyright(C) 2006, Hans J. Koch <hjk@hansjkoch.de> + * Copyright(C) 2006, Greg Kroah-Hartman <greg@kroah.com> + * + * Userspace IO + * + * Base Functions + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/idr.h> +#include <linux/sched/signal.h> +#include <linux/string.h> +#include <linux/kobject.h> +#include <linux/cdev.h> +#include <linux/uio_driver.h> + +#define UIO_MAX_DEVICES (1U << MINORBITS) + +static int uio_major; +static struct cdev *uio_cdev; +static DEFINE_IDR(uio_idr); +static const struct file_operations uio_fops; + +/* Protect idr accesses */ +static DEFINE_MUTEX(minor_lock); + +/* + * attributes + */ + +struct uio_map { + struct kobject kobj; + struct uio_mem *mem; +}; +#define to_map(map) container_of(map, struct uio_map, kobj) + +static ssize_t map_name_show(struct uio_mem *mem, char *buf) +{ + if (unlikely(!mem->name)) + mem->name = ""; + + return sprintf(buf, "%s\n", mem->name); +} + +static ssize_t map_addr_show(struct uio_mem *mem, char *buf) +{ + return sprintf(buf, "%pa\n", &mem->addr); +} + +static ssize_t map_size_show(struct uio_mem *mem, char *buf) +{ + return sprintf(buf, "%pa\n", &mem->size); +} + +static ssize_t map_offset_show(struct uio_mem *mem, char *buf) +{ + return sprintf(buf, "0x%llx\n", (unsigned long long)mem->offs); +} + +struct map_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct uio_mem *, char *); + ssize_t (*store)(struct uio_mem *, const char *, size_t); +}; + +static struct map_sysfs_entry name_attribute = + __ATTR(name, S_IRUGO, map_name_show, NULL); +static struct map_sysfs_entry addr_attribute = + __ATTR(addr, S_IRUGO, map_addr_show, NULL); +static struct map_sysfs_entry size_attribute = + __ATTR(size, S_IRUGO, map_size_show, NULL); +static struct map_sysfs_entry offset_attribute = + __ATTR(offset, S_IRUGO, map_offset_show, NULL); + +static struct attribute *map_attrs[] = { + &name_attribute.attr, + &addr_attribute.attr, + &size_attribute.attr, + &offset_attribute.attr, + NULL, /* need to NULL terminate the list of attributes */ +}; +ATTRIBUTE_GROUPS(map); + +static void map_release(struct kobject *kobj) +{ + struct uio_map *map = to_map(kobj); + kfree(map); +} + +static ssize_t map_type_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct uio_map *map = to_map(kobj); + struct uio_mem *mem = map->mem; + struct map_sysfs_entry *entry; + + entry = container_of(attr, struct map_sysfs_entry, attr); + + if (!entry->show) + return -EIO; + + return entry->show(mem, buf); +} + +static const struct sysfs_ops map_sysfs_ops = { + .show = map_type_show, +}; + +static struct kobj_type map_attr_type = { + .release = map_release, + .sysfs_ops = &map_sysfs_ops, + .default_groups = map_groups, +}; + +struct uio_portio { + struct kobject kobj; + struct uio_port *port; +}; +#define to_portio(portio) container_of(portio, struct uio_portio, kobj) + +static ssize_t portio_name_show(struct uio_port *port, char *buf) +{ + if (unlikely(!port->name)) + port->name = ""; + + return sprintf(buf, "%s\n", port->name); +} + +static ssize_t portio_start_show(struct uio_port *port, char *buf) +{ + return sprintf(buf, "0x%lx\n", port->start); +} + +static ssize_t portio_size_show(struct uio_port *port, char *buf) +{ + return sprintf(buf, "0x%lx\n", port->size); +} + +static ssize_t portio_porttype_show(struct uio_port *port, char *buf) +{ + const char *porttypes[] = {"none", "x86", "gpio", "other"}; + + if ((port->porttype < 0) || (port->porttype > UIO_PORT_OTHER)) + return -EINVAL; + + return sprintf(buf, "port_%s\n", porttypes[port->porttype]); +} + +struct portio_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct uio_port *, char *); + ssize_t (*store)(struct uio_port *, const char *, size_t); +}; + +static struct portio_sysfs_entry portio_name_attribute = + __ATTR(name, S_IRUGO, portio_name_show, NULL); +static struct portio_sysfs_entry portio_start_attribute = + __ATTR(start, S_IRUGO, portio_start_show, NULL); +static struct portio_sysfs_entry portio_size_attribute = + __ATTR(size, S_IRUGO, portio_size_show, NULL); +static struct portio_sysfs_entry portio_porttype_attribute = + __ATTR(porttype, S_IRUGO, portio_porttype_show, NULL); + +static struct attribute *portio_attrs[] = { + &portio_name_attribute.attr, + &portio_start_attribute.attr, + &portio_size_attribute.attr, + &portio_porttype_attribute.attr, + NULL, +}; +ATTRIBUTE_GROUPS(portio); + +static void portio_release(struct kobject *kobj) +{ + struct uio_portio *portio = to_portio(kobj); + kfree(portio); +} + +static ssize_t portio_type_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct uio_portio *portio = to_portio(kobj); + struct uio_port *port = portio->port; + struct portio_sysfs_entry *entry; + + entry = container_of(attr, struct portio_sysfs_entry, attr); + + if (!entry->show) + return -EIO; + + return entry->show(port, buf); +} + +static const struct sysfs_ops portio_sysfs_ops = { + .show = portio_type_show, +}; + +static struct kobj_type portio_attr_type = { + .release = portio_release, + .sysfs_ops = &portio_sysfs_ops, + .default_groups = portio_groups, +}; + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = dev_get_drvdata(dev); + int ret; + + mutex_lock(&idev->info_lock); + if (!idev->info) { + ret = -EINVAL; + dev_err(dev, "the device has been unregistered\n"); + goto out; + } + + ret = sprintf(buf, "%s\n", idev->info->name); + +out: + mutex_unlock(&idev->info_lock); + return ret; +} +static DEVICE_ATTR_RO(name); + +static ssize_t version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = dev_get_drvdata(dev); + int ret; + + mutex_lock(&idev->info_lock); + if (!idev->info) { + ret = -EINVAL; + dev_err(dev, "the device has been unregistered\n"); + goto out; + } + + ret = sprintf(buf, "%s\n", idev->info->version); + +out: + mutex_unlock(&idev->info_lock); + return ret; +} +static DEVICE_ATTR_RO(version); + +static ssize_t event_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = dev_get_drvdata(dev); + return sprintf(buf, "%u\n", (unsigned int)atomic_read(&idev->event)); +} +static DEVICE_ATTR_RO(event); + +static struct attribute *uio_attrs[] = { + &dev_attr_name.attr, + &dev_attr_version.attr, + &dev_attr_event.attr, + NULL, +}; +ATTRIBUTE_GROUPS(uio); + +/* UIO class infrastructure */ +static struct class uio_class = { + .name = "uio", + .dev_groups = uio_groups, +}; + +static bool uio_class_registered; + +/* + * device functions + */ +static int uio_dev_add_attributes(struct uio_device *idev) +{ + int ret; + int mi, pi; + int map_found = 0; + int portio_found = 0; + struct uio_mem *mem; + struct uio_map *map; + struct uio_port *port; + struct uio_portio *portio; + + for (mi = 0; mi < MAX_UIO_MAPS; mi++) { + mem = &idev->info->mem[mi]; + if (mem->size == 0) + break; + if (!map_found) { + map_found = 1; + idev->map_dir = kobject_create_and_add("maps", + &idev->dev.kobj); + if (!idev->map_dir) { + ret = -ENOMEM; + goto err_map; + } + } + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (!map) { + ret = -ENOMEM; + goto err_map; + } + kobject_init(&map->kobj, &map_attr_type); + map->mem = mem; + mem->map = map; + ret = kobject_add(&map->kobj, idev->map_dir, "map%d", mi); + if (ret) + goto err_map_kobj; + ret = kobject_uevent(&map->kobj, KOBJ_ADD); + if (ret) + goto err_map_kobj; + } + + for (pi = 0; pi < MAX_UIO_PORT_REGIONS; pi++) { + port = &idev->info->port[pi]; + if (port->size == 0) + break; + if (!portio_found) { + portio_found = 1; + idev->portio_dir = kobject_create_and_add("portio", + &idev->dev.kobj); + if (!idev->portio_dir) { + ret = -ENOMEM; + goto err_portio; + } + } + portio = kzalloc(sizeof(*portio), GFP_KERNEL); + if (!portio) { + ret = -ENOMEM; + goto err_portio; + } + kobject_init(&portio->kobj, &portio_attr_type); + portio->port = port; + port->portio = portio; + ret = kobject_add(&portio->kobj, idev->portio_dir, + "port%d", pi); + if (ret) + goto err_portio_kobj; + ret = kobject_uevent(&portio->kobj, KOBJ_ADD); + if (ret) + goto err_portio_kobj; + } + + return 0; + +err_portio: + pi--; +err_portio_kobj: + for (; pi >= 0; pi--) { + port = &idev->info->port[pi]; + portio = port->portio; + kobject_put(&portio->kobj); + } + kobject_put(idev->portio_dir); +err_map: + mi--; +err_map_kobj: + for (; mi >= 0; mi--) { + mem = &idev->info->mem[mi]; + map = mem->map; + kobject_put(&map->kobj); + } + kobject_put(idev->map_dir); + dev_err(&idev->dev, "error creating sysfs files (%d)\n", ret); + return ret; +} + +static void uio_dev_del_attributes(struct uio_device *idev) +{ + int i; + struct uio_mem *mem; + struct uio_port *port; + + for (i = 0; i < MAX_UIO_MAPS; i++) { + mem = &idev->info->mem[i]; + if (mem->size == 0) + break; + kobject_put(&mem->map->kobj); + } + kobject_put(idev->map_dir); + + for (i = 0; i < MAX_UIO_PORT_REGIONS; i++) { + port = &idev->info->port[i]; + if (port->size == 0) + break; + kobject_put(&port->portio->kobj); + } + kobject_put(idev->portio_dir); +} + +static int uio_get_minor(struct uio_device *idev) +{ + int retval; + + mutex_lock(&minor_lock); + retval = idr_alloc(&uio_idr, idev, 0, UIO_MAX_DEVICES, GFP_KERNEL); + if (retval >= 0) { + idev->minor = retval; + retval = 0; + } else if (retval == -ENOSPC) { + dev_err(&idev->dev, "too many uio devices\n"); + retval = -EINVAL; + } + mutex_unlock(&minor_lock); + return retval; +} + +static void uio_free_minor(unsigned long minor) +{ + mutex_lock(&minor_lock); + idr_remove(&uio_idr, minor); + mutex_unlock(&minor_lock); +} + +/** + * uio_event_notify - trigger an interrupt event + * @info: UIO device capabilities + */ +void uio_event_notify(struct uio_info *info) +{ + struct uio_device *idev = info->uio_dev; + + atomic_inc(&idev->event); + wake_up_interruptible(&idev->wait); + kill_fasync(&idev->async_queue, SIGIO, POLL_IN); +} +EXPORT_SYMBOL_GPL(uio_event_notify); + +/** + * uio_interrupt - hardware interrupt handler + * @irq: IRQ number, can be UIO_IRQ_CYCLIC for cyclic timer + * @dev_id: Pointer to the devices uio_device structure + */ +static irqreturn_t uio_interrupt(int irq, void *dev_id) +{ + struct uio_device *idev = (struct uio_device *)dev_id; + irqreturn_t ret; + + ret = idev->info->handler(irq, idev->info); + if (ret == IRQ_HANDLED) + uio_event_notify(idev->info); + + return ret; +} + +struct uio_listener { + struct uio_device *dev; + s32 event_count; +}; + +static int uio_open(struct inode *inode, struct file *filep) +{ + struct uio_device *idev; + struct uio_listener *listener; + int ret = 0; + + mutex_lock(&minor_lock); + idev = idr_find(&uio_idr, iminor(inode)); + if (!idev) { + ret = -ENODEV; + mutex_unlock(&minor_lock); + goto out; + } + get_device(&idev->dev); + mutex_unlock(&minor_lock); + + if (!try_module_get(idev->owner)) { + ret = -ENODEV; + goto err_module_get; + } + + listener = kmalloc(sizeof(*listener), GFP_KERNEL); + if (!listener) { + ret = -ENOMEM; + goto err_alloc_listener; + } + + listener->dev = idev; + listener->event_count = atomic_read(&idev->event); + filep->private_data = listener; + + mutex_lock(&idev->info_lock); + if (!idev->info) { + mutex_unlock(&idev->info_lock); + ret = -EINVAL; + goto err_infoopen; + } + + if (idev->info->open) + ret = idev->info->open(idev->info, inode); + mutex_unlock(&idev->info_lock); + if (ret) + goto err_infoopen; + + return 0; + +err_infoopen: + kfree(listener); + +err_alloc_listener: + module_put(idev->owner); + +err_module_get: + put_device(&idev->dev); + +out: + return ret; +} + +static int uio_fasync(int fd, struct file *filep, int on) +{ + struct uio_listener *listener = filep->private_data; + struct uio_device *idev = listener->dev; + + return fasync_helper(fd, filep, on, &idev->async_queue); +} + +static int uio_release(struct inode *inode, struct file *filep) +{ + int ret = 0; + struct uio_listener *listener = filep->private_data; + struct uio_device *idev = listener->dev; + + mutex_lock(&idev->info_lock); + if (idev->info && idev->info->release) + ret = idev->info->release(idev->info, inode); + mutex_unlock(&idev->info_lock); + + module_put(idev->owner); + kfree(listener); + put_device(&idev->dev); + return ret; +} + +static __poll_t uio_poll(struct file *filep, poll_table *wait) +{ + struct uio_listener *listener = filep->private_data; + struct uio_device *idev = listener->dev; + __poll_t ret = 0; + + mutex_lock(&idev->info_lock); + if (!idev->info || !idev->info->irq) + ret = -EIO; + mutex_unlock(&idev->info_lock); + + if (ret) + return ret; + + poll_wait(filep, &idev->wait, wait); + if (listener->event_count != atomic_read(&idev->event)) + return EPOLLIN | EPOLLRDNORM; + return 0; +} + +static ssize_t uio_read(struct file *filep, char __user *buf, + size_t count, loff_t *ppos) +{ + struct uio_listener *listener = filep->private_data; + struct uio_device *idev = listener->dev; + DECLARE_WAITQUEUE(wait, current); + ssize_t retval = 0; + s32 event_count; + + if (count != sizeof(s32)) + return -EINVAL; + + add_wait_queue(&idev->wait, &wait); + + do { + mutex_lock(&idev->info_lock); + if (!idev->info || !idev->info->irq) { + retval = -EIO; + mutex_unlock(&idev->info_lock); + break; + } + mutex_unlock(&idev->info_lock); + + set_current_state(TASK_INTERRUPTIBLE); + + event_count = atomic_read(&idev->event); + if (event_count != listener->event_count) { + __set_current_state(TASK_RUNNING); + if (copy_to_user(buf, &event_count, count)) + retval = -EFAULT; + else { + listener->event_count = event_count; + retval = count; + } + break; + } + + if (filep->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + } while (1); + + __set_current_state(TASK_RUNNING); + remove_wait_queue(&idev->wait, &wait); + + return retval; +} + +static ssize_t uio_write(struct file *filep, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct uio_listener *listener = filep->private_data; + struct uio_device *idev = listener->dev; + ssize_t retval; + s32 irq_on; + + if (count != sizeof(s32)) + return -EINVAL; + + if (copy_from_user(&irq_on, buf, count)) + return -EFAULT; + + mutex_lock(&idev->info_lock); + if (!idev->info) { + retval = -EINVAL; + goto out; + } + + if (!idev->info->irq) { + retval = -EIO; + goto out; + } + + if (!idev->info->irqcontrol) { + retval = -ENOSYS; + goto out; + } + + retval = idev->info->irqcontrol(idev->info, irq_on); + +out: + mutex_unlock(&idev->info_lock); + return retval ? retval : sizeof(s32); +} + +static int uio_find_mem_index(struct vm_area_struct *vma) +{ + struct uio_device *idev = vma->vm_private_data; + + if (vma->vm_pgoff < MAX_UIO_MAPS) { + if (idev->info->mem[vma->vm_pgoff].size == 0) + return -1; + return (int)vma->vm_pgoff; + } + return -1; +} + +static vm_fault_t uio_vma_fault(struct vm_fault *vmf) +{ + struct uio_device *idev = vmf->vma->vm_private_data; + struct page *page; + unsigned long offset; + void *addr; + vm_fault_t ret = 0; + int mi; + + mutex_lock(&idev->info_lock); + if (!idev->info) { + ret = VM_FAULT_SIGBUS; + goto out; + } + + mi = uio_find_mem_index(vmf->vma); + if (mi < 0) { + ret = VM_FAULT_SIGBUS; + goto out; + } + + /* + * We need to subtract mi because userspace uses offset = N*PAGE_SIZE + * to use mem[N]. + */ + offset = (vmf->pgoff - mi) << PAGE_SHIFT; + + addr = (void *)(unsigned long)idev->info->mem[mi].addr + offset; + if (idev->info->mem[mi].memtype == UIO_MEM_LOGICAL) + page = virt_to_page(addr); + else + page = vmalloc_to_page(addr); + get_page(page); + vmf->page = page; + +out: + mutex_unlock(&idev->info_lock); + + return ret; +} + +static const struct vm_operations_struct uio_logical_vm_ops = { + .fault = uio_vma_fault, +}; + +static int uio_mmap_logical(struct vm_area_struct *vma) +{ + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_ops = &uio_logical_vm_ops; + return 0; +} + +static const struct vm_operations_struct uio_physical_vm_ops = { +#ifdef CONFIG_HAVE_IOREMAP_PROT + .access = generic_access_phys, +#endif +}; + +static int uio_mmap_physical(struct vm_area_struct *vma) +{ + struct uio_device *idev = vma->vm_private_data; + int mi = uio_find_mem_index(vma); + struct uio_mem *mem; + + if (mi < 0) + return -EINVAL; + mem = idev->info->mem + mi; + + if (mem->addr & ~PAGE_MASK) + return -ENODEV; + if (vma->vm_end - vma->vm_start > mem->size) + return -EINVAL; + + vma->vm_ops = &uio_physical_vm_ops; + if (idev->info->mem[mi].memtype == UIO_MEM_PHYS) + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* + * We cannot use the vm_iomap_memory() helper here, + * because vma->vm_pgoff is the map index we looked + * up above in uio_find_mem_index(), rather than an + * actual page offset into the mmap. + * + * So we just do the physical mmap without a page + * offset. + */ + return remap_pfn_range(vma, + vma->vm_start, + mem->addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); +} + +static int uio_mmap(struct file *filep, struct vm_area_struct *vma) +{ + struct uio_listener *listener = filep->private_data; + struct uio_device *idev = listener->dev; + int mi; + unsigned long requested_pages, actual_pages; + int ret = 0; + + if (vma->vm_end < vma->vm_start) + return -EINVAL; + + vma->vm_private_data = idev; + + mutex_lock(&idev->info_lock); + if (!idev->info) { + ret = -EINVAL; + goto out; + } + + mi = uio_find_mem_index(vma); + if (mi < 0) { + ret = -EINVAL; + goto out; + } + + requested_pages = vma_pages(vma); + actual_pages = ((idev->info->mem[mi].addr & ~PAGE_MASK) + + idev->info->mem[mi].size + PAGE_SIZE -1) >> PAGE_SHIFT; + if (requested_pages > actual_pages) { + ret = -EINVAL; + goto out; + } + + if (idev->info->mmap) { + ret = idev->info->mmap(idev->info, vma); + goto out; + } + + switch (idev->info->mem[mi].memtype) { + case UIO_MEM_IOVA: + case UIO_MEM_PHYS: + ret = uio_mmap_physical(vma); + break; + case UIO_MEM_LOGICAL: + case UIO_MEM_VIRTUAL: + ret = uio_mmap_logical(vma); + break; + default: + ret = -EINVAL; + } + + out: + mutex_unlock(&idev->info_lock); + return ret; +} + +static const struct file_operations uio_fops = { + .owner = THIS_MODULE, + .open = uio_open, + .release = uio_release, + .read = uio_read, + .write = uio_write, + .mmap = uio_mmap, + .poll = uio_poll, + .fasync = uio_fasync, + .llseek = noop_llseek, +}; + +static int uio_major_init(void) +{ + static const char name[] = "uio"; + struct cdev *cdev = NULL; + dev_t uio_dev = 0; + int result; + + result = alloc_chrdev_region(&uio_dev, 0, UIO_MAX_DEVICES, name); + if (result) + goto out; + + result = -ENOMEM; + cdev = cdev_alloc(); + if (!cdev) + goto out_unregister; + + cdev->owner = THIS_MODULE; + cdev->ops = &uio_fops; + kobject_set_name(&cdev->kobj, "%s", name); + + result = cdev_add(cdev, uio_dev, UIO_MAX_DEVICES); + if (result) + goto out_put; + + uio_major = MAJOR(uio_dev); + uio_cdev = cdev; + return 0; +out_put: + kobject_put(&cdev->kobj); +out_unregister: + unregister_chrdev_region(uio_dev, UIO_MAX_DEVICES); +out: + return result; +} + +static void uio_major_cleanup(void) +{ + unregister_chrdev_region(MKDEV(uio_major, 0), UIO_MAX_DEVICES); + cdev_del(uio_cdev); +} + +static int init_uio_class(void) +{ + int ret; + + /* This is the first time in here, set everything up properly */ + ret = uio_major_init(); + if (ret) + goto exit; + + ret = class_register(&uio_class); + if (ret) { + printk(KERN_ERR "class_register failed for uio\n"); + goto err_class_register; + } + + uio_class_registered = true; + + return 0; + +err_class_register: + uio_major_cleanup(); +exit: + return ret; +} + +static void release_uio_class(void) +{ + uio_class_registered = false; + class_unregister(&uio_class); + uio_major_cleanup(); +} + +static void uio_device_release(struct device *dev) +{ + struct uio_device *idev = dev_get_drvdata(dev); + + kfree(idev); +} + +/** + * __uio_register_device - register a new userspace IO device + * @owner: module that creates the new device + * @parent: parent device + * @info: UIO device capabilities + * + * returns zero on success or a negative error code. + */ +int __uio_register_device(struct module *owner, + struct device *parent, + struct uio_info *info) +{ + struct uio_device *idev; + int ret = 0; + + if (!uio_class_registered) + return -EPROBE_DEFER; + + if (!parent || !info || !info->name || !info->version) + return -EINVAL; + + info->uio_dev = NULL; + + idev = kzalloc(sizeof(*idev), GFP_KERNEL); + if (!idev) { + return -ENOMEM; + } + + idev->owner = owner; + idev->info = info; + mutex_init(&idev->info_lock); + init_waitqueue_head(&idev->wait); + atomic_set(&idev->event, 0); + + ret = uio_get_minor(idev); + if (ret) { + kfree(idev); + return ret; + } + + device_initialize(&idev->dev); + idev->dev.devt = MKDEV(uio_major, idev->minor); + idev->dev.class = &uio_class; + idev->dev.parent = parent; + idev->dev.release = uio_device_release; + dev_set_drvdata(&idev->dev, idev); + + ret = dev_set_name(&idev->dev, "uio%d", idev->minor); + if (ret) + goto err_device_create; + + ret = device_add(&idev->dev); + if (ret) + goto err_device_create; + + ret = uio_dev_add_attributes(idev); + if (ret) + goto err_uio_dev_add_attributes; + + info->uio_dev = idev; + + if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) { + /* + * Note that we deliberately don't use devm_request_irq + * here. The parent module can unregister the UIO device + * and call pci_disable_msi, which requires that this + * irq has been freed. However, the device may have open + * FDs at the time of unregister and therefore may not be + * freed until they are released. + */ + ret = request_irq(info->irq, uio_interrupt, + info->irq_flags, info->name, idev); + if (ret) { + info->uio_dev = NULL; + goto err_request_irq; + } + } + + return 0; + +err_request_irq: + uio_dev_del_attributes(idev); +err_uio_dev_add_attributes: + device_del(&idev->dev); +err_device_create: + uio_free_minor(idev->minor); + put_device(&idev->dev); + return ret; +} +EXPORT_SYMBOL_GPL(__uio_register_device); + +static void devm_uio_unregister_device(struct device *dev, void *res) +{ + uio_unregister_device(*(struct uio_info **)res); +} + +/** + * __devm_uio_register_device - Resource managed uio_register_device() + * @owner: module that creates the new device + * @parent: parent device + * @info: UIO device capabilities + * + * returns zero on success or a negative error code. + */ +int __devm_uio_register_device(struct module *owner, + struct device *parent, + struct uio_info *info) +{ + struct uio_info **ptr; + int ret; + + ptr = devres_alloc(devm_uio_unregister_device, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + *ptr = info; + ret = __uio_register_device(owner, parent, info); + if (ret) { + devres_free(ptr); + return ret; + } + + devres_add(parent, ptr); + + return 0; +} +EXPORT_SYMBOL_GPL(__devm_uio_register_device); + +/** + * uio_unregister_device - unregister a industrial IO device + * @info: UIO device capabilities + * + */ +void uio_unregister_device(struct uio_info *info) +{ + struct uio_device *idev; + unsigned long minor; + + if (!info || !info->uio_dev) + return; + + idev = info->uio_dev; + minor = idev->minor; + + mutex_lock(&idev->info_lock); + uio_dev_del_attributes(idev); + + if (info->irq && info->irq != UIO_IRQ_CUSTOM) + free_irq(info->irq, idev); + + idev->info = NULL; + mutex_unlock(&idev->info_lock); + + wake_up_interruptible(&idev->wait); + kill_fasync(&idev->async_queue, SIGIO, POLL_HUP); + + uio_free_minor(minor); + device_unregister(&idev->dev); + + return; +} +EXPORT_SYMBOL_GPL(uio_unregister_device); + +static int __init uio_init(void) +{ + return init_uio_class(); +} + +static void __exit uio_exit(void) +{ + release_uio_class(); + idr_destroy(&uio_idr); +} + +module_init(uio_init) +module_exit(uio_exit) +MODULE_LICENSE("GPL v2"); diff --git a/drivers/uio/uio_aec.c b/drivers/uio/uio_aec.c new file mode 100644 index 000000000..64eafd59e --- /dev/null +++ b/drivers/uio/uio_aec.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * uio_aec.c -- simple driver for Adrienne Electronics Corp time code PCI device + * + * Copyright (C) 2008 Brandon Philips <brandon@ifup.org> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/uio_driver.h> +#include <linux/slab.h> + +#define PCI_VENDOR_ID_AEC 0xaecb +#define PCI_DEVICE_ID_AEC_VITCLTC 0x6250 + +#define INT_ENABLE_ADDR 0xFC +#define INT_ENABLE 0x10 +#define INT_DISABLE 0x0 + +#define INT_MASK_ADDR 0x2E +#define INT_MASK_ALL 0x3F + +#define INTA_DRVR_ADDR 0xFE +#define INTA_ENABLED_FLAG 0x08 +#define INTA_FLAG 0x01 + +#define MAILBOX 0x0F + +static struct pci_device_id ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AEC, PCI_DEVICE_ID_AEC_VITCLTC), }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, ids); + +static irqreturn_t aectc_irq(int irq, struct uio_info *dev_info) +{ + void __iomem *int_flag = dev_info->priv + INTA_DRVR_ADDR; + unsigned char status = ioread8(int_flag); + + + if ((status & INTA_ENABLED_FLAG) && (status & INTA_FLAG)) { + /* application writes 0x00 to 0x2F to get next interrupt */ + status = ioread8(dev_info->priv + MAILBOX); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void print_board_data(struct pci_dev *pdev, struct uio_info *i) +{ + dev_info(&pdev->dev, "PCI-TC board vendor: %x%x number: %x%x" + " revision: %c%c\n", + ioread8(i->priv + 0x01), + ioread8(i->priv + 0x00), + ioread8(i->priv + 0x03), + ioread8(i->priv + 0x02), + ioread8(i->priv + 0x06), + ioread8(i->priv + 0x07)); +} + +static int probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct uio_info *info; + int ret; + + info = devm_kzalloc(&pdev->dev, sizeof(struct uio_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (pci_enable_device(pdev)) + return -ENODEV; + + if (pci_request_regions(pdev, "aectc")) + goto out_disable; + + info->name = "aectc"; + info->port[0].start = pci_resource_start(pdev, 0); + if (!info->port[0].start) + goto out_release; + info->priv = pci_iomap(pdev, 0, 0); + if (!info->priv) + goto out_release; + info->port[0].size = pci_resource_len(pdev, 0); + info->port[0].porttype = UIO_PORT_GPIO; + + info->version = "0.0.1"; + info->irq = pdev->irq; + info->irq_flags = IRQF_SHARED; + info->handler = aectc_irq; + + print_board_data(pdev, info); + ret = uio_register_device(&pdev->dev, info); + if (ret) + goto out_unmap; + + iowrite32(INT_ENABLE, info->priv + INT_ENABLE_ADDR); + iowrite8(INT_MASK_ALL, info->priv + INT_MASK_ADDR); + if (!(ioread8(info->priv + INTA_DRVR_ADDR) + & INTA_ENABLED_FLAG)) + dev_err(&pdev->dev, "aectc: interrupts not enabled\n"); + + pci_set_drvdata(pdev, info); + + return 0; + +out_unmap: + pci_iounmap(pdev, info->priv); +out_release: + pci_release_regions(pdev); +out_disable: + pci_disable_device(pdev); + return -ENODEV; +} + +static void remove(struct pci_dev *pdev) +{ + struct uio_info *info = pci_get_drvdata(pdev); + + /* disable interrupts */ + iowrite8(INT_DISABLE, info->priv + INT_MASK_ADDR); + iowrite32(INT_DISABLE, info->priv + INT_ENABLE_ADDR); + /* read mailbox to ensure board drops irq */ + ioread8(info->priv + MAILBOX); + + uio_unregister_device(info); + pci_release_regions(pdev); + pci_disable_device(pdev); + pci_iounmap(pdev, info->priv); +} + +static struct pci_driver pci_driver = { + .name = "aectc", + .id_table = ids, + .probe = probe, + .remove = remove, +}; + +module_pci_driver(pci_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/uio/uio_cif.c b/drivers/uio/uio_cif.c new file mode 100644 index 000000000..653f842a1 --- /dev/null +++ b/drivers/uio/uio_cif.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UIO Hilscher CIF card driver + * + * (C) 2007 Hans J. Koch <hjk@hansjkoch.de> + * Original code (C) 2005 Benedikt Spranger <b.spranger@linutronix.de> + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/uio_driver.h> + +#include <asm/io.h> + +#define PLX9030_INTCSR 0x4C +#define INTSCR_INT1_ENABLE 0x01 +#define INTSCR_INT1_STATUS 0x04 +#define INT1_ENABLED_AND_ACTIVE (INTSCR_INT1_ENABLE | INTSCR_INT1_STATUS) + +#define PCI_SUBVENDOR_ID_PEP 0x1518 +#define CIF_SUBDEVICE_PROFIBUS 0x430 +#define CIF_SUBDEVICE_DEVICENET 0x432 + + +static irqreturn_t hilscher_handler(int irq, struct uio_info *dev_info) +{ + void __iomem *plx_intscr = dev_info->mem[0].internal_addr + + PLX9030_INTCSR; + + if ((ioread8(plx_intscr) & INT1_ENABLED_AND_ACTIVE) + != INT1_ENABLED_AND_ACTIVE) + return IRQ_NONE; + + /* Disable interrupt */ + iowrite8(ioread8(plx_intscr) & ~INTSCR_INT1_ENABLE, plx_intscr); + return IRQ_HANDLED; +} + +static int hilscher_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct uio_info *info; + + info = devm_kzalloc(&dev->dev, sizeof(struct uio_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (pci_enable_device(dev)) + return -ENODEV; + + if (pci_request_regions(dev, "hilscher")) + goto out_disable; + + info->mem[0].addr = pci_resource_start(dev, 0); + if (!info->mem[0].addr) + goto out_release; + info->mem[0].internal_addr = pci_ioremap_bar(dev, 0); + if (!info->mem[0].internal_addr) + goto out_release; + + info->mem[0].size = pci_resource_len(dev, 0); + info->mem[0].memtype = UIO_MEM_PHYS; + info->mem[1].addr = pci_resource_start(dev, 2); + info->mem[1].size = pci_resource_len(dev, 2); + info->mem[1].memtype = UIO_MEM_PHYS; + switch (id->subdevice) { + case CIF_SUBDEVICE_PROFIBUS: + info->name = "CIF_Profibus"; + break; + case CIF_SUBDEVICE_DEVICENET: + info->name = "CIF_Devicenet"; + break; + default: + info->name = "CIF_???"; + } + info->version = "0.0.1"; + info->irq = dev->irq; + info->irq_flags = IRQF_SHARED; + info->handler = hilscher_handler; + + if (uio_register_device(&dev->dev, info)) + goto out_unmap; + + pci_set_drvdata(dev, info); + + return 0; +out_unmap: + iounmap(info->mem[0].internal_addr); +out_release: + pci_release_regions(dev); +out_disable: + pci_disable_device(dev); + return -ENODEV; +} + +static void hilscher_pci_remove(struct pci_dev *dev) +{ + struct uio_info *info = pci_get_drvdata(dev); + + uio_unregister_device(info); + pci_release_regions(dev); + pci_disable_device(dev); + iounmap(info->mem[0].internal_addr); +} + +static struct pci_device_id hilscher_pci_ids[] = { + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = PCI_SUBVENDOR_ID_PEP, + .subdevice = CIF_SUBDEVICE_PROFIBUS, + }, + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = PCI_SUBVENDOR_ID_PEP, + .subdevice = CIF_SUBDEVICE_DEVICENET, + }, + { 0, } +}; + +static struct pci_driver hilscher_pci_driver = { + .name = "hilscher", + .id_table = hilscher_pci_ids, + .probe = hilscher_pci_probe, + .remove = hilscher_pci_remove, +}; + +module_pci_driver(hilscher_pci_driver); +MODULE_DEVICE_TABLE(pci, hilscher_pci_ids); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Hans J. Koch, Benedikt Spranger"); diff --git a/drivers/uio/uio_dfl.c b/drivers/uio/uio_dfl.c new file mode 100644 index 000000000..69e93f3e7 --- /dev/null +++ b/drivers/uio/uio_dfl.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic DFL driver for Userspace I/O devicess + * + * Copyright (C) 2021 Intel Corporation, Inc. + */ +#include <linux/dfl.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/uio_driver.h> + +#define DRIVER_NAME "uio_dfl" + +static int uio_dfl_probe(struct dfl_device *ddev) +{ + struct resource *r = &ddev->mmio_res; + struct device *dev = &ddev->dev; + struct uio_info *uioinfo; + struct uio_mem *uiomem; + int ret; + + uioinfo = devm_kzalloc(dev, sizeof(struct uio_info), GFP_KERNEL); + if (!uioinfo) + return -ENOMEM; + + uioinfo->name = DRIVER_NAME; + uioinfo->version = "0"; + + uiomem = &uioinfo->mem[0]; + uiomem->memtype = UIO_MEM_PHYS; + uiomem->addr = r->start & PAGE_MASK; + uiomem->offs = r->start & ~PAGE_MASK; + uiomem->size = (uiomem->offs + resource_size(r) + + PAGE_SIZE - 1) & PAGE_MASK; + uiomem->name = r->name; + + /* Irq is yet to be supported */ + uioinfo->irq = UIO_IRQ_NONE; + + ret = devm_uio_register_device(dev, uioinfo); + if (ret) + dev_err(dev, "unable to register uio device\n"); + + return ret; +} + +#define FME_FEATURE_ID_ETH_GROUP 0x10 +#define FME_FEATURE_ID_HSSI_SUBSYS 0x15 +#define PORT_FEATURE_ID_IOPLL_USRCLK 0x14 + +static const struct dfl_device_id uio_dfl_ids[] = { + { FME_ID, FME_FEATURE_ID_ETH_GROUP }, + { FME_ID, FME_FEATURE_ID_HSSI_SUBSYS }, + { PORT_ID, PORT_FEATURE_ID_IOPLL_USRCLK }, + { } +}; +MODULE_DEVICE_TABLE(dfl, uio_dfl_ids); + +static struct dfl_driver uio_dfl_driver = { + .drv = { + .name = DRIVER_NAME, + }, + .id_table = uio_dfl_ids, + .probe = uio_dfl_probe, +}; +module_dfl_driver(uio_dfl_driver); + +MODULE_DESCRIPTION("Generic DFL driver for Userspace I/O devices"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c new file mode 100644 index 000000000..792c3e9c9 --- /dev/null +++ b/drivers/uio/uio_dmem_genirq.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/uio/uio_dmem_genirq.c + * + * Userspace I/O platform driver with generic IRQ handling code. + * + * Copyright (C) 2012 Damian Hobson-Garcia + * + * Based on uio_pdrv_genirq.c by Magnus Damm + */ + +#include <linux/platform_device.h> +#include <linux/uio_driver.h> +#include <linux/spinlock.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_data/uio_dmem_genirq.h> +#include <linux/stringify.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/irq.h> + +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> + +#define DRIVER_NAME "uio_dmem_genirq" +#define DMEM_MAP_ERROR (~0) + +struct uio_dmem_genirq_platdata { + struct uio_info *uioinfo; + spinlock_t lock; + unsigned long flags; + struct platform_device *pdev; + unsigned int dmem_region_start; + unsigned int num_dmem_regions; + void *dmem_region_vaddr[MAX_UIO_MAPS]; + struct mutex alloc_lock; + unsigned int refcnt; +}; + +static int uio_dmem_genirq_open(struct uio_info *info, struct inode *inode) +{ + struct uio_dmem_genirq_platdata *priv = info->priv; + struct uio_mem *uiomem; + int dmem_region = priv->dmem_region_start; + + uiomem = &priv->uioinfo->mem[priv->dmem_region_start]; + + mutex_lock(&priv->alloc_lock); + while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) { + void *addr; + if (!uiomem->size) + break; + + addr = dma_alloc_coherent(&priv->pdev->dev, uiomem->size, + (dma_addr_t *)&uiomem->addr, GFP_KERNEL); + if (!addr) { + uiomem->addr = DMEM_MAP_ERROR; + } + priv->dmem_region_vaddr[dmem_region++] = addr; + ++uiomem; + } + priv->refcnt++; + + mutex_unlock(&priv->alloc_lock); + /* Wait until the Runtime PM code has woken up the device */ + pm_runtime_get_sync(&priv->pdev->dev); + return 0; +} + +static int uio_dmem_genirq_release(struct uio_info *info, struct inode *inode) +{ + struct uio_dmem_genirq_platdata *priv = info->priv; + struct uio_mem *uiomem; + int dmem_region = priv->dmem_region_start; + + /* Tell the Runtime PM code that the device has become idle */ + pm_runtime_put_sync(&priv->pdev->dev); + + uiomem = &priv->uioinfo->mem[priv->dmem_region_start]; + + mutex_lock(&priv->alloc_lock); + + priv->refcnt--; + while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) { + if (!uiomem->size) + break; + if (priv->dmem_region_vaddr[dmem_region]) { + dma_free_coherent(&priv->pdev->dev, uiomem->size, + priv->dmem_region_vaddr[dmem_region], + uiomem->addr); + } + uiomem->addr = DMEM_MAP_ERROR; + ++dmem_region; + ++uiomem; + } + + mutex_unlock(&priv->alloc_lock); + return 0; +} + +static irqreturn_t uio_dmem_genirq_handler(int irq, struct uio_info *dev_info) +{ + struct uio_dmem_genirq_platdata *priv = dev_info->priv; + + /* Just disable the interrupt in the interrupt controller, and + * remember the state so we can allow user space to enable it later. + */ + + spin_lock(&priv->lock); + if (!test_and_set_bit(0, &priv->flags)) + disable_irq_nosync(irq); + spin_unlock(&priv->lock); + + return IRQ_HANDLED; +} + +static int uio_dmem_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on) +{ + struct uio_dmem_genirq_platdata *priv = dev_info->priv; + unsigned long flags; + + /* Allow user space to enable and disable the interrupt + * in the interrupt controller, but keep track of the + * state to prevent per-irq depth damage. + * + * Serialize this operation to support multiple tasks and concurrency + * with irq handler on SMP systems. + */ + + spin_lock_irqsave(&priv->lock, flags); + if (irq_on) { + if (test_and_clear_bit(0, &priv->flags)) + enable_irq(dev_info->irq); + } else { + if (!test_and_set_bit(0, &priv->flags)) + disable_irq_nosync(dev_info->irq); + } + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static void uio_dmem_genirq_pm_disable(void *data) +{ + struct device *dev = data; + + pm_runtime_disable(dev); +} + +static int uio_dmem_genirq_probe(struct platform_device *pdev) +{ + struct uio_dmem_genirq_pdata *pdata = dev_get_platdata(&pdev->dev); + struct uio_info *uioinfo = &pdata->uioinfo; + struct uio_dmem_genirq_platdata *priv; + struct uio_mem *uiomem; + int ret = -EINVAL; + int i; + + if (pdev->dev.of_node) { + /* alloc uioinfo for one device */ + uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo), GFP_KERNEL); + if (!uioinfo) { + dev_err(&pdev->dev, "unable to kmalloc\n"); + return -ENOMEM; + } + uioinfo->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%pOFn", + pdev->dev.of_node); + uioinfo->version = "devicetree"; + } + + if (!uioinfo || !uioinfo->name || !uioinfo->version) { + dev_err(&pdev->dev, "missing platform_data\n"); + return -EINVAL; + } + + if (uioinfo->handler || uioinfo->irqcontrol || + uioinfo->irq_flags & IRQF_SHARED) { + dev_err(&pdev->dev, "interrupt configuration error\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "unable to kmalloc\n"); + return -ENOMEM; + } + + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "DMA enable failed\n"); + return ret; + } + + priv->uioinfo = uioinfo; + spin_lock_init(&priv->lock); + priv->flags = 0; /* interrupt is enabled to begin with */ + priv->pdev = pdev; + mutex_init(&priv->alloc_lock); + + if (!uioinfo->irq) { + /* Multiple IRQs are not supported */ + ret = platform_get_irq(pdev, 0); + if (ret == -ENXIO && pdev->dev.of_node) + ret = UIO_IRQ_NONE; + else if (ret < 0) + return ret; + uioinfo->irq = ret; + } + + if (uioinfo->irq) { + struct irq_data *irq_data = irq_get_irq_data(uioinfo->irq); + + /* + * If a level interrupt, dont do lazy disable. Otherwise the + * irq will fire again since clearing of the actual cause, on + * device level, is done in userspace + * irqd_is_level_type() isn't used since isn't valid until + * irq is configured. + */ + if (irq_data && + irqd_get_trigger_type(irq_data) & IRQ_TYPE_LEVEL_MASK) { + dev_dbg(&pdev->dev, "disable lazy unmask\n"); + irq_set_status_flags(uioinfo->irq, IRQ_DISABLE_UNLAZY); + } + } + + uiomem = &uioinfo->mem[0]; + + for (i = 0; i < pdev->num_resources; ++i) { + struct resource *r = &pdev->resource[i]; + + if (r->flags != IORESOURCE_MEM) + continue; + + if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) { + dev_warn(&pdev->dev, "device has more than " + __stringify(MAX_UIO_MAPS) + " I/O memory resources.\n"); + break; + } + + uiomem->memtype = UIO_MEM_PHYS; + uiomem->addr = r->start; + uiomem->size = resource_size(r); + ++uiomem; + } + + priv->dmem_region_start = uiomem - &uioinfo->mem[0]; + priv->num_dmem_regions = pdata->num_dynamic_regions; + + for (i = 0; i < pdata->num_dynamic_regions; ++i) { + if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) { + dev_warn(&pdev->dev, "device has more than " + __stringify(MAX_UIO_MAPS) + " dynamic and fixed memory regions.\n"); + break; + } + uiomem->memtype = UIO_MEM_PHYS; + uiomem->addr = DMEM_MAP_ERROR; + uiomem->size = pdata->dynamic_region_sizes[i]; + ++uiomem; + } + + while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) { + uiomem->size = 0; + ++uiomem; + } + + /* This driver requires no hardware specific kernel code to handle + * interrupts. Instead, the interrupt handler simply disables the + * interrupt in the interrupt controller. User space is responsible + * for performing hardware specific acknowledge and re-enabling of + * the interrupt in the interrupt controller. + * + * Interrupt sharing is not supported. + */ + + uioinfo->handler = uio_dmem_genirq_handler; + uioinfo->irqcontrol = uio_dmem_genirq_irqcontrol; + uioinfo->open = uio_dmem_genirq_open; + uioinfo->release = uio_dmem_genirq_release; + uioinfo->priv = priv; + + /* Enable Runtime PM for this device: + * The device starts in suspended state to allow the hardware to be + * turned off by default. The Runtime PM bus code should power on the + * hardware and enable clocks at open(). + */ + pm_runtime_enable(&pdev->dev); + + ret = devm_add_action_or_reset(&pdev->dev, uio_dmem_genirq_pm_disable, &pdev->dev); + if (ret) + return ret; + + return devm_uio_register_device(&pdev->dev, priv->uioinfo); +} + +static int uio_dmem_genirq_runtime_nop(struct device *dev) +{ + /* Runtime PM callback shared between ->runtime_suspend() + * and ->runtime_resume(). Simply returns success. + * + * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() + * are used at open() and release() time. This allows the + * Runtime PM code to turn off power to the device while the + * device is unused, ie before open() and after release(). + * + * This Runtime PM callback does not need to save or restore + * any registers since user space is responsbile for hardware + * register reinitialization after open(). + */ + return 0; +} + +static const struct dev_pm_ops uio_dmem_genirq_dev_pm_ops = { + .runtime_suspend = uio_dmem_genirq_runtime_nop, + .runtime_resume = uio_dmem_genirq_runtime_nop, +}; + +#ifdef CONFIG_OF +static const struct of_device_id uio_of_genirq_match[] = { + { /* empty for now */ }, +}; +MODULE_DEVICE_TABLE(of, uio_of_genirq_match); +#endif + +static struct platform_driver uio_dmem_genirq = { + .probe = uio_dmem_genirq_probe, + .driver = { + .name = DRIVER_NAME, + .pm = &uio_dmem_genirq_dev_pm_ops, + .of_match_table = of_match_ptr(uio_of_genirq_match), + }, +}; + +module_platform_driver(uio_dmem_genirq); + +MODULE_AUTHOR("Damian Hobson-Garcia"); +MODULE_DESCRIPTION("Userspace I/O platform driver with dynamic memory."); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/uio/uio_fsl_elbc_gpcm.c b/drivers/uio/uio_fsl_elbc_gpcm.c new file mode 100644 index 000000000..7d8eb9dc2 --- /dev/null +++ b/drivers/uio/uio_fsl_elbc_gpcm.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0 +/* uio_fsl_elbc_gpcm: UIO driver for eLBC/GPCM peripherals + + Copyright (C) 2014 Linutronix GmbH + Author: John Ogness <john.ogness@linutronix.de> + + This driver provides UIO access to memory of a peripheral connected + to the Freescale enhanced local bus controller (eLBC) interface + using the general purpose chip-select mode (GPCM). + + Here is an example of the device tree entries: + + localbus@ffe05000 { + ranges = <0x2 0x0 0x0 0xff810000 0x10000>; + + dpm@2,0 { + compatible = "fsl,elbc-gpcm-uio"; + reg = <0x2 0x0 0x10000>; + elbc-gpcm-br = <0xff810800>; + elbc-gpcm-or = <0xffff09f7>; + interrupt-parent = <&mpic>; + interrupts = <4 1>; + device_type = "netx5152"; + uio_name = "netx_custom"; + netx5152,init-win0-offset = <0x0>; + }; + }; + + Only the entries reg (to identify bank) and elbc-gpcm-* (initial BR/OR + values) are required. The entries interrupt*, device_type, and uio_name + are optional (as well as any type-specific options such as + netx5152,init-win0-offset). As long as no interrupt handler is needed, + this driver can be used without any type-specific implementation. + + The netx5152 type has been tested to work with the netX 51/52 hardware + from Hilscher using the Hilscher userspace netX stack. + + The netx5152 type should serve as a model to add new type-specific + devices as needed. +*/ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/uio_driver.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#include <asm/fsl_lbc.h> + +#define MAX_BANKS 8 + +struct fsl_elbc_gpcm { + struct device *dev; + struct fsl_lbc_regs __iomem *lbc; + u32 bank; + const char *name; + + void (*init)(struct uio_info *info); + void (*shutdown)(struct uio_info *info, bool init_err); + irqreturn_t (*irq_handler)(int irq, struct uio_info *info); +}; + +static ssize_t reg_show(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t reg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +static DEVICE_ATTR(reg_br, 0664, reg_show, reg_store); +static DEVICE_ATTR(reg_or, 0664, reg_show, reg_store); + +static struct attribute *uio_fsl_elbc_gpcm_attrs[] = { + &dev_attr_reg_br.attr, + &dev_attr_reg_or.attr, + NULL, +}; +ATTRIBUTE_GROUPS(uio_fsl_elbc_gpcm); + +static ssize_t reg_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct uio_info *info = dev_get_drvdata(dev); + struct fsl_elbc_gpcm *priv = info->priv; + struct fsl_lbc_bank *bank = &priv->lbc->bank[priv->bank]; + + if (attr == &dev_attr_reg_br) { + return scnprintf(buf, PAGE_SIZE, "0x%08x\n", + in_be32(&bank->br)); + + } else if (attr == &dev_attr_reg_or) { + return scnprintf(buf, PAGE_SIZE, "0x%08x\n", + in_be32(&bank->or)); + } + + return 0; +} + +static ssize_t reg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct uio_info *info = dev_get_drvdata(dev); + struct fsl_elbc_gpcm *priv = info->priv; + struct fsl_lbc_bank *bank = &priv->lbc->bank[priv->bank]; + unsigned long val; + u32 reg_br_cur; + u32 reg_or_cur; + u32 reg_new; + + /* parse use input */ + if (kstrtoul(buf, 0, &val) != 0) + return -EINVAL; + reg_new = (u32)val; + + /* read current values */ + reg_br_cur = in_be32(&bank->br); + reg_or_cur = in_be32(&bank->or); + + if (attr == &dev_attr_reg_br) { + /* not allowed to change effective base address */ + if ((reg_br_cur & reg_or_cur & BR_BA) != + (reg_new & reg_or_cur & BR_BA)) { + return -EINVAL; + } + + /* not allowed to change mode */ + if ((reg_new & BR_MSEL) != BR_MS_GPCM) + return -EINVAL; + + /* write new value (force valid) */ + out_be32(&bank->br, reg_new | BR_V); + + } else if (attr == &dev_attr_reg_or) { + /* not allowed to change access mask */ + if ((reg_or_cur & OR_GPCM_AM) != (reg_new & OR_GPCM_AM)) + return -EINVAL; + + /* write new value */ + out_be32(&bank->or, reg_new); + + } else { + return -EINVAL; + } + + return count; +} + +#ifdef CONFIG_UIO_FSL_ELBC_GPCM_NETX5152 +#define DPM_HOST_WIN0_OFFSET 0xff00 +#define DPM_HOST_INT_STAT0 0xe0 +#define DPM_HOST_INT_EN0 0xf0 +#define DPM_HOST_INT_MASK 0xe600ffff +#define DPM_HOST_INT_GLOBAL_EN 0x80000000 + +static irqreturn_t netx5152_irq_handler(int irq, struct uio_info *info) +{ + void __iomem *reg_int_en = info->mem[0].internal_addr + + DPM_HOST_WIN0_OFFSET + + DPM_HOST_INT_EN0; + void __iomem *reg_int_stat = info->mem[0].internal_addr + + DPM_HOST_WIN0_OFFSET + + DPM_HOST_INT_STAT0; + + /* check if an interrupt is enabled and active */ + if ((ioread32(reg_int_en) & ioread32(reg_int_stat) & + DPM_HOST_INT_MASK) == 0) { + return IRQ_NONE; + } + + /* disable interrupts */ + iowrite32(ioread32(reg_int_en) & ~DPM_HOST_INT_GLOBAL_EN, reg_int_en); + + return IRQ_HANDLED; +} + +static void netx5152_init(struct uio_info *info) +{ + unsigned long win0_offset = DPM_HOST_WIN0_OFFSET; + struct fsl_elbc_gpcm *priv = info->priv; + const void *prop; + + /* get an optional initial win0 offset */ + prop = of_get_property(priv->dev->of_node, + "netx5152,init-win0-offset", NULL); + if (prop) + win0_offset = of_read_ulong(prop, 1); + + /* disable interrupts */ + iowrite32(0, info->mem[0].internal_addr + win0_offset + + DPM_HOST_INT_EN0); +} + +static void netx5152_shutdown(struct uio_info *info, bool init_err) +{ + if (init_err) + return; + + /* disable interrupts */ + iowrite32(0, info->mem[0].internal_addr + DPM_HOST_WIN0_OFFSET + + DPM_HOST_INT_EN0); +} +#endif + +static void setup_periph(struct fsl_elbc_gpcm *priv, + const char *type) +{ +#ifdef CONFIG_UIO_FSL_ELBC_GPCM_NETX5152 + if (strcmp(type, "netx5152") == 0) { + priv->irq_handler = netx5152_irq_handler; + priv->init = netx5152_init; + priv->shutdown = netx5152_shutdown; + priv->name = "netX 51/52"; + return; + } +#endif +} + +static int check_of_data(struct fsl_elbc_gpcm *priv, + struct resource *res, + u32 reg_br, u32 reg_or) +{ + /* check specified bank */ + if (priv->bank >= MAX_BANKS) { + dev_err(priv->dev, "invalid bank\n"); + return -ENODEV; + } + + /* check specified mode (BR_MS_GPCM is 0) */ + if ((reg_br & BR_MSEL) != BR_MS_GPCM) { + dev_err(priv->dev, "unsupported mode\n"); + return -ENODEV; + } + + /* check specified mask vs. resource size */ + if ((~(reg_or & OR_GPCM_AM) + 1) != resource_size(res)) { + dev_err(priv->dev, "address mask / size mismatch\n"); + return -ENODEV; + } + + /* check specified address */ + if ((reg_br & reg_or & BR_BA) != fsl_lbc_addr(res->start)) { + dev_err(priv->dev, "base address mismatch\n"); + return -ENODEV; + } + + return 0; +} + +static int get_of_data(struct fsl_elbc_gpcm *priv, struct device_node *node, + struct resource *res, u32 *reg_br, + u32 *reg_or, unsigned int *irq, char **name) +{ + const char *dt_name; + const char *type; + int ret; + + /* get the memory resource */ + ret = of_address_to_resource(node, 0, res); + if (ret) { + dev_err(priv->dev, "failed to get resource\n"); + return ret; + } + + /* get the bank number */ + ret = of_property_read_u32(node, "reg", &priv->bank); + if (ret) { + dev_err(priv->dev, "failed to get bank number\n"); + return ret; + } + + /* get BR value to set */ + ret = of_property_read_u32(node, "elbc-gpcm-br", reg_br); + if (ret) { + dev_err(priv->dev, "missing elbc-gpcm-br value\n"); + return ret; + } + + /* get OR value to set */ + ret = of_property_read_u32(node, "elbc-gpcm-or", reg_or); + if (ret) { + dev_err(priv->dev, "missing elbc-gpcm-or value\n"); + return ret; + } + + /* get optional peripheral type */ + priv->name = "generic"; + if (of_property_read_string(node, "device_type", &type) == 0) + setup_periph(priv, type); + + /* get optional irq value */ + *irq = irq_of_parse_and_map(node, 0); + + /* sanity check device tree data */ + ret = check_of_data(priv, res, *reg_br, *reg_or); + if (ret) + return ret; + + /* get optional uio name */ + if (of_property_read_string(node, "uio_name", &dt_name) != 0) + dt_name = "eLBC_GPCM"; + *name = devm_kstrdup(priv->dev, dt_name, GFP_KERNEL); + if (!*name) + return -ENOMEM; + + return 0; +} + +static int uio_fsl_elbc_gpcm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct fsl_elbc_gpcm *priv; + struct uio_info *info; + char *uio_name = NULL; + struct resource res; + unsigned int irq; + u32 reg_br_cur; + u32 reg_or_cur; + u32 reg_br_new; + u32 reg_or_new; + int ret; + + if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs) + return -ENODEV; + + /* allocate private data */ + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dev = &pdev->dev; + priv->lbc = fsl_lbc_ctrl_dev->regs; + + /* get device tree data */ + ret = get_of_data(priv, node, &res, ®_br_new, ®_or_new, + &irq, &uio_name); + if (ret) + return ret; + + /* allocate UIO structure */ + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + /* get current BR/OR values */ + reg_br_cur = in_be32(&priv->lbc->bank[priv->bank].br); + reg_or_cur = in_be32(&priv->lbc->bank[priv->bank].or); + + /* if bank already configured, make sure it matches */ + if ((reg_br_cur & BR_V)) { + if ((reg_br_cur & BR_MSEL) != BR_MS_GPCM || + (reg_br_cur & reg_or_cur & BR_BA) + != fsl_lbc_addr(res.start)) { + dev_err(priv->dev, + "bank in use by another peripheral\n"); + return -ENODEV; + } + + /* warn if behavior settings changing */ + if ((reg_br_cur & ~(BR_BA | BR_V)) != + (reg_br_new & ~(BR_BA | BR_V))) { + dev_warn(priv->dev, + "modifying BR settings: 0x%08x -> 0x%08x", + reg_br_cur, reg_br_new); + } + if ((reg_or_cur & ~OR_GPCM_AM) != (reg_or_new & ~OR_GPCM_AM)) { + dev_warn(priv->dev, + "modifying OR settings: 0x%08x -> 0x%08x", + reg_or_cur, reg_or_new); + } + } + + /* configure the bank (force base address and GPCM) */ + reg_br_new &= ~(BR_BA | BR_MSEL); + reg_br_new |= fsl_lbc_addr(res.start) | BR_MS_GPCM | BR_V; + out_be32(&priv->lbc->bank[priv->bank].or, reg_or_new); + out_be32(&priv->lbc->bank[priv->bank].br, reg_br_new); + + /* map the memory resource */ + info->mem[0].internal_addr = ioremap(res.start, resource_size(&res)); + if (!info->mem[0].internal_addr) { + dev_err(priv->dev, "failed to map chip region\n"); + return -ENODEV; + } + + /* set all UIO data */ + info->mem[0].name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%pOFn", node); + info->mem[0].addr = res.start; + info->mem[0].size = resource_size(&res); + info->mem[0].memtype = UIO_MEM_PHYS; + info->priv = priv; + info->name = uio_name; + info->version = "0.0.1"; + if (irq != NO_IRQ) { + if (priv->irq_handler) { + info->irq = irq; + info->irq_flags = IRQF_SHARED; + info->handler = priv->irq_handler; + } else { + irq = NO_IRQ; + dev_warn(priv->dev, "ignoring irq, no handler\n"); + } + } + + if (priv->init) + priv->init(info); + + /* register UIO device */ + if (uio_register_device(priv->dev, info) != 0) { + dev_err(priv->dev, "UIO registration failed\n"); + ret = -ENODEV; + goto out_err2; + } + + /* store private data */ + platform_set_drvdata(pdev, info); + + dev_info(priv->dev, + "eLBC/GPCM device (%s) at 0x%llx, bank %d, irq=%d\n", + priv->name, (unsigned long long)res.start, priv->bank, + irq != NO_IRQ ? irq : -1); + + return 0; +out_err2: + if (priv->shutdown) + priv->shutdown(info, true); + iounmap(info->mem[0].internal_addr); + return ret; +} + +static int uio_fsl_elbc_gpcm_remove(struct platform_device *pdev) +{ + struct uio_info *info = platform_get_drvdata(pdev); + struct fsl_elbc_gpcm *priv = info->priv; + + platform_set_drvdata(pdev, NULL); + uio_unregister_device(info); + if (priv->shutdown) + priv->shutdown(info, false); + iounmap(info->mem[0].internal_addr); + + return 0; + +} + +static const struct of_device_id uio_fsl_elbc_gpcm_match[] = { + { .compatible = "fsl,elbc-gpcm-uio", }, + {} +}; +MODULE_DEVICE_TABLE(of, uio_fsl_elbc_gpcm_match); + +static struct platform_driver uio_fsl_elbc_gpcm_driver = { + .driver = { + .name = "fsl,elbc-gpcm-uio", + .of_match_table = uio_fsl_elbc_gpcm_match, + .dev_groups = uio_fsl_elbc_gpcm_groups, + }, + .probe = uio_fsl_elbc_gpcm_probe, + .remove = uio_fsl_elbc_gpcm_remove, +}; +module_platform_driver(uio_fsl_elbc_gpcm_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>"); +MODULE_DESCRIPTION("Freescale Enhanced Local Bus Controller GPCM driver"); diff --git a/drivers/uio/uio_hv_generic.c b/drivers/uio/uio_hv_generic.c new file mode 100644 index 000000000..c08a6cfd1 --- /dev/null +++ b/drivers/uio/uio_hv_generic.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * uio_hv_generic - generic UIO driver for VMBus + * + * Copyright (c) 2013-2016 Brocade Communications Systems, Inc. + * Copyright (c) 2016, Microsoft Corporation. + * + * Since the driver does not declare any device ids, you must allocate + * id and bind the device to the driver yourself. For example: + * + * Associate Network GUID with UIO device + * # echo "f8615163-df3e-46c5-913f-f2d2f965ed0e" \ + * > /sys/bus/vmbus/drivers/uio_hv_generic/new_id + * Then rebind + * # echo -n "ed963694-e847-4b2a-85af-bc9cfc11d6f3" \ + * > /sys/bus/vmbus/drivers/hv_netvsc/unbind + * # echo -n "ed963694-e847-4b2a-85af-bc9cfc11d6f3" \ + * > /sys/bus/vmbus/drivers/uio_hv_generic/bind + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/uio_driver.h> +#include <linux/netdevice.h> +#include <linux/if_ether.h> +#include <linux/skbuff.h> +#include <linux/hyperv.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> + +#include "../hv/hyperv_vmbus.h" + +#define DRIVER_VERSION "0.02.1" +#define DRIVER_AUTHOR "Stephen Hemminger <sthemmin at microsoft.com>" +#define DRIVER_DESC "Generic UIO driver for VMBus devices" + +#define HV_RING_SIZE 512 /* pages */ +#define SEND_BUFFER_SIZE (16 * 1024 * 1024) +#define RECV_BUFFER_SIZE (31 * 1024 * 1024) + +/* + * List of resources to be mapped to user space + * can be extended up to MAX_UIO_MAPS(5) items + */ +enum hv_uio_map { + TXRX_RING_MAP = 0, + INT_PAGE_MAP, + MON_PAGE_MAP, + RECV_BUF_MAP, + SEND_BUF_MAP +}; + +struct hv_uio_private_data { + struct uio_info info; + struct hv_device *device; + atomic_t refcnt; + + void *recv_buf; + struct vmbus_gpadl recv_gpadl; + char recv_name[32]; /* "recv_4294967295" */ + + void *send_buf; + struct vmbus_gpadl send_gpadl; + char send_name[32]; +}; + +/* + * This is the irqcontrol callback to be registered to uio_info. + * It can be used to disable/enable interrupt from user space processes. + * + * @param info + * pointer to uio_info. + * @param irq_state + * state value. 1 to enable interrupt, 0 to disable interrupt. + */ +static int +hv_uio_irqcontrol(struct uio_info *info, s32 irq_state) +{ + struct hv_uio_private_data *pdata = info->priv; + struct hv_device *dev = pdata->device; + + dev->channel->inbound.ring_buffer->interrupt_mask = !irq_state; + virt_mb(); + + return 0; +} + +/* + * Callback from vmbus_event when something is in inbound ring. + */ +static void hv_uio_channel_cb(void *context) +{ + struct vmbus_channel *chan = context; + struct hv_device *hv_dev = chan->device_obj; + struct hv_uio_private_data *pdata = hv_get_drvdata(hv_dev); + + chan->inbound.ring_buffer->interrupt_mask = 1; + virt_mb(); + + uio_event_notify(&pdata->info); +} + +/* + * Callback from vmbus_event when channel is rescinded. + */ +static void hv_uio_rescind(struct vmbus_channel *channel) +{ + struct hv_device *hv_dev = channel->primary_channel->device_obj; + struct hv_uio_private_data *pdata = hv_get_drvdata(hv_dev); + + /* + * Turn off the interrupt file handle + * Next read for event will return -EIO + */ + pdata->info.irq = 0; + + /* Wake up reader */ + uio_event_notify(&pdata->info); +} + +/* Sysfs API to allow mmap of the ring buffers + * The ring buffer is allocated as contiguous memory by vmbus_open + */ +static int hv_uio_ring_mmap(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + struct vmbus_channel *channel + = container_of(kobj, struct vmbus_channel, kobj); + void *ring_buffer = page_address(channel->ringbuffer_page); + + if (channel->state != CHANNEL_OPENED_STATE) + return -ENODEV; + + return vm_iomap_memory(vma, virt_to_phys(ring_buffer), + channel->ringbuffer_pagecount << PAGE_SHIFT); +} + +static const struct bin_attribute ring_buffer_bin_attr = { + .attr = { + .name = "ring", + .mode = 0600, + }, + .size = 2 * HV_RING_SIZE * PAGE_SIZE, + .mmap = hv_uio_ring_mmap, +}; + +/* Callback from VMBUS subsystem when new channel created. */ +static void +hv_uio_new_channel(struct vmbus_channel *new_sc) +{ + struct hv_device *hv_dev = new_sc->primary_channel->device_obj; + struct device *device = &hv_dev->device; + const size_t ring_bytes = HV_RING_SIZE * PAGE_SIZE; + int ret; + + /* Create host communication ring */ + ret = vmbus_open(new_sc, ring_bytes, ring_bytes, NULL, 0, + hv_uio_channel_cb, new_sc); + if (ret) { + dev_err(device, "vmbus_open subchannel failed: %d\n", ret); + return; + } + + /* Disable interrupts on sub channel */ + new_sc->inbound.ring_buffer->interrupt_mask = 1; + set_channel_read_mode(new_sc, HV_CALL_ISR); + + ret = sysfs_create_bin_file(&new_sc->kobj, &ring_buffer_bin_attr); + if (ret) { + dev_err(device, "sysfs create ring bin file failed; %d\n", ret); + vmbus_close(new_sc); + } +} + +/* free the reserved buffers for send and receive */ +static void +hv_uio_cleanup(struct hv_device *dev, struct hv_uio_private_data *pdata) +{ + if (pdata->send_gpadl.gpadl_handle) { + vmbus_teardown_gpadl(dev->channel, &pdata->send_gpadl); + vfree(pdata->send_buf); + } + + if (pdata->recv_gpadl.gpadl_handle) { + vmbus_teardown_gpadl(dev->channel, &pdata->recv_gpadl); + vfree(pdata->recv_buf); + } +} + +/* VMBus primary channel is opened on first use */ +static int +hv_uio_open(struct uio_info *info, struct inode *inode) +{ + struct hv_uio_private_data *pdata + = container_of(info, struct hv_uio_private_data, info); + struct hv_device *dev = pdata->device; + int ret; + + if (atomic_inc_return(&pdata->refcnt) != 1) + return 0; + + vmbus_set_chn_rescind_callback(dev->channel, hv_uio_rescind); + vmbus_set_sc_create_callback(dev->channel, hv_uio_new_channel); + + ret = vmbus_connect_ring(dev->channel, + hv_uio_channel_cb, dev->channel); + if (ret == 0) + dev->channel->inbound.ring_buffer->interrupt_mask = 1; + else + atomic_dec(&pdata->refcnt); + + return ret; +} + +/* VMBus primary channel is closed on last close */ +static int +hv_uio_release(struct uio_info *info, struct inode *inode) +{ + struct hv_uio_private_data *pdata + = container_of(info, struct hv_uio_private_data, info); + struct hv_device *dev = pdata->device; + int ret = 0; + + if (atomic_dec_and_test(&pdata->refcnt)) + ret = vmbus_disconnect_ring(dev->channel); + + return ret; +} + +static int +hv_uio_probe(struct hv_device *dev, + const struct hv_vmbus_device_id *dev_id) +{ + struct vmbus_channel *channel = dev->channel; + struct hv_uio_private_data *pdata; + void *ring_buffer; + int ret; + + /* Communicating with host has to be via shared memory not hypercall */ + if (!channel->offermsg.monitor_allocated) { + dev_err(&dev->device, "vmbus channel requires hypercall\n"); + return -ENOTSUPP; + } + + pdata = devm_kzalloc(&dev->device, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = vmbus_alloc_ring(channel, HV_RING_SIZE * PAGE_SIZE, + HV_RING_SIZE * PAGE_SIZE); + if (ret) + return ret; + + set_channel_read_mode(channel, HV_CALL_ISR); + + /* Fill general uio info */ + pdata->info.name = "uio_hv_generic"; + pdata->info.version = DRIVER_VERSION; + pdata->info.irqcontrol = hv_uio_irqcontrol; + pdata->info.open = hv_uio_open; + pdata->info.release = hv_uio_release; + pdata->info.irq = UIO_IRQ_CUSTOM; + atomic_set(&pdata->refcnt, 0); + + /* mem resources */ + pdata->info.mem[TXRX_RING_MAP].name = "txrx_rings"; + ring_buffer = page_address(channel->ringbuffer_page); + pdata->info.mem[TXRX_RING_MAP].addr + = (uintptr_t)virt_to_phys(ring_buffer); + pdata->info.mem[TXRX_RING_MAP].size + = channel->ringbuffer_pagecount << PAGE_SHIFT; + pdata->info.mem[TXRX_RING_MAP].memtype = UIO_MEM_IOVA; + + pdata->info.mem[INT_PAGE_MAP].name = "int_page"; + pdata->info.mem[INT_PAGE_MAP].addr + = (uintptr_t)vmbus_connection.int_page; + pdata->info.mem[INT_PAGE_MAP].size = PAGE_SIZE; + pdata->info.mem[INT_PAGE_MAP].memtype = UIO_MEM_LOGICAL; + + pdata->info.mem[MON_PAGE_MAP].name = "monitor_page"; + pdata->info.mem[MON_PAGE_MAP].addr + = (uintptr_t)vmbus_connection.monitor_pages[1]; + pdata->info.mem[MON_PAGE_MAP].size = PAGE_SIZE; + pdata->info.mem[MON_PAGE_MAP].memtype = UIO_MEM_LOGICAL; + + pdata->recv_buf = vzalloc(RECV_BUFFER_SIZE); + if (pdata->recv_buf == NULL) { + ret = -ENOMEM; + goto fail_free_ring; + } + + ret = vmbus_establish_gpadl(channel, pdata->recv_buf, + RECV_BUFFER_SIZE, &pdata->recv_gpadl); + if (ret) { + vfree(pdata->recv_buf); + goto fail_close; + } + + /* put Global Physical Address Label in name */ + snprintf(pdata->recv_name, sizeof(pdata->recv_name), + "recv:%u", pdata->recv_gpadl.gpadl_handle); + pdata->info.mem[RECV_BUF_MAP].name = pdata->recv_name; + pdata->info.mem[RECV_BUF_MAP].addr + = (uintptr_t)pdata->recv_buf; + pdata->info.mem[RECV_BUF_MAP].size = RECV_BUFFER_SIZE; + pdata->info.mem[RECV_BUF_MAP].memtype = UIO_MEM_VIRTUAL; + + pdata->send_buf = vzalloc(SEND_BUFFER_SIZE); + if (pdata->send_buf == NULL) { + ret = -ENOMEM; + goto fail_close; + } + + ret = vmbus_establish_gpadl(channel, pdata->send_buf, + SEND_BUFFER_SIZE, &pdata->send_gpadl); + if (ret) { + vfree(pdata->send_buf); + goto fail_close; + } + + snprintf(pdata->send_name, sizeof(pdata->send_name), + "send:%u", pdata->send_gpadl.gpadl_handle); + pdata->info.mem[SEND_BUF_MAP].name = pdata->send_name; + pdata->info.mem[SEND_BUF_MAP].addr + = (uintptr_t)pdata->send_buf; + pdata->info.mem[SEND_BUF_MAP].size = SEND_BUFFER_SIZE; + pdata->info.mem[SEND_BUF_MAP].memtype = UIO_MEM_VIRTUAL; + + pdata->info.priv = pdata; + pdata->device = dev; + + ret = uio_register_device(&dev->device, &pdata->info); + if (ret) { + dev_err(&dev->device, "hv_uio register failed\n"); + goto fail_close; + } + + ret = sysfs_create_bin_file(&channel->kobj, &ring_buffer_bin_attr); + if (ret) + dev_notice(&dev->device, + "sysfs create ring bin file failed; %d\n", ret); + + hv_set_drvdata(dev, pdata); + + return 0; + +fail_close: + hv_uio_cleanup(dev, pdata); +fail_free_ring: + vmbus_free_ring(dev->channel); + + return ret; +} + +static int +hv_uio_remove(struct hv_device *dev) +{ + struct hv_uio_private_data *pdata = hv_get_drvdata(dev); + + if (!pdata) + return 0; + + sysfs_remove_bin_file(&dev->channel->kobj, &ring_buffer_bin_attr); + uio_unregister_device(&pdata->info); + hv_uio_cleanup(dev, pdata); + + vmbus_free_ring(dev->channel); + return 0; +} + +static struct hv_driver hv_uio_drv = { + .name = "uio_hv_generic", + .id_table = NULL, /* only dynamic id's */ + .probe = hv_uio_probe, + .remove = hv_uio_remove, +}; + +static int __init +hyperv_module_init(void) +{ + return vmbus_driver_register(&hv_uio_drv); +} + +static void __exit +hyperv_module_exit(void) +{ + vmbus_driver_unregister(&hv_uio_drv); +} + +module_init(hyperv_module_init); +module_exit(hyperv_module_exit); + +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/uio/uio_mf624.c b/drivers/uio/uio_mf624.c new file mode 100644 index 000000000..5065c6a07 --- /dev/null +++ b/drivers/uio/uio_mf624.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * UIO driver fo Humusoft MF624 DAQ card. + * Copyright (C) 2011 Rostislav Lisovy <lisovy@gmail.com>, + * Czech Technical University in Prague + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/uio_driver.h> + +#define PCI_VENDOR_ID_HUMUSOFT 0x186c +#define PCI_DEVICE_ID_MF624 0x0624 +#define PCI_SUBVENDOR_ID_HUMUSOFT 0x186c +#define PCI_SUBDEVICE_DEVICE 0x0624 + +/* BAR0 Interrupt control/status register */ +#define INTCSR 0x4C +#define INTCSR_ADINT_ENABLE (1 << 0) +#define INTCSR_CTR4INT_ENABLE (1 << 3) +#define INTCSR_PCIINT_ENABLE (1 << 6) +#define INTCSR_ADINT_STATUS (1 << 2) +#define INTCSR_CTR4INT_STATUS (1 << 5) + +enum mf624_interrupt_source {ADC, CTR4, ALL}; + +static void mf624_disable_interrupt(enum mf624_interrupt_source source, + struct uio_info *info) +{ + void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR; + + switch (source) { + case ADC: + iowrite32(ioread32(INTCSR_reg) + & ~(INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE), + INTCSR_reg); + break; + + case CTR4: + iowrite32(ioread32(INTCSR_reg) + & ~(INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE), + INTCSR_reg); + break; + + case ALL: + default: + iowrite32(ioread32(INTCSR_reg) + & ~(INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE + | INTCSR_PCIINT_ENABLE), + INTCSR_reg); + break; + } +} + +static void mf624_enable_interrupt(enum mf624_interrupt_source source, + struct uio_info *info) +{ + void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR; + + switch (source) { + case ADC: + iowrite32(ioread32(INTCSR_reg) + | INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE, + INTCSR_reg); + break; + + case CTR4: + iowrite32(ioread32(INTCSR_reg) + | INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE, + INTCSR_reg); + break; + + case ALL: + default: + iowrite32(ioread32(INTCSR_reg) + | INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE + | INTCSR_PCIINT_ENABLE, + INTCSR_reg); + break; + } +} + +static irqreturn_t mf624_irq_handler(int irq, struct uio_info *info) +{ + void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR; + + if ((ioread32(INTCSR_reg) & INTCSR_ADINT_ENABLE) + && (ioread32(INTCSR_reg) & INTCSR_ADINT_STATUS)) { + mf624_disable_interrupt(ADC, info); + return IRQ_HANDLED; + } + + if ((ioread32(INTCSR_reg) & INTCSR_CTR4INT_ENABLE) + && (ioread32(INTCSR_reg) & INTCSR_CTR4INT_STATUS)) { + mf624_disable_interrupt(CTR4, info); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int mf624_irqcontrol(struct uio_info *info, s32 irq_on) +{ + if (irq_on == 0) + mf624_disable_interrupt(ALL, info); + else if (irq_on == 1) + mf624_enable_interrupt(ALL, info); + + return 0; +} + +static int mf624_setup_mem(struct pci_dev *dev, int bar, struct uio_mem *mem, const char *name) +{ + resource_size_t start = pci_resource_start(dev, bar); + resource_size_t len = pci_resource_len(dev, bar); + + mem->name = name; + mem->addr = start & PAGE_MASK; + mem->offs = start & ~PAGE_MASK; + if (!mem->addr) + return -ENODEV; + mem->size = ((start & ~PAGE_MASK) + len + PAGE_SIZE - 1) & PAGE_MASK; + mem->memtype = UIO_MEM_PHYS; + mem->internal_addr = pci_ioremap_bar(dev, bar); + if (!mem->internal_addr) + return -ENODEV; + return 0; +} + +static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct uio_info *info; + + info = devm_kzalloc(&dev->dev, sizeof(struct uio_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (pci_enable_device(dev)) + return -ENODEV; + + if (pci_request_regions(dev, "mf624")) + goto out_disable; + + info->name = "mf624"; + info->version = "0.0.1"; + + /* Note: Datasheet says device uses BAR0, BAR1, BAR2 -- do not trust it */ + + /* BAR0 */ + if (mf624_setup_mem(dev, 0, &info->mem[0], "PCI chipset, interrupts, status " + "bits, special functions")) + goto out_release; + /* BAR2 */ + if (mf624_setup_mem(dev, 2, &info->mem[1], "ADC, DAC, DIO")) + goto out_unmap0; + + /* BAR4 */ + if (mf624_setup_mem(dev, 4, &info->mem[2], "Counter/timer chip")) + goto out_unmap1; + + info->irq = dev->irq; + info->irq_flags = IRQF_SHARED; + info->handler = mf624_irq_handler; + + info->irqcontrol = mf624_irqcontrol; + + if (uio_register_device(&dev->dev, info)) + goto out_unmap2; + + pci_set_drvdata(dev, info); + + return 0; + +out_unmap2: + iounmap(info->mem[2].internal_addr); +out_unmap1: + iounmap(info->mem[1].internal_addr); +out_unmap0: + iounmap(info->mem[0].internal_addr); + +out_release: + pci_release_regions(dev); + +out_disable: + pci_disable_device(dev); + + return -ENODEV; +} + +static void mf624_pci_remove(struct pci_dev *dev) +{ + struct uio_info *info = pci_get_drvdata(dev); + + mf624_disable_interrupt(ALL, info); + + uio_unregister_device(info); + pci_release_regions(dev); + pci_disable_device(dev); + + iounmap(info->mem[0].internal_addr); + iounmap(info->mem[1].internal_addr); + iounmap(info->mem[2].internal_addr); +} + +static const struct pci_device_id mf624_pci_id[] = { + { PCI_DEVICE(PCI_VENDOR_ID_HUMUSOFT, PCI_DEVICE_ID_MF624) }, + { 0, } +}; + +static struct pci_driver mf624_pci_driver = { + .name = "mf624", + .id_table = mf624_pci_id, + .probe = mf624_pci_probe, + .remove = mf624_pci_remove, +}; +MODULE_DEVICE_TABLE(pci, mf624_pci_id); + +module_pci_driver(mf624_pci_driver); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Rostislav Lisovy <lisovy@gmail.com>"); diff --git a/drivers/uio/uio_netx.c b/drivers/uio/uio_netx.c new file mode 100644 index 000000000..2319d6de8 --- /dev/null +++ b/drivers/uio/uio_netx.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UIO driver for Hilscher NetX based fieldbus cards (cifX, comX). + * See http://www.hilscher.com for details. + * + * (C) 2007 Hans J. Koch <hjk@hansjkoch.de> + * (C) 2008 Manuel Traut <manut@linutronix.de> + * + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/uio_driver.h> + +#define PCI_VENDOR_ID_HILSCHER 0x15CF +#define PCI_DEVICE_ID_HILSCHER_NETX 0x0000 +#define PCI_DEVICE_ID_HILSCHER_NETPLC 0x0010 +#define PCI_SUBDEVICE_ID_NETPLC_RAM 0x0000 +#define PCI_SUBDEVICE_ID_NETPLC_FLASH 0x0001 +#define PCI_SUBDEVICE_ID_NXSB_PCA 0x3235 +#define PCI_SUBDEVICE_ID_NXPCA 0x3335 + +#define DPM_HOST_INT_EN0 0xfff0 +#define DPM_HOST_INT_STAT0 0xffe0 + +#define DPM_HOST_INT_MASK 0xe600ffff +#define DPM_HOST_INT_GLOBAL_EN 0x80000000 + +static irqreturn_t netx_handler(int irq, struct uio_info *dev_info) +{ + void __iomem *int_enable_reg = dev_info->mem[0].internal_addr + + DPM_HOST_INT_EN0; + void __iomem *int_status_reg = dev_info->mem[0].internal_addr + + DPM_HOST_INT_STAT0; + + /* Is one of our interrupts enabled and active ? */ + if (!(ioread32(int_enable_reg) & ioread32(int_status_reg) + & DPM_HOST_INT_MASK)) + return IRQ_NONE; + + /* Disable interrupt */ + iowrite32(ioread32(int_enable_reg) & ~DPM_HOST_INT_GLOBAL_EN, + int_enable_reg); + return IRQ_HANDLED; +} + +static int netx_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct uio_info *info; + int bar; + + info = devm_kzalloc(&dev->dev, sizeof(struct uio_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (pci_enable_device(dev)) + return -ENODEV; + + if (pci_request_regions(dev, "netx")) + goto out_disable; + + switch (id->device) { + case PCI_DEVICE_ID_HILSCHER_NETX: + bar = 0; + info->name = "netx"; + break; + case PCI_DEVICE_ID_HILSCHER_NETPLC: + bar = 0; + info->name = "netplc"; + break; + default: + bar = 2; + info->name = "netx_plx"; + } + + /* BAR0 or 2 points to the card's dual port memory */ + info->mem[0].addr = pci_resource_start(dev, bar); + if (!info->mem[0].addr) + goto out_release; + info->mem[0].internal_addr = ioremap(pci_resource_start(dev, bar), + pci_resource_len(dev, bar)); + + if (!info->mem[0].internal_addr) + goto out_release; + + info->mem[0].size = pci_resource_len(dev, bar); + info->mem[0].memtype = UIO_MEM_PHYS; + info->irq = dev->irq; + info->irq_flags = IRQF_SHARED; + info->handler = netx_handler; + info->version = "0.0.1"; + + /* Make sure all interrupts are disabled */ + iowrite32(0, info->mem[0].internal_addr + DPM_HOST_INT_EN0); + + if (uio_register_device(&dev->dev, info)) + goto out_unmap; + + pci_set_drvdata(dev, info); + dev_info(&dev->dev, "Found %s card, registered UIO device.\n", + info->name); + + return 0; + +out_unmap: + iounmap(info->mem[0].internal_addr); +out_release: + pci_release_regions(dev); +out_disable: + pci_disable_device(dev); + return -ENODEV; +} + +static void netx_pci_remove(struct pci_dev *dev) +{ + struct uio_info *info = pci_get_drvdata(dev); + + /* Disable all interrupts */ + iowrite32(0, info->mem[0].internal_addr + DPM_HOST_INT_EN0); + uio_unregister_device(info); + pci_release_regions(dev); + pci_disable_device(dev); + iounmap(info->mem[0].internal_addr); +} + +static struct pci_device_id netx_pci_ids[] = { + { + .vendor = PCI_VENDOR_ID_HILSCHER, + .device = PCI_DEVICE_ID_HILSCHER_NETX, + .subvendor = 0, + .subdevice = 0, + }, + { + .vendor = PCI_VENDOR_ID_HILSCHER, + .device = PCI_DEVICE_ID_HILSCHER_NETPLC, + .subvendor = PCI_VENDOR_ID_HILSCHER, + .subdevice = PCI_SUBDEVICE_ID_NETPLC_RAM, + }, + { + .vendor = PCI_VENDOR_ID_HILSCHER, + .device = PCI_DEVICE_ID_HILSCHER_NETPLC, + .subvendor = PCI_VENDOR_ID_HILSCHER, + .subdevice = PCI_SUBDEVICE_ID_NETPLC_FLASH, + }, + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = PCI_SUBDEVICE_ID_NXSB_PCA, + }, + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = PCI_SUBDEVICE_ID_NXPCA, + }, + { 0, } +}; + +static struct pci_driver netx_pci_driver = { + .name = "netx", + .id_table = netx_pci_ids, + .probe = netx_pci_probe, + .remove = netx_pci_remove, +}; + +module_pci_driver(netx_pci_driver); +MODULE_DEVICE_TABLE(pci, netx_pci_ids); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Hans J. Koch, Manuel Traut"); diff --git a/drivers/uio/uio_pci_generic.c b/drivers/uio/uio_pci_generic.c new file mode 100644 index 000000000..e03f9b532 --- /dev/null +++ b/drivers/uio/uio_pci_generic.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 +/* uio_pci_generic - generic UIO driver for PCI 2.3 devices + * + * Copyright (C) 2009 Red Hat, Inc. + * Author: Michael S. Tsirkin <mst@redhat.com> + * + * Since the driver does not declare any device ids, you must allocate + * id and bind the device to the driver yourself. For example: + * + * # echo "8086 10f5" > /sys/bus/pci/drivers/uio_pci_generic/new_id + * # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/e1000e/unbind + * # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/uio_pci_generic/bind + * # ls -l /sys/bus/pci/devices/0000:00:19.0/driver + * .../0000:00:19.0/driver -> ../../../bus/pci/drivers/uio_pci_generic + * + * Driver won't bind to devices which do not support the Interrupt Disable Bit + * in the command register. All devices compliant to PCI 2.3 (circa 2002) and + * all compliant PCI Express devices should support this bit. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/uio_driver.h> + +#define DRIVER_VERSION "0.01.0" +#define DRIVER_AUTHOR "Michael S. Tsirkin <mst@redhat.com>" +#define DRIVER_DESC "Generic UIO driver for PCI 2.3 devices" + +struct uio_pci_generic_dev { + struct uio_info info; + struct pci_dev *pdev; +}; + +static inline struct uio_pci_generic_dev * +to_uio_pci_generic_dev(struct uio_info *info) +{ + return container_of(info, struct uio_pci_generic_dev, info); +} + +static int release(struct uio_info *info, struct inode *inode) +{ + struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info); + + /* + * This driver is insecure when used with devices doing DMA, but some + * people (mis)use it with such devices. + * Let's at least make sure DMA isn't left enabled after the userspace + * driver closes the fd. + * Note that there's a non-zero chance doing this will wedge the device + * at least until reset. + */ + pci_clear_master(gdev->pdev); + return 0; +} + +/* Interrupt handler. Read/modify/write the command register to disable + * the interrupt. */ +static irqreturn_t irqhandler(int irq, struct uio_info *info) +{ + struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info); + + if (!pci_check_and_mask_intx(gdev->pdev)) + return IRQ_NONE; + + /* UIO core will signal the user process. */ + return IRQ_HANDLED; +} + +static int probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct uio_pci_generic_dev *gdev; + struct uio_mem *uiomem; + int err; + int i; + + err = pcim_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "%s: pci_enable_device failed: %d\n", + __func__, err); + return err; + } + + if (pdev->irq && !pci_intx_mask_supported(pdev)) + return -ENODEV; + + gdev = devm_kzalloc(&pdev->dev, sizeof(struct uio_pci_generic_dev), GFP_KERNEL); + if (!gdev) + return -ENOMEM; + + gdev->info.name = "uio_pci_generic"; + gdev->info.version = DRIVER_VERSION; + gdev->info.release = release; + gdev->pdev = pdev; + if (pdev->irq && (pdev->irq != IRQ_NOTCONNECTED)) { + gdev->info.irq = pdev->irq; + gdev->info.irq_flags = IRQF_SHARED; + gdev->info.handler = irqhandler; + } else { + dev_warn(&pdev->dev, "No IRQ assigned to device: " + "no support for interrupts?\n"); + } + + uiomem = &gdev->info.mem[0]; + for (i = 0; i < MAX_UIO_MAPS; ++i) { + struct resource *r = &pdev->resource[i]; + + if (r->flags != (IORESOURCE_SIZEALIGN | IORESOURCE_MEM)) + continue; + + if (uiomem >= &gdev->info.mem[MAX_UIO_MAPS]) { + dev_warn( + &pdev->dev, + "device has more than " __stringify( + MAX_UIO_MAPS) " I/O memory resources.\n"); + break; + } + + uiomem->memtype = UIO_MEM_PHYS; + uiomem->addr = r->start & PAGE_MASK; + uiomem->offs = r->start & ~PAGE_MASK; + uiomem->size = + (uiomem->offs + resource_size(r) + PAGE_SIZE - 1) & + PAGE_MASK; + uiomem->name = r->name; + ++uiomem; + } + + while (uiomem < &gdev->info.mem[MAX_UIO_MAPS]) { + uiomem->size = 0; + ++uiomem; + } + + return devm_uio_register_device(&pdev->dev, &gdev->info); +} + +static struct pci_driver uio_pci_driver = { + .name = "uio_pci_generic", + .id_table = NULL, /* only dynamic id's */ + .probe = probe, +}; + +module_pci_driver(uio_pci_driver); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c new file mode 100644 index 000000000..63258b6ac --- /dev/null +++ b/drivers/uio/uio_pdrv_genirq.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/uio/uio_pdrv_genirq.c + * + * Userspace I/O platform driver with generic IRQ handling code. + * + * Copyright (C) 2008 Magnus Damm + * + * Based on uio_pdrv.c by Uwe Kleine-Koenig, + * Copyright (C) 2008 by Digi International Inc. + * All rights reserved. + */ + +#include <linux/platform_device.h> +#include <linux/uio_driver.h> +#include <linux/spinlock.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/stringify.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/irq.h> + +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> + +#define DRIVER_NAME "uio_pdrv_genirq" + +struct uio_pdrv_genirq_platdata { + struct uio_info *uioinfo; + spinlock_t lock; + unsigned long flags; + struct platform_device *pdev; +}; + +/* Bits in uio_pdrv_genirq_platdata.flags */ +enum { + UIO_IRQ_DISABLED = 0, +}; + +static int uio_pdrv_genirq_open(struct uio_info *info, struct inode *inode) +{ + struct uio_pdrv_genirq_platdata *priv = info->priv; + + /* Wait until the Runtime PM code has woken up the device */ + pm_runtime_get_sync(&priv->pdev->dev); + return 0; +} + +static int uio_pdrv_genirq_release(struct uio_info *info, struct inode *inode) +{ + struct uio_pdrv_genirq_platdata *priv = info->priv; + + /* Tell the Runtime PM code that the device has become idle */ + pm_runtime_put_sync(&priv->pdev->dev); + return 0; +} + +static irqreturn_t uio_pdrv_genirq_handler(int irq, struct uio_info *dev_info) +{ + struct uio_pdrv_genirq_platdata *priv = dev_info->priv; + + /* Just disable the interrupt in the interrupt controller, and + * remember the state so we can allow user space to enable it later. + */ + + spin_lock(&priv->lock); + if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags)) + disable_irq_nosync(irq); + spin_unlock(&priv->lock); + + return IRQ_HANDLED; +} + +static int uio_pdrv_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on) +{ + struct uio_pdrv_genirq_platdata *priv = dev_info->priv; + unsigned long flags; + + /* Allow user space to enable and disable the interrupt + * in the interrupt controller, but keep track of the + * state to prevent per-irq depth damage. + * + * Serialize this operation to support multiple tasks and concurrency + * with irq handler on SMP systems. + */ + + spin_lock_irqsave(&priv->lock, flags); + if (irq_on) { + if (__test_and_clear_bit(UIO_IRQ_DISABLED, &priv->flags)) + enable_irq(dev_info->irq); + } else { + if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags)) + disable_irq_nosync(dev_info->irq); + } + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static void uio_pdrv_genirq_cleanup(void *data) +{ + struct device *dev = data; + + pm_runtime_disable(dev); +} + +static int uio_pdrv_genirq_probe(struct platform_device *pdev) +{ + struct uio_info *uioinfo = dev_get_platdata(&pdev->dev); + struct device_node *node = pdev->dev.of_node; + struct uio_pdrv_genirq_platdata *priv; + struct uio_mem *uiomem; + int ret = -EINVAL; + int i; + + if (node) { + const char *name; + + /* alloc uioinfo for one device */ + uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo), + GFP_KERNEL); + if (!uioinfo) { + dev_err(&pdev->dev, "unable to kmalloc\n"); + return -ENOMEM; + } + + if (!of_property_read_string(node, "linux,uio-name", &name)) + uioinfo->name = devm_kstrdup(&pdev->dev, name, GFP_KERNEL); + else + uioinfo->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%pOFn", node); + + uioinfo->version = "devicetree"; + /* Multiple IRQs are not supported */ + } + + if (!uioinfo || !uioinfo->name || !uioinfo->version) { + dev_err(&pdev->dev, "missing platform_data\n"); + return ret; + } + + if (uioinfo->handler || uioinfo->irqcontrol || + uioinfo->irq_flags & IRQF_SHARED) { + dev_err(&pdev->dev, "interrupt configuration error\n"); + return ret; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "unable to kmalloc\n"); + return -ENOMEM; + } + + priv->uioinfo = uioinfo; + spin_lock_init(&priv->lock); + priv->flags = 0; /* interrupt is enabled to begin with */ + priv->pdev = pdev; + + if (!uioinfo->irq) { + ret = platform_get_irq_optional(pdev, 0); + uioinfo->irq = ret; + if (ret == -ENXIO) + uioinfo->irq = UIO_IRQ_NONE; + else if (ret == -EPROBE_DEFER) + return ret; + else if (ret < 0) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + return ret; + } + } + + if (uioinfo->irq) { + struct irq_data *irq_data = irq_get_irq_data(uioinfo->irq); + + /* + * If a level interrupt, dont do lazy disable. Otherwise the + * irq will fire again since clearing of the actual cause, on + * device level, is done in userspace + * irqd_is_level_type() isn't used since isn't valid until + * irq is configured. + */ + if (irq_data && + irqd_get_trigger_type(irq_data) & IRQ_TYPE_LEVEL_MASK) { + dev_dbg(&pdev->dev, "disable lazy unmask\n"); + irq_set_status_flags(uioinfo->irq, IRQ_DISABLE_UNLAZY); + } + } + + uiomem = &uioinfo->mem[0]; + + for (i = 0; i < pdev->num_resources; ++i) { + struct resource *r = &pdev->resource[i]; + + if (r->flags != IORESOURCE_MEM) + continue; + + if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) { + dev_warn(&pdev->dev, "device has more than " + __stringify(MAX_UIO_MAPS) + " I/O memory resources.\n"); + break; + } + + uiomem->memtype = UIO_MEM_PHYS; + uiomem->addr = r->start & PAGE_MASK; + uiomem->offs = r->start & ~PAGE_MASK; + uiomem->size = (uiomem->offs + resource_size(r) + + PAGE_SIZE - 1) & PAGE_MASK; + uiomem->name = r->name; + ++uiomem; + } + + while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) { + uiomem->size = 0; + ++uiomem; + } + + /* This driver requires no hardware specific kernel code to handle + * interrupts. Instead, the interrupt handler simply disables the + * interrupt in the interrupt controller. User space is responsible + * for performing hardware specific acknowledge and re-enabling of + * the interrupt in the interrupt controller. + * + * Interrupt sharing is not supported. + */ + + uioinfo->handler = uio_pdrv_genirq_handler; + uioinfo->irqcontrol = uio_pdrv_genirq_irqcontrol; + uioinfo->open = uio_pdrv_genirq_open; + uioinfo->release = uio_pdrv_genirq_release; + uioinfo->priv = priv; + + /* Enable Runtime PM for this device: + * The device starts in suspended state to allow the hardware to be + * turned off by default. The Runtime PM bus code should power on the + * hardware and enable clocks at open(). + */ + pm_runtime_enable(&pdev->dev); + + ret = devm_add_action_or_reset(&pdev->dev, uio_pdrv_genirq_cleanup, + &pdev->dev); + if (ret) + return ret; + + ret = devm_uio_register_device(&pdev->dev, priv->uioinfo); + if (ret) + dev_err(&pdev->dev, "unable to register uio device\n"); + + return ret; +} + +static int uio_pdrv_genirq_runtime_nop(struct device *dev) +{ + /* Runtime PM callback shared between ->runtime_suspend() + * and ->runtime_resume(). Simply returns success. + * + * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() + * are used at open() and release() time. This allows the + * Runtime PM code to turn off power to the device while the + * device is unused, ie before open() and after release(). + * + * This Runtime PM callback does not need to save or restore + * any registers since user space is responsbile for hardware + * register reinitialization after open(). + */ + return 0; +} + +static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = { + .runtime_suspend = uio_pdrv_genirq_runtime_nop, + .runtime_resume = uio_pdrv_genirq_runtime_nop, +}; + +#ifdef CONFIG_OF +static struct of_device_id uio_of_genirq_match[] = { + { /* This is filled with module_parm */ }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, uio_of_genirq_match); +module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0); +MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio"); +#endif + +static struct platform_driver uio_pdrv_genirq = { + .probe = uio_pdrv_genirq_probe, + .driver = { + .name = DRIVER_NAME, + .pm = &uio_pdrv_genirq_dev_pm_ops, + .of_match_table = of_match_ptr(uio_of_genirq_match), + }, +}; + +module_platform_driver(uio_pdrv_genirq); + +MODULE_AUTHOR("Magnus Damm"); +MODULE_DESCRIPTION("Userspace I/O platform driver with generic IRQ handling"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/uio/uio_pruss.c b/drivers/uio/uio_pruss.c new file mode 100644 index 000000000..83966dbd3 --- /dev/null +++ b/drivers/uio/uio_pruss.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Programmable Real-Time Unit Sub System (PRUSS) UIO driver (uio_pruss) + * + * This driver exports PRUSS host event out interrupts and PRUSS, L3 RAM, + * and DDR RAM to user space for applications interacting with PRUSS firmware + * + * Copyright (C) 2010-11 Texas Instruments Incorporated - http://www.ti.com/ + */ +#include <linux/device.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/uio_driver.h> +#include <linux/platform_data/uio_pruss.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/genalloc.h> + +#define DRV_NAME "pruss_uio" +#define DRV_VERSION "1.0" + +static int sram_pool_sz = SZ_16K; +module_param(sram_pool_sz, int, 0); +MODULE_PARM_DESC(sram_pool_sz, "sram pool size to allocate "); + +static int extram_pool_sz = SZ_256K; +module_param(extram_pool_sz, int, 0); +MODULE_PARM_DESC(extram_pool_sz, "external ram pool size to allocate"); + +/* + * Host event IRQ numbers from PRUSS - PRUSS can generate up to 8 interrupt + * events to AINTC of ARM host processor - which can be used for IPC b/w PRUSS + * firmware and user space application, async notification from PRU firmware + * to user space application + * 3 PRU_EVTOUT0 + * 4 PRU_EVTOUT1 + * 5 PRU_EVTOUT2 + * 6 PRU_EVTOUT3 + * 7 PRU_EVTOUT4 + * 8 PRU_EVTOUT5 + * 9 PRU_EVTOUT6 + * 10 PRU_EVTOUT7 +*/ +#define MAX_PRUSS_EVT 8 + +#define PINTC_HIDISR 0x0038 +#define PINTC_HIPIR 0x0900 +#define HIPIR_NOPEND 0x80000000 +#define PINTC_HIER 0x1500 + +struct uio_pruss_dev { + struct uio_info *info; + struct clk *pruss_clk; + dma_addr_t sram_paddr; + dma_addr_t ddr_paddr; + void __iomem *prussio_vaddr; + unsigned long sram_vaddr; + void *ddr_vaddr; + unsigned int hostirq_start; + unsigned int pintc_base; + struct gen_pool *sram_pool; +}; + +static irqreturn_t pruss_handler(int irq, struct uio_info *info) +{ + struct uio_pruss_dev *gdev = info->priv; + int intr_bit = (irq - gdev->hostirq_start + 2); + int val, intr_mask = (1 << intr_bit); + void __iomem *base = gdev->prussio_vaddr + gdev->pintc_base; + void __iomem *intren_reg = base + PINTC_HIER; + void __iomem *intrdis_reg = base + PINTC_HIDISR; + void __iomem *intrstat_reg = base + PINTC_HIPIR + (intr_bit << 2); + + val = ioread32(intren_reg); + /* Is interrupt enabled and active ? */ + if (!(val & intr_mask) && (ioread32(intrstat_reg) & HIPIR_NOPEND)) + return IRQ_NONE; + /* Disable interrupt */ + iowrite32(intr_bit, intrdis_reg); + return IRQ_HANDLED; +} + +static void pruss_cleanup(struct device *dev, struct uio_pruss_dev *gdev) +{ + int cnt; + struct uio_info *p = gdev->info; + + for (cnt = 0; cnt < MAX_PRUSS_EVT; cnt++, p++) { + uio_unregister_device(p); + } + iounmap(gdev->prussio_vaddr); + if (gdev->ddr_vaddr) { + dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr, + gdev->ddr_paddr); + } + if (gdev->sram_vaddr) + gen_pool_free(gdev->sram_pool, + gdev->sram_vaddr, + sram_pool_sz); + clk_disable(gdev->pruss_clk); +} + +static int pruss_probe(struct platform_device *pdev) +{ + struct uio_info *p; + struct uio_pruss_dev *gdev; + struct resource *regs_prussio; + struct device *dev = &pdev->dev; + int ret, cnt, i, len; + struct uio_pruss_pdata *pdata = dev_get_platdata(dev); + + gdev = devm_kzalloc(dev, sizeof(struct uio_pruss_dev), GFP_KERNEL); + if (!gdev) + return -ENOMEM; + + gdev->info = devm_kcalloc(dev, MAX_PRUSS_EVT, sizeof(*p), GFP_KERNEL); + if (!gdev->info) + return -ENOMEM; + + /* Power on PRU in case its not done as part of boot-loader */ + gdev->pruss_clk = devm_clk_get(dev, "pruss"); + if (IS_ERR(gdev->pruss_clk)) { + dev_err(dev, "Failed to get clock\n"); + return PTR_ERR(gdev->pruss_clk); + } + + ret = clk_enable(gdev->pruss_clk); + if (ret) { + dev_err(dev, "Failed to enable clock\n"); + return ret; + } + + regs_prussio = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs_prussio) { + dev_err(dev, "No PRUSS I/O resource specified\n"); + ret = -EIO; + goto err_clk_disable; + } + + if (!regs_prussio->start) { + dev_err(dev, "Invalid memory resource\n"); + ret = -EIO; + goto err_clk_disable; + } + + if (pdata->sram_pool) { + gdev->sram_pool = pdata->sram_pool; + gdev->sram_vaddr = + (unsigned long)gen_pool_dma_alloc(gdev->sram_pool, + sram_pool_sz, &gdev->sram_paddr); + if (!gdev->sram_vaddr) { + dev_err(dev, "Could not allocate SRAM pool\n"); + ret = -ENOMEM; + goto err_clk_disable; + } + } + + gdev->ddr_vaddr = dma_alloc_coherent(dev, extram_pool_sz, + &(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA); + if (!gdev->ddr_vaddr) { + dev_err(dev, "Could not allocate external memory\n"); + ret = -ENOMEM; + goto err_free_sram; + } + + len = resource_size(regs_prussio); + gdev->prussio_vaddr = ioremap(regs_prussio->start, len); + if (!gdev->prussio_vaddr) { + dev_err(dev, "Can't remap PRUSS I/O address range\n"); + ret = -ENOMEM; + goto err_free_ddr_vaddr; + } + + gdev->pintc_base = pdata->pintc_base; + gdev->hostirq_start = platform_get_irq(pdev, 0); + + for (cnt = 0, p = gdev->info; cnt < MAX_PRUSS_EVT; cnt++, p++) { + p->mem[0].addr = regs_prussio->start; + p->mem[0].size = resource_size(regs_prussio); + p->mem[0].memtype = UIO_MEM_PHYS; + + p->mem[1].addr = gdev->sram_paddr; + p->mem[1].size = sram_pool_sz; + p->mem[1].memtype = UIO_MEM_PHYS; + + p->mem[2].addr = gdev->ddr_paddr; + p->mem[2].size = extram_pool_sz; + p->mem[2].memtype = UIO_MEM_PHYS; + + p->name = devm_kasprintf(dev, GFP_KERNEL, "pruss_evt%d", cnt); + p->version = DRV_VERSION; + + /* Register PRUSS IRQ lines */ + p->irq = gdev->hostirq_start + cnt; + p->handler = pruss_handler; + p->priv = gdev; + + ret = uio_register_device(dev, p); + if (ret < 0) + goto err_unloop; + } + + platform_set_drvdata(pdev, gdev); + return 0; + +err_unloop: + for (i = 0, p = gdev->info; i < cnt; i++, p++) { + uio_unregister_device(p); + } + iounmap(gdev->prussio_vaddr); +err_free_ddr_vaddr: + dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr, + gdev->ddr_paddr); +err_free_sram: + if (pdata->sram_pool) + gen_pool_free(gdev->sram_pool, gdev->sram_vaddr, sram_pool_sz); +err_clk_disable: + clk_disable(gdev->pruss_clk); + + return ret; +} + +static int pruss_remove(struct platform_device *dev) +{ + struct uio_pruss_dev *gdev = platform_get_drvdata(dev); + + pruss_cleanup(&dev->dev, gdev); + return 0; +} + +static struct platform_driver pruss_driver = { + .probe = pruss_probe, + .remove = pruss_remove, + .driver = { + .name = DRV_NAME, + }, +}; + +module_platform_driver(pruss_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Amit Chatterjee <amit.chatterjee@ti.com>"); +MODULE_AUTHOR("Pratheesh Gangadhar <pratheesh@ti.com>"); diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c new file mode 100644 index 000000000..b93a5f8f4 --- /dev/null +++ b/drivers/uio/uio_sercos3.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0 +/* sercos3: UIO driver for the Automata Sercos III PCI card + + Copyright (C) 2008 Linutronix GmbH + Author: John Ogness <john.ogness@linutronix.de> + + This is a straight-forward UIO driver, where interrupts are disabled + by the interrupt handler and re-enabled via a write to the UIO device + by the userspace-part. + + The only part that may seem odd is the use of a logical OR when + storing and restoring enabled interrupts. This is done because the + userspace-part could directly modify the Interrupt Enable Register + at any time. To reduce possible conflicts, the kernel driver uses + a logical OR to make more controlled changes (rather than blindly + overwriting previous values). + + Race conditions exist if the userspace-part directly modifies the + Interrupt Enable Register while in operation. The consequences are + that certain interrupts would fail to be enabled or disabled. For + this reason, the userspace-part should only directly modify the + Interrupt Enable Register at the beginning (to get things going). + The userspace-part can safely disable interrupts at any time using + a write to the UIO device. +*/ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/uio_driver.h> +#include <linux/io.h> +#include <linux/slab.h> + +/* ID's for SERCOS III PCI card (PLX 9030) */ +#define SERCOS_SUB_VENDOR_ID 0x1971 +#define SERCOS_SUB_SYSID_3530 0x3530 +#define SERCOS_SUB_SYSID_3535 0x3535 +#define SERCOS_SUB_SYSID_3780 0x3780 + +/* Interrupt Enable Register */ +#define IER0_OFFSET 0x08 + +/* Interrupt Status Register */ +#define ISR0_OFFSET 0x18 + +struct sercos3_priv { + u32 ier0_cache; + spinlock_t ier0_cache_lock; +}; + +/* this function assumes ier0_cache_lock is locked! */ +static void sercos3_disable_interrupts(struct uio_info *info, + struct sercos3_priv *priv) +{ + void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET; + + /* add enabled interrupts to cache */ + priv->ier0_cache |= ioread32(ier0); + + /* disable interrupts */ + iowrite32(0, ier0); +} + +/* this function assumes ier0_cache_lock is locked! */ +static void sercos3_enable_interrupts(struct uio_info *info, + struct sercos3_priv *priv) +{ + void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET; + + /* restore previously enabled interrupts */ + iowrite32(ioread32(ier0) | priv->ier0_cache, ier0); + priv->ier0_cache = 0; +} + +static irqreturn_t sercos3_handler(int irq, struct uio_info *info) +{ + struct sercos3_priv *priv = info->priv; + void __iomem *isr0 = info->mem[3].internal_addr + ISR0_OFFSET; + void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET; + + if (!(ioread32(isr0) & ioread32(ier0))) + return IRQ_NONE; + + spin_lock(&priv->ier0_cache_lock); + sercos3_disable_interrupts(info, priv); + spin_unlock(&priv->ier0_cache_lock); + + return IRQ_HANDLED; +} + +static int sercos3_irqcontrol(struct uio_info *info, s32 irq_on) +{ + struct sercos3_priv *priv = info->priv; + + spin_lock_irq(&priv->ier0_cache_lock); + if (irq_on) + sercos3_enable_interrupts(info, priv); + else + sercos3_disable_interrupts(info, priv); + spin_unlock_irq(&priv->ier0_cache_lock); + + return 0; +} + +static int sercos3_setup_iomem(struct pci_dev *dev, struct uio_info *info, + int n, int pci_bar) +{ + info->mem[n].addr = pci_resource_start(dev, pci_bar); + if (!info->mem[n].addr) + return -1; + info->mem[n].internal_addr = ioremap(pci_resource_start(dev, pci_bar), + pci_resource_len(dev, pci_bar)); + if (!info->mem[n].internal_addr) + return -1; + info->mem[n].size = pci_resource_len(dev, pci_bar); + info->mem[n].memtype = UIO_MEM_PHYS; + return 0; +} + +static int sercos3_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct uio_info *info; + struct sercos3_priv *priv; + int i; + + info = devm_kzalloc(&dev->dev, sizeof(struct uio_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + priv = devm_kzalloc(&dev->dev, sizeof(struct sercos3_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (pci_enable_device(dev)) + return -ENODEV; + + if (pci_request_regions(dev, "sercos3")) + goto out_disable; + + /* we only need PCI BAR's 0, 2, 3, 4, 5 */ + if (sercos3_setup_iomem(dev, info, 0, 0)) + goto out_unmap; + if (sercos3_setup_iomem(dev, info, 1, 2)) + goto out_unmap; + if (sercos3_setup_iomem(dev, info, 2, 3)) + goto out_unmap; + if (sercos3_setup_iomem(dev, info, 3, 4)) + goto out_unmap; + if (sercos3_setup_iomem(dev, info, 4, 5)) + goto out_unmap; + + spin_lock_init(&priv->ier0_cache_lock); + info->priv = priv; + info->name = "Sercos_III_PCI"; + info->version = "0.0.1"; + info->irq = dev->irq; + info->irq_flags = IRQF_SHARED; + info->handler = sercos3_handler; + info->irqcontrol = sercos3_irqcontrol; + + pci_set_drvdata(dev, info); + + if (uio_register_device(&dev->dev, info)) + goto out_unmap; + + return 0; + +out_unmap: + for (i = 0; i < 5; i++) { + if (info->mem[i].internal_addr) + iounmap(info->mem[i].internal_addr); + } + pci_release_regions(dev); +out_disable: + pci_disable_device(dev); + return -ENODEV; +} + +static void sercos3_pci_remove(struct pci_dev *dev) +{ + struct uio_info *info = pci_get_drvdata(dev); + int i; + + uio_unregister_device(info); + pci_release_regions(dev); + pci_disable_device(dev); + for (i = 0; i < 5; i++) { + if (info->mem[i].internal_addr) + iounmap(info->mem[i].internal_addr); + } +} + +static struct pci_device_id sercos3_pci_ids[] = { + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = SERCOS_SUB_VENDOR_ID, + .subdevice = SERCOS_SUB_SYSID_3530, + }, + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = SERCOS_SUB_VENDOR_ID, + .subdevice = SERCOS_SUB_SYSID_3535, + }, + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = SERCOS_SUB_VENDOR_ID, + .subdevice = SERCOS_SUB_SYSID_3780, + }, + { 0, } +}; + +static struct pci_driver sercos3_pci_driver = { + .name = "sercos3", + .id_table = sercos3_pci_ids, + .probe = sercos3_pci_probe, + .remove = sercos3_pci_remove, +}; + +module_pci_driver(sercos3_pci_driver); +MODULE_DESCRIPTION("UIO driver for the Automata Sercos III PCI card"); +MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>"); +MODULE_LICENSE("GPL v2"); |