diff options
author | Haiyang Zhang <haiyangz@microsoft.com> | 2011-11-28 16:35:35 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2011-11-29 02:12:36 -0500 |
commit | 95fa0405c5991726e06c08ffcd8ff872f7fb4f2d (patch) | |
tree | b03a3a6278d9eb2baab16f45082bdb2ac1a6a183 /drivers/net/hyperv/netvsc_drv.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_drv.c')
-rw-r--r-- | drivers/net/hyperv/netvsc_drv.c | 456 |
1 files changed, 456 insertions, 0 deletions
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 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2009, Microsoft Corporation. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify it | ||
5 | * under the terms and conditions of the GNU General Public License, | ||
6 | * version 2, as published by the Free Software Foundation. | ||
7 | * | ||
8 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
11 | * more details. | ||
12 | * | ||
13 | * You should have received a copy of the GNU General Public License along with | ||
14 | * this program; if not, write to the Free Software Foundation, Inc., 59 Temple | ||
15 | * Place - Suite 330, Boston, MA 02111-1307 USA. | ||
16 | * | ||
17 | * Authors: | ||
18 | * Haiyang Zhang <haiyangz@microsoft.com> | ||
19 | * Hank Janssen <hjanssen@microsoft.com> | ||
20 | */ | ||
21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
22 | |||
23 | #include <linux/init.h> | ||
24 | #include <linux/atomic.h> | ||
25 | #include <linux/module.h> | ||
26 | #include <linux/highmem.h> | ||
27 | #include <linux/device.h> | ||
28 | #include <linux/io.h> | ||
29 | #include <linux/delay.h> | ||
30 | #include <linux/netdevice.h> | ||
31 | #include <linux/inetdevice.h> | ||
32 | #include <linux/etherdevice.h> | ||
33 | #include <linux/skbuff.h> | ||
34 | #include <linux/in.h> | ||
35 | #include <linux/slab.h> | ||
36 | #include <net/arp.h> | ||
37 | #include <net/route.h> | ||
38 | #include <net/sock.h> | ||
39 | #include <net/pkt_sched.h> | ||
40 | |||
41 | #include "hyperv_net.h" | ||
42 | |||
43 | struct net_device_context { | ||
44 | /* point back to our device context */ | ||
45 | struct hv_device *device_ctx; | ||
46 | atomic_t avail; | ||
47 | struct delayed_work dwork; | ||
48 | }; | ||
49 | |||
50 | |||
51 | #define PACKET_PAGES_LOWATER 8 | ||
52 | /* Need this many pages to handle worst case fragmented packet */ | ||
53 | #define PACKET_PAGES_HIWATER (MAX_SKB_FRAGS + 2) | ||
54 | |||
55 | static int ring_size = 128; | ||
56 | module_param(ring_size, int, S_IRUGO); | ||
57 | MODULE_PARM_DESC(ring_size, "Ring buffer size (# of pages)"); | ||
58 | |||
59 | /* no-op so the netdev core doesn't return -EINVAL when modifying the the | ||
60 | * multicast address list in SIOCADDMULTI. hv is setup to get all multicast | ||
61 | * when it calls RndisFilterOnOpen() */ | ||
62 | static void netvsc_set_multicast_list(struct net_device *net) | ||
63 | { | ||
64 | } | ||
65 | |||
66 | static int netvsc_open(struct net_device *net) | ||
67 | { | ||
68 | struct net_device_context *net_device_ctx = netdev_priv(net); | ||
69 | struct hv_device *device_obj = net_device_ctx->device_ctx; | ||
70 | int ret = 0; | ||
71 | |||
72 | /* Open up the device */ | ||
73 | ret = rndis_filter_open(device_obj); | ||
74 | if (ret != 0) { | ||
75 | netdev_err(net, "unable to open device (ret %d).\n", ret); | ||
76 | return ret; | ||
77 | } | ||
78 | |||
79 | netif_start_queue(net); | ||
80 | |||
81 | return ret; | ||
82 | } | ||
83 | |||
84 | static int netvsc_close(struct net_device *net) | ||
85 | { | ||
86 | struct net_device_context *net_device_ctx = netdev_priv(net); | ||
87 | struct hv_device *device_obj = net_device_ctx->device_ctx; | ||
88 | int ret; | ||
89 | |||
90 | netif_stop_queue(net); | ||
91 | |||
92 | ret = rndis_filter_close(device_obj); | ||
93 | if (ret != 0) | ||
94 | netdev_err(net, "unable to close device (ret %d).\n", ret); | ||
95 | |||
96 | return ret; | ||
97 | } | ||
98 | |||
99 | static void netvsc_xmit_completion(void *context) | ||
100 | { | ||
101 | struct hv_netvsc_packet *packet = (struct hv_netvsc_packet *)context; | ||
102 | struct sk_buff *skb = (struct sk_buff *) | ||
103 | (unsigned long)packet->completion.send.send_completion_tid; | ||
104 | |||
105 | kfree(packet); | ||
106 | |||
107 | if (skb) { | ||
108 | struct net_device *net = skb->dev; | ||
109 | struct net_device_context *net_device_ctx = netdev_priv(net); | ||
110 | unsigned int num_pages = skb_shinfo(skb)->nr_frags + 2; | ||
111 | |||
112 | dev_kfree_skb_any(skb); | ||
113 | |||
114 | atomic_add(num_pages, &net_device_ctx->avail); | ||
115 | if (atomic_read(&net_device_ctx->avail) >= | ||
116 | PACKET_PAGES_HIWATER) | ||
117 | netif_wake_queue(net); | ||
118 | } | ||
119 | } | ||
120 | |||
121 | static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) | ||
122 | { | ||
123 | struct net_device_context *net_device_ctx = netdev_priv(net); | ||
124 | struct hv_netvsc_packet *packet; | ||
125 | int ret; | ||
126 | unsigned int i, num_pages; | ||
127 | |||
128 | /* Add 1 for skb->data and additional one for RNDIS */ | ||
129 | num_pages = skb_shinfo(skb)->nr_frags + 1 + 1; | ||
130 | if (num_pages > atomic_read(&net_device_ctx->avail)) | ||
131 | return NETDEV_TX_BUSY; | ||
132 | |||
133 | /* Allocate a netvsc packet based on # of frags. */ | ||
134 | packet = kzalloc(sizeof(struct hv_netvsc_packet) + | ||
135 | (num_pages * sizeof(struct hv_page_buffer)) + | ||
136 | sizeof(struct rndis_filter_packet), GFP_ATOMIC); | ||
137 | if (!packet) { | ||
138 | /* out of memory, drop packet */ | ||
139 | netdev_err(net, "unable to allocate hv_netvsc_packet\n"); | ||
140 | |||
141 | dev_kfree_skb(skb); | ||
142 | net->stats.tx_dropped++; | ||
143 | return NETDEV_TX_BUSY; | ||
144 | } | ||
145 | |||
146 | packet->extension = (void *)(unsigned long)packet + | ||
147 | sizeof(struct hv_netvsc_packet) + | ||
148 | (num_pages * sizeof(struct hv_page_buffer)); | ||
149 | |||
150 | /* Setup the rndis header */ | ||
151 | packet->page_buf_cnt = num_pages; | ||
152 | |||
153 | /* Initialize it from the skb */ | ||
154 | packet->total_data_buflen = skb->len; | ||
155 | |||
156 | /* Start filling in the page buffers starting after RNDIS buffer. */ | ||
157 | packet->page_buf[1].pfn = virt_to_phys(skb->data) >> PAGE_SHIFT; | ||
158 | packet->page_buf[1].offset | ||
159 | = (unsigned long)skb->data & (PAGE_SIZE - 1); | ||
160 | packet->page_buf[1].len = skb_headlen(skb); | ||
161 | |||
162 | /* Additional fragments are after SKB data */ | ||
163 | for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { | ||
164 | const skb_frag_t *f = &skb_shinfo(skb)->frags[i]; | ||
165 | |||
166 | packet->page_buf[i+2].pfn = page_to_pfn(skb_frag_page(f)); | ||
167 | packet->page_buf[i+2].offset = f->page_offset; | ||
168 | packet->page_buf[i+2].len = skb_frag_size(f); | ||
169 | } | ||
170 | |||
171 | /* Set the completion routine */ | ||
172 | packet->completion.send.send_completion = netvsc_xmit_completion; | ||
173 | packet->completion.send.send_completion_ctx = packet; | ||
174 | packet->completion.send.send_completion_tid = (unsigned long)skb; | ||
175 | |||
176 | ret = rndis_filter_send(net_device_ctx->device_ctx, | ||
177 | packet); | ||
178 | if (ret == 0) { | ||
179 | net->stats.tx_bytes += skb->len; | ||
180 | net->stats.tx_packets++; | ||
181 | |||
182 | atomic_sub(num_pages, &net_device_ctx->avail); | ||
183 | if (atomic_read(&net_device_ctx->avail) < PACKET_PAGES_LOWATER) | ||
184 | netif_stop_queue(net); | ||
185 | } else { | ||
186 | /* we are shutting down or bus overloaded, just drop packet */ | ||
187 | net->stats.tx_dropped++; | ||
188 | kfree(packet); | ||
189 | dev_kfree_skb_any(skb); | ||
190 | } | ||
191 | |||
192 | return ret ? NETDEV_TX_BUSY : NETDEV_TX_OK; | ||
193 | } | ||
194 | |||
195 | /* | ||
196 | * netvsc_linkstatus_callback - Link up/down notification | ||
197 | */ | ||
198 | void netvsc_linkstatus_callback(struct hv_device *device_obj, | ||
199 | unsigned int status) | ||
200 | { | ||
201 | struct net_device *net; | ||
202 | struct net_device_context *ndev_ctx; | ||
203 | struct netvsc_device *net_device; | ||
204 | |||
205 | net_device = hv_get_drvdata(device_obj); | ||
206 | net = net_device->ndev; | ||
207 | |||
208 | if (!net) { | ||
209 | netdev_err(net, "got link status but net device " | ||
210 | "not initialized yet\n"); | ||
211 | return; | ||
212 | } | ||
213 | |||
214 | if (status == 1) { | ||
215 | netif_carrier_on(net); | ||
216 | netif_wake_queue(net); | ||
217 | ndev_ctx = netdev_priv(net); | ||
218 | schedule_delayed_work(&ndev_ctx->dwork, 0); | ||
219 | schedule_delayed_work(&ndev_ctx->dwork, msecs_to_jiffies(20)); | ||
220 | } else { | ||
221 | netif_carrier_off(net); | ||
222 | netif_stop_queue(net); | ||
223 | } | ||
224 | } | ||
225 | |||
226 | /* | ||
227 | * netvsc_recv_callback - Callback when we receive a packet from the | ||
228 | * "wire" on the specified device. | ||
229 | */ | ||
230 | int netvsc_recv_callback(struct hv_device *device_obj, | ||
231 | struct hv_netvsc_packet *packet) | ||
232 | { | ||
233 | struct net_device *net = dev_get_drvdata(&device_obj->device); | ||
234 | struct sk_buff *skb; | ||
235 | void *data; | ||
236 | int i; | ||
237 | unsigned long flags; | ||
238 | struct netvsc_device *net_device; | ||
239 | |||
240 | net_device = hv_get_drvdata(device_obj); | ||
241 | net = net_device->ndev; | ||
242 | |||
243 | if (!net) { | ||
244 | netdev_err(net, "got receive callback but net device" | ||
245 | " not initialized yet\n"); | ||
246 | return 0; | ||
247 | } | ||
248 | |||
249 | /* Allocate a skb - TODO direct I/O to pages? */ | ||
250 | skb = netdev_alloc_skb_ip_align(net, packet->total_data_buflen); | ||
251 | if (unlikely(!skb)) { | ||
252 | ++net->stats.rx_dropped; | ||
253 | return 0; | ||
254 | } | ||
255 | |||
256 | /* for kmap_atomic */ | ||
257 | local_irq_save(flags); | ||
258 | |||
259 | /* | ||
260 | * Copy to skb. This copy is needed here since the memory pointed by | ||
261 | * hv_netvsc_packet cannot be deallocated | ||
262 | */ | ||
263 | for (i = 0; i < packet->page_buf_cnt; i++) { | ||
264 | data = kmap_atomic(pfn_to_page(packet->page_buf[i].pfn), | ||
265 | KM_IRQ1); | ||
266 | data = (void *)(unsigned long)data + | ||
267 | packet->page_buf[i].offset; | ||
268 | |||
269 | memcpy(skb_put(skb, packet->page_buf[i].len), data, | ||
270 | packet->page_buf[i].len); | ||
271 | |||
272 | kunmap_atomic((void *)((unsigned long)data - | ||
273 | packet->page_buf[i].offset), KM_IRQ1); | ||
274 | } | ||
275 | |||
276 | local_irq_restore(flags); | ||
277 | |||
278 | skb->protocol = eth_type_trans(skb, net); | ||
279 | skb->ip_summed = CHECKSUM_NONE; | ||
280 | |||
281 | net->stats.rx_packets++; | ||
282 | net->stats.rx_bytes += skb->len; | ||
283 | |||
284 | /* | ||
285 | * Pass the skb back up. Network stack will deallocate the skb when it | ||
286 | * is done. | ||
287 | * TODO - use NAPI? | ||
288 | */ | ||
289 | netif_rx(skb); | ||
290 | |||
291 | return 0; | ||
292 | } | ||
293 | |||
294 | static void netvsc_get_drvinfo(struct net_device *net, | ||
295 | struct ethtool_drvinfo *info) | ||
296 | { | ||
297 | strcpy(info->driver, "hv_netvsc"); | ||
298 | strcpy(info->version, HV_DRV_VERSION); | ||
299 | strcpy(info->fw_version, "N/A"); | ||
300 | } | ||
301 | |||
302 | static const struct ethtool_ops ethtool_ops = { | ||
303 | .get_drvinfo = netvsc_get_drvinfo, | ||
304 | .get_link = ethtool_op_get_link, | ||
305 | }; | ||
306 | |||
307 | static const struct net_device_ops device_ops = { | ||
308 | .ndo_open = netvsc_open, | ||
309 | .ndo_stop = netvsc_close, | ||
310 | .ndo_start_xmit = netvsc_start_xmit, | ||
311 | .ndo_set_rx_mode = netvsc_set_multicast_list, | ||
312 | .ndo_change_mtu = eth_change_mtu, | ||
313 | .ndo_validate_addr = eth_validate_addr, | ||
314 | .ndo_set_mac_address = eth_mac_addr, | ||
315 | }; | ||
316 | |||
317 | /* | ||
318 | * Send GARP packet to network peers after migrations. | ||
319 | * After Quick Migration, the network is not immediately operational in the | ||
320 | * current context when receiving RNDIS_STATUS_MEDIA_CONNECT event. So, add | ||
321 | * another netif_notify_peers() into a delayed work, otherwise GARP packet | ||
322 | * will not be sent after quick migration, and cause network disconnection. | ||
323 | */ | ||
324 | static void netvsc_send_garp(struct work_struct *w) | ||
325 | { | ||
326 | struct net_device_context *ndev_ctx; | ||
327 | struct net_device *net; | ||
328 | struct netvsc_device *net_device; | ||
329 | |||
330 | ndev_ctx = container_of(w, struct net_device_context, dwork.work); | ||
331 | net_device = hv_get_drvdata(ndev_ctx->device_ctx); | ||
332 | net = net_device->ndev; | ||
333 | netif_notify_peers(net); | ||
334 | } | ||
335 | |||
336 | |||
337 | static int netvsc_probe(struct hv_device *dev, | ||
338 | const struct hv_vmbus_device_id *dev_id) | ||
339 | { | ||
340 | struct net_device *net = NULL; | ||
341 | struct net_device_context *net_device_ctx; | ||
342 | struct netvsc_device_info device_info; | ||
343 | int ret; | ||
344 | |||
345 | net = alloc_etherdev(sizeof(struct net_device_context)); | ||
346 | if (!net) | ||
347 | return -ENOMEM; | ||
348 | |||
349 | /* Set initial state */ | ||
350 | netif_carrier_off(net); | ||
351 | |||
352 | net_device_ctx = netdev_priv(net); | ||
353 | net_device_ctx->device_ctx = dev; | ||
354 | atomic_set(&net_device_ctx->avail, ring_size); | ||
355 | hv_set_drvdata(dev, net); | ||
356 | INIT_DELAYED_WORK(&net_device_ctx->dwork, netvsc_send_garp); | ||
357 | |||
358 | net->netdev_ops = &device_ops; | ||
359 | |||
360 | /* TODO: Add GSO and Checksum offload */ | ||
361 | net->hw_features = NETIF_F_SG; | ||
362 | net->features = NETIF_F_SG; | ||
363 | |||
364 | SET_ETHTOOL_OPS(net, ðtool_ops); | ||
365 | SET_NETDEV_DEV(net, &dev->device); | ||
366 | |||
367 | ret = register_netdev(net); | ||
368 | if (ret != 0) { | ||
369 | pr_err("Unable to register netdev.\n"); | ||
370 | free_netdev(net); | ||
371 | goto out; | ||
372 | } | ||
373 | |||
374 | /* Notify the netvsc driver of the new device */ | ||
375 | device_info.ring_size = ring_size; | ||
376 | ret = rndis_filter_device_add(dev, &device_info); | ||
377 | if (ret != 0) { | ||
378 | netdev_err(net, "unable to add netvsc device (ret %d)\n", ret); | ||
379 | unregister_netdev(net); | ||
380 | free_netdev(net); | ||
381 | hv_set_drvdata(dev, NULL); | ||
382 | return ret; | ||
383 | } | ||
384 | memcpy(net->dev_addr, device_info.mac_adr, ETH_ALEN); | ||
385 | |||
386 | netif_carrier_on(net); | ||
387 | |||
388 | out: | ||
389 | return ret; | ||
390 | } | ||
391 | |||
392 | static int netvsc_remove(struct hv_device *dev) | ||
393 | { | ||
394 | struct net_device *net; | ||
395 | struct net_device_context *ndev_ctx; | ||
396 | struct netvsc_device *net_device; | ||
397 | |||
398 | net_device = hv_get_drvdata(dev); | ||
399 | net = net_device->ndev; | ||
400 | |||
401 | if (net == NULL) { | ||
402 | dev_err(&dev->device, "No net device to remove\n"); | ||
403 | return 0; | ||
404 | } | ||
405 | |||
406 | ndev_ctx = netdev_priv(net); | ||
407 | cancel_delayed_work_sync(&ndev_ctx->dwork); | ||
408 | |||
409 | /* Stop outbound asap */ | ||
410 | netif_stop_queue(net); | ||
411 | |||
412 | unregister_netdev(net); | ||
413 | |||
414 | /* | ||
415 | * Call to the vsc driver to let it know that the device is being | ||
416 | * removed | ||
417 | */ | ||
418 | rndis_filter_device_remove(dev); | ||
419 | |||
420 | free_netdev(net); | ||
421 | return 0; | ||
422 | } | ||
423 | |||
424 | static const struct hv_vmbus_device_id id_table[] = { | ||
425 | /* Network guid */ | ||
426 | { VMBUS_DEVICE(0x63, 0x51, 0x61, 0xF8, 0x3E, 0xDF, 0xc5, 0x46, | ||
427 | 0x91, 0x3F, 0xF2, 0xD2, 0xF9, 0x65, 0xED, 0x0E) }, | ||
428 | { }, | ||
429 | }; | ||
430 | |||
431 | MODULE_DEVICE_TABLE(vmbus, id_table); | ||
432 | |||
433 | /* The one and only one */ | ||
434 | static struct hv_driver netvsc_drv = { | ||
435 | .name = "netvsc", | ||
436 | .id_table = id_table, | ||
437 | .probe = netvsc_probe, | ||
438 | .remove = netvsc_remove, | ||
439 | }; | ||
440 | |||
441 | static void __exit netvsc_drv_exit(void) | ||
442 | { | ||
443 | vmbus_driver_unregister(&netvsc_drv); | ||
444 | } | ||
445 | |||
446 | static int __init netvsc_drv_init(void) | ||
447 | { | ||
448 | return vmbus_driver_register(&netvsc_drv); | ||
449 | } | ||
450 | |||
451 | MODULE_LICENSE("GPL"); | ||
452 | MODULE_VERSION(HV_DRV_VERSION); | ||
453 | MODULE_DESCRIPTION("Microsoft Hyper-V network driver"); | ||
454 | |||
455 | module_init(netvsc_drv_init); | ||
456 | module_exit(netvsc_drv_exit); | ||