tun: vringfd xmit support.

This patch modifies tun to allow a vringfd to specify the xmit
buffer.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
---
 drivers/net/tun.c      |  100 ++++++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/if_tun.h |    1 
 2 files changed, 99 insertions(+), 2 deletions(-)

diff -r 18bb976a1eb3 drivers/net/tun.c
--- a/drivers/net/tun.c	Mon Aug 25 10:14:36 2008 +1000
+++ b/drivers/net/tun.c	Mon Aug 25 10:15:03 2008 +1000
@@ -64,6 +64,7 @@
 #include <linux/vring.h>
 #include <linux/virtio_net.h>
 #include <linux/file.h>
+#include <linux/spinlock.h>
 #include <net/net_namespace.h>
 #include <net/netns/generic.h>
 
@@ -105,8 +106,10 @@ struct tun_struct {
 
 	struct tap_filter       txflt;
 
-	struct vring_info	*inring;
-	struct file		*infile;
+	/* List for user-mapped skbs successfully transmitted */
+	struct vring_info	*inring, *outring;
+	struct file		*infile, *outfile;
+	bool			outring_progress;
 
 #ifdef TUN_DEBUG
 	int debug;
@@ -737,6 +740,58 @@ static struct vring_ops recvops = {
 	.pull = pull_recv_skbs,
 };
 
+/* Returns whether there are queued buffers */
+static bool finished_xmit_buffers(void *_tun)
+{
+	struct tun_struct *tun = _tun;
+
+	return tun->outring_progress;
+}
+
+/* Returns 0, or negative errno. */
+static int pull_finished_buffers(void *_tun)
+{
+	struct tun_struct *tun = _tun;
+
+	/* Simply clear progress flag. */
+	tun->outring_progress = false;
+	return 0;
+}
+
+static int xmit_packets(void *_tun)
+{
+	struct tun_struct *tun = _tun;
+	struct iovec iov[2+MAX_SKB_FRAGS];
+	int id;
+	unsigned long len;
+
+	while ((id = vring_get_buffer(tun->outring, NULL, 0, NULL,
+				      iov, ARRAY_SIZE(iov), &len)) > 0) {
+		ssize_t err;
+
+		err = tun_get_user(tun, iov, len);
+		if (unlikely(err < 0))
+			return err;
+
+		err = vring_used_buffer(tun->outring, id, 0);
+		if (unlikely(err))
+			return err;
+
+		tun->outring_progress = true;
+	}
+
+	if (tun->outring_progress)
+		vring_wake(tun->outring);
+
+	return 0;
+}
+
+static struct vring_ops xmitops = {
+	.push = xmit_packets,
+	.can_pull = finished_xmit_buffers,
+	.pull = pull_finished_buffers,
+};
+
 static int set_recv_vring(struct tun_struct *tun, int fd)
 {
 	int err;
@@ -773,9 +828,47 @@ static void unset_vrings(struct tun_stru
 		vring_unset_ops(tun->inring);
 		fput(tun->infile);
 	}
+	if (tun->outring) {
+		vring_unset_ops(tun->outring);
+		fput(tun->outfile);
+	}
+}
+
+static int set_xmit_vring(struct tun_struct *tun, int fd)
+{
+	int err;
+
+	if (tun->outring)
+		return -EBUSY;
+
+	tun->outfile = fget(fd);
+	if (!tun->outfile)
+		return -EBADF;
+
+	tun->outring = vring_get(tun->outfile);
+	if (!tun->outring) {
+		err = -EBADF;
+		goto put;
+	}
+
+	err = vring_set_ops(tun->outring, &xmitops, tun);
+	if (err) {
+		tun->outring = NULL;
+		goto put;
+	}
+	return 0;
+
+put:
+	fput(tun->outfile);
+	tun->outfile = NULL;
+	return err;
 }
 #else /* ... !CONFIG_VRING */
 static int set_recv_vring(struct tun_struct *tun, int fd)
+{
+	return -ENOTTY;
+}
+static int set_xmit_vring(struct tun_struct *tun, int fd)
 {
 	return -ENOTTY;
 }
@@ -1124,6 +1217,9 @@ static int tun_chr_ioctl(struct inode *i
 	case TUNSETRECVVRING:
 		return set_recv_vring(tun, arg);
 
+	case TUNSETXMITVRING:
+		return set_xmit_vring(tun, arg);
+
 	case SIOCGIFHWADDR:
 		/* Get hw addres */
 		memcpy(ifr.ifr_hwaddr.sa_data, tun->dev->dev_addr, ETH_ALEN);
diff -r 18bb976a1eb3 include/linux/if_tun.h
--- a/include/linux/if_tun.h	Mon Aug 25 10:14:36 2008 +1000
+++ b/include/linux/if_tun.h	Mon Aug 25 10:15:03 2008 +1000
@@ -47,6 +47,7 @@
 #define TUNSETTXFILTER _IOW('T', 209, unsigned int)
 #define TUNGETIFF      _IOR('T', 210, unsigned int)
 #define TUNSETRECVVRING _IOW('T', 211, int)
+#define TUNSETXMITVRING _IOW('T', 212, int)
 
 /* TUNSETIFF ifr flags */
 #define IFF_TUN		0x0001
