Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > 27922b4260f65d317aabda37e42bbbff > files > 3214

kernel-2.6.18-238.el5.src.rpm

From: Anthony Liguori <aliguori@redhat.com>
Date: Sat, 25 Sep 2010 02:18:48 -0400
Subject: [net] virtio_net: defer skb allocation in receive path
Message-id: <1285381128-9565-1-git-send-email-aliguori@redhat.com>
Patchwork-id: 28412
O-Subject: [RHEL5.6 PATCH] virtio_net: Defer skb allocation in receive path
Bugzilla: 565560
RH-Acked-by: Thomas Graf <tgraf@redhat.com>
RH-Acked-by: David S. Miller <davem@redhat.com>
RH-Acked-by: Jes Sorensen <Jes.Sorensen@redhat.com>

BZ#565560
Brew build: https://brewweb.devel.redhat.com/taskinfo?taskID=2779705

This is the combination of three commits.  One was a bug fix to a previous patch
that caused networking to stall.  The other one is almost empty because of
another backport so I combined them to make bisecting more sane.

This has been tested on top of a RHEL5.5 KVM with a RHEL5.6 guest under heavy
network load.

Upstream commits:

  virtio_net: missing sg_init_table

  Add missing sg_init_table for sg_set_buf in virtio_net which
  induced in defer skb patch.

  Reported-by: Thomas Müller <thomas@mathtm.de>
  Tested-by: Thomas Müller <thomas@mathtm.de>
  Signed-off-by: Shirley Ma <xma@us.ibm.com>
  Signed-off-by: David S. Miller <davem@davemloft.net>

  virtio_net: remove send queue

  Now we have a virtio detach API (in commit
  f9bfbebf34eab707b065116cdc9699d25ba4252a), we don't need to track xmit
  skbs in the virio_net driver, which improves transmission performance.

  Signed-off-by: Shirley Ma <xma@us.ibm.com>
  Acked-by: Rusty Russell <rusty@rustcorp.com.au>
  Acked-by: Michael S. Tsirkin <mst@redhat.com>
  Signed-off-by: David S. Miller <davem@davemloft.net>

  virtio_net: Defer skb allocation in receive path Date: Wed, 13 Jan 2010 12:5

  virtio_net receives packets from its pre-allocated vring buffers, then it
  delivers these packets to upper layer protocols as skb buffs. So it's not
  necessary to pre-allocate skb for each mergable buffer, then frees extra
  skbs when buffers are merged into a large packet. This patch has deferred
  skb allocation in receiving packets for both big packets and mergeable buffe
  to reduce skb pre-allocations and skb frees. It frees unused buffers by call
  detach_unused_buf in vring, so recv skb queue is not needed.

  Signed-off-by: Shirley Ma <xma@us.ibm.com>
  Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
  Signed-off-by: David S. Miller <davem@davemloft.net>

Signed-off-by: Shirley Ma <xma@us.ibm.com>
Signed-off-by: Anthony Liguori <aliguori@redhat.com>
Signed-off-by: Jarod Wilson <jarod@redhat.com>

diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index d80ef30..89c9835 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -63,10 +63,6 @@ struct virtnet_info
 	/* Host will merge rx buffers for big packets (shake it! shake it!) */
 	bool mergeable_rx_bufs;
 
-	/* Receive & send queues. */
-	struct sk_buff_head recv;
-	struct sk_buff_head send;
-
 	/* Timer for refilling if we run low on memory. */
 	struct timer_list refill;
 
@@ -76,34 +72,52 @@ struct virtnet_info
 	struct net_device_stats stats;
 };
 
-static inline void *skb_vnet_hdr(struct sk_buff *skb)
-{
-	return (struct virtio_net_hdr *)skb->cb;
-}
+struct skb_vnet_hdr {
+	union {
+		struct virtio_net_hdr hdr;
+		struct virtio_net_hdr_mrg_rxbuf mhdr;
+	};
+};
 
