diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2007-11-19 22:50:46 -0500 |
---|---|---|
committer | Jeff Garzik <jeff@garzik.org> | 2007-11-23 20:52:09 -0500 |
commit | 61dbcecef568450de954115180881bf2f68511bc (patch) | |
tree | 5cd9d0bff1ab065764230f870a1a48bbdb22142d /drivers/net | |
parent | 2ffbb8377c7a0713baf6644e285adc27a5654582 (diff) |
ibm_newemac: Fix possible lockup on close
It's a bad idea to call flush_scheduled_work from within a
netdev->stop because the linkwatch will occasionally take the
rtnl lock from a workqueue context, and thus that can deadlock.
This reworks things a bit in that area to avoid the problem.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/ibm_newemac/core.c | 31 | ||||
-rw-r--r-- | drivers/net/ibm_newemac/core.h | 1 |
2 files changed, 21 insertions, 11 deletions
diff --git a/drivers/net/ibm_newemac/core.c b/drivers/net/ibm_newemac/core.c index 0de3aa2a2e44..eb0718b441bb 100644 --- a/drivers/net/ibm_newemac/core.c +++ b/drivers/net/ibm_newemac/core.c | |||
@@ -642,9 +642,11 @@ static void emac_reset_work(struct work_struct *work) | |||
642 | DBG(dev, "reset_work" NL); | 642 | DBG(dev, "reset_work" NL); |
643 | 643 | ||
644 | mutex_lock(&dev->link_lock); | 644 | mutex_lock(&dev->link_lock); |
645 | emac_netif_stop(dev); | 645 | if (dev->opened) { |
646 | emac_full_tx_reset(dev); | 646 | emac_netif_stop(dev); |
647 | emac_netif_start(dev); | 647 | emac_full_tx_reset(dev); |
648 | emac_netif_start(dev); | ||
649 | } | ||
648 | mutex_unlock(&dev->link_lock); | 650 | mutex_unlock(&dev->link_lock); |
649 | } | 651 | } |
650 | 652 | ||
@@ -1063,10 +1065,9 @@ static int emac_open(struct net_device *ndev) | |||
1063 | dev->rx_sg_skb = NULL; | 1065 | dev->rx_sg_skb = NULL; |
1064 | 1066 | ||
1065 | mutex_lock(&dev->link_lock); | 1067 | mutex_lock(&dev->link_lock); |
1068 | dev->opened = 1; | ||
1066 | 1069 | ||
1067 | /* XXX Start PHY polling now. Shouldn't wr do like sungem instead and | 1070 | /* Start PHY polling now. |
1068 | * always poll the PHY even when the iface is down ? That would allow | ||
1069 | * things like laptop-net to work. --BenH | ||
1070 | */ | 1071 | */ |
1071 | if (dev->phy.address >= 0) { | 1072 | if (dev->phy.address >= 0) { |
1072 | int link_poll_interval; | 1073 | int link_poll_interval; |
@@ -1145,9 +1146,11 @@ static void emac_link_timer(struct work_struct *work) | |||
1145 | int link_poll_interval; | 1146 | int link_poll_interval; |
1146 | 1147 | ||
1147 | mutex_lock(&dev->link_lock); | 1148 | mutex_lock(&dev->link_lock); |
1148 | |||
1149 | DBG2(dev, "link timer" NL); | 1149 | DBG2(dev, "link timer" NL); |
1150 | 1150 | ||
1151 | if (!dev->opened) | ||
1152 | goto bail; | ||
1153 | |||
1151 | if (dev->phy.def->ops->poll_link(&dev->phy)) { | 1154 | if (dev->phy.def->ops->poll_link(&dev->phy)) { |
1152 | if (!netif_carrier_ok(dev->ndev)) { | 1155 | if (!netif_carrier_ok(dev->ndev)) { |
1153 | /* Get new link parameters */ | 1156 | /* Get new link parameters */ |
@@ -1170,13 +1173,14 @@ static void emac_link_timer(struct work_struct *work) | |||
1170 | link_poll_interval = PHY_POLL_LINK_OFF; | 1173 | link_poll_interval = PHY_POLL_LINK_OFF; |
1171 | } | 1174 | } |
1172 | schedule_delayed_work(&dev->link_work, link_poll_interval); | 1175 | schedule_delayed_work(&dev->link_work, link_poll_interval); |
1173 | 1176 | bail: | |
1174 | mutex_unlock(&dev->link_lock); | 1177 | mutex_unlock(&dev->link_lock); |
1175 | } | 1178 | } |
1176 | 1179 | ||
1177 | static void emac_force_link_update(struct emac_instance *dev) | 1180 | static void emac_force_link_update(struct emac_instance *dev) |
1178 | { | 1181 | { |
1179 | netif_carrier_off(dev->ndev); | 1182 | netif_carrier_off(dev->ndev); |
1183 | smp_rmb(); | ||
1180 | if (dev->link_polling) { | 1184 | if (dev->link_polling) { |
1181 | cancel_rearming_delayed_work(&dev->link_work); | 1185 | cancel_rearming_delayed_work(&dev->link_work); |
1182 | if (dev->link_polling) | 1186 | if (dev->link_polling) |
@@ -1191,11 +1195,14 @@ static int emac_close(struct net_device *ndev) | |||
1191 | 1195 | ||
1192 | DBG(dev, "close" NL); | 1196 | DBG(dev, "close" NL); |
1193 | 1197 | ||
1194 | if (dev->phy.address >= 0) | 1198 | if (dev->phy.address >= 0) { |
1199 | dev->link_polling = 0; | ||
1195 | cancel_rearming_delayed_work(&dev->link_work); | 1200 | cancel_rearming_delayed_work(&dev->link_work); |
1196 | 1201 | } | |
1202 | mutex_lock(&dev->link_lock); | ||
1197 | emac_netif_stop(dev); | 1203 | emac_netif_stop(dev); |
1198 | flush_scheduled_work(); | 1204 | dev->opened = 0; |
1205 | mutex_unlock(&dev->link_lock); | ||
1199 | 1206 | ||
1200 | emac_rx_disable(dev); | 1207 | emac_rx_disable(dev); |
1201 | emac_tx_disable(dev); | 1208 | emac_tx_disable(dev); |
@@ -2756,6 +2763,8 @@ static int __devexit emac_remove(struct of_device *ofdev) | |||
2756 | 2763 | ||
2757 | unregister_netdev(dev->ndev); | 2764 | unregister_netdev(dev->ndev); |
2758 | 2765 | ||
2766 | flush_scheduled_work(); | ||
2767 | |||
2759 | if (emac_has_feature(dev, EMAC_FTR_HAS_TAH)) | 2768 | if (emac_has_feature(dev, EMAC_FTR_HAS_TAH)) |
2760 | tah_detach(dev->tah_dev, dev->tah_port); | 2769 | tah_detach(dev->tah_dev, dev->tah_port); |
2761 | if (emac_has_feature(dev, EMAC_FTR_HAS_RGMII)) | 2770 | if (emac_has_feature(dev, EMAC_FTR_HAS_RGMII)) |
diff --git a/drivers/net/ibm_newemac/core.h b/drivers/net/ibm_newemac/core.h index 4011803117ca..a010b2463fd9 100644 --- a/drivers/net/ibm_newemac/core.h +++ b/drivers/net/ibm_newemac/core.h | |||
@@ -258,6 +258,7 @@ struct emac_instance { | |||
258 | int stop_timeout; /* in us */ | 258 | int stop_timeout; /* in us */ |
259 | int no_mcast; | 259 | int no_mcast; |
260 | int mcast_pending; | 260 | int mcast_pending; |
261 | int opened; | ||
261 | struct work_struct reset_work; | 262 | struct work_struct reset_work; |
262 | spinlock_t lock; | 263 | spinlock_t lock; |
263 | }; | 264 | }; |