[RFC] Generic VME UIO

Dmitry Kalinkin dmitry.kalinkin at gmail.com
Wed Jul 22 18:09:06 UTC 2015


Linux kernel has supported VME bus since 2009. The support comes in a form
of kernel driver API that is backed by a couple drivers for PCI-VME
bridges.  There is also a vme_user driver that provides a generic userpsace
interface to do data transfer and generate interrupts on the bus. Due to
specifics of the VME bus, this interface doesn't fit into the UIO
framework. The other useful feature is to be able to receive VME interrupts
in the userspace can, on the other hand, benefit from reusing UIO.

VME bus offers seven interrupt request lines IRQ1-IRQ7 corresponding to
seven interrupt levels.  In the event of interrupt, the interrupt handler
board is to prioritize interrupts in accordance to their levels.  The
interrupt handler then takes ownership over the data bus to perform an
interrupt acknowledge cycle where it supplies an interrupt level to be
acknowledged. When multiple interrupters are producing interrupt at the
same level, only one interrupt gets acknowledged based on interrupters
position in IACKIN/IACKOUT daisy- chain.  The response of the interrupter
to a relevant interrupt will contain a 8, 16 or 32 bit interrupt vector
(also called STATUS/ID). After the interrupt acknowledge cycle, the
interrupter is to remove it's interrupt request from the bus. Such
standartized scheme is called "Release On AcKnowledge" (ROAK).

Like PCI, VME has it's own corner case where interrupt request is removed
on VME device register access. VME specification acknowledges this
behaviour and calls it "Release On Register Access" (RORA) and requires
RORA devices to still provide interrupt vector value in acknowledge cycles.
I'm not aware how widespread the RORA devices are.

The driver below provides a generic userspace interface to handle ROAK VME
device interrupts.

The user is to enable interrupt vectors through a sysfs interface. For
example, enabling handler for interrupt vector 0x6b at the level 1 will
look like:

  echo 1 > /sys/bus/vme/devices/vme_uio.0-0/irq/1/6b/enabled

A separate UIO device is created for each handler. Waiting for event can be
done as:

  dd if=/dev/uio0 bs=4 count=1

In response for this RFC I would like to hear your comments or suggestions
on the proposed sysfs interface, about the idea in general. Some tips on
how to better handle kobject cleanup are also very welcome.

The vme_uio driver is provided separately for the ease of review, it's code
is intended for the merge into vme_user.

Signed-off-by: Dmitry Kalinkin <dmitry.kalinkin at gmail.com>
Cc: Igor Alekseev <igor.alekseev at itep.ru>

---
 drivers/staging/vme/devices/Kconfig   |  10 +++
 drivers/staging/vme/devices/Makefile  |   1 +
 drivers/staging/vme/devices/vme_uio.c | 158 ++++++++++++++++++++++++++++++++++
 drivers/vme/vme_bridge.h              |   4 +-
 include/linux/vme.h                   |   3 +
 5 files changed, 175 insertions(+), 1 deletion(-)
 create mode 100644 drivers/staging/vme/devices/vme_uio.c

diff --git a/drivers/staging/vme/devices/Kconfig b/drivers/staging/vme/devices/Kconfig
index 1d2ff0c..0300226 100644
--- a/drivers/staging/vme/devices/Kconfig
+++ b/drivers/staging/vme/devices/Kconfig
@@ -1,5 +1,15 @@
 comment "VME Device Drivers"
 
+config VME_UIO
+	tristate "VME UIO user space access driver"
+	depends on STAGING && VME_BUS && UIO
+	help
+	 Say Y here to include UIO interface to VME. This module currently
+	 allows you to deliver VME interrupts to user space.
+
+	 To compile this driver as a module, choose M here. The module will
+	 be called vme_uio. If unsure, say N.
+
 config VME_USER
 	tristate "VME user space access driver"
 	depends on STAGING
diff --git a/drivers/staging/vme/devices/Makefile b/drivers/staging/vme/devices/Makefile
index 172512c..c198004 100644
--- a/drivers/staging/vme/devices/Makefile
+++ b/drivers/staging/vme/devices/Makefile
@@ -2,6 +2,7 @@
 # Makefile for the VME device drivers.
 #
 
+obj-$(CONFIG_VME_UIO)		+= vme_uio.o
 obj-$(CONFIG_VME_USER)		+= vme_user.o
 
 vme_pio2-objs	:= vme_pio2_cntr.o vme_pio2_gpio.o vme_pio2_core.o