-static void give_a_page(struct virtnet_info *vi, struct page *page)
+struct padded_vnet_hdr {
+	struct virtio_net_hdr hdr;
+	/*
+	 * virtio_net_hdr should be in a separated sg buffer because of a
+	 * QEMU bug, and data sg buffer shares same page with this header sg.
+	 * This padding makes next sg 16 byte aligned after virtio_net_hdr.
+	 */
+	char padding[6];
+};
+
+static inline struct skb_vnet_hdr *skb_vnet_hdr(struct sk_buff *skb)
 {
-	page->private = (unsigned long)vi->pages;
-	vi->pages = page;
+	return (struct skb_vnet_hdr *)skb->cb;
 }
 
-static void trim_pages(struct virtnet_info *vi, struct sk_buff *skb)
+/*
+ * private is used to chain pages for big packets, put the whole
+ * most recent used list in the beginning for reuse
+ */
+static void give_pages(struct virtnet_info *vi, struct page *page)
 {
-	unsigned int i;
+	struct page *end;
 
-	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
-		give_a_page(vi, skb_shinfo(skb)->frags[i].page);
-	skb_shinfo(skb)->nr_frags = 0;
-	skb->data_len = 0;
+	/* Find end of list, sew whole thing into vi->pages. */
+	for (end = page; end->private; end = (struct page *)end->private);
+	end->private = (unsigned long)vi->pages;
+	vi->pages = page;
 }
 
+
 static struct page *get_a_page(struct virtnet_info *vi, gfp_t gfp_mask)
 {
 	struct page *p = vi->pages;
 
-	if (p)
+	if (p) {
 		vi->pages = (struct page *)p->private;
-	else
+		/* clear private here, it is used to chain pages */
+		p->private = 0;
+	} else
 		p = alloc_page(gfp_mask);
 	return p;
 }
@@ -123,114 +137,158 @@ static void skb_xmit_done(struct virtqueue *svq)
 	tasklet_schedule(&vi->tasklet);
 }
 
-static void receive_skb(struct net_device *dev, struct sk_buff *skb,
-			unsigned len)
+static void set_skb_frag(struct sk_buff *skb, struct page *page,
+			 unsigned int offset, unsigned int *len)
 {
-	struct virtnet_info *vi = netdev_priv(dev);
-	struct virtio_net_hdr *hdr = skb_vnet_hdr(skb);
-	int err;
-	int i;
+	int i = skb_shinfo(skb)->nr_frags;
+	skb_frag_t *f;
+
+	f = &skb_shinfo(skb)->frags[i];
+	f->size = min((unsigned)PAGE_SIZE - offset, *len);
+	f->page_offset = offset;
+	f->page = page;
+
+	skb->data_len += f->size;
+	skb->len += f->size;
+	skb_shinfo(skb)->nr_frags++;
+	*len -= f->size;
+}
 
