Subject: Linux support for vdevice bus

This patch provides the Linux implementation of the vdevice bus.

FIXME: currently it does not support save/restore of the domain: it
should call stop before shutting down, and remap shares afterwards
before calling reconnect.  This depends on exactly what we do with
shared pages on restore.

Index: xen-sane/linux-2.6-xen-sparse/drivers/xen/Makefile
===================================================================
--- xen-sane.orig/linux-2.6-xen-sparse/drivers/xen/Makefile	2006-07-03 13:23:31.000000000 +1000
+++ xen-sane/linux-2.6-xen-sparse/drivers/xen/Makefile	2006-07-03 13:53:56.000000000 +1000
@@ -3,6 +3,7 @@
 obj-y	+= evtchn/
 obj-y	+= privcmd/
 obj-y	+= xenbus/
+obj-y	+= vdevice/
 obj-y	+= xenshare.o
 
 obj-$(CONFIG_XEN_UTIL)			+= util.o
Index: xen-sane/linux-2.6-xen-sparse/drivers/xen/vdevice/Makefile
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ xen-sane/linux-2.6-xen-sparse/drivers/xen/vdevice/Makefile	2006-07-03 13:53:56.000000000 +1000
@@ -0,0 +1 @@
+obj-y := vdevice.o
Index: xen-sane/linux-2.6-xen-sparse/drivers/xen/vdevice/vdevice.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ xen-sane/linux-2.6-xen-sparse/drivers/xen/vdevice/vdevice.c	2006-07-03 14:07:23.000000000 +1000
@@ -0,0 +1,286 @@
+#define DEBUG
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/vdevice.h>
+#include <linux/page-flags.h>
+#include <linux/vmalloc.h>
+#include <linux/workqueue.h>
+#include <xen/evtchn.h>
+#include <asm/page.h>
+#include <xen/interface/share.h>
+
+static struct work_struct vdevice_add;
+static struct xen_share *vdevice_share;
+static struct vdevice_desc *vdevices;
+static int vdevice_change_counter = 1;
+static struct device **devices_installed;
+
+static ssize_t show_ref(struct bus_type *bus, char *buf)
+{
+	return sprintf(buf, "0x%lx\n", xen_start_info->vdevice_share);
+}
+static BUS_ATTR(share_ref, 0444, show_ref, NULL);
+
+static ssize_t type_show(struct device *_dev,
+                         struct device_attribute *attr, char *buf)
+{
+	struct vdevice *dev = container_of(_dev, struct vdevice, dev);
+	return sprintf(buf, "%i", dev->id.type);
+}
+static ssize_t features_show(struct device *_dev,
+                             struct device_attribute *attr, char *buf)
+{
+	struct vdevice *dev = container_of(_dev, struct vdevice, dev);
+	return sprintf(buf, "%i", dev->id.features);
+}
+static ssize_t share_ref_show(struct device *_dev,
+                              struct device_attribute *attr, char *buf)
+{
+	struct vdevice *dev = container_of(_dev, struct vdevice, dev);
+	return sprintf(buf, "%li",
+		       (long)vdevices[dev->vdevice_index].shared_ref);
+}
+static ssize_t status_show(struct device *_dev,
+                           struct device_attribute *attr, char *buf)
+{
+	struct vdevice *dev = container_of(_dev, struct vdevice, dev);
+	return sprintf(buf, "%i", vdevices[dev->vdevice_index].status);
+}
+static ssize_t status_store(struct device *_dev, struct device_attribute *attr,
+                            const char *buf, size_t count)
+{
+	struct vdevice *dev = container_of(_dev, struct vdevice, dev);
+	if (sscanf(buf, "%i", &vdevices[dev->vdevice_index].status) != 1)
+		return -EINVAL;
+	return count;
+}
+static struct device_attribute vdevice_dev_attrs[] = {
+	__ATTR_RO(type),
+	__ATTR_RO(features),
+	__ATTR_RO(share_ref),
+	__ATTR(status, 0644, status_show, status_store),
+	__ATTR_NULL
+};
+
+static int vdevice_match(struct device *_dev, struct device_driver *_drv)
+{
+	const struct vdevice_id *i;
+	struct vdevice *dev = container_of(_dev, struct vdevice, dev);
+	struct vdevice_driver *drv = container_of(_drv, struct vdevice_driver,
+	                                         driver);
+
+	for (i = drv->ids; i->type != 0; i++) {
+		if (dev->id.type == i->type &&
+		    (dev->id.features & i->features) == i->features)
+			return 1;
+	}
+	return 0;
+}
+
+struct vdevice_bus {
+	struct bus_type bus;
+	struct vdevice dev;
+};
+
+static struct vdevice_bus vd_bus = {
+	.bus = {
+		.name  = "vdevice",
+		.match = vdevice_match,
+		.dev_attrs = vdevice_dev_attrs,
+	},
+	.dev.dev = {
+		.parent = NULL,
+		.bus_id = "vdevice",
+	}
+};
+
+static int vdevice_dev_probe(struct device *_dev)
+{
+	int ret;
+	struct vdevice *dev = container_of(_dev, struct vdevice, dev);
+	struct vdevice_driver *drv = container_of(dev->dev.driver,
+			struct vdevice_driver, driver);
+	struct vdevice_desc *me = &vdevices[dev->vdevice_index];
+
+	me->status |= VDEVICE_S_DRIVER;
+
+	/* We only set this up when we actually probe, as userspace
+	 * drivers don't want this.  Previous probe might have failed,
+	 * so we could already have it mapped. */
+	if (!dev->share) {
+		dev->share = xen_share_get(me->shared_ref, me->nr_pages);
+		if (IS_ERR(dev->share)) {
+			printk(KERN_ERR
+			       "vdevice: failed mapping %u@%li for %i/%i\n",
+			       me->nr_pages, (long)me->shared_ref,
+			       dev->id.type, dev->id.features);
+			me->status |= VDEVICE_S_FAILED;
+			ret = PTR_ERR(dev->share);
+			dev->share = NULL;
+			return ret;
+		}
+		me->status |= VDEVICE_S_MAPPED;
+	}
+
+	ret = drv->probe(dev, &dev->id);
+	if (ret == 0)
+		me->status |= VDEVICE_S_DRIVER_OK;
+	return ret;
+}
+
+static int vdevice_dev_remove(struct device *_dev)
+{
+	struct vdevice *dev = container_of(_dev, struct vdevice, dev);
+	struct vdevice_driver *drv = container_of(dev->dev.driver,
+			struct vdevice_driver, driver);
+
+	if (drv && drv->remove)
+		drv->remove(dev);
+	if (dev->share)
+		xen_share_put(dev->share);
+	put_device(_dev);
+	return 0;
+}
+
+int register_vdevice_driver(struct vdevice_driver *drv)
+{
+	drv->driver.bus = &vd_bus.bus;
+	drv->driver.name = drv->name;
+	drv->driver.owner = drv->owner;
+	drv->driver.probe = vdevice_dev_probe;
+	drv->driver.remove = vdevice_dev_remove;
+
+	return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(register_vdevice_driver);
+
+void unregister_vdevice_driver(struct vdevice_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(unregister_vdevice_driver);
+
+static share_ref_t new_shared_page(void)
+{
+	dom0_op_t op = { .cmd = DOM0_CREATESHAREDPAGES,
+			 .interface_version = DOM0_INTERFACE_VERSION,
+			 .u.createsharedpages.num = 1 };
+
+	return HYPERVISOR_dom0_op(&op);
+}
+
+static void release_vdevice(struct device *_dev)
+{
+	struct vdevice *dev = container_of(_dev, struct vdevice, dev);
+
+	devices_installed[dev->vdevice_index] = NULL;
+	kfree(dev);
+}
+
+static void add_vdevice(unsigned int num)
+{
+	struct vdevice *new;
+
+	vdevices[num].status = VDEVICE_S_ACKNOWLEDGE;
+	new = kmalloc(sizeof(struct vdevice), GFP_KERNEL);
+	if (!new) {
+		printk(KERN_EMERG "Could not allocate vdevice %u\n", num);
+		vdevices[num].status |= VDEVICE_S_FAILED;
+		return;
+	}
+
+	new->vdevice_index = num;
+	new->id = vdevices[num].id;
+	new->private = NULL;
+	memset(&new->dev, 0, sizeof(new->dev));
+	new->dev.parent = &vd_bus.dev.dev;
+	new->dev.bus = &vd_bus.bus;
+	new->dev.release = release_vdevice;
+	sprintf(new->dev.bus_id, "%u", num);
+	new->share = NULL;
+	if (device_register(&new->dev) != 0) {
+		printk(KERN_EMERG "Could not register vdevice %u\n", num);
+		vdevices[num].status |= VDEVICE_S_FAILED;
+		kfree(new);
+	}
+
+	devices_installed[num] = &new->dev;
+}
+
+static void vdevice_work(void *unused)
+{
+	unsigned int i;
+
+	/* Something changed: look for differences. */
+	for (i = 0; i < PAGE_SIZE / sizeof(struct vdevice_desc); i++) {
+		char name[20];
+		struct device *dev;
+
+		sprintf(name, "%i", i);
+		dev = devices_installed[i];
+		if (vdevices[i].id.type != 0 && !dev)
+			add_vdevice(i);
+		else if (dev && vdevices[i].id.type == 0)
+			device_unregister(dev);
+	}
+
+	/* Re-arm trigger */
+	vdevice_change_counter = 1;
+
+	/* Acknowledge. */
+	HYPERVISOR_share(XEN_SHARE_trigger, xen_start_info->vdevice_share,
+			 0, 0, 0);
+}
+
+static void vdevice_handler(struct xen_share_handler *h)
+{
+	schedule_work(&vdevice_add);
+}
+static struct xen_share_handler handler = {
+	.handler = vdevice_handler,
+};
+
+static int __init vdevice_init(void)
+{
+	int err;
+
+	if (!xen_start_info->vdevice_share) {
+		/* We could be dom0, in which case we can create it. */
+		xen_start_info->vdevice_share = new_shared_page();
+		if (IS_ERR_VALUE(xen_start_info->vdevice_share)) {
+			printk(KERN_INFO "Vdevice bus not found\n");
+			xen_start_info->vdevice_share = 0;
+			return 0;
+		}
+	}
+	printk(KERN_INFO "vdevice bus found at 0x%lx\n", xen_start_info->vdevice_share);
+
+	vdevice_share = xen_share_get(xen_start_info->vdevice_share, 1);
+	BUG_ON(IS_ERR(vdevice_share));
+	vdevices = vdevice_share->addr;
+
+	/* Allocate space for the same number of devices as can fit
+	 * on the vdevices page */
+	devices_installed = kcalloc(sizeof(struct device*),
+	                         PAGE_SIZE / sizeof(struct vdevice_desc),
+				 GFP_KERNEL);
+	BUG_ON(!devices_installed);
+
+	bus_register(&vd_bus.bus);
+	device_register(&vd_bus.dev.dev);
+	bus_create_file(&vd_bus.bus, &bus_attr_share_ref);
+
+	/* Scan bus once for existing devices before setting up interrupt */
+	vdevice_work(NULL);
+
+	INIT_WORK(&vdevice_add, vdevice_work, NULL);
+	xen_share_add_handler(vdevice_share, &handler);
+	err = xen_share_watch(vdevice_share, 1, &vdevice_change_counter);
+	BUG_ON(err<0);
+
+	return 0;
+}
+postcore_initcall(vdevice_init);
Index: xen-sane/linux-2.6-xen-sparse/include/linux/vdevice.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ xen-sane/linux-2.6-xen-sparse/include/linux/vdevice.h	2006-07-03 13:53:56.000000000 +1000
@@ -0,0 +1,40 @@
+#ifndef _LINUX_VDEVICE_H_
+#define _LINUX_VDEVICE_H_
+
+#include <linux/device.h>
+#include <xen/interface/io/vdevice.h>
+#include <xen/interface/share.h>
+#include <asm/share.h>
+
+struct vdevice {
+	/* Unique busid */
+	int vdevice_index;
+
+	/* Shared region for this device. */
+	struct xen_share *share;
+
+	struct device dev;
+	struct vdevice_id id;
+
+	/* Driver can hang data off here. */
+	void *private;
+};
+
+struct vdevice_driver {
+	/* I can drive the following type of device(s) */
+	char *name;
+	struct module *owner;
+	const struct vdevice_id *ids;
+	int (*probe)(struct vdevice *dev, const struct vdevice_id *id);
+	void (*remove)(struct vdevice *dev);
+
+	void (*stop)(struct vdevice *dev);
+	int (*reconnect)(struct vdevice *dev);
+
+	struct device_driver driver;
+};
+
+extern int register_vdevice_driver(struct vdevice_driver *drv);
+extern void unregister_vdevice_driver(struct vdevice_driver *drv);
+
+#endif /* _LINUX_VDEVICE_H_ */
