This allows userspace applications to access vbus devices

Signed-off-by: Gregory Haskins <[email protected]>
---

 include/linux/vbus.h           |    4 
 include/linux/vbus_client.h    |    2 
 include/linux/vbus_userspace.h |   48 ++++
 kernel/vbus/Kconfig            |   10 +
 kernel/vbus/Makefile           |    2 
 kernel/vbus/userspace-client.c |  485 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 550 insertions(+), 1 deletions(-)
 create mode 100644 include/linux/vbus_userspace.h
 create mode 100644 kernel/vbus/userspace-client.c

diff --git a/include/linux/vbus.h b/include/linux/vbus.h
index 04db4ff..f967e59 100644
--- a/include/linux/vbus.h
+++ b/include/linux/vbus.h
@@ -23,6 +23,8 @@
 #ifndef _LINUX_VBUS_H
 #define _LINUX_VBUS_H
 
+#ifdef __KERNEL__
+
 #ifdef CONFIG_VBUS
 
 #include <linux/module.h>
@@ -159,4 +161,6 @@ int vbus_notifier_unregister(struct vbus *vbus, struct 
notifier_block *nb);
 
 #endif /* CONFIG_VBUS */
 
+#endif /* __KERNEL__ */
+
 #endif /* _LINUX_VBUS_H */
diff --git a/include/linux/vbus_client.h b/include/linux/vbus_client.h
index 62dab78..4c82822 100644
--- a/include/linux/vbus_client.h
+++ b/include/linux/vbus_client.h
@@ -35,7 +35,7 @@
 #ifndef _LINUX_VBUS_CLIENT_H
 #define _LINUX_VBUS_CLIENT_H
 
-#include <linux/types.h>
+#include <asm/types.h>
 #include <linux/compiler.h>
 
 struct vbus_deviceopen {
diff --git a/include/linux/vbus_userspace.h b/include/linux/vbus_userspace.h
new file mode 100644
index 0000000..0b78686
--- /dev/null
+++ b/include/linux/vbus_userspace.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2009 Novell.  All Rights Reserved.
+ *
+ * Virtual-Bus
+ *
+ * Author:
+ *      Gregory Haskins <[email protected]>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _LINUX_VBUS_USERSPACE_H
+#define _LINUX_VBUS_USERSPACE_H
+
+#include <linux/ioctl.h>
+#include <linux/vbus.h>
+#include <linux/vbus_client.h>
+
+#define VBUS_USERSPACE_ABI_MAGIC 0x4fa23b58
+#define VBUS_USERSPACE_ABI_VERSION 1
+
+struct vbus_userspace_busopen {
+       __u32 magic;
+       __u32 version;
+       __u64 capabilities;
+};
+
+#define VBUS_IOCTL_MAGIC 'v'
+
+#define VBUS_BUSOPEN       _IOWR(VBUS_IOCTL_MAGIC, 0x00, struct 
vbus_userspace_busopen)
+#define VBUS_DEVICEOPEN   _IOWR(VBUS_IOCTL_MAGIC, 0x01, struct vbus_deviceopen)
+#define VBUS_DEVICECLOSE  _IOWR(VBUS_IOCTL_MAGIC, 0x02, __u64)
+#define VBUS_DEVICECALL   _IOWR(VBUS_IOCTL_MAGIC, 0x03, struct vbus_devicecall)
+#define VBUS_DEVICESHM    _IOWR(VBUS_IOCTL_MAGIC, 0x04, struct vbus_deviceshm)
+#define VBUS_SHMSIGNAL    _IOWR(VBUS_IOCTL_MAGIC, 0x05, __u64)
+
+#endif /* _LINUX_VBUS_USERSPACE_H */
diff --git a/kernel/vbus/Kconfig b/kernel/vbus/Kconfig
index 3ce0adc..b894dd1 100644
--- a/kernel/vbus/Kconfig
+++ b/kernel/vbus/Kconfig
@@ -25,6 +25,16 @@ config VBUS_DEVICES
 
 source "drivers/vbus/devices/Kconfig"
 
+config VBUS_USERSPACE
+       tristate "Virtual-Bus userspace client support"
+       depends on VBUS
+       default y
+       help
+         Provides facilities for userspace applications to access virtual-
+        bus objects.
+
+        If unsure, say N
+
 config VBUS_DRIVERS
        tristate "VBUS Driver support"
        select IOQ
diff --git a/kernel/vbus/Makefile b/kernel/vbus/Makefile
index 45f6503..61d0371 100644
--- a/kernel/vbus/Makefile
+++ b/kernel/vbus/Makefile
@@ -4,3 +4,5 @@ obj-$(CONFIG_VBUS) += shm-ioq.o
 vbus-proxy-objs += proxy.o
 obj-$(CONFIG_VBUS_DRIVERS) += vbus-proxy.o
 
