[RFC PATCH v3 03/17] vbus: add connection-client helper infrastructure

Gregory Haskins
2009-04-21T18:34:33Z

We expect to have various types of connection-clients (e.g. userspace,
kvm, etc), each of which is likely to have common access patterns and
marshalling duties. Therefore we create a "client" API to simplify
client development by helping with mundane tasks such as handle-2-pointer
translation, etc.

Special thanks to Pat Mullaney for suggesting the optimization to pass
a cookie object down during DEVICESHM operations to save lookup overhead
on the event channel.

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

include/linux/vbus_client.h | 115 +++++++++
kernel/vbus/Makefile | 2
kernel/vbus/client.c | 543 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 659 insertions(+), 1 deletions(-)
create mode 100644 include/linux/vbus_client.h
create mode 100644 kernel/vbus/client.c

diff --git a/include/linux/vbus_client.h b/include/linux/vbus_client.h
new file mode 100644
index 0000000..62dab78
--- /dev/null
+++ b/include/linux/vbus_client.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2009 Novell. All Rights Reserved.
+ *
+ * Virtual-Bus - Client interface
+ *
+ * We expect to have various types of connection-clients (e.g. userspace,
+ * kvm, etc). Each client will be connecting from some environment outside
+ * of the kernel, and therefore will not have direct access to the API as
+ * presented in ./linux/vbus.h. There will undoubtedly be some parameter
+ * marshalling that must occur, as well as common patterns for the handling
+ * of those marshalled parameters (e.g. translating a handle into a pointer,
+ * etc).
+ *
+ * Therefore this "client" API is provided to simplify the development
+ * of any clients. Of course, a client is free to bypass this API entirely
+ * and communicate with the direct VBUS API if desired.
+ *
+ * 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_CLIENT_H
+#define _LINUX_VBUS_CLIENT_H
+
+#include <linux/types.h>
+#include <linux/compiler.h>
+
+struct vbus_deviceopen {
+ __u32 devid;
+ __u32 version; /* device ABI version */
+ __u64 handle; /* return value for devh */
+};
+
+struct vbus_devicecall {
+ __u64 devh; /* device-handle (returned from DEVICEOPEN */
+ __u32 func;
+ __u32 len;
+ __u32 flags;
+ __u64 datap;
+};
+
+struct vbus_deviceshm {
+ __u64 devh; /* device-handle (returned from DEVICEOPEN */
+ __u32 id;
+ __u32 len;
+ __u32 flags;
+ struct {
+ __u32 offset;
+ __u32 prio;
+ __u64 cookie; /* token to pass back when signaling client */
+ } signal;
+ __u64 datap;
+ __u64 handle; /* return value for signaling from client to kernel */
+};
+
+#ifdef __KERNEL__
+
+#include <linux/ioq.h>
+#include <linux/module.h>
+#include <asm/atomic.h>
+
+struct vbus_client;
+
+struct vbus_client_ops {
+ int (*deviceopen)(struct vbus_client *client, struct vbus_memctx *ctx,
+ __u32 devid, __u32 version, __u64 *devh);
+ int (*deviceclose)(struct vbus_client *client, __u64 devh);
+ int (*devicecall)(struct vbus_client *client,
+ __u64 devh, __u32 func,
+ void *data, __u32 len, __u32 flags);
+ int (*deviceshm)(struct vbus_client *client,
+ __u64 devh, __u32 id,
+ struct vbus_shm *shm, struct shm_signal *signal,
+ __u32 flags, __u64 *handle);
+ int (*shmsignal)(struct vbus_client *client, __u64 handle);
+ void (*release)(struct vbus_client *client);
+};
+
+struct vbus_client {
+ atomic_t refs;
+ struct vbus_client_ops *ops;
+};
+
+static inline void vbus_client_get(struct vbus_client *client)
+{
+ atomic_inc(&client->refs);
+}
+
+static inline void vbus_client_put(struct vbus_client *client)
+{
+ if (atomic_dec_and_test(&client->refs))
+ client->ops->release(client);
+}
+
+struct vbus_client *vbus_client_attach(struct vbus *bus);
+
+extern struct vbus_memctx *current_memctx;
+struct vbus_memctx *task_memctx_alloc(struct task_struct *task);
+
+#endif /* __KERNEL__ */
+
+#endif /* _LINUX_VBUS_CLIENT_H */
diff --git a/kernel/vbus/Makefile b/kernel/vbus/Makefile
index 367f65b..4d440e5 100644
--- a/kernel/vbus/Makefile
+++ b/kernel/vbus/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_VBUS) += core.o devclass.o config.o attribute.o map.o
+obj-$(CONFIG_VBUS) += core.o devclass.o config.o attribute.o map.o client.o
diff --git a/kernel/vbus/client.c b/kernel/vbus/client.c
new file mode 100644
index 0000000..f9c3dcf
--- /dev/null
+++ b/kernel/vbus/client.c
@@ -0,0 +1,543 @@
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/highmem.h>
+#include <linux/uaccess.h>
+#include <linux/vbus.h>
+#include <linux/vbus_client.h>
+#include "vbus.h"
+
+static int
+nodeptr_item_compare(struct rb_node *lhs, struct rb_node *rhs)
+{
+ unsigned long l = (unsigned long)lhs;
+ unsigned long r = (unsigned long)rhs;
+
+ return l - r;
+}
+
+static int
+nodeptr_key_compare(const void *key, struct rb_node *node)
+{
+ unsigned long item = (unsigned long)node;
+ unsigned long _key = *(unsigned long *)key;
+
+ return _key - item;
+}
+
+static struct map_ops nodeptr_map_ops = {
+ .key_compare = &nodeptr_key_compare,
+ .item_compare = &nodeptr_item_compare,
+};
+
+struct _signal {
+ atomic_t refs;
+ struct rb_node node;
+ struct list_head list;
+ struct shm_signal *signal;
+};
+
+struct _connection {
+ atomic_t refs;
+ struct rb_node node;
+ struct list_head signals;
+ struct vbus_connection *conn;
+ int closed:1;
+};
+
+static inline void _signal_get(struct _signal *_signal)
+{
+ atomic_inc(&_signal->refs);
+}
+
+static inline void _signal_put(struct _signal *_signal)
+{
+ if (atomic_dec_and_test(&_signal->refs)) {
+ shm_signal_put(_signal->signal);
+ kfree(_signal);
+ }
+}
+
+static inline void conn_get(struct _connection *_conn)
+{
+ atomic_inc(&_conn->refs);
+}
+
+static inline void conn_close(struct _connection *_conn)
+{
+ struct vbus_connection *conn = _conn->conn;
+
+ if (conn->ops->close)
+ conn->ops->close(conn);
+
+ _conn->closed = true;
+}
+
+static inline void conn_put(struct _connection *_conn)
+{
+ if (atomic_dec_and_test(&_conn->refs)) {
+ struct _signal *_signal, *tmp;
+
+ if (!_conn->closed)
+ conn_close(_conn);
+
+ list_for_each_entry_safe(_signal, tmp, &_conn->signals,
+ list) {
+ list_del(&_signal->list);
+ _signal_put(_signal);
+ }
+
+ vbus_connection_put(_conn->conn);
+ kfree(_conn);
+ }
+}
+
+struct _client {
+ struct mutex lock;
+ struct map conn_map;
+ struct map signal_map;
+ struct vbus *vbus;
+ struct vbus_client client;
+};
+
+struct _connection *to_conn(struct rb_node *node)
+{
+ return node ? container_of(node, struct _connection, node) : NULL;
+}
+
+static struct _signal *to_signal(struct rb_node *node)
+{
+ return node ? container_of(node, struct _signal, node) : NULL;
+}
+
+static struct _client *to_client(struct vbus_client *client)
+{
+ return container_of(client, struct _client, client);
+}
+
+static struct _connection *
+connection_find(struct _client *c, unsigned long devid)
+{
+ struct _connection *_conn;
+
+ /*
+ * We could, in theory, cast devid to _conn->node, but this would
+ * be pretty stupid to trust. Therefore, we must validate that
+ * the pointer is legit by seeing if it exists in our conn_map
+ */
+
+ mutex_lock(&c->lock);
+
+ _conn = to_conn(map_find(&c->conn_map, &devid));
+ if (likely(_conn))
+ conn_get(_conn);
+
+ mutex_unlock(&c->lock);
+
+ return _conn;
+}
+
+static int
+_deviceopen(struct vbus_client *client, struct vbus_memctx *ctx,
+ __u32 devid, __u32 version, __u64 *devh)
+{
+ struct _client *c = to_client(client);
+ struct vbus_connection *conn;
+ struct _connection *_conn;
+ struct vbus_device_interface *intf = NULL;
+ int ret;
+
+ /*
+ * We only get here if the device has never been opened before,
+ * so we need to create a new connection
+ */
+ ret = vbus_interface_find(c->vbus, devid, &intf);
+ if (ret < 0)
+ return ret;
+
+ ret = intf->ops->open(intf, ctx, version, &conn);
+ kobject_put(&intf->kobj);
+ if (ret < 0)
+ return ret;
+
+ _conn = kzalloc(sizeof(*_conn), GFP_KERNEL);
+ if (!_conn) {
+ vbus_connection_put(conn);
+ return -ENOMEM;
+ }
+
+ atomic_set(&_conn->refs, 1);
+ _conn->conn = conn;
+
+ INIT_LIST_HEAD(&_conn->signals);
+
+ mutex_lock(&c->lock);
+ ret = map_add(&c->conn_map, &_conn->node);
+ mutex_unlock(&c->lock);
+
+ if (ret < 0) {
+ conn_put(_conn);
+ return ret;
+ }
+
+ /* in theory, &_conn->node should be unique */
+ *devh = (__u64)&_conn->node;
+
+ return 0;
+
+}
+
+/*
+ * Assumes client->lock is held (or we are releasing and dont need to lock)
+ */
+static void
+conn_del(struct _client *c, struct _connection *_conn)
+{
+ struct _signal *_signal, *tmp;
+
+ /* Delete and release each opened queue */
+ list_for_each_entry_safe(_signal, tmp, &_conn->signals, list) {
+ map_del(&c->signal_map, &_signal->node);
+ _signal_put(_signal);
+ }
+
+ map_del(&c->conn_map, &_conn->node);
+}
+
+static int
+_deviceclose(struct vbus_client *client, __u64 devh)
+{
+ struct _client *c = to_client(client);
+ struct _connection *_conn;
+
+ mutex_lock(&c->lock);
+
+ _conn = to_conn(map_find(&c->conn_map, &devh));
+ if (likely(_conn))
+ conn_del(c, _conn);
+
+ mutex_unlock(&c->lock);
+
+ if (unlikely(!_conn))
+ return -ENOENT;
+
+ conn_close(_conn);
+
+ /* this _put is the compliment to the _get performed at _deviceopen */
+ conn_put(_conn);
+
+ return 0;
+}
+
+static int
+_devicecall(struct vbus_client *client,
+ __u64 devh, __u32 func, void *data, __u32 len, __u32 flags)
+{
+ struct _client *c = to_client(client);
+ struct _connection *_conn;
+ struct vbus_connection *conn;
+ int ret;
+
+ _conn = connection_find(c, devh);
+ if (!_conn)
+ return -ENOENT;
+
+ conn = _conn->conn;
+
+ ret = conn->ops->call(conn, func, data, len, flags);
+
+ conn_put(_conn);
+
+ return ret;
+}
+
+static int
+_deviceshm(struct vbus_client *client,
+ __u64 devh,
+ __u32 id,
+ struct vbus_shm *shm,
+ struct shm_signal *signal,
+ __u32 flags,
+ __u64 *handle)
+{
+ struct _client *c = to_client(client);
+ struct _signal *_signal = NULL;
+ struct _connection *_conn;
+ struct vbus_connection *conn;
+ int ret;
+
+ *handle = 0;
+
+ _conn = connection_find(c, devh);
+ if (!_conn)
+ return -ENOENT;
+
+ conn = _conn->conn;
+
+ ret = conn->ops->shm(conn, id, shm, signal, flags);
+ if (ret < 0) {
+ conn_put(_conn);
+ return ret;
+ }
+
+ if (signal) {
+ _signal = kzalloc(sizeof(*_signal), GFP_KERNEL);
+ if (!_signal) {
+ conn_put(_conn);
+ return -ENOMEM;
+ }
+
+ /* one for map-ref, one for list-ref */
+ atomic_set(&_signal->refs, 2);
+ _signal->signal = signal;
+ shm_signal_get(signal);
+
+ mutex_lock(&c->lock);
+ ret = map_add(&c->signal_map, &_signal->node);
+ list_add_tail(&_signal->list, &_conn->signals);
+ mutex_unlock(&c->lock);
+
+ if (!ret)
+ *handle = (__u64)&_signal->node;
+ }
+
+ conn_put(_conn);
+
+ return 0;
+}
+
+static int
+_shmsignal(struct vbus_client *client, __u64 handle)
+{
+ struct _client *c = to_client(client);
+ struct _signal *_signal;
+
+ mutex_lock(&c->lock);
+
+ _signal = to_signal(map_find(&c->signal_map, &handle));
+ if (likely(_signal))
+ _signal_get(_signal);
+
+ mutex_unlock(&c->lock);
+
+ if (!_signal)
+ return -ENOENT;
+
+ _shm_signal_wakeup(_signal->signal);
+
+ _signal_put(_signal);
+
+ return 0;
+}
+
+static void
+_release(struct vbus_client *client)
+{
+ struct _client *c = to_client(client);
+ struct rb_node *node;
+
+ /* Drop all of our open connections */
+ while ((node = rb_first(&c->conn_map.root))) {
+ struct _connection *_conn = to_conn(node);
+
+ conn_del(c, _conn);
+ conn_put(_conn);
+ }
+
+ vbus_put(c->vbus);
+ kfree(c);
+}
+
+struct vbus_client_ops _client_ops = {
+ .deviceopen = _deviceopen,
+ .deviceclose = _deviceclose,
+ .devicecall = _devicecall,
+ .deviceshm = _deviceshm,
+ .shmsignal = _shmsignal,
+ .release = _release,
+};
+
+struct vbus_client *vbus_client_attach(struct vbus *vbus)
+{
+ struct _client *c;
+
+ BUG_ON(!vbus);
+
+ c = kzalloc(sizeof(*c), GFP_KERNEL);
+ if (!c)
+ return NULL;
+
+ atomic_set(&c->client.refs, 1);
+ c->client.ops = &_client_ops;
+
+ mutex_init(&c->lock);
+ map_init(&c->conn_map, &nodeptr_map_ops);
+ map_init(&c->signal_map, &nodeptr_map_ops);
+ c->vbus = vbus_get(vbus);
+
+ return &c->client;
+}
+EXPORT_SYMBOL_GPL(vbus_client_attach);
+
+/*
+ * memory context helpers
+ */
+
+static unsigned long
+current_memctx_copy_to(struct vbus_memctx *ctx, void *dst, const void *src,
+ unsigned long len)
+{
+ return copy_to_user(dst, src, len);
+}
+
+static unsigned long
+current_memctx_copy_from(struct vbus_memctx *ctx, void *dst, const void *src,
+ unsigned long len)
+{
+ return copy_from_user(dst, src, len);
+}
+
+static void
+current_memctx_release(struct vbus_memctx *ctx)
+{
+ panic("dropped last reference to current_memctx");
+}
+
+static struct vbus_memctx_ops current_memctx_ops = {
+ .copy_to = &current_memctx_copy_to,
+ .copy_from = &current_memctx_copy_from,
+ .release = &current_memctx_release,
+};
+
+static struct vbus_memctx _current_memctx =
+ VBUS_MEMCTX_INIT((&current_memctx_ops));
+
+struct vbus_memctx *current_memctx = &_current_memctx;
+
+/*
+ * task_mem allows you to have a copy_from_user/copy_to_user like
+ * environment, except that it supports copying to tasks other
+ * than "current" as ctu/cfu() do
+ */
+struct task_memctx {
+ struct task_struct *task;
+ struct vbus_memctx ctx;
+};
+
+static struct task_memctx *to_task_memctx(struct vbus_memctx *ctx)
+{
+ return container_of(ctx, struct task_memctx, ctx);
+}
+
+static unsigned long
+task_memctx_copy_to(struct vbus_memctx *ctx, void *dst, const void *src,
+ unsigned long n)
+{
+ struct task_memctx *tm = to_task_memctx(ctx);
+ struct task_struct *p = tm->task;
+
+ while (n) {
+ unsigned long offset = ((unsigned long)dst)%PAGE_SIZE;
+ unsigned long len = PAGE_SIZE - offset;
+ int ret;
+ struct page *pg;
+ void *maddr;
+
+ if (len > n)
+ len = n;
+
+ down_read(&p->mm->mmap_sem);
+ ret = get_user_pages(p, p->mm,
+ (unsigned long)dst, 1, 1, 0, &pg, NULL);
+
+ if (ret != 1) {
+ up_read(&p->mm->mmap_sem);
+ break;
+ }
+
+ maddr = kmap_atomic(pg, KM_USER0);
+ memcpy(maddr + offset, src, len);
+ kunmap_atomic(maddr, KM_USER0);
+ set_page_dirty_lock(pg);
+ put_page(pg);
+ up_read(&p->mm->mmap_sem);
+
+ src += len;
+ dst += len;
+ n -= len;
+ }
+
+ return n;
+}
+
+static unsigned long
+task_memctx_copy_from(struct vbus_memctx *ctx, void *dst, const void *src,
+ unsigned long n)
+{
+ struct task_memctx *tm = to_task_memctx(ctx);
+ struct task_struct *p = tm->task;
+
+ while (n) {
+ unsigned long offset = ((unsigned long)src)%PAGE_SIZE;
+ unsigned long len = PAGE_SIZE - offset;
+ int ret;
+ struct page *pg;
+ void *maddr;
+
+ if (len > n)
+ len = n;
+
+ down_read(&p->mm->mmap_sem);
+ ret = get_user_pages(p, p->mm,
+ (unsigned long)src, 1, 1, 0, &pg, NULL);
+
+ if (ret != 1) {
+ up_read(&p->mm->mmap_sem);
+ break;
+ }
+
+ maddr = kmap_atomic(pg, KM_USER0);
+ memcpy(dst, maddr + offset, len);
+ kunmap_atomic(maddr, KM_USER0);
+ put_page(pg);
+ up_read(&p->mm->mmap_sem);
+
+ src += len;
+ dst += len;
+ n -= len;
+ }
+
+ return n;
+}
+
+static void
+task_memctx_release(struct vbus_memctx *ctx)
+{
+ struct task_memctx *tm = to_task_memctx(ctx);
+
+ put_task_struct(tm->task);
+ kfree(tm);
+}
+
+static struct vbus_memctx_ops task_memctx_ops = {
+ .copy_to = &task_memctx_copy_to,
+ .copy_from = &task_memctx_copy_from,
+ .release = &task_memctx_release,
+};
+
+struct vbus_memctx *task_memctx_alloc(struct task_struct *task)
+{
+ struct task_memctx *tm;
+
+ tm = kzalloc(sizeof(*tm), GFP_KERNEL);
+ if (!tm)
+ return NULL;
+
+ get_task_struct(task);
+
+ tm->task = task;
+ vbus_memctx_init(&tm->ctx, &task_memctx_ops);
+
+ return &tm->ctx;
+}
+EXPORT_SYMBOL_GPL(task_memctx_alloc);