/drivers/staging/hv/netvsc_drv.c
C | 490 lines | 316 code | 90 blank | 84 comment | 34 complexity | 461a76540d795b25e1111178b734daef MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, AGPL-1.0
- /*
- * 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/init.h>
- #include <linux/module.h>
- #include <linux/highmem.h>
- #include <linux/device.h>
- #include <linux/io.h>
- #include <linux/delay.h>
- #include <linux/netdevice.h>
- #include <linux/inetdevice.h>
- #include <linux/etherdevice.h>
- #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 "hyperv.h"
- #include "hyperv_net.h"
- struct net_device_context {
- /* point back to our device context */
- struct hv_device *device_ctx;
- unsigned long avail;
- struct work_struct work;
- };
- #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;
- if (netif_carrier_ok(net)) {
- /* 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);
- } else {
- netdev_err(net, "unable to open device...link is down.\n");
- }
- 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);
- net_device_ctx->avail += num_pages;
- if (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 > 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, silently drop packet */
- netdev_err(net, "unable to allocate hv_netvsc_packet\n");
- dev_kfree_skb(skb);
- net->stats.tx_dropped++;
- return NETDEV_TX_OK;
- }
- 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;
- /* TODO: Flush all write buffers/ memory fence ??? */
- /* wmb(); */
- /* 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++) {
- skb_frag_t *f = &skb_shinfo(skb)->frags[i];
- packet->page_buf[i+2].pfn = page_to_pfn(f->page);
- packet->page_buf[i+2].offset = f->page_offset;
- packet->page_buf[i+2].len = f->size;
- }
- /* 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++;
- net_device_ctx->avail -= num_pages;
- if (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++;
- netvsc_xmit_completion(packet);
- }
- return 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 = dev_get_drvdata(&device_obj->device);
- struct net_device_context *ndev_ctx;
- 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);
- netif_notify_peers(net);
- ndev_ctx = netdev_priv(net);
- schedule_work(&ndev_ctx->work);
- } 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;
- if (!net) {
- netdev_err(net, "got receive callback but net device"
- " not initialized yet\n");
- return 0;
- }