aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/hv/netvsc_drv.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/hv/netvsc_drv.c')
-rw-r--r--drivers/staging/hv/netvsc_drv.c243
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, &ethtool_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);