-	if (unlikely(len < sizeof(struct virtio_net_hdr) + ETH_HLEN)) {
-		pr_debug("%s: short packet %i\n", dev->name, len);
-		vi->stats.rx_length_errors++;
-		goto drop;
-	}
+static struct sk_buff *page_to_skb(struct virtnet_info *vi,
+				   struct page *page, unsigned int len)
+{
+	struct sk_buff *skb;
+	struct skb_vnet_hdr *hdr;
+	unsigned int copy, hdr_len, offset;
+	char *p;
 
-	if (vi->mergeable_rx_bufs) {
-		struct virtio_net_hdr_mrg_rxbuf *mhdr = skb_vnet_hdr(skb);
-		unsigned int copy;
-		char *p = page_address(skb_shinfo(skb)->frags[0].page);
+	p = page_address(page);
 
-		if (len > PAGE_SIZE)
-			len = PAGE_SIZE;
-		len -= sizeof(struct virtio_net_hdr_mrg_rxbuf);
+	/* copy small packet so we can reuse these pages for small data */
+	skb = netdev_alloc_skb(vi->dev, GOOD_COPY_LEN + NET_IP_ALIGN);
+	if (unlikely(!skb))
+		return NULL;
 
-		memcpy(hdr, p, sizeof(*mhdr));
-		p += sizeof(*mhdr);
+	skb_reserve(skb, NET_IP_ALIGN);
 
-		copy = len;
-		if (copy > skb_tailroom(skb))
-			copy = skb_tailroom(skb);
+	hdr = skb_vnet_hdr(skb);
 
-		memcpy(skb_put(skb, copy), p, copy);
+	if (vi->mergeable_rx_bufs) {
+		hdr_len = sizeof hdr->mhdr;
+		offset = hdr_len;
+	} else {
+		hdr_len = sizeof hdr->hdr;
+		offset = sizeof(struct padded_vnet_hdr);
+	}
 
-		len -= copy;
+	memcpy(hdr, p, hdr_len);
 
-		if (!len) {
-			give_a_page(vi, skb_shinfo(skb)->frags[0].page);
-			skb_shinfo(skb)->nr_frags--;
-		} else {
-			skb_shinfo(skb)->frags[0].page_offset +=
-				sizeof(*mhdr) + copy;
-			skb_shinfo(skb)->frags[0].size = len;
-			skb->data_len += len;
-			skb->len += len;
-		}
+	len -= hdr_len;
+	p += offset;
 
-		while (--mhdr->num_buffers) {
-			struct sk_buff *nskb;
+	copy = len;
+	if (copy > skb_tailroom(skb))
+		copy = skb_tailroom(skb);
+	memcpy(skb_put(skb, copy), p, copy);
 
-			i = skb_shinfo(skb)->nr_frags;
-			if (i >= MAX_SKB_FRAGS) {
-				pr_debug("%s: packet too long %d\n", dev->name,
-					 len);
-				vi->stats.rx_length_errors++;
-				goto drop;
-			}
+	len -= copy;
+	offset += copy;
 
-			nskb = vi->rvq->vq_ops->get_buf(vi->rvq, &len);
-			if (!nskb) {
-				pr_debug("%s: rx error: %d buffers missing\n",
-					 dev->name, mhdr->num_buffers);
-				vi->stats.rx_length_errors++;
-				goto drop;
-			}
+	while (len) {
+		set_skb_frag(skb, page, offset, &len);
+		page = (struct page *)page->private;
+		offset = 0;
+	}
 
-			__skb_unlink(nskb, &vi->recv);
-			vi->num--;
+	if (page)
+		give_pages(vi, page);
 
-			skb_shinfo(skb)->frags[i] = skb_shinfo(nskb)->frags[0];
-			skb_shinfo(nskb)->nr_frags = 0;
-			kfree_skb(nskb);
+	return skb;
+}
 
-			if (len > PAGE_SIZE)
-				len = PAGE_SIZE;
+static int receive_mergeable(struct virtnet_info *vi, struct sk_buff *skb)
+{
+	struct skb_vnet_hdr *hdr = skb_vnet_hdr(skb);
+	struct page *page;
+	int num_buf, i, len;
+
+	num_buf = hdr->mhdr.num_buffers;
+	while (--num_buf) {
+		i = skb_shinfo(skb)->nr_frags;
+		if (i >= MAX_SKB_FRAGS) {
+			pr_debug("%s: packet too long\n", skb->dev->name);
+			vi->stats.rx_length_errors++;
+			return -EINVAL;
+		}
 
-			skb_shinfo(skb)->frags[i].size = len;
-			skb_shinfo(skb)->nr_frags++;
-			skb->data_len += len;
-			skb->len += len;
+		page = vi->rvq->vq_ops->get_buf(vi->rvq, &len);
+		if (!page) {
+			pr_debug("%s: rx error: %d buffers missing\n",
+				 skb->dev->name, hdr->mhdr.num_buffers);
+			vi->stats.rx_length_errors++;
+			return -EINVAL;
 		}
-	} else {
-		len -= sizeof(struct virtio_net_hdr);
+		if (len > PAGE_SIZE)
+			len = PAGE_SIZE;
+
+		set_skb_frag(skb, page, 0, &len);
+
+		--vi->num;
+	}
+	return 0;
+}
 
-		if (len <= MAX_PACKET_LEN)
-			trim_pages(vi, skb);
+static void receive_buf(struct net_device *dev, void *buf, unsigned int len)
+{
+	struct virtnet_info *vi = netdev_priv(dev);
+	struct sk_buff *skb;
+	struct page *page;
+	struct skb_vnet_hdr *hdr;
+
+	if (unlikely(len < sizeof(struct virtio_net_hdr) + ETH_HLEN)) {
+		pr_debug("%s: short packet %i\n", dev->name, len);
+		vi->stats.rx_length_errors++;
+		if (vi->mergeable_rx_bufs || vi->big_packets)
+			give_pages(vi, buf);
+		else
+			dev_kfree_skb(buf);
+		return;
+	}
 
-		err = pskb_trim(skb, len);
-		if (err) {
-			pr_debug("%s: pskb_trim failed %i %d\n", dev->name,
-				 len, err);
+	if (!vi->mergeable_rx_bufs && !vi->big_packets) {
+		skb = buf;
+		len -= sizeof(struct virtio_net_hdr);
+		skb_trim(skb, len);
+	} else {
+		page = buf;
+		skb = page_to_skb(vi, page, len);
+		if (unlikely(!skb)) {
 			vi->stats.rx_dropped++;
-			goto drop;
+			give_pages(vi, page);
+			return;
 		}
+		if (vi->mergeable_rx_bufs)
+			if (receive_mergeable(vi, skb)) {
+				dev_kfree_skb(skb);
+				return;
+			}
 	}
 
+	hdr = skb_vnet_hdr(skb);
 	skb->truesize += skb->data_len;
 	vi->stats.rx_bytes += skb->len;
 	vi->stats.rx_packets++;
 
-	if (hdr->flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)
+	if (hdr->hdr.flags & VIRTIO_NET_HDR_F_NEEDS_CSUM)
 		skb->ip_summed = CHECKSUM_UNNECESSARY;
 
 	skb->protocol = eth_type_trans(skb, dev);
 	pr_debug("Receiving skb proto 0x%04x len %i type %i\n",
 		 ntohs(skb->protocol), skb->len, skb->pkt_type);
 
-	if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
+	if (hdr->hdr.gso_type != VIRTIO_NET_HDR_GSO_NONE) {
 		pr_debug("GSO!\n");
-		switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
+		switch (hdr->hdr.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
 		case VIRTIO_NET_HDR_GSO_TCPV4:
 			skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
 			break;
@@ -243,14 +301,14 @@ static void receive_skb(struct net_device *dev, struct sk_buff *skb,
 		default:
 			if (net_ratelimit())
 				printk(KERN_WARNING "%s: bad gso type %u.\n",
-				       dev->name, hdr->gso_type);
+				       dev->name, hdr->hdr.gso_type);
 			goto frame_err;
 		}
 
-		if (hdr->gso_type & VIRTIO_NET_HDR_GSO_ECN)
+		if (hdr->hdr.gso_type & VIRTIO_NET_HDR_GSO_ECN)
 			skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN;
 
-		skb_shinfo(skb)->gso_size = hdr->gso_size;
+		skb_shinfo(skb)->gso_size = hdr->hdr.gso_size;
 		if (skb_shinfo(skb)->gso_size == 0) {
 			if (net_ratelimit())
 				printk(KERN_WARNING "%s: zero gso size.\n",
@@ -268,112 +326,129 @@ static void receive_skb(struct net_device *dev, struct sk_buff *skb,
 
 frame_err:
 	vi->stats.rx_frame_errors++;
-drop:
 	dev_kfree_skb(skb);
 }
 
-static bool try_fill_recv_maxbufs(struct virtnet_info *vi, gfp_t gfp)
+static inline void sg_init_table(struct scatterlist *sgl, unsigned int nents)
 {
-	struct sk_buff *skb;
-	struct scatterlist sg[2+MAX_SKB_FRAGS];
-	int num, err, i;
-	bool oom = false;
-
-	for (;;) {
-		struct virtio_net_hdr *hdr;
+	memset(sgl, 0, sizeof(*sgl) * nents);
+}
 
-		skb = netdev_alloc_skb(vi->dev, MAX_PACKET_LEN);
-		if (unlikely(!skb)) {
-			oom = true;
-			break;
-		}
+static int add_recvbuf_small(struct virtnet_info *vi, gfp_t gfp)
+{
+	struct sk_buff *skb;
+	struct skb_vnet_hdr *hdr;
+	struct scatterlist sg[2];
+	int err;
 
-		skb_put(skb, MAX_PACKET_LEN);
+	sg_init_table(sg, 2);
+	skb = netdev_alloc_skb(vi->dev, MAX_PACKET_LEN + NET_IP_ALIGN);
+	if (unlikely(!skb))
+		return -ENOMEM;
+	
+	skb_reserve(skb, NET_IP_ALIGN);
+	skb_put(skb, MAX_PACKET_LEN);
 
-		hdr = skb_vnet_hdr(skb);
-		sg_init_one(sg, hdr, sizeof(*hdr));
+	hdr = skb_vnet_hdr(skb);
 
-		if (vi->big_packets) {
-			for (i = 0; i < MAX_SKB_FRAGS; i++) {
-				skb_frag_t *f = &skb_shinfo(skb)->frags[i];
-				f->page = get_a_page(vi, gfp);
-				if (!f->page)
-					break;
+	sg_init_one(sg, &hdr->hdr, sizeof hdr->hdr);
+	skb_to_sgvec(skb, sg + 1, 0, skb->len);
 
-				f->page_offset = 0;
-				f->size = PAGE_SIZE;
+	err = vi->rvq->vq_ops->add_buf(vi->rvq, sg, 0, 2, skb);
+	if (err < 0)
+		dev_kfree_skb(skb);
 
-				skb->data_len += PAGE_SIZE;
-				skb->len += PAGE_SIZE;
+	return err;
+}
 
-				skb_shinfo(skb)->nr_frags++;
-			}
+static int add_recvbuf_big(struct virtnet_info *vi, gfp_t gfp)
+{
+	struct scatterlist sg[MAX_SKB_FRAGS + 2];
+	struct page *first, *list = NULL;
+	char *p;
+	int i, err, offset;
+
+	sg_init_table(sg, MAX_SKB_FRAGS + 2);
+	/* page in sg[MAX_SKB_FRAGS + 1] is list tail */
+	for (i = MAX_SKB_FRAGS + 1; i > 1; --i) {
+		first = get_a_page(vi, gfp);
+		if (!first) {
+			if (list)
+				give_pages(vi, list);
+			return -ENOMEM;
 		}
+		sg_init_one(&sg[i], page_address(first), PAGE_SIZE);
 
-		num = skb_to_sgvec(skb, sg+1, 0, skb->len) + 1;
-		skb_queue_head(&vi->recv, skb);
+		/* chain new page in list head to match sg */
+		first->private = (unsigned long)list;
+		list = first;
+	}
 
-		err = vi->rvq->vq_ops->add_buf(vi->rvq, sg, 0, num, skb);
-		if (err < 0) {
-			skb_unlink(skb, &vi->recv);
-			trim_pages(vi, skb);
-			kfree_skb(skb);
-			break;
-		}
-		vi->num++;
+	first = get_a_page(vi, gfp);
+	if (!first) {
+		give_pages(vi, list);
+		return -ENOMEM;
 	}
-	if (unlikely(vi->num > vi->max))
-		vi->max = vi->num;
-	vi->rvq->vq_ops->kick(vi->rvq);
-	return !oom;
+	p = page_address(first);
+
+	/* sg[0], sg[1] share the same page */
+	/* a separated sg[0] for  virtio_net_hdr only during to QEMU bug*/
+	sg_init_one(&sg[0], p, sizeof(struct virtio_net_hdr));
+
+	/* sg[1] for data packet, from offset */
+	offset = sizeof(struct padded_vnet_hdr);
+	sg_init_one(&sg[1], p + offset, PAGE_SIZE - offset);
+
+	/* chain first in list head */
+	first->private = (unsigned long)list;
+	err = vi->rvq->vq_ops->add_buf(vi->rvq, sg, 0, MAX_SKB_FRAGS + 2,
+				       first);
+	if (err < 0)
+		give_pages(vi, first);
+
+	return err;
 }
 
-/* Returns false if we couldn't fill entirely (OOM). */
-static bool try_fill_recv(struct virtnet_info *vi, gfp_t gfp)
+static int add_recvbuf_mergeable(struct virtnet_info *vi, gfp_t gfp)
 {
-	struct sk_buff *skb;
-	struct scatterlist sg[1];
+	struct page *page;
+	struct scatterlist sg;
 	int err;
-	bool oom = false;
-
-	if (!vi->mergeable_rx_bufs)
-		return try_fill_recv_maxbufs(vi, gfp);
 
-	for (;;) {
-		skb_frag_t *f;
-
-		skb = netdev_alloc_skb(vi->dev, GOOD_COPY_LEN + NET_IP_ALIGN);
-		if (unlikely(!skb)) {
-			oom = true;
-			break;
-		}
+	page = get_a_page(vi, gfp);
+	if (!page)
+		return -ENOMEM;
 
-		skb_reserve(skb, NET_IP_ALIGN);
+	sg_init_one(&sg, page_address(page), PAGE_SIZE);
 
-		f = &skb_shinfo(skb)->frags[0];
-		f->page = get_a_page(vi, gfp);
-		if (!f->page) {
-			oom = true;
-			kfree_skb(skb);
-			break;
-		}
+	err = vi->rvq->vq_ops->add_buf(vi->rvq, &sg, 0, 1, page);
+	if (err < 0)
+		give_pages(vi, page);
 
-		f->page_offset = 0;
-		f->size = PAGE_SIZE;
+	return err;
+}
 
-		skb_shinfo(skb)->nr_frags++;
+/* Returns false if we couldn't fill entirely (OOM). */
+static bool try_fill_recv(struct virtnet_info *vi, gfp_t gfp)
+{
+	int err;
+	bool oom = false;
 
-		sg_init_one(sg, page_address(f->page), PAGE_SIZE);
-		skb_queue_head(&vi->recv, skb);
+	do {
+		if (vi->mergeable_rx_bufs)
+			err = add_recvbuf_mergeable(vi, gfp);
+		else if (vi->big_packets)
+			err = add_recvbuf_big(vi, gfp);
+		else
+			err = add_recvbuf_small(vi, gfp);
 
-		err = vi->rvq->vq_ops->add_buf(vi->rvq, sg, 0, 1, skb);
 		if (err < 0) {
-			skb_unlink(skb, &vi->recv);
-			kfree_skb(skb);
+			oom = true;
 			break;
 		}
-		vi->num++;
-	}
+		++vi->num;
+	} while (err > 0);
+
 	if (unlikely(vi->num > vi->max))
 		vi->max = vi->num;
 	vi->rvq->vq_ops->kick(vi->rvq);
@@ -401,14 +476,13 @@ static int virtnet_poll(struct net_device *dev, int *budget)
 	struct virtnet_info *vi = netdev_priv(dev);
 	int max_received = min(dev->quota, *budget);
 	bool no_work;
-	struct sk_buff *skb = NULL;
+	void *buf = NULL;
 	unsigned int len, received = 0;
 
 again:
 	while (received < max_received &&
-	       (skb = vi->rvq->vq_ops->get_buf(vi->rvq, &len)) != NULL) {
-		__skb_unlink(skb, &vi->recv);
-		receive_skb(vi->dev, skb, len);
+	       (buf = vi->rvq->vq_ops->get_buf(vi->rvq, &len)) != NULL) {
+		receive_buf(vi->dev, buf, len);
 		vi->num--;
 		received++;
 	}
@@ -419,7 +493,7 @@ again:
 	}
 
 	/* Out of packets? */
-	if (skb) {
+	if (buf) {
 		*budget -= received;
 		dev->quota -= received;
 		return 1;
@@ -448,7 +522,6 @@ static void free_old_xmit_skbs(struct virtnet_info *vi)
 
 	while ((skb = vi->svq->vq_ops->get_buf(vi->svq, &len)) != NULL) {
 		pr_debug("Sent skb %p\n", skb);
-		__skb_unlink(skb, &vi->send);
 		vi->stats.tx_bytes += skb->len;
 		vi->stats.tx_packets++;
 		kfree_skb(skb);
@@ -462,12 +535,7 @@ static void xmit_free(unsigned long data)
 	struct virtnet_info *vi = (void *)data;
 
 	netif_tx_lock(vi->dev);
-
 	free_old_xmit_skbs(vi);
-
-	if (!skb_queue_empty(&vi->send))
-		mod_timer(&vi->xmit_free_timer, jiffies + (HZ/10));
-
 	netif_tx_unlock(vi->dev);
 }
 
@@ -475,8 +543,7 @@ static int xmit_skb(struct virtnet_info *vi, struct sk_buff *skb)
 {
 	int num, err;
 	struct scatterlist sg[2+MAX_SKB_FRAGS];
-	struct virtio_net_hdr_mrg_rxbuf *mhdr = skb_vnet_hdr(skb);
-	struct virtio_net_hdr *hdr = skb_vnet_hdr(skb);
+	struct skb_vnet_hdr *hdr = skb_vnet_hdr(skb);
 
 #ifdef DEBUG
 	const unsigned char *dest = ((struct ethhdr *)skb->data)->h_dest;
@@ -487,39 +554,39 @@ static int xmit_skb(struct virtnet_info *vi, struct sk_buff *skb)
 #endif
 
 	if (skb->ip_summed == CHECKSUM_HW) {
-		hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
-		hdr->csum_start = skb->h.raw - skb->data;
-		hdr->csum_offset = skb->csum;
+		hdr->hdr.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
+		hdr->hdr.csum_start = skb->h.raw - skb->data;
+		hdr->hdr.csum_offset = skb->csum;
 	} else {
-		hdr->flags = 0;
-		hdr->csum_offset = hdr->csum_start = 0;
+		hdr->hdr.flags = 0;
+		hdr->hdr.csum_offset = hdr->hdr.csum_start = 0;
 	}
 
 	if (skb_is_gso(skb)) {
-		hdr->hdr_len = skb->h.raw - skb->data;
-		hdr->gso_size = skb_shinfo(skb)->gso_size;
+		hdr->hdr.hdr_len = skb->h.raw - skb->data;
+		hdr->hdr.gso_size = skb_shinfo(skb)->gso_size;
 		if (skb_shinfo(skb)->gso_type & SKB_GSO_TCPV4)
-			hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4;
+			hdr->hdr.gso_type = VIRTIO_NET_HDR_GSO_TCPV4;
 		else if (skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6)
-			hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6;
+			hdr->hdr.gso_type = VIRTIO_NET_HDR_GSO_TCPV6;
 		else if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP)
-			hdr->gso_type = VIRTIO_NET_HDR_GSO_UDP;
+			hdr->hdr.gso_type = VIRTIO_NET_HDR_GSO_UDP;
 		else
 			BUG();
 		if (skb_shinfo(skb)->gso_type & SKB_GSO_TCP_ECN)
-			hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN;
+			hdr->hdr.gso_type |= VIRTIO_NET_HDR_GSO_ECN;
 	} else {
-		hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE;
-		hdr->gso_size = hdr->hdr_len = 0;
+		hdr->hdr.gso_type = VIRTIO_NET_HDR_GSO_NONE;
+		hdr->hdr.gso_size = hdr->hdr.hdr_len = 0;
 	}
 
-	mhdr->num_buffers = 0;
+	hdr->mhdr.num_buffers = 0;
 
 	/* Encode metadata header at front. */
 	if (vi->mergeable_rx_bufs)
-		sg_init_one(sg, mhdr, sizeof(*mhdr));
+		sg_init_one(sg, &hdr->mhdr, sizeof(hdr->mhdr));
 	else
-		sg_init_one(sg, hdr, sizeof(*hdr));
+		sg_init_one(sg, &hdr->hdr, sizeof(hdr->hdr));
 
 	num = skb_to_sgvec(skb, sg+1, 0, skb->len) + 1;
 
@@ -563,7 +630,6 @@ again:
 
 	/* Put new one in send queue and do transmit */
 	if (likely(skb)) {
-		__skb_queue_head(&vi->send, skb);
 		if (xmit_skb(vi, skb) < 0) {
 			vi->last_xmit_skb = skb;
 			skb = NULL;
@@ -757,10 +823,6 @@ static int virtnet_probe(struct virtio_device *vdev)
 	vi->rvq = vqs[0];
 	vi->svq = vqs[1];
 
-	/* Initialize our empty receive and send queues. */
-	skb_queue_head_init(&vi->recv);
-	skb_queue_head_init(&vi->send);
-
 	tasklet_init(&vi->tasklet, xmit_tasklet, (unsigned long)vi);
 
 	if (!vi->free_in_tasklet)
@@ -794,10 +856,31 @@ free:
 	return err;
 }
 
+static void free_unused_bufs(struct virtnet_info *vi)
+{
+	void *buf;
+	while (1) {
+		buf = vi->svq->vq_ops->detach_unused_buf(vi->svq);
+		if (!buf)
+			break;
+		dev_kfree_skb(buf);
+	}
+	while (1) {
+		buf = vi->rvq->vq_ops->detach_unused_buf(vi->rvq);
+		if (!buf)
+			break;
+		if (vi->mergeable_rx_bufs || vi->big_packets)
+			give_pages(vi, buf);
+		else
+			dev_kfree_skb(buf);
+		--vi->num;
+	}
+	BUG_ON(vi->num != 0);
+}
+
 static void virtnet_remove(struct virtio_device *vdev)
 {
 	struct virtnet_info *vi = vdev->priv;
-	struct sk_buff *skb;
 
 	/* Stop all the virtqueues. */
 	vdev->config->reset(vdev);
@@ -805,18 +888,12 @@ static void virtnet_remove(struct virtio_device *vdev)
 	if (!vi->free_in_tasklet)
 		del_timer_sync(&vi->xmit_free_timer);
 
-	/* Free our skbs in send and recv queues, if any. */
-	while ((skb = __skb_dequeue(&vi->recv)) != NULL) {
-		kfree_skb(skb);
-		vi->num--;
-	}
-	__skb_queue_purge(&vi->send);
-
-	BUG_ON(vi->num != 0);
-
 	unregister_netdev(vi->dev);
 	del_timer_sync(&vi->refill);
 
+	/* Free unused buffers in both send and recv, if any. */
+	free_unused_bufs(vi);
+
 	vdev->config->del_vqs(vi->vdev);
 
 	while (vi->pages)