[RFC PATCH v3 16/17] vbus: add a userspace connector

Gregory Haskins
2009-04-21T18:35:42Z

This allows userspace applications to access vbus devices

Signed-off-by: Gregory Haskins <ghaskins@novell.com>
---

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 <ghaskins@novell.com>
+ *
+ * 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);