net/hyperv: Add support for jumbo frame up to 64KB

Allow the user set the MTU up to 65536 for Linux guests running on
Hyper-V 2008 R2 or later.

Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: K. Y. Srinivasan <kys@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c
index b7cbd12..462d05f 100644
--- a/drivers/net/hyperv/netvsc_drv.c
+++ b/drivers/net/hyperv/netvsc_drv.c
@@ -148,10 +148,12 @@
 	struct net_device_context *net_device_ctx = netdev_priv(net);
 	struct hv_netvsc_packet *packet;
 	int ret;
-	unsigned int i, num_pages;
+	unsigned int i, num_pages, npg_data;
 
-	/* Add 1 for skb->data and additional one for RNDIS */
-	num_pages = skb_shinfo(skb)->nr_frags + 1 + 1;
+	/* Add multipage for skb->data and additional one for RNDIS */
+	npg_data = (((unsigned long)skb->data + skb_headlen(skb) - 1)
+		>> PAGE_SHIFT) - ((unsigned long)skb->data >> PAGE_SHIFT) + 1;
+	num_pages = skb_shinfo(skb)->nr_frags + npg_data + 1;
 
 	/* Allocate a netvsc packet based on # of frags. */
 	packet = kzalloc(sizeof(struct hv_netvsc_packet) +
@@ -174,21 +176,36 @@
 	packet->page_buf_cnt = num_pages;
 
 	/* Initialize it from the skb */
-	packet->total_data_buflen	= skb->len;
+	packet->total_data_buflen = skb->len;
 
 	/* Start filling in the page buffers starting after RNDIS buffer. */
 	packet->page_buf[1].pfn = virt_to_phys(skb->data) >> PAGE_SHIFT;
 	packet->page_buf[1].offset
 		= (unsigned long)skb->data & (PAGE_SIZE - 1);
-	packet->page_buf[1].len = skb_headlen(skb);
+	if (npg_data == 1)
+		packet->page_buf[1].len = skb_headlen(skb);
+	else
+		packet->page_buf[1].len = PAGE_SIZE
+			- packet->page_buf[1].offset;
+
+	for (i = 2; i <= npg_data; i++) {
+		packet->page_buf[i].pfn = virt_to_phys(skb->data
+			+ PAGE_SIZE * (i-1)) >> PAGE_SHIFT;
+		packet->page_buf[i].offset = 0;
+		packet->page_buf[i].len = PAGE_SIZE;
+	}
+	if (npg_data > 1)
+		packet->page_buf[npg_data].len = (((unsigned long)skb->data
+			+ skb_headlen(skb) - 1) & (PAGE_SIZE - 1)) + 1;
 
 	/* Additional fragments are after SKB data */
 	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
 		const skb_frag_t *f = &skb_shinfo(skb)->frags[i];
 
-		packet->page_buf[i+2].pfn = page_to_pfn(skb_frag_page(f));
-		packet->page_buf[i+2].offset = f->page_offset;
-		packet->page_buf[i+2].len = skb_frag_size(f);
+		packet->page_buf[i+npg_data+1].pfn =
+			page_to_pfn(skb_frag_page(f));
+		packet->page_buf[i+npg_data+1].offset = f->page_offset;
+		packet->page_buf[i+npg_data+1].len = skb_frag_size(f);
 	}
 
 	/* Set the completion routine */
@@ -300,6 +317,39 @@
 	strcpy(info->fw_version, "N/A");
 }
 
+static int netvsc_change_mtu(struct net_device *ndev, int mtu)
+{
+	struct net_device_context *ndevctx = netdev_priv(ndev);
+	struct hv_device *hdev =  ndevctx->device_ctx;
+	struct netvsc_device *nvdev = hv_get_drvdata(hdev);
+	struct netvsc_device_info device_info;
+	int limit = ETH_DATA_LEN;
+
+	if (nvdev == NULL || nvdev->destroy)
+		return -ENODEV;
+
+	if (nvdev->nvsp_version == NVSP_PROTOCOL_VERSION_2)
+		limit = NETVSC_MTU;
+
+	if (mtu < 68 || mtu > limit)
+		return -EINVAL;
+
+	nvdev->start_remove = true;
+	cancel_delayed_work_sync(&ndevctx->dwork);
+	netif_stop_queue(ndev);
+	rndis_filter_device_remove(hdev);
+
+	ndev->mtu = mtu;
+
+	ndevctx->device_ctx = hdev;
+	hv_set_drvdata(hdev, ndev);
+	device_info.ring_size = ring_size;
+	rndis_filter_device_add(hdev, &device_info);
+	netif_wake_queue(ndev);
+
+	return 0;
+}
+
 static const struct ethtool_ops ethtool_ops = {
 	.get_drvinfo	= netvsc_get_drvinfo,
 	.get_link	= ethtool_op_get_link,
@@ -310,7 +360,7 @@
 	.ndo_stop =			netvsc_close,
 	.ndo_start_xmit =		netvsc_start_xmit,
 	.ndo_set_rx_mode =		netvsc_set_multicast_list,
-	.ndo_change_mtu =		eth_change_mtu,
+	.ndo_change_mtu =		netvsc_change_mtu,
 	.ndo_validate_addr =		eth_validate_addr,
 	.ndo_set_mac_address =		eth_mac_addr,
 };
@@ -403,6 +453,8 @@
 		return 0;
 	}
 
+	net_device->start_remove = true;
+
 	ndev_ctx = netdev_priv(net);
 	cancel_delayed_work_sync(&ndev_ctx->dwork);