diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /drivers/uio | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/uio')
| -rw-r--r-- | drivers/uio/Kconfig | 179 | ||||
| -rw-r--r-- | drivers/uio/Makefile | 14 | ||||
| -rw-r--r-- | drivers/uio/uio.c | 1160 | ||||
| -rw-r--r-- | drivers/uio/uio_aec.c | 148 | ||||
| -rw-r--r-- | drivers/uio/uio_cif.c | 135 | ||||
| -rw-r--r-- | drivers/uio/uio_dfl.c | 72 | ||||
| -rw-r--r-- | drivers/uio/uio_dmem_genirq.c | 320 | ||||
| -rw-r--r-- | drivers/uio/uio_fsl_elbc_gpcm.c | 457 | ||||
| -rw-r--r-- | drivers/uio/uio_hv_generic.c | 430 | ||||
| -rw-r--r-- | drivers/uio/uio_mf624.c | 226 | ||||
| -rw-r--r-- | drivers/uio/uio_netx.c | 175 | ||||
| -rw-r--r-- | drivers/uio/uio_pci_generic.c | 149 | ||||
| -rw-r--r-- | drivers/uio/uio_pci_generic_sva.c | 187 | ||||
| -rw-r--r-- | drivers/uio/uio_pdrv_genirq.c | 273 | ||||
| -rw-r--r-- | drivers/uio/uio_sercos3.c | 227 |
15 files changed, 4152 insertions, 0 deletions
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig new file mode 100644 index 000000000..9242e7738 --- /dev/null +++ b/drivers/uio/Kconfig @@ -0,0 +1,179 @@ +# 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_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_VMBUS + 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. + +config UIO_PCI_GENERIC_SVA + tristate "Generic driver for PCI Express that supports sva" + depends on PCI && IOMMU_SVA + help + Userspace I/O driver for PCI devices that support Shared Virtual + Addressing (SVA), enabling direct use of user-space virtual + addresses in device DMA operations via IOMMU hardware. + + This driver binds to PCI devices and exposes them to userspace + via the UIO framework. + +endif diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile new file mode 100644 index 000000000..5352e21e9 --- /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_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 +obj-$(CONFIG_UIO_PCI_GENERIC_SVA) += uio_pci_generic_sva.o diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c new file mode 100644 index 000000000..f8fa20522 --- /dev/null +++ b/drivers/uio/uio.c @@ -0,0 +1,1160 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/uio/uio.c + * + * Copyright(C) 2005, Benedikt Spranger <b.spranger@linutronix.de> + * Copyright(C) 2005, Linutronix GmbH, Thomas Gleixner <tglx@kernel.org> + * 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> +#include <linux/dma-mapping.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 const 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) +{ + static const char * const 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 const 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_obj(*map); + 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_obj(*portio); + 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_handler - 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_handler(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) + ret = IRQ_WAKE_THREAD; + + return ret; +} + +/** + * uio_interrupt_thread - irq thread handler + * @irq: IRQ number + * @dev_id: Pointer to the devices uio_device structure + */ +static irqreturn_t uio_interrupt_thread(int irq, void *dev_id) +{ + struct uio_device *idev = (struct uio_device *)dev_id; + + uio_event_notify(idev->info); + + return IRQ_HANDLED; +} + +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_obj(*listener); + 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 = EPOLLERR; + 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) +{ + vm_flags_set(vma, 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_dma_coherent(struct vm_area_struct *vma) +{ + struct uio_device *idev = vma->vm_private_data; + struct uio_mem *mem; + void *addr; + int ret = 0; + int mi; + + mi = uio_find_mem_index(vma); + if (mi < 0) + return -EINVAL; + + mem = idev->info->mem + mi; + + if (mem->addr & ~PAGE_MASK) + return -ENODEV; + if (mem->dma_addr & ~PAGE_MASK) + return -ENODEV; + if (!mem->dma_device) + return -ENODEV; + if (vma->vm_end - vma->vm_start > mem->size) + return -EINVAL; + + dev_warn(mem->dma_device, + "use of UIO_MEM_DMA_COHERENT is highly discouraged"); + + /* + * UIO uses offset to index into the maps for a device. + * We need to clear vm_pgoff for dma_mmap_coherent. + */ + vma->vm_pgoff = 0; + + addr = (void *)(uintptr_t)mem->addr; + ret = dma_mmap_coherent(mem->dma_device, + vma, + addr, + mem->dma_addr, + vma->vm_end - vma->vm_start); + vma->vm_pgoff = mi; + + return ret; +} + +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_prepare) { + struct vm_area_desc desc; + + compat_set_desc_from_vma(&desc, filep, vma); + ret = idev->info->mmap_prepare(idev->info, &desc); + if (ret) + goto out; + ret = __compat_vma_mmap(&desc, 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; + case UIO_MEM_DMA_COHERENT: + ret = uio_mmap_dma_coherent(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_obj(*idev); + 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_threaded_irq(info->irq, uio_interrupt_handler, uio_interrupt_thread, + 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: + mutex_lock(&idev->info_lock); + idev->info = NULL; + mutex_unlock(&idev->info_lock); + wake_up_interruptible(&idev->wait); + kill_fasync(&idev->async_queue, SIGIO, POLL_HUP); + 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_DESCRIPTION("Userspace IO core module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/uio/uio_aec.c b/drivers/uio/uio_aec.c new file mode 100644 index 000000000..dafcc5f44 --- /dev/null +++ b/drivers/uio/uio_aec.c @@ -0,0 +1,148 @@ +// 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 const 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_DESCRIPTION("Adrienne Electronics Corp time code PCI device"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uio/uio_cif.c b/drivers/uio/uio_cif.c new file mode 100644 index 000000000..4e4b589dd --- /dev/null +++ b/drivers/uio/uio_cif.c @@ -0,0 +1,135 @@ +// 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 const 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_DESCRIPTION("UIO Hilscher CIF card driver"); +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..6d99e5a06 --- /dev/null +++ b/drivers/uio/uio_dfl.c @@ -0,0 +1,72 @@ +// 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 FME_FEATURE_ID_VENDOR_SPECIFIC 0x23 +#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 }, + { FME_ID, FME_FEATURE_ID_VENDOR_SPECIFIC }, + { 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..41c18ec62 --- /dev/null +++ b/drivers/uio/uio_dmem_genirq.c @@ -0,0 +1,320 @@ +// 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; + struct mutex alloc_lock; + unsigned int refcnt; +}; + +/* Bits in uio_dmem_genirq_platdata.flags */ +enum { + UIO_IRQ_DISABLED = 0, +}; + +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; + + 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, + &uiomem->dma_addr, GFP_KERNEL); + uiomem->addr = addr ? (uintptr_t) addr : DMEM_MAP_ERROR; + ++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; + + /* 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 (uiomem->addr) { + dma_free_coherent(uiomem->dma_device, uiomem->size, + (void *) (uintptr_t) uiomem->addr, + uiomem->dma_addr); + } + uiomem->addr = DMEM_MAP_ERROR; + ++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(UIO_IRQ_DISABLED, &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(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_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); + if (!uioinfo->name) + return -ENOMEM; + 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) { + /* + * 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_get_trigger_type(uioinfo->irq) & 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_DMA_COHERENT; + uiomem->dma_device = &pdev->dev; + 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); +} + +#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, + .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..08cb150fb --- /dev/null +++ b/drivers/uio/uio_fsl_elbc_gpcm.c @@ -0,0 +1,457 @@ +// 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.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, char **name) +{ + const char *dt_name; + const char *type; + int 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); + + /* 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; + void __iomem *base; + 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; + + /* map the memory resource */ + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + /* get optional irq value */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + /* 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, + &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); + + /* set all UIO data */ + info->mem[0].name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%pOFn", node); + if (!info->mem[0].name) + return -ENODEV; + + info->mem[0].internal_addr = base; + 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) { + if (priv->irq_handler) { + info->irq = irq; + info->irq_flags = IRQF_SHARED; + info->handler = priv->irq_handler; + } else { + irq = 0; + 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 ? : -1); + + return 0; +out_err2: + if (priv->shutdown) + priv->shutdown(info, true); + return ret; +} + +static void 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); + +} + +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..7b4cc456c --- /dev/null +++ b/drivers/uio/uio_hv_generic.c @@ -0,0 +1,430 @@ +// 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 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]; +}; + +static void set_event(struct vmbus_channel *channel, s32 irq_state) +{ + channel->inbound.ring_buffer->interrupt_mask = !irq_state; + if (!channel->offermsg.monitor_allocated && irq_state) { + /* MB is needed for host to see the interrupt mask first */ + virt_mb(); + vmbus_set_event(channel); + } +} + +/* + * 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; + struct vmbus_channel *primary, *sc; + + primary = dev->channel; + set_event(primary, irq_state); + + mutex_lock(&vmbus_connection.channel_mutex); + list_for_each_entry(sc, &primary->sc_list, sc_list) + set_event(sc, irq_state); + mutex_unlock(&vmbus_connection.channel_mutex); + + 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; + struct hv_uio_private_data *pdata; + + virt_mb(); + + /* + * The callback may come from a subchannel, in which case look + * for the hv device in the primary channel + */ + hv_dev = chan->primary_channel ? + chan->primary_channel->device_obj : chan->device_obj; + pdata = hv_get_drvdata(hv_dev); + uio_event_notify(&pdata->info); +} + +/* + * Callback from vmbus_event when channel is rescinded. + * It is meant for rescind of primary channels only. + */ +static void hv_uio_rescind(struct vmbus_channel *channel) +{ + struct hv_device *hv_dev = 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); + + /* + * With rescind callback registered, rescind path will not unregister the device + * from vmbus when the primary channel is rescinded. + * Without it, rescind handling is incomplete and next onoffer msg does not come. + * Unregister the device from vmbus here. + */ + vmbus_device_unregister(channel->device_obj); +} + +/* Function used for mmap of ring buffer sysfs interface. + * The ring buffer is allocated as contiguous memory by vmbus_open + */ +static int +hv_uio_ring_mmap_prepare(struct vmbus_channel *channel, struct vm_area_desc *desc) +{ + void *ring_buffer = page_address(channel->ringbuffer_page); + + if (channel->state != CHANNEL_OPENED_STATE) + return -ENODEV; + + mmap_action_simple_ioremap(desc, virt_to_phys(ring_buffer), + channel->ringbuffer_pagecount << PAGE_SHIFT); + return 0; +} + +/* 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 = SZ_2M; + 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; + } + + set_channel_read_mode(new_sc, HV_CALL_ISR); + ret = hv_create_ring_sysfs(new_sc, hv_uio_ring_mmap_prepare); + 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); + if (!pdata->send_gpadl.decrypted) + vfree(pdata->send_buf); + } + + if (pdata->recv_gpadl.gpadl_handle) { + vmbus_teardown_gpadl(dev->channel, &pdata->recv_gpadl); + if (!pdata->recv_gpadl.decrypted) + 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) + 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; + size_t ring_size = hv_dev_ring_size(channel); + + if (!ring_size) + ring_size = SZ_2M; + + /* Adjust ring size if necessary to have it page aligned */ + ring_size = VMBUS_RING_SIZE(ring_size); + + pdata = devm_kzalloc(&dev->device, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = vmbus_alloc_ring(channel, ring_size, ring_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 = HV_HYP_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 = HV_HYP_PAGE_SIZE; + pdata->info.mem[MON_PAGE_MAP].memtype = UIO_MEM_LOGICAL; + + if (channel->device_id == HV_NIC) { + pdata->recv_buf = vzalloc(RECV_BUFFER_SIZE); + if (!pdata->recv_buf) { + ret = -ENOMEM; + goto fail_free_ring; + } + + ret = vmbus_establish_gpadl(channel, pdata->recv_buf, + RECV_BUFFER_SIZE, &pdata->recv_gpadl); + if (ret) { + if (!pdata->recv_gpadl.decrypted) + 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) { + ret = -ENOMEM; + goto fail_close; + } + + ret = vmbus_establish_gpadl(channel, pdata->send_buf, + SEND_BUFFER_SIZE, &pdata->send_gpadl); + if (ret) { + if (!pdata->send_gpadl.decrypted) + 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; + } + + /* + * This internally calls sysfs_update_group, which returns a non-zero value if it executes + * before sysfs_create_group. This is expected as the 'ring' will be created later in + * vmbus_device_register() -> vmbus_add_channel_kobj(). Thus, no need to check the return + * value and print warning. + * + * Creating/exposing sysfs in driver probe is not encouraged as it can lead to race + * conditions with userspace. For backward compatibility, "ring" sysfs could not be removed + * or decoupled from uio_hv_generic probe. Userspace programs can make use of inotify + * APIs to make sure that ring is created. + */ + hv_create_ring_sysfs(channel, hv_uio_ring_mmap_prepare); + + 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 void +hv_uio_remove(struct hv_device *dev) +{ + struct hv_uio_private_data *pdata = hv_get_drvdata(dev); + + if (!pdata) + return; + + hv_remove_ring_sysfs(dev->channel); + uio_unregister_device(&pdata->info); + hv_uio_cleanup(dev, pdata); + + vmbus_free_ring(dev->channel); +} + +static const struct hv_vmbus_device_id hv_uio_id_table[] = { + { HV_FCOPY_GUID }, + {} +}; +MODULE_DEVICE_TABLE(vmbus, hv_uio_id_table); + +static struct hv_driver hv_uio_drv = { + .name = "uio_hv_generic", + .id_table = hv_uio_id_table, + .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..790412f8d --- /dev/null +++ b/drivers/uio/uio_mf624.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * UIO driver for 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_DESCRIPTION("UIO driver for Humusoft MF624 DAQ card"); +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..18917b2ac --- /dev/null +++ b/drivers/uio/uio_netx.c @@ -0,0 +1,175 @@ +// 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 const 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_DESCRIPTION("UIO driver for Hilscher NetX based fieldbus cards"); +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_pci_generic_sva.c b/drivers/uio/uio_pci_generic_sva.c new file mode 100644 index 000000000..d05ef77f7 --- /dev/null +++ b/drivers/uio/uio_pci_generic_sva.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * UIO PCI Express sva driver + * + * Copyright (c) 2025 Beijing Institute of Open Source Chip (BOSC) + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/uio_driver.h> +#include <linux/iommu.h> + +struct uio_pci_sva_dev { + struct pci_dev *pdev; + struct uio_info info; + struct iommu_sva *sva_handle; + int pasid; +}; + +static irqreturn_t irq_handler(int irq, struct uio_info *dev_info) +{ + return IRQ_HANDLED; +} + +static int uio_pci_sva_open(struct uio_info *info, struct inode *inode) +{ + struct iommu_sva *handle; + struct uio_pci_sva_dev *udev = info->priv; + struct iommu_domain *domain; + + if (!udev || !udev->pdev) + return -ENODEV; + + domain = iommu_get_domain_for_dev(&udev->pdev->dev); + if (domain) + iommu_detach_device(domain, &udev->pdev->dev); + + handle = iommu_sva_bind_device(&udev->pdev->dev, current->mm); + if (IS_ERR(handle)) + return -EINVAL; + + udev->pasid = iommu_sva_get_pasid(handle); + + udev->sva_handle = handle; + + return 0; +} + +static int uio_pci_sva_release(struct uio_info *info, struct inode *inode) +{ + struct uio_pci_sva_dev *udev = info->priv; + + if (!udev || !udev->pdev) + return -ENODEV; + + iommu_sva_unbind_device(udev->sva_handle); + + return 0; +} + +static int probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct uio_pci_sva_dev *udev; + int ret, i, irq = 0; + + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "pci_enable_device failed: %d\n", ret); + return ret; + } + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) + goto out_disable; + + pci_set_master(pdev); + + ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX | PCI_IRQ_MSI); + if (ret > 0) { + irq = pci_irq_vector(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "Failed to get MSI vector\n"); + ret = irq; + goto out_disable; + } + } else + dev_warn(&pdev->dev, + "No IRQ vectors available (%d), using polling\n", ret); + + udev = devm_kzalloc(&pdev->dev, sizeof(struct uio_pci_sva_dev), + GFP_KERNEL); + if (!udev) { + ret = -ENOMEM; + goto out_disable; + } + + udev->pdev = pdev; + udev->info.name = "uio_pci_sva"; + udev->info.version = "0.0.1"; + udev->info.open = uio_pci_sva_open; + udev->info.release = uio_pci_sva_release; + udev->info.irq = irq; + udev->info.handler = irq_handler; + udev->info.priv = udev; + + for (i = 0; i < MAX_UIO_MAPS; i++) { + struct resource *r = &pdev->resource[i]; + struct uio_mem *uiomem = &udev->info.mem[i]; + + if (r->flags != (IORESOURCE_SIZEALIGN | IORESOURCE_MEM)) + continue; + + if (uiomem >= &udev->info.mem[MAX_UIO_MAPS]) { + dev_warn(&pdev->dev, "Do not support more than %d iomem\n", + MAX_UIO_MAPS); + 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; + } + + ret = devm_uio_register_device(&pdev->dev, &udev->info); + if (ret) { + dev_err(&pdev->dev, "Failed to register uio device\n"); + goto out_disable; + } + + pci_set_drvdata(pdev, udev); + + return 0; + +out_disable: + pci_disable_device(pdev); + + return ret; +} + +static void remove(struct pci_dev *pdev) +{ + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static ssize_t pasid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct uio_pci_sva_dev *udev = pci_get_drvdata(pdev); + + return sysfs_emit(buf, "%d\n", udev->pasid); +} +static DEVICE_ATTR_RO(pasid); + +static struct attribute *uio_pci_sva_attrs[] = { + &dev_attr_pasid.attr, + NULL +}; + +static const struct attribute_group uio_pci_sva_attr_group = { + .attrs = uio_pci_sva_attrs, +}; + +static const struct attribute_group *uio_pci_sva_attr_groups[] = { + &uio_pci_sva_attr_group, + NULL +}; + +static struct pci_driver uio_pci_generic_sva_driver = { + .name = "uio_pci_sva", + .dev_groups = uio_pci_sva_attr_groups, + .id_table = NULL, + .probe = probe, + .remove = remove, +}; + +module_pci_driver(uio_pci_generic_sva_driver); +MODULE_VERSION("0.0.01"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yaxing Guo <guoyaxing@bosc.ac.cn>"); +MODULE_DESCRIPTION("Generic UIO sva driver for PCI"); diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c new file mode 100644 index 000000000..0c8d73e7b --- /dev/null +++ b/drivers/uio/uio_pdrv_genirq.c @@ -0,0 +1,273 @@ +// 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/property.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 fwnode_handle *node = dev_fwnode(&pdev->dev); + 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 (!device_property_read_string(&pdev->dev, "linux,uio-name", &name)) + uioinfo->name = devm_kstrdup(&pdev->dev, name, GFP_KERNEL); + else + uioinfo->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%pfwP", 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) { + /* + * 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_get_trigger_type(uioinfo->irq) & 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; +} + +#ifdef CONFIG_OF +static struct of_device_id uio_of_genirq_match[] = { + { /* This is filled with module_parm */ }, + { /* Sentinel */ }, +}; +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, + .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_sercos3.c b/drivers/uio/uio_sercos3.c new file mode 100644 index 000000000..80087897f --- /dev/null +++ b/drivers/uio/uio_sercos3.c @@ -0,0 +1,227 @@ +// 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 const 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, } +}; +MODULE_DEVICE_TABLE(pci, sercos3_pci_ids); + +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"); |
