From 95fa0405c5991726e06c08ffcd8ff872f7fb4f2d Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Mon, 28 Nov 2011 13:35:35 -0800 Subject: staging: hv: move hv_netvsc out of staging area hv_netvsc has been reviewed on netdev mailing list on 6/09/2011. All recommended changes have been made. We are requesting to move it out of staging area. Signed-off-by: Haiyang Zhang Signed-off-by: KY Srinivasan Signed-off-by: Mike Sterling Acked-by: Stephen Hemminger Acked-by: David S. Miller Signed-off-by: Greg Kroah-Hartman --- drivers/net/hyperv/netvsc_drv.c | 456 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 drivers/net/hyperv/netvsc_drv.c (limited to 'drivers/net/hyperv/netvsc_drv.c') diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c new file mode 100644 index 000000000000..93b0e91cbf98 --- /dev/null +++ b/drivers/net/hyperv/netvsc_drv.c @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang + * Hank Janssen + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hyperv_net.h" + +struct net_device_context { + /* point back to our device context */ + struct hv_device *device_ctx; + atomic_t avail; + struct delayed_work dwork; +}; + + +#define PACKET_PAGES_LOWATER 8 +/* Need this many pages to handle worst case fragmented packet */ +#define PACKET_PAGES_HIWATER (MAX_SKB_FRAGS + 2) + +static int ring_size = 128; +module_param(ring_size, int, S_IRUGO); +MODULE_PARM_DESC(ring_size, "Ring buffer size (# of pages)"); + +/* no-op so the netdev core doesn't return -EINVAL when modifying the the + * multicast address list in SIOCADDMULTI. hv is setup to get all multicast + * when it calls RndisFilterOnOpen() */ +static void netvsc_set_multicast_list(struct net_device *net) +{ +} + +static int netvsc_open(struct net_device *net) +{ + struct net_device_context *net_device_ctx = netdev_priv(net); + struct hv_device *device_obj = net_device_ctx->device_ctx; + int ret = 0; + + /* Open up the device */ + ret = rndis_filter_open(device_obj); + if (ret != 0) { + netdev_err(net, "unable to open device (ret %d).\n", ret); + return ret; + } + + netif_start_queue(net); + + return ret; +} + +static int netvsc_close(struct net_device *net) +{ + struct net_device_context *net_device_ctx = netdev_priv(net); + struct hv_device *device_obj = net_device_ctx->device_ctx; + int ret; + + netif_stop_queue(net); + + ret = rndis_filter_close(device_obj); + if (ret != 0) + netdev_err(net, "unable to close device (ret %d).\n", ret); + + return ret; +} + +static void netvsc_xmit_completion(void *context) +{ + struct hv_netvsc_packet *packet = (struct hv_netvsc_packet *)context; + struct sk_buff *skb = (struct sk_buff *) + (unsigned long)packet->completion.send.send_completion_tid; + + kfree(packet); + + if (skb) { + struct net_device *net = skb->dev; + struct net_device_context *net_device_ctx = netdev_priv(net); + unsigned int num_pages = skb_shinfo(skb)->nr_frags + 2; + + dev_kfree_skb_any(skb); + + atomic_add(num_pages, &net_device_ctx->avail); + if (atomic_read(&net_device_ctx->avail) >= + PACKET_PAGES_HIWATER) + netif_wake_queue(net); + } +} + +static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct net_device_context *net_device_ctx = netdev_priv(net); + struct hv_netvsc_packet *packet; + int ret; + unsigned int i, num_pages; + + /* Add 1 for skb->data and additional one for RNDIS */ + num_pages = skb_shinfo(skb)->nr_frags + 1 + 1; + if (num_pages > atomic_read(&net_device_ctx->avail)) + return NETDEV_TX_BUSY; + + /* Allocate a netvsc packet based on # of frags. */ + packet = kzalloc(sizeof(struct hv_netvsc_packet) + + (num_pages * sizeof(struct hv_page_buffer)) + + sizeof(struct rndis_filter_packet), GFP_ATOMIC); + if (!packet) { + /* out of memory, drop packet */ + netdev_err(net, "unable to allocate hv_netvsc_packet\n"); + + dev_kfree_skb(skb); + net->stats.tx_dropped++; + return NETDEV_TX_BUSY; + } + + packet->extension = (void *)(unsigned long)packet + + sizeof(struct hv_netvsc_packet) + + (num_pages * sizeof(struct hv_page_buffer)); + + /* Setup the rndis header */ + packet->page_buf_cnt = num_pages; + + /* Initialize it from the skb */ + 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); + + /* 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); + } + + /* Set the completion routine */ + packet->completion.send.send_completion = netvsc_xmit_completion; + packet->completion.send.send_completion_ctx = packet; + packet->completion.send.send_completion_tid = (unsigned long)skb; + + ret = rndis_filter_send(net_device_ctx->device_ctx, + packet); + if (ret == 0) { + net->stats.tx_bytes += skb->len; + net->stats.tx_packets++; + + atomic_sub(num_pages, &net_device_ctx->avail); + if (atomic_read(&net_device_ctx->avail) < PACKET_PAGES_LOWATER) + netif_stop_queue(net); + } else { + /* we are shutting down or bus overloaded, just drop packet */ + net->stats.tx_dropped++; + kfree(packet); + dev_kfree_skb_any(skb); + } + + return ret ? NETDEV_TX_BUSY : NETDEV_TX_OK; +} + +/* + * netvsc_linkstatus_callback - Link up/down notification + */ +void netvsc_linkstatus_callback(struct hv_device *device_obj, + unsigned int status) +{ + struct net_device *net; + struct net_device_context *ndev_ctx; + struct netvsc_device *net_device; + + net_device = hv_get_drvdata(device_obj); + net = net_device->ndev; + + if (!net) { + netdev_err(net, "got link status but net device " + "not initialized yet\n"); + return; + } + + if (status == 1) { + netif_carrier_on(net); + netif_wake_queue(net); + ndev_ctx = netdev_priv(net); + schedule_delayed_work(&ndev_ctx->dwork, 0); + schedule_delayed_work(&ndev_ctx->dwork, msecs_to_jiffies(20)); + } else { + netif_carrier_off(net); + netif_stop_queue(net); + } +} + +/* + * netvsc_recv_callback - Callback when we receive a packet from the + * "wire" on the specified device. + */ +int netvsc_recv_callback(struct hv_device *device_obj, + struct hv_netvsc_packet *packet) +{ + struct net_device *net = dev_get_drvdata(&device_obj->device); + struct sk_buff *skb; + void *data; + int i; + unsigned long flags; + struct netvsc_device *net_device; + + net_device = hv_get_drvdata(device_obj); + net = net_device->ndev; + + if (!net) { + netdev_err(net, "got receive callback but net device" + " not initialized yet\n"); + return 0; + } + + /* Allocate a skb - TODO direct I/O to pages? */ + skb = netdev_alloc_skb_ip_align(net, packet->total_data_buflen); + if (unlikely(!skb)) { + ++net->stats.rx_dropped; + return 0; + } + + /* for kmap_atomic */ + local_irq_save(flags); + + /* + * Copy to skb. This copy is needed here since the memory pointed by + * hv_netvsc_packet cannot be deallocated + */ + for (i = 0; i < packet->page_buf_cnt; i++) { + data = kmap_atomic(pfn_to_page(packet->page_buf[i].pfn), + KM_IRQ1); + data = (void *)(unsigned long)data + + packet->page_buf[i].offset; + + memcpy(skb_put(skb, packet->page_buf[i].len), data, + packet->page_buf[i].len); + + kunmap_atomic((void *)((unsigned long)data - + packet->page_buf[i].offset), KM_IRQ1); + } + + local_irq_restore(flags); + + skb->protocol = eth_type_trans(skb, net); + skb->ip_summed = CHECKSUM_NONE; + + net->stats.rx_packets++; + net->stats.rx_bytes += skb->len; + + /* + * Pass the skb back up. Network stack will deallocate the skb when it + * is done. + * TODO - use NAPI? + */ + netif_rx(skb); + + return 0; +} + +static void netvsc_get_drvinfo(struct net_device *net, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, "hv_netvsc"); + strcpy(info->version, HV_DRV_VERSION); + strcpy(info->fw_version, "N/A"); +} + +static const struct ethtool_ops ethtool_ops = { + .get_drvinfo = netvsc_get_drvinfo, + .get_link = ethtool_op_get_link, +}; + +static const struct net_device_ops device_ops = { + .ndo_open = netvsc_open, + .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_validate_addr = eth_validate_addr, + .ndo_set_mac_address = eth_mac_addr, +}; + +/* + * Send GARP packet to network peers after migrations. + * After Quick Migration, the network is not immediately operational in the + * current context when receiving RNDIS_STATUS_MEDIA_CONNECT event. So, add + * another netif_notify_peers() into a delayed work, otherwise GARP packet + * will not be sent after quick migration, and cause network disconnection. + */ +static void netvsc_send_garp(struct work_struct *w) +{ + struct net_device_context *ndev_ctx; + struct net_device *net; + struct netvsc_device *net_device; + + ndev_ctx = container_of(w, struct net_device_context, dwork.work); + net_device = hv_get_drvdata(ndev_ctx->device_ctx); + net = net_device->ndev; + netif_notify_peers(net); +} + + +static int netvsc_probe(struct hv_device *dev, + const struct hv_vmbus_device_id *dev_id) +{ + struct net_device *net = NULL; + struct net_device_context *net_device_ctx; + struct netvsc_device_info device_info; + int ret; + + net = alloc_etherdev(sizeof(struct net_device_context)); + if (!net) + return -ENOMEM; + + /* Set initial state */ + netif_carrier_off(net); + + net_device_ctx = netdev_priv(net); + net_device_ctx->device_ctx = dev; + atomic_set(&net_device_ctx->avail, ring_size); + hv_set_drvdata(dev, net); + INIT_DELAYED_WORK(&net_device_ctx->dwork, netvsc_send_garp); + + net->netdev_ops = &device_ops; + + /* TODO: Add GSO and Checksum offload */ + net->hw_features = NETIF_F_SG; + net->features = NETIF_F_SG; + + SET_ETHTOOL_OPS(net, ðtool_ops); + SET_NETDEV_DEV(net, &dev->device); + + ret = register_netdev(net); + if (ret != 0) { + pr_err("Unable to register netdev.\n"); + free_netdev(net); + goto out; + } + + /* Notify the netvsc driver of the new device */ + device_info.ring_size = ring_size; + ret = rndis_filter_device_add(dev, &device_info); + if (ret != 0) { + netdev_err(net, "unable to add netvsc device (ret %d)\n", ret); + unregister_netdev(net); + free_netdev(net); + hv_set_drvdata(dev, NULL); + return ret; + } + memcpy(net->dev_addr, device_info.mac_adr, ETH_ALEN); + + netif_carrier_on(net); + +out: + return ret; +} + +static int netvsc_remove(struct hv_device *dev) +{ + struct net_device *net; + struct net_device_context *ndev_ctx; + struct netvsc_device *net_device; + + net_device = hv_get_drvdata(dev); + net = net_device->ndev; + + if (net == NULL) { + dev_err(&dev->device, "No net device to remove\n"); + return 0; + } + + ndev_ctx = netdev_priv(net); + cancel_delayed_work_sync(&ndev_ctx->dwork); + + /* Stop outbound asap */ + netif_stop_queue(net); + + unregister_netdev(net); + + /* + * Call to the vsc driver to let it know that the device is being + * removed + */ + rndis_filter_device_remove(dev); + + free_netdev(net); + return 0; +} + +static const struct hv_vmbus_device_id id_table[] = { + /* Network guid */ + { VMBUS_DEVICE(0x63, 0x51, 0x61, 0xF8, 0x3E, 0xDF, 0xc5, 0x46, + 0x91, 0x3F, 0xF2, 0xD2, 0xF9, 0x65, 0xED, 0x0E) }, + { }, +}; + +MODULE_DEVICE_TABLE(vmbus, id_table); + +/* The one and only one */ +static struct hv_driver netvsc_drv = { + .name = "netvsc", + .id_table = id_table, + .probe = netvsc_probe, + .remove = netvsc_remove, +}; + +static void __exit netvsc_drv_exit(void) +{ + vmbus_driver_unregister(&netvsc_drv); +} + +static int __init netvsc_drv_init(void) +{ + return vmbus_driver_register(&netvsc_drv); +} + +MODULE_LICENSE("GPL"); +MODULE_VERSION(HV_DRV_VERSION); +MODULE_DESCRIPTION("Microsoft Hyper-V network driver"); + +module_init(netvsc_drv_init); +module_exit(netvsc_drv_exit); -- cgit v1.2.3 From d426b2e3d91f8ec3203f8852e7ad0153b5dfdf71 Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Wed, 30 Nov 2011 07:19:08 -0800 Subject: net/hyperv: Add support for promiscuous mode setting Add code to accept promiscuous mode setting, and pass it to RNDIS filter. Signed-off-by: Haiyang Zhang Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/net/hyperv/hyperv_net.h | 24 ++++++++++++++++++++ drivers/net/hyperv/netvsc_drv.c | 46 ++++++++++++++++++++++++++++++++++++--- drivers/net/hyperv/rndis_filter.c | 23 +------------------- 3 files changed, 68 insertions(+), 25 deletions(-) (limited to 'drivers/net/hyperv/netvsc_drv.c') diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h index ac1ec8405124..49b131f71d7a 100644 --- a/drivers/net/hyperv/hyperv_net.h +++ b/drivers/net/hyperv/hyperv_net.h @@ -87,6 +87,27 @@ struct netvsc_device_info { int ring_size; }; +enum rndis_device_state { + RNDIS_DEV_UNINITIALIZED = 0, + RNDIS_DEV_INITIALIZING, + RNDIS_DEV_INITIALIZED, + RNDIS_DEV_DATAINITIALIZED, +}; + +struct rndis_device { + struct netvsc_device *net_dev; + + enum rndis_device_state state; + bool link_state; + atomic_t new_req_id; + + spinlock_t request_lock; + struct list_head req_list; + + unsigned char hw_mac_adr[ETH_ALEN]; +}; + + /* Interface */ int netvsc_device_add(struct hv_device *device, void *additional_info); int netvsc_device_remove(struct hv_device *device); @@ -109,6 +130,9 @@ int rndis_filter_receive(struct hv_device *dev, int rndis_filter_send(struct hv_device *dev, struct hv_netvsc_packet *pkt); +int rndis_filter_set_packet_filter(struct rndis_device *dev, u32 new_filter); + + #define NVSP_INVALID_PROTOCOL_VERSION ((u32)0xFFFFFFFF) #define NVSP_PROTOCOL_VERSION_1 2 diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index 93b0e91cbf98..b69c3a4d1e9e 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -56,11 +56,51 @@ static int ring_size = 128; module_param(ring_size, int, S_IRUGO); MODULE_PARM_DESC(ring_size, "Ring buffer size (# of pages)"); -/* no-op so the netdev core doesn't return -EINVAL when modifying the the - * multicast address list in SIOCADDMULTI. hv is setup to get all multicast - * when it calls RndisFilterOnOpen() */ +struct set_multicast_work { + struct work_struct work; + struct net_device *net; +}; + +static void do_set_multicast(struct work_struct *w) +{ + struct set_multicast_work *swk = + container_of(w, struct set_multicast_work, work); + struct net_device *net = swk->net; + + struct net_device_context *ndevctx = netdev_priv(net); + struct netvsc_device *nvdev; + struct rndis_device *rdev; + + nvdev = hv_get_drvdata(ndevctx->device_ctx); + if (nvdev == NULL) + return; + + rdev = nvdev->extension; + if (rdev == NULL) + return; + + if (net->flags & IFF_PROMISC) + rndis_filter_set_packet_filter(rdev, + NDIS_PACKET_TYPE_PROMISCUOUS); + else + rndis_filter_set_packet_filter(rdev, + NDIS_PACKET_TYPE_BROADCAST | + NDIS_PACKET_TYPE_ALL_MULTICAST | + NDIS_PACKET_TYPE_DIRECTED); + + kfree(w); +} + static void netvsc_set_multicast_list(struct net_device *net) { + struct set_multicast_work *swk = + kmalloc(sizeof(struct set_multicast_work), GFP_ATOMIC); + if (swk == NULL) + return; + + swk->net = net; + INIT_WORK(&swk->work, do_set_multicast); + schedule_work(&swk->work); } static int netvsc_open(struct net_device *net) diff --git a/drivers/net/hyperv/rndis_filter.c b/drivers/net/hyperv/rndis_filter.c index bafccb360041..418e7aac229c 100644 --- a/drivers/net/hyperv/rndis_filter.c +++ b/drivers/net/hyperv/rndis_filter.c @@ -30,26 +30,6 @@ #include "hyperv_net.h" -enum rndis_device_state { - RNDIS_DEV_UNINITIALIZED = 0, - RNDIS_DEV_INITIALIZING, - RNDIS_DEV_INITIALIZED, - RNDIS_DEV_DATAINITIALIZED, -}; - -struct rndis_device { - struct netvsc_device *net_dev; - - enum rndis_device_state state; - bool link_state; - atomic_t new_req_id; - - spinlock_t request_lock; - struct list_head req_list; - - unsigned char hw_mac_adr[ETH_ALEN]; -}; - struct rndis_request { struct list_head list_ent; struct completion wait_event; @@ -522,8 +502,7 @@ static int rndis_filter_query_device_link_status(struct rndis_device *dev) return ret; } -static int rndis_filter_set_packet_filter(struct rndis_device *dev, - u32 new_filter) +int rndis_filter_set_packet_filter(struct rndis_device *dev, u32 new_filter) { struct rndis_request *request; struct rndis_set_request *set; -- cgit v1.2.3 From 1d06825b0ede541f63b5577435abd2fc649a9b5e Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Fri, 2 Dec 2011 11:56:25 -0800 Subject: net/hyperv: Fix the stop/wake queue mechanism The ring buffer is only used to pass meta data for outbound packets. The actual payload is accessed by DMA from the host. So the stop/wake queue mechanism based on counting and comparing number of pages sent v.s. number of pages in the ring buffer is wrong. Also, there is a race condition in the stop/wake queue calls, which can stop xmit queue forever. The new stop/wake queue mechanism is based on the actual bytes used by outbound packets in the ring buffer. The check for number of outstanding sends after stop queue prevents the race condition that can cause wake queue happening earlier than stop queue. Signed-off-by: Haiyang Zhang Signed-off-by: K. Y. Srinivasan Reported-by: Long Li Signed-off-by: Greg Kroah-Hartman --- drivers/net/hyperv/netvsc.c | 14 +++++++++++--- drivers/net/hyperv/netvsc_drv.c | 24 +----------------------- 2 files changed, 12 insertions(+), 26 deletions(-) (limited to 'drivers/net/hyperv/netvsc_drv.c') diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c index 4a807e44ec60..b6ac152a9bd0 100644 --- a/drivers/net/hyperv/netvsc.c +++ b/drivers/net/hyperv/netvsc.c @@ -435,6 +435,9 @@ static void netvsc_send_completion(struct hv_device *device, nvsc_packet->completion.send.send_completion_ctx); atomic_dec(&net_device->num_outstanding_sends); + + if (netif_queue_stopped(ndev)) + netif_wake_queue(ndev); } else { netdev_err(ndev, "Unknown send completion packet type- " "%d received!!\n", nvsp_packet->hdr.msg_type); @@ -485,11 +488,16 @@ int netvsc_send(struct hv_device *device, } - if (ret != 0) + if (ret == 0) { + atomic_inc(&net_device->num_outstanding_sends); + } else if (ret == -EAGAIN) { + netif_stop_queue(ndev); + if (atomic_read(&net_device->num_outstanding_sends) < 1) + netif_wake_queue(ndev); + } else { netdev_err(ndev, "Unable to send packet %p ret %d\n", packet, ret); - else - atomic_inc(&net_device->num_outstanding_sends); + } return ret; } diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index b69c3a4d1e9e..7da85ebd7ac6 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -43,15 +43,10 @@ struct net_device_context { /* point back to our device context */ struct hv_device *device_ctx; - atomic_t avail; struct delayed_work dwork; }; -#define PACKET_PAGES_LOWATER 8 -/* Need this many pages to handle worst case fragmented packet */ -#define PACKET_PAGES_HIWATER (MAX_SKB_FRAGS + 2) - static int ring_size = 128; module_param(ring_size, int, S_IRUGO); MODULE_PARM_DESC(ring_size, "Ring buffer size (# of pages)"); @@ -144,18 +139,8 @@ static void netvsc_xmit_completion(void *context) kfree(packet); - if (skb) { - struct net_device *net = skb->dev; - struct net_device_context *net_device_ctx = netdev_priv(net); - unsigned int num_pages = skb_shinfo(skb)->nr_frags + 2; - + if (skb) dev_kfree_skb_any(skb); - - atomic_add(num_pages, &net_device_ctx->avail); - if (atomic_read(&net_device_ctx->avail) >= - PACKET_PAGES_HIWATER) - netif_wake_queue(net); - } } static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) @@ -167,8 +152,6 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) /* Add 1 for skb->data and additional one for RNDIS */ num_pages = skb_shinfo(skb)->nr_frags + 1 + 1; - if (num_pages > atomic_read(&net_device_ctx->avail)) - return NETDEV_TX_BUSY; /* Allocate a netvsc packet based on # of frags. */ packet = kzalloc(sizeof(struct hv_netvsc_packet) + @@ -218,10 +201,6 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) if (ret == 0) { net->stats.tx_bytes += skb->len; net->stats.tx_packets++; - - atomic_sub(num_pages, &net_device_ctx->avail); - if (atomic_read(&net_device_ctx->avail) < PACKET_PAGES_LOWATER) - netif_stop_queue(net); } else { /* we are shutting down or bus overloaded, just drop packet */ net->stats.tx_dropped++; @@ -391,7 +370,6 @@ static int netvsc_probe(struct hv_device *dev, net_device_ctx = netdev_priv(net); net_device_ctx->device_ctx = dev; - atomic_set(&net_device_ctx->avail, ring_size); hv_set_drvdata(dev, net); INIT_DELAYED_WORK(&net_device_ctx->dwork, netvsc_send_garp); -- cgit v1.2.3 From 453263421f88b4a7e508c2e7b639c97e99c5b118 Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Thu, 15 Dec 2011 13:45:15 -0800 Subject: net/hyperv: Remove unnecessary kmap_atomic in netvsc driver __get_free_pages() doesn't return HI memory, so the memory is always mapped. kmap_atomic() is not necessary here. This patch removes the kmap_atomic() calls and related code for locking and page manipulation. Signed-off-by: Haiyang Zhang Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/net/hyperv/hyperv_net.h | 6 ++--- drivers/net/hyperv/netvsc.c | 53 +++------------------------------------ drivers/net/hyperv/netvsc_drv.c | 22 ++-------------- drivers/net/hyperv/rndis_filter.c | 21 ++-------------- 4 files changed, 10 insertions(+), 92 deletions(-) (limited to 'drivers/net/hyperv/netvsc_drv.c') diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h index 49b131f71d7a..ff1b5209b45f 100644 --- a/drivers/net/hyperv/hyperv_net.h +++ b/drivers/net/hyperv/hyperv_net.h @@ -39,9 +39,6 @@ struct xferpage_packet { u32 count; }; -/* The number of pages which are enough to cover jumbo frame buffer. */ -#define NETVSC_PACKET_MAXPAGE 4 - /* * Represent netvsc packet which contains 1 RNDIS and 1 ethernet frame * within the RNDIS @@ -77,8 +74,9 @@ struct hv_netvsc_packet { u32 total_data_buflen; /* Points to the send/receive buffer where the ethernet frame is */ + void *data; u32 page_buf_cnt; - struct hv_page_buffer page_buf[NETVSC_PACKET_MAXPAGE]; + struct hv_page_buffer page_buf[0]; }; struct netvsc_device_info { diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c index b6ac152a9bd0..bab627f261c4 100644 --- a/drivers/net/hyperv/netvsc.c +++ b/drivers/net/hyperv/netvsc.c @@ -603,12 +603,10 @@ static void netvsc_receive(struct hv_device *device, struct vmtransfer_page_packet_header *vmxferpage_packet; struct nvsp_message *nvsp_packet; struct hv_netvsc_packet *netvsc_packet = NULL; - unsigned long start; - unsigned long end, end_virtual; /* struct netvsc_driver *netvscDriver; */ struct xferpage_packet *xferpage_packet = NULL; - int i, j; - int count = 0, bytes_remain = 0; + int i; + int count = 0; unsigned long flags; struct net_device *ndev; @@ -717,53 +715,10 @@ static void netvsc_receive(struct hv_device *device, netvsc_packet->completion.recv.recv_completion_tid = vmxferpage_packet->d.trans_id; + netvsc_packet->data = (void *)((unsigned long)net_device-> + recv_buf + vmxferpage_packet->ranges[i].byte_offset); netvsc_packet->total_data_buflen = vmxferpage_packet->ranges[i].byte_count; - netvsc_packet->page_buf_cnt = 1; - - netvsc_packet->page_buf[0].len = - vmxferpage_packet->ranges[i].byte_count; - - start = virt_to_phys((void *)((unsigned long)net_device-> - recv_buf + vmxferpage_packet->ranges[i].byte_offset)); - - netvsc_packet->page_buf[0].pfn = start >> PAGE_SHIFT; - end_virtual = (unsigned long)net_device->recv_buf - + vmxferpage_packet->ranges[i].byte_offset - + vmxferpage_packet->ranges[i].byte_count - 1; - end = virt_to_phys((void *)end_virtual); - - /* Calculate the page relative offset */ - netvsc_packet->page_buf[0].offset = - vmxferpage_packet->ranges[i].byte_offset & - (PAGE_SIZE - 1); - if ((end >> PAGE_SHIFT) != (start >> PAGE_SHIFT)) { - /* Handle frame across multiple pages: */ - netvsc_packet->page_buf[0].len = - (netvsc_packet->page_buf[0].pfn << - PAGE_SHIFT) - + PAGE_SIZE - start; - bytes_remain = netvsc_packet->total_data_buflen - - netvsc_packet->page_buf[0].len; - for (j = 1; j < NETVSC_PACKET_MAXPAGE; j++) { - netvsc_packet->page_buf[j].offset = 0; - if (bytes_remain <= PAGE_SIZE) { - netvsc_packet->page_buf[j].len = - bytes_remain; - bytes_remain = 0; - } else { - netvsc_packet->page_buf[j].len = - PAGE_SIZE; - bytes_remain -= PAGE_SIZE; - } - netvsc_packet->page_buf[j].pfn = - virt_to_phys((void *)(end_virtual - - bytes_remain)) >> PAGE_SHIFT; - netvsc_packet->page_buf_cnt++; - if (bytes_remain == 0) - break; - } - } /* Pass it to the upper layer */ rndis_filter_receive(device, netvsc_packet); diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index 7da85ebd7ac6..b7cbd126f20a 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -251,9 +251,6 @@ int netvsc_recv_callback(struct hv_device *device_obj, { struct net_device *net = dev_get_drvdata(&device_obj->device); struct sk_buff *skb; - void *data; - int i; - unsigned long flags; struct netvsc_device *net_device; net_device = hv_get_drvdata(device_obj); @@ -272,27 +269,12 @@ int netvsc_recv_callback(struct hv_device *device_obj, return 0; } - /* for kmap_atomic */ - local_irq_save(flags); - /* * Copy to skb. This copy is needed here since the memory pointed by * hv_netvsc_packet cannot be deallocated */ - for (i = 0; i < packet->page_buf_cnt; i++) { - data = kmap_atomic(pfn_to_page(packet->page_buf[i].pfn), - KM_IRQ1); - data = (void *)(unsigned long)data + - packet->page_buf[i].offset; - - memcpy(skb_put(skb, packet->page_buf[i].len), data, - packet->page_buf[i].len); - - kunmap_atomic((void *)((unsigned long)data - - packet->page_buf[i].offset), KM_IRQ1); - } - - local_irq_restore(flags); + memcpy(skb_put(skb, packet->total_data_buflen), packet->data, + packet->total_data_buflen); skb->protocol = eth_type_trans(skb, net); skb->ip_summed = CHECKSUM_NONE; diff --git a/drivers/net/hyperv/rndis_filter.c b/drivers/net/hyperv/rndis_filter.c index 418e7aac229c..da181f9a49d1 100644 --- a/drivers/net/hyperv/rndis_filter.c +++ b/drivers/net/hyperv/rndis_filter.c @@ -309,7 +309,6 @@ static void rndis_filter_receive_data(struct rndis_device *dev, { struct rndis_packet *rndis_pkt; u32 data_offset; - int i; rndis_pkt = &msg->msg.pkt; @@ -322,17 +321,7 @@ static void rndis_filter_receive_data(struct rndis_device *dev, data_offset = RNDIS_HEADER_SIZE + rndis_pkt->data_offset; pkt->total_data_buflen -= data_offset; - pkt->page_buf[0].offset += data_offset; - pkt->page_buf[0].len -= data_offset; - - /* Drop the 0th page, if rndis data go beyond page boundary */ - if (pkt->page_buf[0].offset >= PAGE_SIZE) { - pkt->page_buf[1].offset = pkt->page_buf[0].offset - PAGE_SIZE; - pkt->page_buf[1].len -= pkt->page_buf[1].offset; - pkt->page_buf_cnt--; - for (i = 0; i < pkt->page_buf_cnt; i++) - pkt->page_buf[i] = pkt->page_buf[i+1]; - } + pkt->data = (void *)((unsigned long)pkt->data + data_offset); pkt->is_data_pkt = true; @@ -367,11 +356,7 @@ int rndis_filter_receive(struct hv_device *dev, return -ENODEV; } - rndis_hdr = (struct rndis_message *)kmap_atomic( - pfn_to_page(pkt->page_buf[0].pfn), KM_IRQ0); - - rndis_hdr = (void *)((unsigned long)rndis_hdr + - pkt->page_buf[0].offset); + rndis_hdr = pkt->data; /* Make sure we got a valid rndis message */ if ((rndis_hdr->ndis_msg_type != REMOTE_NDIS_PACKET_MSG) && @@ -387,8 +372,6 @@ int rndis_filter_receive(struct hv_device *dev, sizeof(struct rndis_message) : rndis_hdr->msg_len); - kunmap_atomic(rndis_hdr - pkt->page_buf[0].offset, KM_IRQ0); - dump_rndis_message(dev, &rndis_msg); switch (rndis_msg.ndis_msg_type) { -- cgit v1.2.3 From 4d447c9a6ebc0142d320f075c5bac6d202a79fd4 Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Thu, 15 Dec 2011 13:45:17 -0800 Subject: 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 Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/net/hyperv/hyperv_net.h | 8 ++--- drivers/net/hyperv/netvsc.c | 6 ++-- drivers/net/hyperv/netvsc_drv.c | 70 +++++++++++++++++++++++++++++++++++------ include/linux/hyperv.h | 2 +- 4 files changed, 68 insertions(+), 18 deletions(-) (limited to 'drivers/net/hyperv/netvsc_drv.c') diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h index 287767055125..dec5836ae075 100644 --- a/drivers/net/hyperv/hyperv_net.h +++ b/drivers/net/hyperv/hyperv_net.h @@ -456,12 +456,9 @@ struct nvsp_message { } __packed; +#define NETVSC_MTU 65536 - -/* #define NVSC_MIN_PROTOCOL_VERSION 1 */ -/* #define NVSC_MAX_PROTOCOL_VERSION 1 */ - -#define NETVSC_RECEIVE_BUFFER_SIZE (1024*1024) /* 1MB */ +#define NETVSC_RECEIVE_BUFFER_SIZE (1024*1024*2) /* 2MB */ #define NETVSC_RECEIVE_BUFFER_ID 0xcafe @@ -479,6 +476,7 @@ struct netvsc_device { u32 nvsp_version; atomic_t num_outstanding_sends; + bool start_remove; bool destroy; /* * List of free preallocated hv_netvsc_packet to represent receive diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c index 46828b4dd8ab..8965b45ce5a5 100644 --- a/drivers/net/hyperv/netvsc.c +++ b/drivers/net/hyperv/netvsc.c @@ -42,7 +42,7 @@ static struct netvsc_device *alloc_net_device(struct hv_device *device) if (!net_device) return NULL; - + net_device->start_remove = false; net_device->destroy = false; net_device->dev = device; net_device->ndev = ndev; @@ -299,7 +299,7 @@ static int negotiate_nvsp_ver(struct hv_device *device, /* NVSPv2 only: Send NDIS config */ memset(init_packet, 0, sizeof(struct nvsp_message)); init_packet->hdr.msg_type = NVSP_MSG2_TYPE_SEND_NDIS_CONFIG; - init_packet->msg.v2_msg.send_ndis_config.mtu = ETH_DATA_LEN; + init_packet->msg.v2_msg.send_ndis_config.mtu = net_device->ndev->mtu; ret = vmbus_sendpacket(device->channel, init_packet, sizeof(struct nvsp_message), @@ -464,7 +464,7 @@ static void netvsc_send_completion(struct hv_device *device, atomic_dec(&net_device->num_outstanding_sends); - if (netif_queue_stopped(ndev)) + if (netif_queue_stopped(ndev) && !net_device->start_remove) netif_wake_queue(ndev); } else { netdev_err(ndev, "Unknown send completion packet type- " diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index b7cbd126f20a..462d05f05e84 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -148,10 +148,12 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) 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 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) 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 @@ static void netvsc_get_drvinfo(struct net_device *net, 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 @@ static const struct net_device_ops device_ops = { .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 @@ static int netvsc_remove(struct hv_device *dev) return 0; } + net_device->start_remove = true; + ndev_ctx = netdev_priv(net); cancel_delayed_work_sync(&ndev_ctx->dwork); diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 12ec328481de..62b908e0e591 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -35,7 +35,7 @@ #include -#define MAX_PAGE_BUFFER_COUNT 16 +#define MAX_PAGE_BUFFER_COUNT 18 #define MAX_MULTIPAGE_BUFFER_COUNT 32 /* 128K */ #pragma pack(push, 1) -- cgit v1.2.3 From c11bf1c8baff170fa478adc04964da519d160e62 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Tue, 24 Jan 2012 10:21:28 +0000 Subject: net/hyperv: fix possible memory leak in do_set_multicast() do_set_multicast() may not free the memory malloc in netvsc_set_multicast_list(). Signed-off-by: Wei Yongjun Signed-off-by: Haiyang Zhang Signed-off-by: K. Y. Srinivasan Signed-off-by: David S. Miller --- drivers/net/hyperv/netvsc_drv.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/net/hyperv/netvsc_drv.c') diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index 462d05f05e84..1a1ca6cfc74a 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -68,11 +68,11 @@ static void do_set_multicast(struct work_struct *w) nvdev = hv_get_drvdata(ndevctx->device_ctx); if (nvdev == NULL) - return; + goto out; rdev = nvdev->extension; if (rdev == NULL) - return; + goto out; if (net->flags & IFF_PROMISC) rndis_filter_set_packet_filter(rdev, @@ -83,6 +83,7 @@ static void do_set_multicast(struct work_struct *w) NDIS_PACKET_TYPE_ALL_MULTICAST | NDIS_PACKET_TYPE_DIRECTED); +out: kfree(w); } -- cgit v1.2.3 From 48c3883999cb06246911e29356d194f96f1c75ef Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sun, 29 Jan 2012 22:14:02 +0000 Subject: net/hyperv: rx_bytes should account the ether header size skb->len after call eth_type_trans() does not include the ether header size, but rx_bytes should account it. Signed-off-by: Wei Yongjun Signed-off-by: David S. Miller --- drivers/net/hyperv/netvsc_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/net/hyperv/netvsc_drv.c') diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index 1a1ca6cfc74a..9dccc7a3a220 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -298,7 +298,7 @@ int netvsc_recv_callback(struct hv_device *device_obj, skb->ip_summed = CHECKSUM_NONE; net->stats.rx_packets++; - net->stats.rx_bytes += skb->len; + net->stats.rx_bytes += packet->total_data_buflen; /* * Pass the skb back up. Network stack will deallocate the skb when it -- cgit v1.2.3 From 0a282538cc1977655004cdb2eb25dd2b63f20637 Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Thu, 2 Feb 2012 07:17:59 +0000 Subject: net/hyperv: Use netif_tx_disable() instead of netif_stop_queue() when necessary For code path not on the xmit, use netif_tx_disable() instead of netif_stop_queue() to ensure other CPUs are not doing xmit. Signed-off-by: Haiyang Zhang Signed-off-by: K. Y. Srinivasan Signed-off-by: David S. Miller --- drivers/net/hyperv/netvsc_drv.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/net/hyperv/netvsc_drv.c') diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index 9dccc7a3a220..69193fcb7648 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -123,7 +123,7 @@ static int netvsc_close(struct net_device *net) struct hv_device *device_obj = net_device_ctx->device_ctx; int ret; - netif_stop_queue(net); + netif_tx_disable(net); ret = rndis_filter_close(device_obj); if (ret != 0) @@ -256,7 +256,7 @@ void netvsc_linkstatus_callback(struct hv_device *device_obj, schedule_delayed_work(&ndev_ctx->dwork, msecs_to_jiffies(20)); } else { netif_carrier_off(net); - netif_stop_queue(net); + netif_tx_disable(net); } } @@ -337,7 +337,7 @@ static int netvsc_change_mtu(struct net_device *ndev, int mtu) nvdev->start_remove = true; cancel_delayed_work_sync(&ndevctx->dwork); - netif_stop_queue(ndev); + netif_tx_disable(ndev); rndis_filter_device_remove(hdev); ndev->mtu = mtu; @@ -460,7 +460,7 @@ static int netvsc_remove(struct hv_device *dev) cancel_delayed_work_sync(&ndev_ctx->dwork); /* Stop outbound asap */ - netif_stop_queue(net); + netif_tx_disable(net); unregister_netdev(net); -- cgit v1.2.3 From c31c151b1c4a29da4dc92212aa8648fb4f8557b9 Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Thu, 2 Feb 2012 07:18:00 +0000 Subject: net/hyperv: Fix the page buffer when an RNDIS message goes beyond page boundary There is a possible data corruption if an RNDIS message goes beyond page boundary in the sending code path. This patch fixes the problem. Signed-off-by: Haiyang Zhang Signed-off-by: K. Y. Srinivasan Signed-off-by: David S. Miller --- drivers/net/hyperv/netvsc_drv.c | 8 ++++---- drivers/net/hyperv/rndis_filter.c | 13 +++++++++++++ include/linux/hyperv.h | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) (limited to 'drivers/net/hyperv/netvsc_drv.c') diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index 69193fcb7648..466c58a7353d 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -151,10 +151,10 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) int ret; unsigned int i, num_pages, npg_data; - /* Add multipage for skb->data and additional one for RNDIS */ + /* Add multipages for skb->data and additional 2 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; + num_pages = skb_shinfo(skb)->nr_frags + npg_data + 2; /* Allocate a netvsc packet based on # of frags. */ packet = kzalloc(sizeof(struct hv_netvsc_packet) + @@ -173,8 +173,8 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) sizeof(struct hv_netvsc_packet) + (num_pages * sizeof(struct hv_page_buffer)); - /* Setup the rndis header */ - packet->page_buf_cnt = num_pages; + /* If the rndis msg goes beyond 1 page, we will add 1 later */ + packet->page_buf_cnt = num_pages - 1; /* Initialize it from the skb */ packet->total_data_buflen = skb->len; diff --git a/drivers/net/hyperv/rndis_filter.c b/drivers/net/hyperv/rndis_filter.c index dc2e3849573b..133b7fbf8595 100644 --- a/drivers/net/hyperv/rndis_filter.c +++ b/drivers/net/hyperv/rndis_filter.c @@ -797,6 +797,19 @@ int rndis_filter_send(struct hv_device *dev, (unsigned long)rndisMessage & (PAGE_SIZE-1); pkt->page_buf[0].len = rndisMessageSize; + /* Add one page_buf if the rndis msg goes beyond page boundary */ + if (pkt->page_buf[0].offset + rndisMessageSize > PAGE_SIZE) { + int i; + for (i = pkt->page_buf_cnt; i > 1; i--) + pkt->page_buf[i] = pkt->page_buf[i-1]; + pkt->page_buf_cnt++; + pkt->page_buf[0].len = PAGE_SIZE - pkt->page_buf[0].offset; + pkt->page_buf[1].pfn = virt_to_phys((void *)((ulong) + rndisMessage + pkt->page_buf[0].len)) >> PAGE_SHIFT; + pkt->page_buf[1].offset = 0; + pkt->page_buf[1].len = rndisMessageSize - pkt->page_buf[0].len; + } + /* Save the packet send completion and context */ filterPacket->completion = pkt->completion.send.send_completion; filterPacket->completion_ctx = diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 62b908e0e591..0ae065a5fcb2 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -35,7 +35,7 @@ #include -#define MAX_PAGE_BUFFER_COUNT 18 +#define MAX_PAGE_BUFFER_COUNT 19 #define MAX_MULTIPAGE_BUFFER_COUNT 32 /* 128K */ #pragma pack(push, 1) -- cgit v1.2.3