[Lguest] [PATCH 2/2] lguest: Implement the merged receive buffers allocation scheme

Mark McLoughlin markmc at redhat.com
Thu Oct 9 06:35:08 EST 2008


Implement support for using receive buffers less than the maximum
packet size by merging the smaller buffers together as needed.

Signed-off-by: Mark McLoughlin <markmc at redhat.com>
Cc: Herbert Xu <herbert.xu at redhat.com>

---
 Documentation/lguest/lguest.c |   98 +++++++++++++++++++++++++++++++++-------
 1 files changed, 81 insertions(+), 17 deletions(-)

diff --git a/Documentation/lguest/lguest.c b/Documentation/lguest/lguest.c
index 2ddf0c6..da934c2 100644
--- a/Documentation/lguest/lguest.c
+++ b/Documentation/lguest/lguest.c
@@ -68,6 +68,8 @@ typedef uint8_t u8;
 #define DEVICE_PAGES 256
 /* This will occupy 3 pages: it must be a power of 2. */
 #define VIRTQUEUE_NUM 256
+/* The largest MAX_SKB_FRAGS pages supported. */
+#define NET_MAX_RECV_PAGES 18
 
 /*L:120 verbose is both a global flag and a macro.  The C preprocessor allows
  * this, and although I wouldn't recommend it, it works quite nicely here. */
@@ -970,16 +972,15 @@ static void handle_net_output(int fd, struct virtqueue *vq, bool timeout)
 	}
 }
 
-/* This is where we handle a packet coming in from the tun device to our
- * Guest. */
-static bool handle_tun_input(int fd, struct device *dev)
+/* Network receive buffers are odd creatures; the Guest usually gives us single
+ * page buffers and - if we're handling big packets - we use multiple buffers per
+ * packet. */
+static unsigned int get_net_recv_head(struct device *dev, struct iovec *iov,
+				      unsigned int *in_num)
 {
-	unsigned int head, in_num, out_num;
-	int len;
-	struct iovec iov[dev->vq->vring.num];
+	unsigned int head, out_num;
 
-	/* First we need a network buffer from the Guests's recv virtqueue. */
-	head = get_vq_desc(dev->vq, iov, &out_num, &in_num);
+	head = get_vq_desc(dev->vq, iov, &out_num, in_num);
 	if (head == dev->vq->vring.num) {
 		/* Now, it's expected that if we try to send a packet too
 		 * early, the Guest won't be ready yet.  Wait until the device
@@ -989,23 +990,85 @@ static bool handle_tun_input(int fd, struct device *dev)
 		/* Now tell it we want to know if new things appear. */
 		dev->vq->vring.used->flags &= ~VRING_USED_F_NO_NOTIFY;
 		wmb();
-
-		/* We'll turn this back on if input buffers are registered. */
-		return false;
 	} else if (out_num)
 		errx(1, "Output buffers in network recv queue?");
 
+	return head;
+}
+
+/* Here we add used recv buffers to the used queue but, also, return unused
+ * buffers to the avail queue. */
+static void add_net_recv_used(struct device *dev, unsigned int *heads,
+			      int *bufsizes, int nheads, int used_len)
+{
+	int len, idx;
+
+	/* Add the buffers we've actually used to the used queue */
+	len = idx = 0;
+	while (len < used_len) {
+		add_used(dev->vq, heads[idx], used_len, idx);
+		len += bufsizes[idx++];
+	}
+
+	/* Return the rest of them back to the avail queue */
+	lg_last_avail(dev->vq) -= nheads - idx;
+	dev->vq->inflight      -= nheads - idx;
+
+	/* Finally, tell the other side */
+	flush_used(dev->vq, idx);
+}
+
+/* This is where we handle a packet coming in from the tun device to our
+ * Guest. */
+static bool handle_tun_input(int fd, struct device *dev)
+{
+	struct iovec iov[dev->vq->vring.num];
+	unsigned int heads[NET_MAX_RECV_PAGES];
+	int bufsizes[NET_MAX_RECV_PAGES];
+	int nheads, len, iovcnt;
+
+	nheads = len = iovcnt = 0;
+
+	/* First we need enough network buffers from the Guests's recv
+	 * virtqueue for the largest possible packet. */
+	while (len < (getpagesize() * NET_MAX_RECV_PAGES)) {
+		unsigned int in_num;
+
+		heads[nheads] = get_net_recv_head(dev, &iov[iovcnt], &in_num);
+		if (heads[nheads] == dev->vq->vring.num) {
+			/* It's all our nothing - give these back again */
+			lg_last_avail(dev->vq) -= nheads;
+			dev->vq->inflight      -= nheads;
+
+			/* We'll turn this back on if input buffers are
+			 * registered. */
+			return false;
+		}
+
+		bufsizes[nheads] = 0;
+		while (in_num--)
+			bufsizes[nheads] += iov[iovcnt++].iov_len;
+
+		/* Enforce a minimum of a page per buffer */
+		if (bufsizes[nheads] < getpagesize())
+			errx(1, "Network receive buffer too small");
+
+		len += bufsizes[nheads++];
+	}
+
 	/* Read the packet from the device directly into the Guest's buffer. */
-	len = readv(dev->fd, iov, in_num);
+	len = readv(dev->fd, iov, iovcnt);
 	if (len <= 0)
 		err(1, "reading network");
 
-	/* Tell the Guest about the new packet. */
-	add_used_and_trigger(fd, dev->vq, head, len);
+	/* Return unused buffers to the recv queue */
+	add_net_recv_used(dev, heads, bufsizes, nheads, len);
+
+	/* Fire in the hole ! */
+	trigger_irq(fd, dev->vq);
 
-	verbose("tun input packet len %i [%02x %02x] (%s)\n", len,
-		((u8 *)iov[1].iov_base)[0], ((u8 *)iov[1].iov_base)[1],
-		head != dev->vq->vring.num ? "sent" : "discarded");
+	verbose("tun input packet len %i [%02x %02x] sent\n", len,
+		((u8 *)iov[0].iov_base)[10], ((u8 *)iov[0].iov_base)[11]);
 
 	/* All good. */
 	return true;
@@ -1561,6 +1624,7 @@ static void setup_tun_net(char *arg)
 	add_feature(dev, VIRTIO_NET_F_HOST_TSO4);
 	add_feature(dev, VIRTIO_NET_F_HOST_TSO6);
 	add_feature(dev, VIRTIO_NET_F_HOST_ECN);
+	add_feature(dev, VIRTIO_NET_F_MRG_RXBUF);
 	set_config(dev, sizeof(conf), &conf);
 
 	/* We don't need the socket any more; setup is done. */
-- 
1.5.4.3




More information about the Lguest mailing list