diff mbox

[RFC,v2,03/19] vbus: add connection-client helper infrastructure

Message ID 20090409163057.32740.27829.stgit@dev.haskins.net
State RFC, archived
Delegated to: David Miller
Headers show

Commit Message

Gregory Haskins April 9, 2009, 4:30 p.m. UTC
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


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

Comments

Michael S. Tsirkin June 4, 2009, 6:06 p.m. UTC | #1
On Thu, Apr 09, 2009 at 12:30:57PM -0400, Gregory Haskins wrote:
> +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;
> +}

BTW, why did you decide to use get_user_pages?
Would switch_mm + copy_to_user work as well
avoiding page walk if all pages are present?

Also - if we just had vmexit because a process executed
io (or hypercall), can't we just do copy_to_user there?
Avi, I think at some point you said that we can?
Gregory Haskins June 4, 2009, 6:18 p.m. UTC | #2
Michael S. Tsirkin wrote:
> On Thu, Apr 09, 2009 at 12:30:57PM -0400, Gregory Haskins wrote:
>   
>> +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;
>> +}
>>     
>
> BTW, why did you decide to use get_user_pages?
> Would switch_mm + copy_to_user work as well
> avoiding page walk if all pages are present?
>   

Well, basic c_t_u() won't work because its likely not "current" if you
are updating the ring from some other task, but I think you have already
figured that out based on the switch_mm suggestion.  The simple truth is
I was not familiar with switch_mm at the time I wrote this (nor am I
now).  If this is a superior method that allows you to acquire
c_t_u(some_other_ctx) like behavior, I see no problem in changing.  I
will look into this, and thanks for the suggestion!

> Also - if we just had vmexit because a process executed
> io (or hypercall), can't we just do copy_to_user there?
> Avi, I think at some point you said that we can?
>   

Right, and yes that will work I believe.  We could always do a "if (p ==
current)" check to test for this.  To date, I don't typically do
anything mem-ops related directly in vcpu context so this wasn't an
issue...but that doesn't mean someone wont try in the future.  
Therefore, I agree we should strive to optimize it if we can.
>
>   

Thanks Michael,
-Greg
Avi Kivity June 4, 2009, 6:23 p.m. UTC | #3
Michael S. Tsirkin wrote:
> Also - if we just had vmexit because a process executed
> io (or hypercall), can't we just do copy_to_user there?
> Avi, I think at some point you said that we can?
>   

You can do copy_to_user() whereever it is legal in Linux.  Almost all of 
kvm runs in process context, preemptible, and with interrupts enabled.
Avi Kivity June 4, 2009, 6:24 p.m. UTC | #4
Gregory Haskins wrote:

    

>> BTW, why did you decide to use get_user_pages?
>> Would switch_mm + copy_to_user work as well
>> avoiding page walk if all pages are present?
>>   
>>     
>
> Well, basic c_t_u() won't work because its likely not "current" if you
> are updating the ring from some other task, but I think you have already
> figured that out based on the switch_mm suggestion.  The simple truth is
> I was not familiar with switch_mm at the time I wrote this (nor am I
> now).  If this is a superior method that allows you to acquire
> c_t_u(some_other_ctx) like behavior, I see no problem in changing.  I
> will look into this, and thanks for the suggestion!
>   

copy_to_user() is significantly faster than get_user_pages() + kmap() + 
memcmp() (or their variants).
Gregory Haskins June 4, 2009, 6:30 p.m. UTC | #5
Avi Kivity wrote:
> Gregory Haskins wrote:
>
>   
>>> BTW, why did you decide to use get_user_pages?
>>> Would switch_mm + copy_to_user work as well
>>> avoiding page walk if all pages are present?
>>>       
>>
>> Well, basic c_t_u() won't work because its likely not "current" if you
>> are updating the ring from some other task, but I think you have already
>> figured that out based on the switch_mm suggestion.  The simple truth is
>> I was not familiar with switch_mm at the time I wrote this (nor am I
>> now).  If this is a superior method that allows you to acquire
>> c_t_u(some_other_ctx) like behavior, I see no problem in changing.  I
>> will look into this, and thanks for the suggestion!
>>   
>
> copy_to_user() is significantly faster than get_user_pages() + kmap()
> + memcmp() (or their variants).
>

Oh, I don't doubt that (in fact, I was pretty sure that was the case
based on some of the optimizations I could see in studying the c_t_u()
path).  I just didn't realize there were other ways to do it if its a
non "current" task. ;)

I guess the enigma for me right now is what cost does switch_mm have? 
(Thats not a slam against the suggested approach...I really do not know
and am curious).

As an aside, note that we seem to be reviewing v2, where v3 is really
the last set I pushed.  I think this patch is more or less the same
across both iterations, but FYI that I would recommend looking at v3
instead.

-Greg
Avi Kivity June 4, 2009, 7:04 p.m. UTC | #6
Gregory Haskins wrote:
> Oh, I don't doubt that (in fact, I was pretty sure that was the case
> based on some of the optimizations I could see in studying the c_t_u()
> path).  I just didn't realize there were other ways to do it if its a
> non "current" task. ;)
>
> I guess the enigma for me right now is what cost does switch_mm have? 
> (Thats not a slam against the suggested approach...I really do not know
> and am curious).
>   

switch_mm() is probably very cheap (reloads cr3), but it does dirty the 
current cpu's tlb.  When the kernel needs to flush a process' tlb, it 
will have to IPI that cpu in addition to all others.  This takes place, 
for example, after munmap() or after a page is swapped out (though 
significant batching is done there).

It's still plenty cheaper in my estimation.
diff mbox

Patch

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);