diff options
author | Haiyang Zhang <haiyangz@microsoft.com> | 2011-11-28 13:35:35 -0800 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2011-11-29 16:12:36 +0900 |
commit | 95fa0405c5991726e06c08ffcd8ff872f7fb4f2d (patch) | |
tree | b03a3a6278d9eb2baab16f45082bdb2ac1a6a183 /drivers/net/hyperv/netvsc.c | |
parent | 3b724ca14565747926c23af1fa1afb1848c3f448 (diff) |
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 <haiyangz@microsoft.com>
Signed-off-by: KY Srinivasan <kys@microsoft.com>
Signed-off-by: Mike Sterling <Mike.Sterling@microsoft.com>
Acked-by: Stephen Hemminger <shemminger@vyatta.com>
Acked-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/net/hyperv/netvsc.c')
-rw-r--r-- | drivers/net/hyperv/netvsc.c | 939 |
1 files changed, 939 insertions, 0 deletions
diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c new file mode 100644 index 00000000000..28e69a6c74a --- /dev/null +++ b/drivers/net/hyperv/netvsc.c @@ -0,0 +1,939 @@ +/* + * 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 <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/netdevice.h> + +#include "hyperv_net.h" + + +static struct netvsc_device *alloc_net_device(struct hv_device *device) +{ + struct netvsc_device *net_device; + struct net_device *ndev = hv_get_drvdata(device); + + net_device = kzalloc(sizeof(struct netvsc_device), GFP_KERNEL); + if (!net_device) + return NULL; + + + net_device->destroy = false; + net_device->dev = device; + net_device->ndev = ndev; + + hv_set_drvdata(device, net_device); + return net_device; +} + +static struct netvsc_device *get_outbound_net_device(struct hv_device *device) +{ + struct netvsc_device *net_device; + + net_device = hv_get_drvdata(device); + if (net_device && net_device->destroy) + net_device = NULL; + + return net_device; +} + +static struct netvsc_device *get_inbound_net_device(struct hv_device *device) +{ + struct netvsc_device *net_device; + + net_device = hv_get_drvdata(device); + + if (!net_device) + goto get_in_err; + + if (net_device->destroy && + atomic_read(&net_device->num_outstanding_sends) == 0) + net_device = NULL; + +get_in_err: + return net_device; +} + + +static int netvsc_destroy_recv_buf(struct netvsc_device *net_device) +{ + struct nvsp_message *revoke_packet; + int ret = 0; + struct net_device *ndev = net_device->ndev; + + /* + * If we got a section count, it means we received a + * SendReceiveBufferComplete msg (ie sent + * NvspMessage1TypeSendReceiveBuffer msg) therefore, we need + * to send a revoke msg here + */ + if (net_device->recv_section_cnt) { + /* Send the revoke receive buffer */ + revoke_packet = &net_device->revoke_packet; + memset(revoke_packet, 0, sizeof(struct nvsp_message)); + + revoke_packet->hdr.msg_type = + NVSP_MSG1_TYPE_REVOKE_RECV_BUF; + revoke_packet->msg.v1_msg. + revoke_recv_buf.id = NETVSC_RECEIVE_BUFFER_ID; + + ret = vmbus_sendpacket(net_device->dev->channel, + revoke_packet, + sizeof(struct nvsp_message), + (unsigned long)revoke_packet, + VM_PKT_DATA_INBAND, 0); + /* + * If we failed here, we might as well return and + * have a leak rather than continue and a bugchk + */ + if (ret != 0) { + netdev_err(ndev, "unable to send " + "revoke receive buffer to netvsp\n"); + return ret; + } + } + + /* Teardown the gpadl on the vsp end */ + if (net_device->recv_buf_gpadl_handle) { + ret = vmbus_teardown_gpadl(net_device->dev->channel, + net_device->recv_buf_gpadl_handle); + + /* If we failed here, we might as well return and have a leak + * rather than continue and a bugchk + */ + if (ret != 0) { + netdev_err(ndev, + "unable to teardown receive buffer's gpadl\n"); + return ret; + } + net_device->recv_buf_gpadl_handle = 0; + } + + if (net_device->recv_buf) { + /* Free up the receive buffer */ + free_pages((unsigned long)net_device->recv_buf, + get_order(net_device->recv_buf_size)); + net_device->recv_buf = NULL; + } + + if (net_device->recv_section) { + net_device->recv_section_cnt = 0; + kfree(net_device->recv_section); + net_device->recv_section = NULL; + } + + return ret; +} + +static int netvsc_init_recv_buf(struct hv_device *device) +{ + int ret = 0; + int t; + struct netvsc_device *net_device; + struct nvsp_message *init_packet; + struct net_device *ndev; + + net_device = get_outbound_net_device(device); + if (!net_device) + return -ENODEV; + ndev = net_device->ndev; + + net_device->recv_buf = + (void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO, + get_order(net_device->recv_buf_size)); + if (!net_device->recv_buf) { + netdev_err(ndev, "unable to allocate receive " + "buffer of size %d\n", net_device->recv_buf_size); + ret = -ENOMEM; + goto cleanup; + } + + /* + * Establish the gpadl handle for this buffer on this + * channel. Note: This call uses the vmbus connection rather + * than the channel to establish the gpadl handle. + */ + ret = vmbus_establish_gpadl(device->channel, net_device->recv_buf, + net_device->recv_buf_size, + &net_device->recv_buf_gpadl_handle); + if (ret != 0) { + netdev_err(ndev, + "unable to establish receive buffer's gpadl\n"); + goto cleanup; + } + + + /* Notify the NetVsp of the gpadl handle */ + init_packet = &net_device->channel_init_pkt; + + memset(init_packet, 0, sizeof(struct nvsp_message)); + + init_packet->hdr.msg_type = NVSP_MSG1_TYPE_SEND_RECV_BUF; + init_packet->msg.v1_msg.send_recv_buf. + gpadl_handle = net_device->recv_buf_gpadl_handle; + init_packet->msg.v1_msg. + send_recv_buf.id = NETVSC_RECEIVE_BUFFER_ID; + + /* Send the gpadl notification request */ + ret = vmbus_sendpacket(device->channel, init_packet, + sizeof(struct nvsp_message), + (unsigned long)init_packet, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret != 0) { + netdev_err(ndev, + "unable to send receive buffer's gpadl to netvsp\n"); + goto cleanup; + } + + t = wait_for_completion_timeout(&net_device->channel_init_wait, 5*HZ); + BUG_ON(t == 0); + + + /* Check the response */ + if (init_packet->msg.v1_msg. + send_recv_buf_complete.status != NVSP_STAT_SUCCESS) { + netdev_err(ndev, "Unable to complete receive buffer " + "initialization with NetVsp - status %d\n", + init_packet->msg.v1_msg. + send_recv_buf_complete.status); + ret = -EINVAL; + goto cleanup; + } + + /* Parse the response */ + + net_device->recv_section_cnt = init_packet->msg. + v1_msg.send_recv_buf_complete.num_sections; + + net_device->recv_section = kmemdup(init_packet->msg.v1_msg.send_recv_buf_complete.sections, + net_device->recv_section_cnt * sizeof(struct nvsp_1_receive_buffer_section), + GFP_KERNEL); + if (net_device->recv_section == NULL) { + ret = -EINVAL; + goto cleanup; + } + + /* + * For 1st release, there should only be 1 section that represents the + * entire receive buffer + */ + if (net_device->recv_section_cnt != 1 || + net_device->recv_section->offset != 0) { + ret = -EINVAL; + goto cleanup; + } + + goto exit; + +cleanup: + netvsc_destroy_recv_buf(net_device); + +exit: + return ret; +} + + +static int netvsc_connect_vsp(struct hv_device *device) +{ + int ret, t; + struct netvsc_device *net_device; + struct nvsp_message *init_packet; + int ndis_version; + struct net_device *ndev; + + net_device = get_outbound_net_device(device); + if (!net_device) + return -ENODEV; + ndev = net_device->ndev; + + init_packet = &net_device->channel_init_pkt; + + memset(init_packet, 0, sizeof(struct nvsp_message)); + init_packet->hdr.msg_type = NVSP_MSG_TYPE_INIT; + init_packet->msg.init_msg.init.min_protocol_ver = + NVSP_MIN_PROTOCOL_VERSION; + init_packet->msg.init_msg.init.max_protocol_ver = + NVSP_MAX_PROTOCOL_VERSION; + + /* Send the init request */ + ret = vmbus_sendpacket(device->channel, init_packet, + sizeof(struct nvsp_message), + (unsigned long)init_packet, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + + if (ret != 0) + goto cleanup; + + t = wait_for_completion_timeout(&net_device->channel_init_wait, 5*HZ); + + if (t == 0) { + ret = -ETIMEDOUT; + goto cleanup; + } + + if (init_packet->msg.init_msg.init_complete.status != + NVSP_STAT_SUCCESS) { + ret = -EINVAL; + goto cleanup; + } + + if (init_packet->msg.init_msg.init_complete. + negotiated_protocol_ver != NVSP_PROTOCOL_VERSION_1) { + ret = -EPROTO; + goto cleanup; + } + /* Send the ndis version */ + memset(init_packet, 0, sizeof(struct nvsp_message)); + + ndis_version = 0x00050000; + + init_packet->hdr.msg_type = NVSP_MSG1_TYPE_SEND_NDIS_VER; + init_packet->msg.v1_msg. + send_ndis_ver.ndis_major_ver = + (ndis_version & 0xFFFF0000) >> 16; + init_packet->msg.v1_msg. + send_ndis_ver.ndis_minor_ver = + ndis_version & 0xFFFF; + + /* Send the init request */ + ret = vmbus_sendpacket(device->channel, init_packet, + sizeof(struct nvsp_message), + (unsigned long)init_packet, + VM_PKT_DATA_INBAND, 0); + if (ret != 0) + goto cleanup; + + /* Post the big receive buffer to NetVSP */ + ret = netvsc_init_recv_buf(device); + +cleanup: + return ret; +} + +static void netvsc_disconnect_vsp(struct netvsc_device *net_device) +{ + netvsc_destroy_recv_buf(net_device); +} + +/* + * netvsc_device_remove - Callback when the root bus device is removed + */ +int netvsc_device_remove(struct hv_device *device) +{ + struct netvsc_device *net_device; + struct hv_netvsc_packet *netvsc_packet, *pos; + unsigned long flags; + + net_device = hv_get_drvdata(device); + spin_lock_irqsave(&device->channel->inbound_lock, flags); + net_device->destroy = true; + spin_unlock_irqrestore(&device->channel->inbound_lock, flags); + + /* Wait for all send completions */ + while (atomic_read(&net_device->num_outstanding_sends)) { + dev_info(&device->device, + "waiting for %d requests to complete...\n", + atomic_read(&net_device->num_outstanding_sends)); + udelay(100); + } + + netvsc_disconnect_vsp(net_device); + + /* + * Since we have already drained, we don't need to busy wait + * as was done in final_release_stor_device() + * Note that we cannot set the ext pointer to NULL until + * we have drained - to drain the outgoing packets, we need to + * allow incoming packets. + */ + + spin_lock_irqsave(&device->channel->inbound_lock, flags); + hv_set_drvdata(device, NULL); + spin_unlock_irqrestore(&device->channel->inbound_lock, flags); + + /* + * At this point, no one should be accessing net_device + * except in here + */ + dev_notice(&device->device, "net device safe to remove\n"); + + /* Now, we can close the channel safely */ + vmbus_close(device->channel); + + /* Release all resources */ + list_for_each_entry_safe(netvsc_packet, pos, + &net_device->recv_pkt_list, list_ent) { + list_del(&netvsc_packet->list_ent); + kfree(netvsc_packet); + } + + kfree(net_device); + return 0; +} + +static void netvsc_send_completion(struct hv_device *device, + struct vmpacket_descriptor *packet) +{ + struct netvsc_device *net_device; + struct nvsp_message *nvsp_packet; + struct hv_netvsc_packet *nvsc_packet; + struct net_device *ndev; + + net_device = get_inbound_net_device(device); + if (!net_device) + return; + ndev = net_device->ndev; + + nvsp_packet = (struct nvsp_message *)((unsigned long)packet + + (packet->offset8 << 3)); + + if ((nvsp_packet->hdr.msg_type == NVSP_MSG_TYPE_INIT_COMPLETE) || + (nvsp_packet->hdr.msg_type == + NVSP_MSG1_TYPE_SEND_RECV_BUF_COMPLETE) || + (nvsp_packet->hdr.msg_type == + NVSP_MSG1_TYPE_SEND_SEND_BUF_COMPLETE)) { + /* Copy the response back */ + memcpy(&net_device->channel_init_pkt, nvsp_packet, + sizeof(struct nvsp_message)); + complete(&net_device->channel_init_wait); + } else if (nvsp_packet->hdr.msg_type == + NVSP_MSG1_TYPE_SEND_RNDIS_PKT_COMPLETE) { + /* Get the send context */ + nvsc_packet = (struct hv_netvsc_packet *)(unsigned long) + packet->trans_id; + + /* Notify the layer above us */ + nvsc_packet->completion.send.send_completion( + nvsc_packet->completion.send.send_completion_ctx); + + atomic_dec(&net_device->num_outstanding_sends); + } else { + netdev_err(ndev, "Unknown send completion packet type- " + "%d received!!\n", nvsp_packet->hdr.msg_type); + } + +} + +int netvsc_send(struct hv_device *device, + struct hv_netvsc_packet *packet) +{ + struct netvsc_device *net_device; + int ret = 0; + struct nvsp_message sendMessage; + struct net_device *ndev; + + net_device = get_outbound_net_device(device); + if (!net_device) + return -ENODEV; + ndev = net_device->ndev; + + sendMessage.hdr.msg_type = NVSP_MSG1_TYPE_SEND_RNDIS_PKT; + if (packet->is_data_pkt) { + /* 0 is RMC_DATA; */ + sendMessage.msg.v1_msg.send_rndis_pkt.channel_type = 0; + } else { + /* 1 is RMC_CONTROL; */ + sendMessage.msg.v1_msg.send_rndis_pkt.channel_type = 1; + } + + /* Not using send buffer section */ + sendMessage.msg.v1_msg.send_rndis_pkt.send_buf_section_index = + 0xFFFFFFFF; + sendMessage.msg.v1_msg.send_rndis_pkt.send_buf_section_size = 0; + + if (packet->page_buf_cnt) { + ret = vmbus_sendpacket_pagebuffer(device->channel, + packet->page_buf, + packet->page_buf_cnt, + &sendMessage, + sizeof(struct nvsp_message), + (unsigned long)packet); + } else { + ret = vmbus_sendpacket(device->channel, &sendMessage, + sizeof(struct nvsp_message), + (unsigned long)packet, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + + } + + if (ret != 0) + netdev_err(ndev, "Unable to send packet %p ret %d\n", + packet, ret); + else + atomic_inc(&net_device->num_outstanding_sends); + + return ret; +} + +static void netvsc_send_recv_completion(struct hv_device *device, + u64 transaction_id) +{ + struct nvsp_message recvcompMessage; + int retries = 0; + int ret; + struct net_device *ndev; + struct netvsc_device *net_device = hv_get_drvdata(device); + + ndev = net_device->ndev; + + recvcompMessage.hdr.msg_type = + NVSP_MSG1_TYPE_SEND_RNDIS_PKT_COMPLETE; + + /* FIXME: Pass in the status */ + recvcompMessage.msg.v1_msg.send_rndis_pkt_complete.status = + NVSP_STAT_SUCCESS; + +retry_send_cmplt: + /* Send the completion */ + ret = vmbus_sendpacket(device->channel, &recvcompMessage, + sizeof(struct nvsp_message), transaction_id, + VM_PKT_COMP, 0); + if (ret == 0) { + /* success */ + /* no-op */ + } else if (ret == -EAGAIN) { + /* no more room...wait a bit and attempt to retry 3 times */ + retries++; + netdev_err(ndev, "unable to send receive completion pkt" + " (tid %llx)...retrying %d\n", transaction_id, retries); + + if (retries < 4) { + udelay(100); + goto retry_send_cmplt; + } else { + netdev_err(ndev, "unable to send receive " + "completion pkt (tid %llx)...give up retrying\n", + transaction_id); + } + } else { + netdev_err(ndev, "unable to send receive " + "completion pkt - %llx\n", transaction_id); + } +} + +/* Send a receive completion packet to RNDIS device (ie NetVsp) */ +static void netvsc_receive_completion(void *context) +{ + struct hv_netvsc_packet *packet = context; + struct hv_device *device = (struct hv_device *)packet->device; + struct netvsc_device *net_device; + u64 transaction_id = 0; + bool fsend_receive_comp = false; + unsigned long flags; + struct net_device *ndev; + + /* + * Even though it seems logical to do a GetOutboundNetDevice() here to + * send out receive completion, we are using GetInboundNetDevice() + * since we may have disable outbound traffic already. + */ + net_device = get_inbound_net_device(device); + if (!net_device) + return; + ndev = net_device->ndev; + + /* Overloading use of the lock. */ + spin_lock_irqsave(&net_device->recv_pkt_list_lock, flags); + + packet->xfer_page_pkt->count--; + + /* + * Last one in the line that represent 1 xfer page packet. + * Return the xfer page packet itself to the freelist + */ + if (packet->xfer_page_pkt->count == 0) { + fsend_receive_comp = true; + transaction_id = packet->completion.recv.recv_completion_tid; + list_add_tail(&packet->xfer_page_pkt->list_ent, + &net_device->recv_pkt_list); + + } + + /* Put the packet back */ + list_add_tail(&packet->list_ent, &net_device->recv_pkt_list); + spin_unlock_irqrestore(&net_device->recv_pkt_list_lock, flags); + + /* Send a receive completion for the xfer page packet */ + if (fsend_receive_comp) + netvsc_send_recv_completion(device, transaction_id); + +} + +static void netvsc_receive(struct hv_device *device, + struct vmpacket_descriptor *packet) +{ + struct netvsc_device *net_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; + unsigned long flags; + struct net_device *ndev; + + LIST_HEAD(listHead); + + net_device = get_inbound_net_device(device); + if (!net_device) + return; + ndev = net_device->ndev; + + /* + * All inbound packets other than send completion should be xfer page + * packet + */ + if (packet->type != VM_PKT_DATA_USING_XFER_PAGES) { + netdev_err(ndev, "Unknown packet type received - %d\n", + packet->type); + return; + } + + nvsp_packet = (struct nvsp_message *)((unsigned long)packet + + (packet->offset8 << 3)); + + /* Make sure this is a valid nvsp packet */ + if (nvsp_packet->hdr.msg_type != + NVSP_MSG1_TYPE_SEND_RNDIS_PKT) { + netdev_err(ndev, "Unknown nvsp packet type received-" + " %d\n", nvsp_packet->hdr.msg_type); + return; + } + + vmxferpage_packet = (struct vmtransfer_page_packet_header *)packet; + + if (vmxferpage_packet->xfer_pageset_id != NETVSC_RECEIVE_BUFFER_ID) { + netdev_err(ndev, "Invalid xfer page set id - " + "expecting %x got %x\n", NETVSC_RECEIVE_BUFFER_ID, + vmxferpage_packet->xfer_pageset_id); + return; + } + + /* + * Grab free packets (range count + 1) to represent this xfer + * page packet. +1 to represent the xfer page packet itself. + * We grab it here so that we know exactly how many we can + * fulfil + */ + spin_lock_irqsave(&net_device->recv_pkt_list_lock, flags); + while (!list_empty(&net_device->recv_pkt_list)) { + list_move_tail(net_device->recv_pkt_list.next, &listHead); + if (++count == vmxferpage_packet->range_cnt + 1) + break; + } + spin_unlock_irqrestore(&net_device->recv_pkt_list_lock, flags); + + /* + * We need at least 2 netvsc pkts (1 to represent the xfer + * page and at least 1 for the range) i.e. we can handled + * some of the xfer page packet ranges... + */ + if (count < 2) { + netdev_err(ndev, "Got only %d netvsc pkt...needed " + "%d pkts. Dropping this xfer page packet completely!\n", + count, vmxferpage_packet->range_cnt + 1); + + /* Return it to the freelist */ + spin_lock_irqsave(&net_device->recv_pkt_list_lock, flags); + for (i = count; i != 0; i--) { + list_move_tail(listHead.next, + &net_device->recv_pkt_list); + } + spin_unlock_irqrestore(&net_device->recv_pkt_list_lock, + flags); + + netvsc_send_recv_completion(device, + vmxferpage_packet->d.trans_id); + + return; + } + + /* Remove the 1st packet to represent the xfer page packet itself */ + xferpage_packet = (struct xferpage_packet *)listHead.next; + list_del(&xferpage_packet->list_ent); + + /* This is how much we can satisfy */ + xferpage_packet->count = count - 1; + + if (xferpage_packet->count != vmxferpage_packet->range_cnt) { + netdev_err(ndev, "Needed %d netvsc pkts to satisfy " + "this xfer page...got %d\n", + vmxferpage_packet->range_cnt, xferpage_packet->count); + } + + /* Each range represents 1 RNDIS pkt that contains 1 ethernet frame */ + for (i = 0; i < (count - 1); i++) { + netvsc_packet = (struct hv_netvsc_packet *)listHead.next; + list_del(&netvsc_packet->list_ent); + + /* Initialize the netvsc packet */ + netvsc_packet->xfer_page_pkt = xferpage_packet; + netvsc_packet->completion.recv.recv_completion = + netvsc_receive_completion; + netvsc_packet->completion.recv.recv_completion_ctx = + netvsc_packet; + netvsc_packet->device = device; + /* Save this so that we can send it back */ + netvsc_packet->completion.recv.recv_completion_tid = + vmxferpage_packet->d.trans_id; + + 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); + + netvsc_receive_completion(netvsc_packet-> + completion.recv.recv_completion_ctx); + } + +} + +static void netvsc_channel_cb(void *context) +{ + int ret; + struct hv_device *device = context; + struct netvsc_device *net_device; + u32 bytes_recvd; + u64 request_id; + unsigned char *packet; + struct vmpacket_descriptor *desc; + unsigned char *buffer; + int bufferlen = NETVSC_PACKET_SIZE; + struct net_device *ndev; + + packet = kzalloc(NETVSC_PACKET_SIZE * sizeof(unsigned char), + GFP_ATOMIC); + if (!packet) + return; + buffer = packet; + + net_device = get_inbound_net_device(device); + if (!net_device) + goto out; + ndev = net_device->ndev; + + do { + ret = vmbus_recvpacket_raw(device->channel, buffer, bufferlen, + &bytes_recvd, &request_id); + if (ret == 0) { + if (bytes_recvd > 0) { + desc = (struct vmpacket_descriptor *)buffer; + switch (desc->type) { + case VM_PKT_COMP: + netvsc_send_completion(device, desc); + break; + + case VM_PKT_DATA_USING_XFER_PAGES: + netvsc_receive(device, desc); + break; + + default: + netdev_err(ndev, + "unhandled packet type %d, " + "tid %llx len %d\n", + desc->type, request_id, + bytes_recvd); + break; + } + + /* reset */ + if (bufferlen > NETVSC_PACKET_SIZE) { + kfree(buffer); + buffer = packet; + bufferlen = NETVSC_PACKET_SIZE; + } + } else { + /* reset */ + if (bufferlen > NETVSC_PACKET_SIZE) { + kfree(buffer); + buffer = packet; + bufferlen = NETVSC_PACKET_SIZE; + } + + break; + } + } else if (ret == -ENOBUFS) { + /* Handle large packet */ + buffer = kmalloc(bytes_recvd, GFP_ATOMIC); + if (buffer == NULL) { + /* Try again next time around */ + netdev_err(ndev, + "unable to allocate buffer of size " + "(%d)!!\n", bytes_recvd); + break; + } + + bufferlen = bytes_recvd; + } + } while (1); + +out: + kfree(buffer); + return; +} + +/* + * netvsc_device_add - Callback when the device belonging to this + * driver is added + */ +int netvsc_device_add(struct hv_device *device, void *additional_info) +{ + int ret = 0; + int i; + int ring_size = + ((struct netvsc_device_info *)additional_info)->ring_size; + struct netvsc_device *net_device; + struct hv_netvsc_packet *packet, *pos; + struct net_device *ndev; + + net_device = alloc_net_device(device); + if (!net_device) { + ret = -ENOMEM; + goto cleanup; + } + + /* + * Coming into this function, struct net_device * is + * registered as the driver private data. + * In alloc_net_device(), we register struct netvsc_device * + * as the driver private data and stash away struct net_device * + * in struct netvsc_device *. + */ + ndev = net_device->ndev; + + /* Initialize the NetVSC channel extension */ + net_device->recv_buf_size = NETVSC_RECEIVE_BUFFER_SIZE; + spin_lock_init(&net_device->recv_pkt_list_lock); + + INIT_LIST_HEAD(&net_device->recv_pkt_list); + + for (i = 0; i < NETVSC_RECEIVE_PACKETLIST_COUNT; i++) { + packet = kzalloc(sizeof(struct hv_netvsc_packet) + + (NETVSC_RECEIVE_SG_COUNT * + sizeof(struct hv_page_buffer)), GFP_KERNEL); + if (!packet) + break; + + list_add_tail(&packet->list_ent, + &net_device->recv_pkt_list); + } + init_completion(&net_device->channel_init_wait); + + /* Open the channel */ + ret = vmbus_open(device->channel, ring_size * PAGE_SIZE, + ring_size * PAGE_SIZE, NULL, 0, + netvsc_channel_cb, device); + + if (ret != 0) { + netdev_err(ndev, "unable to open channel: %d\n", ret); + goto cleanup; + } + + /* Channel is opened */ + pr_info("hv_netvsc channel opened successfully\n"); + + /* Connect with the NetVsp */ + ret = netvsc_connect_vsp(device); + if (ret != 0) { + netdev_err(ndev, + "unable to connect to NetVSP - %d\n", ret); + goto close; + } + + return ret; + +close: + /* Now, we can close the channel safely */ + vmbus_close(device->channel); + +cleanup: + + if (net_device) { + list_for_each_entry_safe(packet, pos, + &net_device->recv_pkt_list, + list_ent) { + list_del(&packet->list_ent); + kfree(packet); + } + + kfree(net_device); + } + + return ret; +} |