diff options
author | Andreas Mohr <andi@lisas.de> | 2010-01-30 21:58:42 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-02-03 21:28:58 -0500 |
commit | ace2a4d0fbf868c00625a8fd91c44ad9fabe7012 (patch) | |
tree | 64c11f1a46591b13c74a5f8b25858e40fa6c257c /drivers/net/usb | |
parent | c774651a5ffc0250f82d72730753f196c86884c5 (diff) |
MCS7830 USB-Ether: resume _with_ working link, via .reset_resume support
ChangeLog:
Implement .reset_resume support to retain a live network connection
during suspend despite USB power loss.
- rework operation to reference cached data in mcs7830_data and
netdev->dev_addr
- update netdev->dev_addr only in case new MAC was set successfully
. Tests done:
. ethtool -d pre-/post-suspend: register values match
. running ssh session suspend, resume: works
. ifdown device, suspend, resume: works
. ifup, suspend, unplug, resume: WORKS (eth1 is removed, re-ifup of eth1
after card replug works)
. verified identical MAC in ifconfig post-resume
(ok, should be verified on network side to be fully certain...)
Keywords: suspend resume network connection dead interface down
Signed-off-by: Andreas Mohr <andi@lisas.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/usb')
-rw-r--r-- | drivers/net/usb/mcs7830.c | 190 |
1 files changed, 123 insertions, 67 deletions
diff --git a/drivers/net/usb/mcs7830.c b/drivers/net/usb/mcs7830.c index 36a029218e3d..6fc098fe9ff7 100644 --- a/drivers/net/usb/mcs7830.c +++ b/drivers/net/usb/mcs7830.c | |||
@@ -12,8 +12,6 @@ | |||
12 | * Definitions gathered from MOSCHIP, Data Sheet_7830DA.pdf (thanks!). | 12 | * Definitions gathered from MOSCHIP, Data Sheet_7830DA.pdf (thanks!). |
13 | * | 13 | * |
14 | * TODO: | 14 | * TODO: |
15 | * - add .reset_resume support (iface is _gone_ after resume w/ power loss) | ||
16 | * - verify that mcs7830_get_regs() does have same output pre-/post-suspend | ||
17 | * - support HIF_REG_CONFIG_SLEEPMODE/HIF_REG_CONFIG_TXENABLE (via autopm?) | 15 | * - support HIF_REG_CONFIG_SLEEPMODE/HIF_REG_CONFIG_TXENABLE (via autopm?) |
18 | * - implement ethtool_ops get_pauseparam/set_pauseparam | 16 | * - implement ethtool_ops get_pauseparam/set_pauseparam |
19 | * via HIF_REG_PAUSE_THRESHOLD (>= revision C only!) | 17 | * via HIF_REG_PAUSE_THRESHOLD (>= revision C only!) |
@@ -137,7 +135,7 @@ static int mcs7830_get_reg(struct usbnet *dev, u16 index, u16 size, void *data) | |||
137 | return ret; | 135 | return ret; |
138 | } | 136 | } |
139 | 137 | ||
140 | static int mcs7830_set_reg(struct usbnet *dev, u16 index, u16 size, void *data) | 138 | static int mcs7830_set_reg(struct usbnet *dev, u16 index, u16 size, const void *data) |
141 | { | 139 | { |
142 | struct usb_device *xdev = dev->udev; | 140 | struct usb_device *xdev = dev->udev; |
143 | int ret; | 141 | int ret; |
@@ -211,13 +209,43 @@ out: | |||
211 | usb_free_urb(urb); | 209 | usb_free_urb(urb); |
212 | } | 210 | } |
213 | 211 | ||
214 | static int mcs7830_get_address(struct usbnet *dev) | 212 | static int mcs7830_hif_get_mac_address(struct usbnet *dev, unsigned char *addr) |
213 | { | ||
214 | int ret = mcs7830_get_reg(dev, HIF_REG_ETHERNET_ADDR, ETH_ALEN, addr); | ||
215 | if (ret < 0) | ||
216 | return ret; | ||
217 | return 0; | ||
218 | } | ||
219 | |||
220 | static int mcs7830_hif_set_mac_address(struct usbnet *dev, unsigned char *addr) | ||
221 | { | ||
222 | int ret = mcs7830_set_reg(dev, HIF_REG_ETHERNET_ADDR, ETH_ALEN, addr); | ||
223 | |||
224 | if (ret < 0) | ||
225 | return ret; | ||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | static int mcs7830_set_mac_address(struct net_device *netdev, void *p) | ||
215 | { | 230 | { |
216 | int ret; | 231 | int ret; |
217 | ret = mcs7830_get_reg(dev, HIF_REG_ETHERNET_ADDR, ETH_ALEN, | 232 | struct usbnet *dev = netdev_priv(netdev); |
218 | dev->net->dev_addr); | 233 | struct sockaddr *addr = p; |
234 | |||
235 | if (netif_running(netdev)) | ||
236 | return -EBUSY; | ||
237 | |||
238 | if (!is_valid_ether_addr(addr->sa_data)) | ||
239 | return -EINVAL; | ||
240 | |||
241 | ret = mcs7830_hif_set_mac_address(dev, addr->sa_data); | ||
242 | |||
219 | if (ret < 0) | 243 | if (ret < 0) |
220 | return ret; | 244 | return ret; |
245 | |||
246 | /* it worked --> adopt it on netdev side */ | ||
247 | memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); | ||
248 | |||
221 | return 0; | 249 | return 0; |
222 | } | 250 | } |
223 | 251 | ||
@@ -359,33 +387,6 @@ static void mcs7830_rev_C_fixup(struct usbnet *dev) | |||
359 | } | 387 | } |
360 | } | 388 | } |
361 | 389 | ||
362 | static int mcs7830_init_dev(struct usbnet *dev) | ||
363 | { | ||
364 | int ret; | ||
365 | int retry; | ||
366 | |||
367 | /* Read MAC address from EEPROM */ | ||
368 | ret = -EINVAL; | ||
369 | for (retry = 0; retry < 5 && ret; retry++) | ||
370 | ret = mcs7830_get_address(dev); | ||
371 | if (ret) { | ||
372 | dev_warn(&dev->udev->dev, "Cannot read MAC address\n"); | ||
373 | goto out; | ||
374 | } | ||
375 | |||
376 | /* Set up PHY */ | ||
377 | ret = mcs7830_set_autoneg(dev, 0); | ||
378 | if (ret) { | ||
379 | dev_info(&dev->udev->dev, "Cannot set autoneg\n"); | ||
380 | goto out; | ||
381 | } | ||
382 | |||
383 | mcs7830_rev_C_fixup(dev); | ||
384 | ret = 0; | ||
385 | out: | ||
386 | return ret; | ||
387 | } | ||
388 | |||
389 | static int mcs7830_mdio_read(struct net_device *netdev, int phy_id, | 390 | static int mcs7830_mdio_read(struct net_device *netdev, int phy_id, |
390 | int location) | 391 | int location) |
391 | { | 392 | { |
@@ -406,11 +407,33 @@ static int mcs7830_ioctl(struct net_device *net, struct ifreq *rq, int cmd) | |||
406 | return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL); | 407 | return generic_mii_ioctl(&dev->mii, if_mii(rq), cmd, NULL); |
407 | } | 408 | } |
408 | 409 | ||
409 | /* credits go to asix_set_multicast */ | 410 | static inline struct mcs7830_data *mcs7830_get_data(struct usbnet *dev) |
410 | static void mcs7830_set_multicast(struct net_device *net) | 411 | { |
412 | return (struct mcs7830_data *)&dev->data; | ||
413 | } | ||
414 | |||
415 | static void mcs7830_hif_update_multicast_hash(struct usbnet *dev) | ||
416 | { | ||
417 | struct mcs7830_data *data = mcs7830_get_data(dev); | ||
418 | mcs7830_set_reg_async(dev, HIF_REG_MULTICAST_HASH, | ||
419 | sizeof data->multi_filter, | ||
420 | data->multi_filter); | ||
421 | } | ||
422 | |||
423 | static void mcs7830_hif_update_config(struct usbnet *dev) | ||
424 | { | ||
425 | /* implementation specific to data->config | ||
426 | (argument needs to be heap-based anyway - USB DMA!) */ | ||
427 | struct mcs7830_data *data = mcs7830_get_data(dev); | ||
428 | mcs7830_set_reg_async(dev, HIF_REG_CONFIG, 1, &data->config); | ||
429 | } | ||
430 | |||
431 | static void mcs7830_data_set_multicast(struct net_device *net) | ||
411 | { | 432 | { |
412 | struct usbnet *dev = netdev_priv(net); | 433 | struct usbnet *dev = netdev_priv(net); |
413 | struct mcs7830_data *data = (struct mcs7830_data *)&dev->data; | 434 | struct mcs7830_data *data = mcs7830_get_data(dev); |
435 | |||
436 | memset(data->multi_filter, 0, sizeof data->multi_filter); | ||
414 | 437 | ||
415 | data->config = HIF_REG_CONFIG_TXENABLE; | 438 | data->config = HIF_REG_CONFIG_TXENABLE; |
416 | 439 | ||
@@ -433,21 +456,51 @@ static void mcs7830_set_multicast(struct net_device *net) | |||
433 | u32 crc_bits; | 456 | u32 crc_bits; |
434 | int i; | 457 | int i; |
435 | 458 | ||
436 | memset(data->multi_filter, 0, sizeof data->multi_filter); | ||
437 | |||
438 | /* Build the multicast hash filter. */ | 459 | /* Build the multicast hash filter. */ |
439 | for (i = 0; i < net->mc_count; i++) { | 460 | for (i = 0; i < net->mc_count; i++) { |
440 | crc_bits = ether_crc(ETH_ALEN, mc_list->dmi_addr) >> 26; | 461 | crc_bits = ether_crc(ETH_ALEN, mc_list->dmi_addr) >> 26; |
441 | data->multi_filter[crc_bits >> 3] |= 1 << (crc_bits & 7); | 462 | data->multi_filter[crc_bits >> 3] |= 1 << (crc_bits & 7); |
442 | mc_list = mc_list->next; | 463 | mc_list = mc_list->next; |
443 | } | 464 | } |
465 | } | ||
466 | } | ||
444 | 467 | ||
445 | mcs7830_set_reg_async(dev, HIF_REG_MULTICAST_HASH, | 468 | static int mcs7830_apply_base_config(struct usbnet *dev) |
446 | sizeof data->multi_filter, | 469 | { |
447 | data->multi_filter); | 470 | int ret; |
471 | |||
472 | /* re-configure known MAC (suspend case etc.) */ | ||
473 | ret = mcs7830_hif_set_mac_address(dev, dev->net->dev_addr); | ||
474 | if (ret) { | ||
475 | dev_info(&dev->udev->dev, "Cannot set MAC address\n"); | ||
476 | goto out; | ||
448 | } | 477 | } |
449 | 478 | ||
450 | mcs7830_set_reg_async(dev, HIF_REG_CONFIG, 1, &data->config); | 479 | /* Set up PHY */ |
480 | ret = mcs7830_set_autoneg(dev, 0); | ||
481 | if (ret) { | ||
482 | dev_info(&dev->udev->dev, "Cannot set autoneg\n"); | ||
483 | goto out; | ||
484 | } | ||
485 | |||
486 | mcs7830_hif_update_multicast_hash(dev); | ||
487 | mcs7830_hif_update_config(dev); | ||
488 | |||
489 | mcs7830_rev_C_fixup(dev); | ||
490 | ret = 0; | ||
491 | out: | ||
492 | return ret; | ||
493 | } | ||
494 | |||
495 | /* credits go to asix_set_multicast */ | ||
496 | static void mcs7830_set_multicast(struct net_device *net) | ||
497 | { | ||
498 | struct usbnet *dev = netdev_priv(net); | ||
499 | |||
500 | mcs7830_data_set_multicast(net); | ||
501 | |||
502 | mcs7830_hif_update_multicast_hash(dev); | ||
503 | mcs7830_hif_update_config(dev); | ||
451 | } | 504 | } |
452 | 505 | ||
453 | static int mcs7830_get_regs_len(struct net_device *net) | 506 | static int mcs7830_get_regs_len(struct net_device *net) |
@@ -491,29 +544,6 @@ static const struct ethtool_ops mcs7830_ethtool_ops = { | |||
491 | .nway_reset = usbnet_nway_reset, | 544 | .nway_reset = usbnet_nway_reset, |
492 | }; | 545 | }; |
493 | 546 | ||
494 | static int mcs7830_set_mac_address(struct net_device *netdev, void *p) | ||
495 | { | ||
496 | int ret; | ||
497 | struct usbnet *dev = netdev_priv(netdev); | ||
498 | struct sockaddr *addr = p; | ||
499 | |||
500 | if (netif_running(netdev)) | ||
501 | return -EBUSY; | ||
502 | |||
503 | if (!is_valid_ether_addr(addr->sa_data)) | ||
504 | return -EINVAL; | ||
505 | |||
506 | memcpy(netdev->dev_addr, addr->sa_data, netdev->addr_len); | ||
507 | |||
508 | ret = mcs7830_set_reg(dev, HIF_REG_ETHERNET_ADDR, ETH_ALEN, | ||
509 | netdev->dev_addr); | ||
510 | |||
511 | if (ret < 0) | ||
512 | return ret; | ||
513 | |||
514 | return 0; | ||
515 | } | ||
516 | |||
517 | static const struct net_device_ops mcs7830_netdev_ops = { | 547 | static const struct net_device_ops mcs7830_netdev_ops = { |
518 | .ndo_open = usbnet_open, | 548 | .ndo_open = usbnet_open, |
519 | .ndo_stop = usbnet_stop, | 549 | .ndo_stop = usbnet_stop, |
@@ -530,14 +560,25 @@ static int mcs7830_bind(struct usbnet *dev, struct usb_interface *udev) | |||
530 | { | 560 | { |
531 | struct net_device *net = dev->net; | 561 | struct net_device *net = dev->net; |
532 | int ret; | 562 | int ret; |
563 | int retry; | ||
533 | 564 | ||
534 | ret = mcs7830_init_dev(dev); | 565 | /* Initial startup: Gather MAC address setting from EEPROM */ |
566 | ret = -EINVAL; | ||
567 | for (retry = 0; retry < 5 && ret; retry++) | ||
568 | ret = mcs7830_hif_get_mac_address(dev, net->dev_addr); | ||
569 | if (ret) { | ||
570 | dev_warn(&dev->udev->dev, "Cannot read MAC address\n"); | ||
571 | goto out; | ||
572 | } | ||
573 | |||
574 | mcs7830_data_set_multicast(net); | ||
575 | |||
576 | ret = mcs7830_apply_base_config(dev); | ||
535 | if (ret) | 577 | if (ret) |
536 | goto out; | 578 | goto out; |
537 | 579 | ||
538 | net->ethtool_ops = &mcs7830_ethtool_ops; | 580 | net->ethtool_ops = &mcs7830_ethtool_ops; |
539 | net->netdev_ops = &mcs7830_netdev_ops; | 581 | net->netdev_ops = &mcs7830_netdev_ops; |
540 | mcs7830_set_multicast(net); | ||
541 | 582 | ||
542 | /* reserve space for the status byte on rx */ | 583 | /* reserve space for the status byte on rx */ |
543 | dev->rx_urb_size = ETH_FRAME_LEN + 1; | 584 | dev->rx_urb_size = ETH_FRAME_LEN + 1; |
@@ -622,6 +663,20 @@ static const struct usb_device_id products[] = { | |||
622 | }; | 663 | }; |
623 | MODULE_DEVICE_TABLE(usb, products); | 664 | MODULE_DEVICE_TABLE(usb, products); |
624 | 665 | ||
666 | static int mcs7830_reset_resume (struct usb_interface *intf) | ||
667 | { | ||
668 | /* YES, this function is successful enough that ethtool -d | ||
669 | does show same output pre-/post-suspend */ | ||
670 | |||
671 | struct usbnet *dev = usb_get_intfdata(intf); | ||
672 | |||
673 | mcs7830_apply_base_config(dev); | ||
674 | |||
675 | usbnet_resume(intf); | ||
676 | |||
677 | return 0; | ||
678 | } | ||
679 | |||
625 | static struct usb_driver mcs7830_driver = { | 680 | static struct usb_driver mcs7830_driver = { |
626 | .name = driver_name, | 681 | .name = driver_name, |
627 | .id_table = products, | 682 | .id_table = products, |
@@ -629,6 +684,7 @@ static struct usb_driver mcs7830_driver = { | |||
629 | .disconnect = usbnet_disconnect, | 684 | .disconnect = usbnet_disconnect, |
630 | .suspend = usbnet_suspend, | 685 | .suspend = usbnet_suspend, |
631 | .resume = usbnet_resume, | 686 | .resume = usbnet_resume, |
687 | .reset_resume = mcs7830_reset_resume, | ||
632 | }; | 688 | }; |
633 | 689 | ||
634 | static int __init mcs7830_init(void) | 690 | static int __init mcs7830_init(void) |