diff --git a/drivers/staging/vme/devices/vme_uio.c b/drivers/staging/vme/devices/vme_uio.c
new file mode 100644
index 0000000..4c55a23
--- /dev/null
+++ b/drivers/staging/vme/devices/vme_uio.c
@@ -0,0 +1,158 @@
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uio_driver.h>
+#include <linux/vme.h>
+
+static void vme_uio_int(int level, int status_id, void *priv)
+{
+	struct uio_info *info = priv;
+
+	uio_event_notify(info);
+}
+
+struct int_sysfs_entry {
+	struct kobj_attribute kobj_attr;
+	struct vme_dev *vdev;
+	struct uio_info uio;
+	int level;
+	int statid;
+	int enabled;
+};
+
+static ssize_t int_enabled_show(struct kobject *kobj,
+				struct kobj_attribute *attr, char *buf)
+{
+	struct int_sysfs_entry *entry;
+
+	entry = container_of(attr, struct int_sysfs_entry, kobj_attr);
+	return sprintf(buf, "%d\n", entry->enabled);
+}
+
+static ssize_t int_enabled_store(struct kobject *kobj,
+				 struct kobj_attribute *attr, const char *buf,
+				 size_t count)
+{
+	int enabled;
+	struct int_sysfs_entry *entry;
+	int ret;
+
+	entry = container_of(attr, struct int_sysfs_entry, kobj_attr);
+
+	ret = kstrtoint(buf, 0, &enabled);
+	if (ret)
+		return ret;
+	enabled = !!enabled;
+
+	if (enabled == entry->enabled)
+		return count;
+
+	if (enabled) {
+		ret = uio_register_device(&entry->vdev->dev, &entry->uio);
+		if (ret) {
+			enabled = 0;
+			return ret;
+		}
+
+		ret = vme_irq_request(entry->vdev, entry->level, entry->statid,
+				      vme_uio_int, &entry->uio);
+		if (ret) {
+			enabled = 0;
+			return ret;
+		}
+	} else {
+		vme_irq_free(entry->vdev, entry->level, entry->statid);
+
+		uio_unregister_device(&entry->uio);
+	}
+
+	entry->enabled = enabled;
+
+	return count;
+}
+
+static struct kobj_attribute int_enabled_attribute =
+	__ATTR(enabled, 0644, int_enabled_show, int_enabled_store);
+
+static int vme_uio_match(struct vme_dev *vdev)
+{
+	return 1;
+}
+
+static int vme_uio_probe(struct vme_dev *vdev)
+{
+	int ret, level, statid;
+
+	int bus_num = vme_bus_num(vdev);
+
+	struct kobject *kobj = kobject_create_and_add("irq", &vdev->dev.kobj);
+
+	for (level = 1; level <= 7; level++) {
+		char *level_node_name = kasprintf(GFP_KERNEL, "%d", level);
+		struct kobject *level_node = kobject_create_and_add(
+			level_node_name, kobj);
+		if (!level_node)
+			return -ENOMEM;
+
+		for (statid = 0; statid < VME_NUM_STATUSID; statid++) {
+			char *statid_node_name = kasprintf(GFP_KERNEL,
+							   "%02x", statid);
+			struct kobject *statid_node;
+			struct int_sysfs_entry *entry;
+
+			statid_node = kobject_create_and_add(statid_node_name,
+							     level_node);
+			if (!statid_node)
+				return -ENOMEM;
+
+			entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+			if (!entry)
+				return -ENOMEM;
+			entry->uio.name = kasprintf(GFP_KERNEL,
+						    "vme_irq_%d_%d_%02x",
+						    bus_num, level, statid);
+			entry->uio.version = "1";
+			entry->uio.irq = UIO_IRQ_CUSTOM;
+			entry->level = level;
+			entry->statid = statid;
+			entry->vdev = vdev;
+			entry->enabled = 0;
+			entry->kobj_attr = int_enabled_attribute;
+			ret = sysfs_create_file(statid_node,
+						&entry->kobj_attr.attr);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int vme_uio_remove(struct vme_dev *vdev)
+{
+	/* XXX Cleanup here */
+	return 0;
+}
+
+static struct vme_driver vme_uio_driver = {
+	.name = "vme_uio",
+	.match = vme_uio_match,
+	.probe = vme_uio_probe,
+	.remove = vme_uio_remove,
+};
+
+static int __init vme_uio_init(void)
+{
+	return vme_register_driver(&vme_uio_driver, 1);
+}
+
+static void __exit vme_uio_exit(void)
+{
+	vme_unregister_driver(&vme_uio_driver);
+}
+
+module_init(vme_uio_init);
+module_exit(vme_uio_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Dmitry Kalinkin <dmitry.kalinkin at gmail.com>");
diff --git a/drivers/vme/vme_bridge.h b/drivers/vme/vme_bridge.h
index 37d2fd7..a3ef63b 100644
--- a/drivers/vme/vme_bridge.h
+++ b/drivers/vme/vme_bridge.h
@@ -1,6 +1,8 @@
 #ifndef _VME_BRIDGE_H_
 #define _VME_BRIDGE_H_
 
+#include <linux/vme.h>
+
 #define VME_CRCSR_BUF_SIZE (508*1024)
 /*
  * Resource structures
@@ -88,7 +90,7 @@ struct vme_callback {
 
 struct vme_irq {
 	int count;
-	struct vme_callback callback[256];
+	struct vme_callback callback[VME_NUM_STATUSID];
 };
 
 /* Allow 16 characters for name (including null character) */
diff --git a/include/linux/vme.h b/include/linux/vme.h
index c013135..71e4a6d 100644
--- a/include/linux/vme.h
+++ b/include/linux/vme.h
@@ -81,6 +81,9 @@ struct vme_resource {
 
 extern struct bus_type vme_bus_type;
 
+/* Number of VME interrupt vectors */
+#define VME_NUM_STATUSID	256
+
 /* VME_MAX_BRIDGES comes from the type of vme_bus_numbers */
 #define VME_MAX_BRIDGES		(sizeof(unsigned int)*8)
 #define VME_MAX_SLOTS		32
-- 
1.8.3.1



More information about the devel mailing list