diff options
author | Frank Li <Frank.Li@freescale.com> | 2013-05-07 10:08:44 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-05-08 16:13:30 -0400 |
commit | 54309fa60b5f57b90c1842176f6045e665d21142 (patch) | |
tree | 31bb2d5574adc10d342a01b2081c9907e917c554 | |
parent | 2c006994520f3a4bb1d47a6afe5c58ff856497ce (diff) |
net: fec: fix kernel oops when plug/unplug cable many times
reproduce steps
1. flood ping from other machine
ping -f -s 41000 IP
2. run below script
while [ 1 ]; do ethtool -s eth0 autoneg off;
sleep 3;ethtool -s eth0 autoneg on; sleep 4; done;
You can see oops in one hour.
The reason is fec_restart clear BD but NAPI may use it.
The solution is disable NAPI and stop xmit when reset BD.
disable NAPI may sleep, so fec_restart can't be call in
atomic context.
Signed-off-by: Frank Li <Frank.Li@freescale.com>
Reviewed-by: Lucas Stach <l.stach@pengutronix.de>
Tested-by: Lucas Stach <l.stach@pengutronix.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/ethernet/freescale/fec.h | 10 | ||||
-rw-r--r-- | drivers/net/ethernet/freescale/fec_main.c | 44 |
2 files changed, 39 insertions, 15 deletions
diff --git a/drivers/net/ethernet/freescale/fec.h b/drivers/net/ethernet/freescale/fec.h index ceb4d43c132d..9ce5b7185fda 100644 --- a/drivers/net/ethernet/freescale/fec.h +++ b/drivers/net/ethernet/freescale/fec.h | |||
@@ -198,6 +198,11 @@ struct bufdesc_ex { | |||
198 | #define FLAG_RX_CSUM_ENABLED (BD_ENET_RX_ICE | BD_ENET_RX_PCR) | 198 | #define FLAG_RX_CSUM_ENABLED (BD_ENET_RX_ICE | BD_ENET_RX_PCR) |
199 | #define FLAG_RX_CSUM_ERROR (BD_ENET_RX_ICE | BD_ENET_RX_PCR) | 199 | #define FLAG_RX_CSUM_ERROR (BD_ENET_RX_ICE | BD_ENET_RX_PCR) |
200 | 200 | ||
201 | struct fec_enet_delayed_work { | ||
202 | struct delayed_work delay_work; | ||
203 | bool timeout; | ||
204 | }; | ||
205 | |||
201 | /* The FEC buffer descriptors track the ring buffers. The rx_bd_base and | 206 | /* The FEC buffer descriptors track the ring buffers. The rx_bd_base and |
202 | * tx_bd_base always point to the base of the buffer descriptors. The | 207 | * tx_bd_base always point to the base of the buffer descriptors. The |
203 | * cur_rx and cur_tx point to the currently available buffer. | 208 | * cur_rx and cur_tx point to the currently available buffer. |
@@ -232,9 +237,6 @@ struct fec_enet_private { | |||
232 | /* The ring entries to be free()ed */ | 237 | /* The ring entries to be free()ed */ |
233 | struct bufdesc *dirty_tx; | 238 | struct bufdesc *dirty_tx; |
234 | 239 | ||
235 | /* hold while accessing the HW like ringbuffer for tx/rx but not MAC */ | ||
236 | spinlock_t hw_lock; | ||
237 | |||
238 | struct platform_device *pdev; | 240 | struct platform_device *pdev; |
239 | 241 | ||
240 | int opened; | 242 | int opened; |
@@ -269,7 +271,7 @@ struct fec_enet_private { | |||
269 | int hwts_rx_en; | 271 | int hwts_rx_en; |
270 | int hwts_tx_en; | 272 | int hwts_tx_en; |
271 | struct timer_list time_keep; | 273 | struct timer_list time_keep; |
272 | 274 | struct fec_enet_delayed_work delay_work; | |
273 | }; | 275 | }; |
274 | 276 | ||
275 | void fec_ptp_init(struct net_device *ndev, struct platform_device *pdev); | 277 | void fec_ptp_init(struct net_device *ndev, struct platform_device *pdev); |
diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c index e25bf832e6b3..aff0310a778b 100644 --- a/drivers/net/ethernet/freescale/fec_main.c +++ b/drivers/net/ethernet/freescale/fec_main.c | |||
@@ -445,6 +445,13 @@ fec_restart(struct net_device *ndev, int duplex) | |||
445 | u32 rcntl = OPT_FRAME_SIZE | 0x04; | 445 | u32 rcntl = OPT_FRAME_SIZE | 0x04; |
446 | u32 ecntl = 0x2; /* ETHEREN */ | 446 | u32 ecntl = 0x2; /* ETHEREN */ |
447 | 447 | ||
448 | if (netif_running(ndev)) { | ||
449 | netif_device_detach(ndev); | ||
450 | napi_disable(&fep->napi); | ||
451 | netif_stop_queue(ndev); | ||
452 | netif_tx_lock(ndev); | ||
453 | } | ||
454 | |||
448 | /* Whack a reset. We should wait for this. */ | 455 | /* Whack a reset. We should wait for this. */ |
449 | writel(1, fep->hwp + FEC_ECNTRL); | 456 | writel(1, fep->hwp + FEC_ECNTRL); |
450 | udelay(10); | 457 | udelay(10); |
@@ -605,6 +612,13 @@ fec_restart(struct net_device *ndev, int duplex) | |||
605 | 612 | ||
606 | /* Enable interrupts we wish to service */ | 613 | /* Enable interrupts we wish to service */ |
607 | writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK); | 614 | writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK); |
615 | |||
616 | if (netif_running(ndev)) { | ||
617 | netif_device_attach(ndev); | ||
618 | napi_enable(&fep->napi); | ||
619 | netif_wake_queue(ndev); | ||
620 | netif_tx_unlock(ndev); | ||
621 | } | ||
608 | } | 622 | } |
609 | 623 | ||
610 | static void | 624 | static void |
@@ -644,8 +658,22 @@ fec_timeout(struct net_device *ndev) | |||
644 | 658 | ||
645 | ndev->stats.tx_errors++; | 659 | ndev->stats.tx_errors++; |
646 | 660 | ||
647 | fec_restart(ndev, fep->full_duplex); | 661 | fep->delay_work.timeout = true; |
648 | netif_wake_queue(ndev); | 662 | schedule_delayed_work(&(fep->delay_work.delay_work), 0); |
663 | } | ||
664 | |||
665 | static void fec_enet_work(struct work_struct *work) | ||
666 | { | ||
667 | struct fec_enet_private *fep = | ||
668 | container_of(work, | ||
669 | struct fec_enet_private, | ||
670 | delay_work.delay_work.work); | ||
671 | |||
672 | if (fep->delay_work.timeout) { | ||
673 | fep->delay_work.timeout = false; | ||
674 | fec_restart(fep->netdev, fep->full_duplex); | ||
675 | netif_wake_queue(fep->netdev); | ||
676 | } | ||
649 | } | 677 | } |
650 | 678 | ||
651 | static void | 679 | static void |
@@ -1024,16 +1052,12 @@ static void fec_enet_adjust_link(struct net_device *ndev) | |||
1024 | { | 1052 | { |
1025 | struct fec_enet_private *fep = netdev_priv(ndev); | 1053 | struct fec_enet_private *fep = netdev_priv(ndev); |
1026 | struct phy_device *phy_dev = fep->phy_dev; | 1054 | struct phy_device *phy_dev = fep->phy_dev; |
1027 | unsigned long flags; | ||
1028 | |||
1029 | int status_change = 0; | 1055 | int status_change = 0; |
1030 | 1056 | ||
1031 | spin_lock_irqsave(&fep->hw_lock, flags); | ||
1032 | |||
1033 | /* Prevent a state halted on mii error */ | 1057 | /* Prevent a state halted on mii error */ |
1034 | if (fep->mii_timeout && phy_dev->state == PHY_HALTED) { | 1058 | if (fep->mii_timeout && phy_dev->state == PHY_HALTED) { |
1035 | phy_dev->state = PHY_RESUMING; | 1059 | phy_dev->state = PHY_RESUMING; |
1036 | goto spin_unlock; | 1060 | return; |
1037 | } | 1061 | } |
1038 | 1062 | ||
1039 | if (phy_dev->link) { | 1063 | if (phy_dev->link) { |
@@ -1061,9 +1085,6 @@ static void fec_enet_adjust_link(struct net_device *ndev) | |||
1061 | } | 1085 | } |
1062 | } | 1086 | } |
1063 | 1087 | ||
1064 | spin_unlock: | ||
1065 | spin_unlock_irqrestore(&fep->hw_lock, flags); | ||
1066 | |||
1067 | if (status_change) | 1088 | if (status_change) |
1068 | phy_print_status(phy_dev); | 1089 | phy_print_status(phy_dev); |
1069 | } | 1090 | } |
@@ -1732,7 +1753,6 @@ static int fec_enet_init(struct net_device *ndev) | |||
1732 | return -ENOMEM; | 1753 | return -ENOMEM; |
1733 | 1754 | ||
1734 | memset(cbd_base, 0, PAGE_SIZE); | 1755 | memset(cbd_base, 0, PAGE_SIZE); |
1735 | spin_lock_init(&fep->hw_lock); | ||
1736 | 1756 | ||
1737 | fep->netdev = ndev; | 1757 | fep->netdev = ndev; |
1738 | 1758 | ||
@@ -1952,6 +1972,7 @@ fec_probe(struct platform_device *pdev) | |||
1952 | if (fep->bufdesc_ex && fep->ptp_clock) | 1972 | if (fep->bufdesc_ex && fep->ptp_clock) |
1953 | netdev_info(ndev, "registered PHC device %d\n", fep->dev_id); | 1973 | netdev_info(ndev, "registered PHC device %d\n", fep->dev_id); |
1954 | 1974 | ||
1975 | INIT_DELAYED_WORK(&(fep->delay_work.delay_work), fec_enet_work); | ||
1955 | return 0; | 1976 | return 0; |
1956 | 1977 | ||
1957 | failed_register: | 1978 | failed_register: |
@@ -1984,6 +2005,7 @@ fec_drv_remove(struct platform_device *pdev) | |||
1984 | struct fec_enet_private *fep = netdev_priv(ndev); | 2005 | struct fec_enet_private *fep = netdev_priv(ndev); |
1985 | int i; | 2006 | int i; |
1986 | 2007 | ||
2008 | cancel_delayed_work_sync(&(fep->delay_work.delay_work)); | ||
1987 | unregister_netdev(ndev); | 2009 | unregister_netdev(ndev); |
1988 | fec_enet_mii_remove(fep); | 2010 | fec_enet_mii_remove(fep); |
1989 | del_timer_sync(&fep->time_keep); | 2011 | del_timer_sync(&fep->time_keep); |