diff options
Diffstat (limited to 'drivers/staging/hv/netvsc_drv.c')
-rw-r--r-- | drivers/staging/hv/netvsc_drv.c | 243 |
1 files changed, 119 insertions, 124 deletions
diff --git a/drivers/staging/hv/netvsc_drv.c b/drivers/staging/hv/netvsc_drv.c index ab27d9a4446d..55b993298ff4 100644 --- a/drivers/staging/hv/netvsc_drv.c +++ b/drivers/staging/hv/netvsc_drv.c @@ -30,20 +30,22 @@ #include <linux/skbuff.h> #include <linux/in.h> #include <linux/slab.h> +#include <linux/dmi.h> +#include <linux/pci.h> #include <net/arp.h> #include <net/route.h> #include <net/sock.h> #include <net/pkt_sched.h> #include "osd.h" #include "logging.h" -#include "VersionInfo.h" +#include "version_info.h" #include "vmbus.h" -#include "NetVscApi.h" +#include "netvsc_api.h" struct net_device_context { /* point back to our device context */ struct vm_device *device_ctx; - struct net_device_stats stats; + unsigned long avail; }; struct netvsc_driver_context { @@ -53,18 +55,17 @@ struct netvsc_driver_context { struct netvsc_driver drv_obj; }; -static int netvsc_ringbuffer_size = NETVSC_DEVICE_RING_BUFFER_SIZE; +#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 = roundup_pow_of_two(2*MAX_SKB_FRAGS+1); +module_param(ring_size, int, S_IRUGO); +MODULE_PARM_DESC(ring_size, "Ring buffer size (# of pages)"); /* The one and only one */ static struct netvsc_driver_context g_netvsc_drv; -static struct net_device_stats *netvsc_get_stats(struct net_device *net) -{ - struct net_device_context *net_device_ctx = netdev_priv(net); - - return &net_device_ctx->stats; -} - static void netvsc_set_multicast_list(struct net_device *net) { } @@ -78,9 +79,6 @@ static int netvsc_open(struct net_device *net) DPRINT_ENTER(NETVSC_DRV); if (netif_carrier_ok(net)) { - memset(&net_device_ctx->stats, 0, - sizeof(struct net_device_stats)); - /* Open up the device */ ret = RndisFilterOnOpen(device_obj); if (ret != 0) { @@ -122,22 +120,20 @@ 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.SendCompletionTid; - struct net_device *net; DPRINT_ENTER(NETVSC_DRV); kfree(packet); if (skb) { - net = skb->dev; - dev_kfree_skb_any(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 (netif_queue_stopped(net)) { - DPRINT_INFO(NETVSC_DRV, "net device (%p) waking up...", - net); + dev_kfree_skb_any(skb); - netif_wake_queue(net); - } + if ((net_device_ctx->avail += num_pages) >= PACKET_PAGES_HIWATER) + netif_wake_queue(net); } DPRINT_EXIT(NETVSC_DRV); @@ -152,65 +148,58 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) (struct netvsc_driver_context *)driver_ctx; struct netvsc_driver *net_drv_obj = &net_drv_ctx->drv_obj; struct hv_netvsc_packet *packet; - int i; int ret; - int num_frags; - int retries = 0; + unsigned int i, num_pages; DPRINT_ENTER(NETVSC_DRV); - /* Support only 1 chain of frags */ - ASSERT(skb_shinfo(skb)->frag_list == NULL); - ASSERT(skb->dev == net); - DPRINT_DBG(NETVSC_DRV, "xmit packet - len %d data_len %d", skb->len, skb->data_len); - /* Add 1 for skb->data and any additional ones requested */ - num_frags = skb_shinfo(skb)->nr_frags + 1 + - net_drv_obj->AdditionalRequestPageBufferCount; + /* Add 1 for skb->data and additional one for RNDIS */ + num_pages = skb_shinfo(skb)->nr_frags + 1 + 1; + if (num_pages > net_device_ctx->avail) + return NETDEV_TX_BUSY; /* Allocate a netvsc packet based on # of frags. */ packet = kzalloc(sizeof(struct hv_netvsc_packet) + - (num_frags * sizeof(struct hv_page_buffer)) + + (num_pages * sizeof(struct hv_page_buffer)) + net_drv_obj->RequestExtSize, GFP_ATOMIC); if (!packet) { + /* out of memory, silently drop packet */ DPRINT_ERR(NETVSC_DRV, "unable to allocate hv_netvsc_packet"); - return -1; + + dev_kfree_skb(skb); + net->stats.tx_dropped++; + return NETDEV_TX_OK; } packet->Extension = (void *)(unsigned long)packet + sizeof(struct hv_netvsc_packet) + - (num_frags * sizeof(struct hv_page_buffer)); + (num_pages * sizeof(struct hv_page_buffer)); /* Setup the rndis header */ - packet->PageBufferCount = num_frags; + packet->PageBufferCount = num_pages; /* TODO: Flush all write buffers/ memory fence ??? */ /* wmb(); */ /* Initialize it from the skb */ - ASSERT(skb->data); packet->TotalDataBufferLength = skb->len; - /* - * Start filling in the page buffers starting at - * AdditionalRequestPageBufferCount offset - */ - packet->PageBuffers[net_drv_obj->AdditionalRequestPageBufferCount].Pfn = virt_to_phys(skb->data) >> PAGE_SHIFT; - packet->PageBuffers[net_drv_obj->AdditionalRequestPageBufferCount].Offset = (unsigned long)skb->data & (PAGE_SIZE - 1); - packet->PageBuffers[net_drv_obj->AdditionalRequestPageBufferCount].Length = skb->len - skb->data_len; - - ASSERT((skb->len - skb->data_len) <= PAGE_SIZE); - - for (i = net_drv_obj->AdditionalRequestPageBufferCount + 1; - i < num_frags; i++) { - packet->PageBuffers[i].Pfn = - page_to_pfn(skb_shinfo(skb)->frags[i-(net_drv_obj->AdditionalRequestPageBufferCount+1)].page); - packet->PageBuffers[i].Offset = - skb_shinfo(skb)->frags[i-(net_drv_obj->AdditionalRequestPageBufferCount+1)].page_offset; - packet->PageBuffers[i].Length = - skb_shinfo(skb)->frags[i-(net_drv_obj->AdditionalRequestPageBufferCount+1)].size; + /* Start filling in the page buffers starting after RNDIS buffer. */ + packet->PageBuffers[1].Pfn = virt_to_phys(skb->data) >> PAGE_SHIFT; + packet->PageBuffers[1].Offset + = (unsigned long)skb->data & (PAGE_SIZE - 1); + packet->PageBuffers[1].Length = skb_headlen(skb); + + /* Additional fragments are after SKB data */ + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_frag_t *f = &skb_shinfo(skb)->frags[i]; + + packet->PageBuffers[i+2].Pfn = page_to_pfn(f->page); + packet->PageBuffers[i+2].Offset = f->page_offset; + packet->PageBuffers[i+2].Length = f->size; } /* Set the completion routine */ @@ -218,55 +207,29 @@ static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) packet->Completion.Send.SendCompletionContext = packet; packet->Completion.Send.SendCompletionTid = (unsigned long)skb; -retry_send: ret = net_drv_obj->OnSend(&net_device_ctx->device_ctx->device_obj, packet); - if (ret == 0) { - ret = NETDEV_TX_OK; - net_device_ctx->stats.tx_bytes += skb->len; - net_device_ctx->stats.tx_packets++; - } else { - retries++; - if (retries < 4) { - DPRINT_ERR(NETVSC_DRV, "unable to send..." - "retrying %d...", retries); - udelay(100); - goto retry_send; - } - - /* no more room or we are shutting down */ - DPRINT_ERR(NETVSC_DRV, "unable to send (%d)..." - "marking net device (%p) busy", ret, net); - DPRINT_INFO(NETVSC_DRV, "net device (%p) stopping", net); + net->stats.tx_bytes += skb->len; + net->stats.tx_packets++; - ret = NETDEV_TX_BUSY; - net_device_ctx->stats.tx_dropped++; + DPRINT_DBG(NETVSC_DRV, "# of xmits %lu total size %lu", + net->stats.tx_packets, + net->stats.tx_bytes); - netif_stop_queue(net); - - /* - * Null it since the caller will free it instead of the - * completion routine - */ - packet->Completion.Send.SendCompletionTid = 0; - - /* - * Release the resources since we will not get any send - * completion - */ - netvsc_xmit_completion((void *)packet); + if ((net_device_ctx->avail -= num_pages) < PACKET_PAGES_LOWATER) + netif_stop_queue(net); + } else { + /* we are shutting down or bus overloaded, just drop packet */ + net->stats.tx_dropped++; + netvsc_xmit_completion(packet); } - DPRINT_DBG(NETVSC_DRV, "# of xmits %lu total size %lu", - net_device_ctx->stats.tx_packets, - net_device_ctx->stats.tx_bytes); - DPRINT_EXIT(NETVSC_DRV); - return ret; + return NETDEV_TX_OK; } -/** +/* * netvsc_linkstatus_callback - Link up/down notification */ static void netvsc_linkstatus_callback(struct hv_device *device_obj, @@ -293,18 +256,17 @@ static void netvsc_linkstatus_callback(struct hv_device *device_obj, DPRINT_EXIT(NETVSC_DRV); } -/** - * netvsc_recv_callback - Callback when we receive a packet from the "wire" on the specified device. +/* + * netvsc_recv_callback - Callback when we receive a packet from the + * "wire" on the specified device. */ static int netvsc_recv_callback(struct hv_device *device_obj, struct hv_netvsc_packet *packet) { struct vm_device *device_ctx = to_vm_device(device_obj); struct net_device *net = dev_get_drvdata(&device_ctx->device); - struct net_device_context *net_device_ctx; struct sk_buff *skb; void *data; - int ret; int i; unsigned long flags; @@ -316,14 +278,12 @@ static int netvsc_recv_callback(struct hv_device *device_obj, return 0; } - net_device_ctx = netdev_priv(net); - - /* Allocate a skb - TODO preallocate this */ - /* Pad 2-bytes to align IP header to 16 bytes */ - skb = dev_alloc_skb(packet->TotalDataBufferLength + 2); - ASSERT(skb); - skb_reserve(skb, 2); - skb->dev = net; + /* Allocate a skb - TODO direct I/O to pages? */ + skb = netdev_alloc_skb_ip_align(net, packet->TotalDataBufferLength); + if (unlikely(!skb)) { + ++net->stats.rx_dropped; + return 0; + } /* for kmap_atomic */ local_irq_save(flags); @@ -348,39 +308,45 @@ static int netvsc_recv_callback(struct hv_device *device_obj, 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 + * is done. + * TODO - use NAPI? */ - ret = netif_rx(skb); - - switch (ret) { - case NET_RX_DROP: - net_device_ctx->stats.rx_dropped++; - break; - default: - net_device_ctx->stats.rx_packets++; - net_device_ctx->stats.rx_bytes += skb->len; - break; + netif_rx(skb); - } DPRINT_DBG(NETVSC_DRV, "# of recvs %lu total size %lu", - net_device_ctx->stats.rx_packets, - net_device_ctx->stats.rx_bytes); + net->stats.rx_packets, net->stats.rx_bytes); DPRINT_EXIT(NETVSC_DRV); 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_sg = ethtool_op_get_sg, + .set_sg = ethtool_op_set_sg, + .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_get_stats = netvsc_get_stats, .ndo_set_multicast_list = netvsc_set_multicast_list, }; @@ -413,6 +379,7 @@ static int netvsc_probe(struct device *device) net_device_ctx = netdev_priv(net); net_device_ctx->device_ctx = device_ctx; + net_device_ctx->avail = ring_size; dev_set_drvdata(device, net); /* Notify the netvsc driver of the new device */ @@ -442,6 +409,10 @@ static int netvsc_probe(struct device *device) net->netdev_ops = &device_ops; + /* TODO: Add GSO and Checksum offload */ + net->features = NETIF_F_SG; + + SET_ETHTOOL_OPS(net, ðtool_ops); SET_NETDEV_DEV(net, device); ret = register_netdev(net); @@ -559,7 +530,7 @@ static int netvsc_drv_init(int (*drv_init)(struct hv_driver *drv)) vmbus_get_interface(&net_drv_obj->Base.VmbusChannelInterface); - net_drv_obj->RingBufferSize = netvsc_ringbuffer_size; + net_drv_obj->RingBufferSize = ring_size * PAGE_SIZE; net_drv_obj->OnReceiveCallback = netvsc_recv_callback; net_drv_obj->OnLinkStatusChanged = netvsc_linkstatus_callback; @@ -581,6 +552,20 @@ static int netvsc_drv_init(int (*drv_init)(struct hv_driver *drv)) return ret; } +static const struct dmi_system_id __initconst +hv_netvsc_dmi_table[] __maybe_unused = { + { + .ident = "Hyper-V", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Virtual Machine"), + DMI_MATCH(DMI_BOARD_NAME, "Virtual Machine"), + }, + }, + { }, +}; +MODULE_DEVICE_TABLE(dmi, hv_netvsc_dmi_table); + static int __init netvsc_init(void) { int ret; @@ -588,6 +573,9 @@ static int __init netvsc_init(void) DPRINT_ENTER(NETVSC_DRV); DPRINT_INFO(NETVSC_DRV, "Netvsc initializing...."); + if (!dmi_check_system(hv_netvsc_dmi_table)) + return -ENODEV; + ret = netvsc_drv_init(NetVscInitialize); DPRINT_EXIT(NETVSC_DRV); @@ -602,9 +590,16 @@ static void __exit netvsc_exit(void) DPRINT_EXIT(NETVSC_DRV); } +static const struct pci_device_id __initconst +hv_netvsc_pci_table[] __maybe_unused = { + { PCI_DEVICE(0x1414, 0x5353) }, /* VGA compatible controller */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, hv_netvsc_pci_table); + MODULE_LICENSE("GPL"); MODULE_VERSION(HV_DRV_VERSION); -module_param(netvsc_ringbuffer_size, int, S_IRUGO); +MODULE_DESCRIPTION("Microsoft Hyper-V network driver"); module_init(netvsc_init); module_exit(netvsc_exit); |