The race causes a kernel oops when smc_hardware_send_pkt() tries to
dereference pending_tx_skb which would have been freed from one of the
driver reset paths just after the tx_task tasklet has been scheduled.
This race is possible on SMP but was uncovered by the kernel RT work.
Signed-off-by: Nicolas Pitre <nico@cam.org>
struct smc_local *lp = netdev_priv(dev);
void __iomem *ioaddr = lp->base;
unsigned int ctl, cfg;
struct smc_local *lp = netdev_priv(dev);
void __iomem *ioaddr = lp->base;
unsigned int ctl, cfg;
+ struct sk_buff *pending_skb;
DBG(2, "%s: %s\n", dev->name, __FUNCTION__);
DBG(2, "%s: %s\n", dev->name, __FUNCTION__);
- /* Disable all interrupts */
+ /* Disable all interrupts, block TX tasklet */
spin_lock(&lp->lock);
SMC_SELECT_BANK(2);
SMC_SET_INT_MASK(0);
spin_lock(&lp->lock);
SMC_SELECT_BANK(2);
SMC_SET_INT_MASK(0);
+ pending_skb = lp->pending_tx_skb;
+ lp->pending_tx_skb = NULL;
+ /* free any pending tx skb */
+ if (pending_skb) {
+ dev_kfree_skb(pending_skb);
+ lp->stats.tx_errors++;
+ lp->stats.tx_aborted_errors++;
+ }
+
/*
* This resets the registers mostly to defaults, but doesn't
* affect EEPROM. That seems unnecessary
/*
* This resets the registers mostly to defaults, but doesn't
* affect EEPROM. That seems unnecessary
SMC_SELECT_BANK(2);
SMC_SET_MMU_CMD(MC_RESET);
SMC_WAIT_MMU_BUSY();
SMC_SELECT_BANK(2);
SMC_SET_MMU_CMD(MC_RESET);
SMC_WAIT_MMU_BUSY();
-
- /* clear anything saved */
- if (lp->pending_tx_skb != NULL) {
- dev_kfree_skb (lp->pending_tx_skb);
- lp->pending_tx_skb = NULL;
- lp->stats.tx_errors++;
- lp->stats.tx_aborted_errors++;
- }
{
struct smc_local *lp = netdev_priv(dev);
void __iomem *ioaddr = lp->base;
{
struct smc_local *lp = netdev_priv(dev);
void __iomem *ioaddr = lp->base;
+ struct sk_buff *pending_skb;
DBG(2, "%s: %s\n", CARDNAME, __FUNCTION__);
DBG(2, "%s: %s\n", CARDNAME, __FUNCTION__);
spin_lock(&lp->lock);
SMC_SELECT_BANK(2);
SMC_SET_INT_MASK(0);
spin_lock(&lp->lock);
SMC_SELECT_BANK(2);
SMC_SET_INT_MASK(0);
+ pending_skb = lp->pending_tx_skb;
+ lp->pending_tx_skb = NULL;
+ if (pending_skb)
+ dev_kfree_skb(pending_skb);
/* and tell the card to stay away from that nasty outside world */
SMC_SELECT_BANK(0);
/* and tell the card to stay away from that nasty outside world */
SMC_SELECT_BANK(0);
}
skb = lp->pending_tx_skb;
}
skb = lp->pending_tx_skb;
+ if (unlikely(!skb)) {
+ smc_special_unlock(&lp->lock);
+ return;
+ }
lp->pending_tx_skb = NULL;
lp->pending_tx_skb = NULL;
packet_no = SMC_GET_AR();
if (unlikely(packet_no & AR_FAILED)) {
printk("%s: Memory allocation failed.\n", dev->name);
packet_no = SMC_GET_AR();
if (unlikely(packet_no & AR_FAILED)) {
printk("%s: Memory allocation failed.\n", dev->name);
DBG(3, "%s: %s\n", dev->name, __FUNCTION__);
BUG_ON(lp->pending_tx_skb != NULL);
DBG(3, "%s: %s\n", dev->name, __FUNCTION__);
BUG_ON(lp->pending_tx_skb != NULL);
- lp->pending_tx_skb = skb;
/*
* The MMU wants the number of pages to be the number of 256 bytes
/*
* The MMU wants the number of pages to be the number of 256 bytes
numPages = ((skb->len & ~1) + (6 - 1)) >> 8;
if (unlikely(numPages > 7)) {
printk("%s: Far too big packet error.\n", dev->name);
numPages = ((skb->len & ~1) + (6 - 1)) >> 8;
if (unlikely(numPages > 7)) {
printk("%s: Far too big packet error.\n", dev->name);
- lp->pending_tx_skb = NULL;
lp->stats.tx_errors++;
lp->stats.tx_dropped++;
dev_kfree_skb(skb);
lp->stats.tx_errors++;
lp->stats.tx_dropped++;
dev_kfree_skb(skb);
smc_special_unlock(&lp->lock);
smc_special_unlock(&lp->lock);
+ lp->pending_tx_skb = skb;
if (!poll_count) {
/* oh well, wait until the chip finds memory later */
netif_stop_queue(dev);
if (!poll_count) {
/* oh well, wait until the chip finds memory later */
netif_stop_queue(dev);
above). linkwatch_event() also wants the netlink semaphore.
*/
while(lp->work_pending)
above). linkwatch_event() also wants the netlink semaphore.
*/
while(lp->work_pending)
bmcr = smc_phy_read(dev, phy, MII_BMCR);
smc_phy_write(dev, phy, MII_BMCR, bmcr | BMCR_PDOWN);
bmcr = smc_phy_read(dev, phy, MII_BMCR);
smc_phy_write(dev, phy, MII_BMCR, bmcr | BMCR_PDOWN);
/* clear everything */
smc_shutdown(dev);
/* clear everything */
smc_shutdown(dev);
+ tasklet_kill(&lp->tx_task);
-
- if (lp->pending_tx_skb) {
- dev_kfree_skb(lp->pending_tx_skb);
- lp->pending_tx_skb = NULL;
- }
-