+vbus-userspace-objs += userspace-client.o
+obj-$(CONFIG_VBUS_USERSPACE) += vbus-userspace.o
diff --git a/kernel/vbus/userspace-client.c b/kernel/vbus/userspace-client.c
new file mode 100644
index 0000000..b2fe447
--- /dev/null
+++ b/kernel/vbus/userspace-client.c
@@ -0,0 +1,485 @@
+#include <linux/ioctl.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/uaccess.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+
+#include <linux/vbus.h>
+#include <linux/vbus_userspace.h>
+
+#include "vbus.h"
+
+MODULE_AUTHOR("Gregory Haskins");
+MODULE_LICENSE("GPL");
+
+struct userspace_chardev;
+
+struct userspace_signal {
+       struct userspace_chardev *cd;
+       struct vbus_shm          *shm;
+       struct shm_signal         signal;
+       struct list_head          list;
+       int                       signaled;
+       int                       prio;
+       u64                       cookie;
+};
+
+struct userspace_shm {
+       struct vbus_shm shm;
+};
+
+struct userspace_chardev {
+       spinlock_t lock;
+       int opened;
+       struct vbus_memctx *ctx;
+       struct vbus_client *client;
+       struct list_head signal_list;
+       wait_queue_head_t wq;
+       struct vbus *vbus;
+};
+
+static long
+_busopen(struct userspace_chardev *cd, struct vbus_userspace_busopen *args)
+{
+       if (cd->opened)
+               return -EINVAL;
+
+       if (args->magic != VBUS_USERSPACE_ABI_MAGIC)
+               return -EINVAL;
+
+       if (args->version != VBUS_USERSPACE_ABI_VERSION)
+               return -EINVAL;
+
+       /*
+        * We have no extended capabilities yet, so we dont care if they set
+        * any option bits.  Just clear them all.
+        */
+       args->capabilities = 0;
+
+       cd->opened = 1;
+
+       return 0;
+}
+
+static long
+_deviceopen(struct userspace_chardev *cd, struct vbus_deviceopen *args)
+{
+       struct vbus_client *c = cd->client;
+
+       return c->ops->deviceopen(c, cd->ctx, args->devid, args->version,
+                                 &args->handle);
+}
+
+static long
+_deviceclose(struct userspace_chardev *cd, unsigned long devh)
+{
+       struct vbus_client *c = cd->client;
+
+       return c->ops->deviceclose(c, devh);
+}
+
+static long
+_devicecall(struct userspace_chardev *cd, struct vbus_devicecall *args)
+{
+       struct vbus_client *c = cd->client;
+
+       return c->ops->devicecall(c, args->devh, args->func,
+                                 (void *)args->datap,
+                                 args->len, args->flags);
+}
+
+static void*
+userspace_vmap(__u64 addr, size_t len)
+{
+       struct page **page_list;
+       void *ptr = NULL;
+       unsigned long base;
+       off_t offset;
+       size_t npages;
+       int ret;
+
+       base = (unsigned long)addr & PAGE_MASK;
+       offset = (unsigned long)addr & ~PAGE_MASK;
+       npages = PAGE_ALIGN(len + offset) >> PAGE_SHIFT;
+
+       if (npages > (PAGE_SIZE / sizeof(struct page *)))
+               return NULL;
+
+       page_list = (struct page **) __get_free_page(GFP_KERNEL);
+       if (!page_list)
+               return NULL;
+
+       down_write(&current->mm->mmap_sem);
+
+       ret = get_user_pages(current, current->mm, base, npages,
+                            1, 0, page_list, NULL);
+       if (ret < 0)
+               goto out;
+
+       ptr = vmap(page_list, npages, VM_MAP, PAGE_KERNEL);
+       if (ptr)
+               current->mm->locked_vm += npages;
+
+out:
+       up_write(&current->mm->mmap_sem);
+       free_page((unsigned long)page_list);
+
+       return ptr+offset;
+}
+
+static struct userspace_signal *to_userspace(struct shm_signal *signal)
+{
+       return container_of(signal, struct userspace_signal, signal);
+}
+
+static int
+userspace_signal_inject(struct shm_signal *signal)
+{
+       struct userspace_signal *_signal = to_userspace(signal);
+       struct userspace_chardev *cd = _signal->cd;
+       unsigned long flags;
+
+       spin_lock_irqsave(&cd->lock, flags);
+
+       if (!_signal->signaled) {
+               _signal->signaled = 1;
+               list_add_tail(&_signal->list, &cd->signal_list);
+               wake_up_interruptible(&cd->wq);
+       }
+
+       spin_unlock_irqrestore(&cd->lock, flags);
+
+       return 0;
+}
+
+static void
+userspace_signal_release(struct shm_signal *signal)
+{
+       struct userspace_signal *_signal = to_userspace(signal);
+
+       vbus_shm_put(_signal->shm);
+       kfree(_signal);
+}
+
+static struct shm_signal_ops userspace_signal_ops = {
+       .inject  = userspace_signal_inject,
+       .release = userspace_signal_release,
+};
+
+static long
+userspace_signal_alloc(struct vbus_shm *shm,
+                      u32 offset, u32 prio, u64 cookie,
+                      struct userspace_signal **usignal)
+{
+       struct userspace_signal *_signal;
+       struct shm_signal *signal;
+       struct shm_signal_desc *desc;
+       int ret = -EINVAL;
+
+       _signal = kzalloc(sizeof(*_signal), GFP_KERNEL);
+       if (!_signal)
+               return -ENOMEM;
+
+       desc = (struct shm_signal_desc *)(shm->ptr + offset);
+
+       if (desc->magic != SHM_SIGNAL_MAGIC)
+               goto out;
+
+       if (desc->ver != SHM_SIGNAL_VER)
+               goto out;
+
+       signal = &_signal->signal;
+
+       shm_signal_init(signal);
+
+       signal->locale    = shm_locality_south;
+       signal->ops       = &userspace_signal_ops;
+       signal->desc      = desc;
+
+       _signal->shm      = shm;
+       _signal->prio     = prio;
+       _signal->cookie   = cookie;
+       vbus_shm_get(shm); /* dropped when the signal is released */
+
+       *usignal = _signal;
+
+       return 0;
+
+out:
+       kfree(_signal);
+
+       return ret;
+}
+
+static void
+userspace_shm_release(struct vbus_shm *shm)
+{
+       struct userspace_shm *_shm = container_of(shm, struct userspace_shm,
+                                                 shm);
+
+       /* FIXME: do we need to adjust current->mm->locked_vm? */
+       vunmap((void *)((unsigned long)shm->ptr & PAGE_MASK));
+       kfree(_shm);
+}
+
+static struct vbus_shm_ops userspace_shm_ops = {
+       .release = userspace_shm_release,
+};
+
+static int
+userspace_shm_map(struct userspace_chardev *cd,
+                 __u64 ptr, __u32 len,
+                 struct userspace_shm **ushm)
+{
+       struct userspace_shm *_shm;
+       struct vbus_shm *shm;
+       void *vmap;
+
+       _shm = kzalloc(sizeof(*_shm), GFP_KERNEL);
+       if (!_shm)
+               return -ENOMEM;
+
+       shm = &_shm->shm;
+
+       vmap = userspace_vmap(ptr, len);
+       if (!vmap) {
+               kfree(_shm);
+               return -EFAULT;
+       }
+
+       vbus_shm_init(shm, &userspace_shm_ops, vmap, len);
+
+       *ushm = _shm;
+
+       return 0;
+}
+
+static long
+_deviceshm(struct userspace_chardev *cd, struct vbus_deviceshm *args)
+{
+       struct vbus_client *c = cd->client;
+       struct userspace_signal *_signal = NULL;
+       struct shm_signal *signal = NULL;
+       struct userspace_shm *_shm;
+       u64 handle;
+       long ret;
+
+       ret = userspace_shm_map(cd, args->datap, args->len, &_shm);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * Establishing a signal is optional
+        */
+       if (args->signal.offset != -1) {
+               ret = userspace_signal_alloc(&_shm->shm,
+                                            args->signal.offset,
+                                            args->signal.prio,
+                                            args->signal.cookie,
+                                            &_signal);
+               if (ret < 0)
+                       goto out;
+
+               _signal->cd = cd;
+               signal = &_signal->signal;
+       }
+
+       ret = c->ops->deviceshm(c, args->devh, args->id,
+                               &_shm->shm, signal,
+                               args->flags, &handle);
+       if (ret < 0)
+               goto out;
+
+       args->handle = handle;
+
+       return 0;
+
+out:
+       if (signal)
+               shm_signal_put(signal);
+
+       vbus_shm_put(&_shm->shm);
+       return ret;
+}
+
+static long
+_shmsignal(struct userspace_chardev *cd, unsigned long handle)
+{
+       struct vbus_client *c = cd->client;
+
+       return c->ops->shmsignal(c, handle);
+}
+
+static int
+vbus_chardev_open(struct inode *inode, struct file *filp)
+{
+       struct vbus *vbus = task_vbus_get(current);
+       struct vbus_client *client;
+       struct vbus_memctx *ctx;
+       struct userspace_chardev *cd;
+
+       if (!vbus)
+               return -EPERM;
+
+       client = vbus_client_attach(vbus);
+       vbus_put(vbus);
+       if (!client)
+               return -ENOMEM;
+
+       ctx = task_memctx_alloc(current);
+       if (!ctx) {
+               vbus_client_put(client);
+               return -ENOMEM;
+       }
+
+       cd = kzalloc(sizeof(*cd), GFP_KERNEL);
+       if (!cd) {
+               vbus_memctx_put(ctx);
+               vbus_client_put(client);
+               return -ENOMEM;
+       }
+
+       spin_lock_init(&cd->lock);
+       cd->opened = 0;
+       cd->client = client;
+       cd->ctx = ctx;
+
+       INIT_LIST_HEAD(&cd->signal_list);
+       init_waitqueue_head(&cd->wq);
+
+       filp->private_data = cd;
+
+       return 0;
+}
+
+static long
+vbus_chardev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
+{
+       struct userspace_chardev *cd = filp->private_data;
+
+       if (!cd->opened && ioctl != VBUS_BUSOPEN)
+               return -EINVAL;
+
+       switch (ioctl) {
+       case VBUS_BUSOPEN:
+               return _busopen(cd, (struct vbus_userspace_busopen *)arg);
+       case VBUS_DEVICEOPEN:
+               return _deviceopen(cd, (struct vbus_deviceopen *)arg);
+       case VBUS_DEVICECLOSE:
+               return _deviceclose(cd, *(__u64 *)arg);
+       case VBUS_DEVICECALL:
+               return _devicecall(cd, (struct vbus_devicecall *)arg);
+       case VBUS_DEVICESHM:
+               return _deviceshm(cd, (struct vbus_deviceshm *)arg);
+       case VBUS_SHMSIGNAL:
+               return _shmsignal(cd, *(__u64 *)arg);
+       default:
+               return -EINVAL;
+       }
+}
+
+static ssize_t
+vbus_chardev_read(struct file *filp, char __user *buf, size_t len,
+                 loff_t *ppos)
+{
+       DEFINE_WAIT(wait);
+       struct userspace_chardev *cd = filp->private_data;
+       ssize_t bytes = 0;
+       int count, i;
+       __u64 __user *p = (__u64 __user *)buf;
+       unsigned long flags;
+
+       count = len/sizeof(__u64);
+
+       if (!count)
+               return -EINVAL;
+
+       spin_lock_irqsave(&cd->lock, flags);
+
+       for (;;) {
+               prepare_to_wait(&cd->wq, &wait, TASK_INTERRUPTIBLE);
+
+               if (!list_empty(&cd->signal_list))
+                       break;
+
+               if (signal_pending(current)) {
+                       finish_wait(&cd->wq, &wait);
+                       spin_unlock_irqrestore(&cd->lock, flags);
+                       return -EINTR;
+               }
+
+               spin_unlock_irqrestore(&cd->lock, flags);
+               schedule();
+               spin_lock_irqsave(&cd->lock, flags);
+       }
+
+       finish_wait(&cd->wq, &wait);
+
+       for (i = 0; i < count; i++) {
+               struct userspace_signal *_signal;
+               __u64 cookie;
+
+               if (list_empty(&cd->signal_list))
+                       break;
+
+               _signal = list_first_entry(&cd->signal_list,
+                                          struct userspace_signal, list);
+
+               _signal->signaled = 0;
+               list_del(&_signal->list);
+
+               cookie = _signal->cookie;
+
+               put_user(cookie, p++);
+
+               bytes += sizeof(cookie);
+       }
+
+       spin_unlock_irqrestore(&cd->lock, flags);
+
+       return bytes;
+}
+
+static int
+vbus_chardev_release(struct inode *inode, struct file *filp)
+{
+       struct userspace_chardev *cd = filp->private_data;
+
+       vbus_memctx_put(cd->ctx);
+       vbus_client_put(cd->client);
+       kfree(cd);
+
+       return 0;
+}
+
+static const struct file_operations vbus_chardev_ops = {
+       .open           = vbus_chardev_open,
+       .read           = vbus_chardev_read,
+       .unlocked_ioctl = vbus_chardev_ioctl,
+       .compat_ioctl   = vbus_chardev_ioctl,
+       .release        = vbus_chardev_release,
+};
+
+static struct miscdevice vbus_chardev = {
+       MISC_DYNAMIC_MINOR,
+       "vbus",
+       &vbus_chardev_ops,
+};
+
+static int __init
+vbus_userspace_init(void)
+{
+       return misc_register(&vbus_chardev);
+}
+
+static void __exit
+vbus_userspace_cleanup(void)
+{
+       misc_deregister(&vbus_chardev);
+}
+
+module_init(vbus_userspace_init);
+module_exit(vbus_userspace_cleanup);

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to