diff options
Diffstat (limited to 'drivers/net/virtio_net.c')
| -rw-r--r-- | drivers/net/virtio_net.c | 117 | 
1 files changed, 102 insertions, 15 deletions
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 4452306d5328..0196a0df9021 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -19,6 +19,7 @@  //#define DEBUG  #include <linux/netdevice.h>  #include <linux/etherdevice.h> +#include <linux/ethtool.h>  #include <linux/module.h>  #include <linux/virtio.h>  #include <linux/virtio_net.h> @@ -54,9 +55,15 @@ struct virtnet_info  	struct tasklet_struct tasklet;  	bool free_in_tasklet; +	/* I like... big packets and I cannot lie! */ +	bool big_packets; +  	/* Receive & send queues. */  	struct sk_buff_head recv;  	struct sk_buff_head send; + +	/* Chain pages by the private ptr. */ +	struct page *pages;  };  static inline struct virtio_net_hdr *skb_vnet_hdr(struct sk_buff *skb) @@ -69,6 +76,23 @@ static inline void vnet_hdr_to_sg(struct scatterlist *sg, struct sk_buff *skb)  	sg_init_one(sg, skb_vnet_hdr(skb), sizeof(struct virtio_net_hdr));  } +static void give_a_page(struct virtnet_info *vi, struct page *page) +{ +	page->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) +		vi->pages = (struct page *)p->private; +	else +		p = alloc_page(gfp_mask); +	return p; +} +  static void skb_xmit_done(struct virtqueue *svq)  {  	struct virtnet_info *vi = svq->vdev->priv; @@ -88,6 +112,7 @@ static void receive_skb(struct net_device *dev, struct sk_buff *skb,  			unsigned len)  {  	struct virtio_net_hdr *hdr = skb_vnet_hdr(skb); +	int err;  	if (unlikely(len < sizeof(struct virtio_net_hdr) + ETH_HLEN)) {  		pr_debug("%s: short packet %i\n", dev->name, len); @@ -95,10 +120,23 @@ static void receive_skb(struct net_device *dev, struct sk_buff *skb,  		goto drop;  	}  	len -= sizeof(struct virtio_net_hdr); -	BUG_ON(len > MAX_PACKET_LEN); -	skb_trim(skb, len); +	if (len <= MAX_PACKET_LEN) { +		unsigned int i; +		for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) +			give_a_page(dev->priv, skb_shinfo(skb)->frags[i].page); +		skb->data_len = 0; +		skb_shinfo(skb)->nr_frags = 0; +	} + +	err = pskb_trim(skb, len); +	if (err) { +		pr_debug("%s: pskb_trim failed %i %d\n", dev->name, len, err); +		dev->stats.rx_dropped++; +		goto drop; +	} +	skb->truesize += skb->data_len;  	dev->stats.rx_bytes += skb->len;  	dev->stats.rx_packets++; @@ -160,7 +198,7 @@ static void try_fill_recv(struct virtnet_info *vi)  {  	struct sk_buff *skb;  	struct scatterlist sg[2+MAX_SKB_FRAGS]; -	int num, err; +	int num, err, i;  	sg_init_table(sg, 2+MAX_SKB_FRAGS);  	for (;;) { @@ -170,6 +208,24 @@ static void try_fill_recv(struct virtnet_info *vi)  		skb_put(skb, MAX_PACKET_LEN);  		vnet_hdr_to_sg(sg, 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_ATOMIC); +				if (!f->page) +					break; + +				f->page_offset = 0; +				f->size = PAGE_SIZE; + +				skb->data_len += PAGE_SIZE; +				skb->len += PAGE_SIZE; + +				skb_shinfo(skb)->nr_frags++; +			} +		} +  		num = skb_to_sgvec(skb, sg+1, 0, skb->len) + 1;  		skb_queue_head(&vi->recv, skb); @@ -335,16 +391,11 @@ again:  	free_old_xmit_skbs(vi);  	/* If we has a buffer left over from last time, send it now. */ -	if (unlikely(vi->last_xmit_skb)) { -		if (xmit_skb(vi, vi->last_xmit_skb) != 0) { -			/* Drop this skb: we only queue one. */ -			vi->dev->stats.tx_dropped++; -			kfree_skb(skb); -			skb = NULL; -			goto stop_queue; -		} -		vi->last_xmit_skb = NULL; -	} +	if (unlikely(vi->last_xmit_skb) && +	    xmit_skb(vi, vi->last_xmit_skb) != 0) +		goto stop_queue; + +	vi->last_xmit_skb = NULL;  	/* Put new one in send queue and do transmit */  	if (likely(skb)) { @@ -370,6 +421,11 @@ stop_queue:  		netif_start_queue(dev);  		goto again;  	} +	if (skb) { +		/* Drop this skb: we only queue one. */ +		vi->dev->stats.tx_dropped++; +		kfree_skb(skb); +	}  	goto done;  } @@ -408,6 +464,22 @@ static int virtnet_close(struct net_device *dev)  	return 0;  } +static int virtnet_set_tx_csum(struct net_device *dev, u32 data) +{ +	struct virtnet_info *vi = netdev_priv(dev); +	struct virtio_device *vdev = vi->vdev; + +	if (data && !virtio_has_feature(vdev, VIRTIO_NET_F_CSUM)) +		return -ENOSYS; + +	return ethtool_op_set_tx_hw_csum(dev, data); +} + +static struct ethtool_ops virtnet_ethtool_ops = { +	.set_tx_csum = virtnet_set_tx_csum, +	.set_sg = ethtool_op_set_sg, +}; +  static int virtnet_probe(struct virtio_device *vdev)  {  	int err; @@ -427,6 +499,7 @@ static int virtnet_probe(struct virtio_device *vdev)  #ifdef CONFIG_NET_POLL_CONTROLLER  	dev->poll_controller = virtnet_netpoll;  #endif +	SET_ETHTOOL_OPS(dev, &virtnet_ethtool_ops);  	SET_NETDEV_DEV(dev, &vdev->dev);  	/* Do we support "hardware" checksums? */ @@ -462,11 +535,18 @@ static int virtnet_probe(struct virtio_device *vdev)  	vi->dev = dev;  	vi->vdev = vdev;  	vdev->priv = vi; +	vi->pages = NULL;  	/* If they give us a callback when all buffers are done, we don't need  	 * the timer. */  	vi->free_in_tasklet = virtio_has_feature(vdev,VIRTIO_F_NOTIFY_ON_EMPTY); +	/* If we can receive ANY GSO packets, we must allocate large ones. */ +	if (virtio_has_feature(vdev, VIRTIO_NET_F_GUEST_TSO4) +	    || virtio_has_feature(vdev, VIRTIO_NET_F_GUEST_TSO6) +	    || virtio_has_feature(vdev, VIRTIO_NET_F_GUEST_ECN)) +		vi->big_packets = true; +  	/* We expect two virtqueues, receive then send. */  	vi->rvq = vdev->config->find_vq(vdev, 0, skb_recv_done);  	if (IS_ERR(vi->rvq)) { @@ -541,6 +621,10 @@ static void virtnet_remove(struct virtio_device *vdev)  	vdev->config->del_vq(vi->svq);  	vdev->config->del_vq(vi->rvq);  	unregister_netdev(vi->dev); + +	while (vi->pages) +		__free_pages(get_a_page(vi, GFP_KERNEL), 0); +  	free_netdev(vi->dev);  } @@ -550,9 +634,12 @@ static struct virtio_device_id id_table[] = {  };  static unsigned int features[] = { -	VIRTIO_NET_F_CSUM, VIRTIO_NET_F_GSO, VIRTIO_NET_F_MAC, +	VIRTIO_NET_F_CSUM, VIRTIO_NET_F_GUEST_CSUM, +	VIRTIO_NET_F_GSO, VIRTIO_NET_F_MAC,  	VIRTIO_NET_F_HOST_TSO4, VIRTIO_NET_F_HOST_UFO, VIRTIO_NET_F_HOST_TSO6, -	VIRTIO_NET_F_HOST_ECN, VIRTIO_F_NOTIFY_ON_EMPTY, +	VIRTIO_NET_F_HOST_ECN, VIRTIO_NET_F_GUEST_TSO4, VIRTIO_NET_F_GUEST_TSO6, +	VIRTIO_NET_F_GUEST_ECN, /* We don't yet handle UFO input. */ +	VIRTIO_F_NOTIFY_ON_EMPTY,  };  static struct virtio_driver virtio_net = {  |