Commit 585aaa59 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] smc91x: straighten SMP locking

From: Nicolas Pitre <nico@cam.org>

Fix SMP locking, and add special cases for SMP in order to preserve UP's
slightly better efficiency and reliability.
Signed-off-by: default avatarNicolas Pitre <nico@cam.org>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
parent 05bdb0fd
...@@ -260,24 +260,22 @@ static void PRINT_PKT(u_char *buf, int length) ...@@ -260,24 +260,22 @@ static void PRINT_PKT(u_char *buf, int length)
/* this enables an interrupt in the interrupt mask register */ /* this enables an interrupt in the interrupt mask register */
#define SMC_ENABLE_INT(x) do { \ #define SMC_ENABLE_INT(x) do { \
unsigned long flags; \
unsigned char mask; \ unsigned char mask; \
spin_lock_irqsave(&lp->lock, flags); \ spin_lock_irq(&lp->lock); \
mask = SMC_GET_INT_MASK(); \ mask = SMC_GET_INT_MASK(); \
mask |= (x); \ mask |= (x); \
SMC_SET_INT_MASK(mask); \ SMC_SET_INT_MASK(mask); \
spin_unlock_irqrestore(&lp->lock, flags); \ spin_unlock_irq(&lp->lock); \
} while (0) } while (0)
/* this disables an interrupt from the interrupt mask register */ /* this disables an interrupt from the interrupt mask register */
#define SMC_DISABLE_INT(x) do { \ #define SMC_DISABLE_INT(x) do { \
unsigned long flags; \
unsigned char mask; \ unsigned char mask; \
spin_lock_irqsave(&lp->lock, flags); \ spin_lock_irq(&lp->lock); \
mask = SMC_GET_INT_MASK(); \ mask = SMC_GET_INT_MASK(); \
mask &= ~(x); \ mask &= ~(x); \
SMC_SET_INT_MASK(mask); \ SMC_SET_INT_MASK(mask); \
spin_unlock_irqrestore(&lp->lock, flags); \ spin_unlock_irq(&lp->lock); \
} while (0) } while (0)
/* /*
...@@ -312,8 +310,10 @@ static void smc_reset(struct net_device *dev) ...@@ -312,8 +310,10 @@ static void smc_reset(struct net_device *dev)
DBG(2, "%s: %s\n", dev->name, __FUNCTION__); DBG(2, "%s: %s\n", dev->name, __FUNCTION__);
/* Disable all interrupts */ /* Disable all interrupts */
spin_lock(&lp->lock);
SMC_SELECT_BANK(2); SMC_SELECT_BANK(2);
SMC_SET_INT_MASK(0); SMC_SET_INT_MASK(0);
spin_unlock(&lp->lock);
/* /*
* This resets the registers mostly to defaults, but doesn't * This resets the registers mostly to defaults, but doesn't
...@@ -427,13 +427,18 @@ static void smc_enable(struct net_device *dev) ...@@ -427,13 +427,18 @@ static void smc_enable(struct net_device *dev)
/* /*
* this puts the device in an inactive state * this puts the device in an inactive state
*/ */
static void smc_shutdown(unsigned long ioaddr) static void smc_shutdown(struct net_device *dev)
{ {
unsigned long ioaddr = dev->base_addr;
struct smc_local *lp = netdev_priv(dev);
DBG(2, "%s: %s\n", CARDNAME, __FUNCTION__); DBG(2, "%s: %s\n", CARDNAME, __FUNCTION__);
/* no more interrupts for me */ /* no more interrupts for me */
spin_lock(&lp->lock);
SMC_SELECT_BANK(2); SMC_SELECT_BANK(2);
SMC_SET_INT_MASK(0); SMC_SET_INT_MASK(0);
spin_unlock(&lp->lock);
/* and tell the card to stay away from that nasty outside world */ /* and tell the card to stay away from that nasty outside world */
SMC_SELECT_BANK(0); SMC_SELECT_BANK(0);
...@@ -535,6 +540,37 @@ static inline void smc_rcv(struct net_device *dev) ...@@ -535,6 +540,37 @@ static inline void smc_rcv(struct net_device *dev)
SMC_SET_MMU_CMD(MC_RELEASE); SMC_SET_MMU_CMD(MC_RELEASE);
} }
#ifdef CONFIG_SMP
/*
* On SMP we have the following problem:
*
* A = smc_hardware_send_pkt()
* B = smc_hard_start_xmit()
* C = smc_interrupt()
*
* A and B can never be executed simultaneously. However, at least on UP,
* it is possible (and even desirable) for C to interrupt execution of
* A or B in order to have better RX reliability and avoid overruns.
* C, just like A and B, must have exclusive access to the chip and
* each of them must lock against any other concurrent access.
* Unfortunately this is not possible to have C suspend execution of A or
* B taking place on another CPU. On UP this is no an issue since A and B
* are run from softirq context and C from hard IRQ context, and there is
* no other CPU where concurrent access can happen.
* If ever there is a way to force at least B and C to always be executed
* on the same CPU then we could use read/write locks to protect against
* any other concurrent access and C would always interrupt B. But life
* isn't that easy in a SMP world...
*/
#define smc_special_trylock(lock) spin_trylock_irq(lock)
#define smc_special_lock(lock) spin_lock_irq(lock)
#define smc_special_unlock(lock) spin_unlock_irq(lock)
#else
#define smc_special_trylock(lock) (1)
#define smc_special_lock(lock) do { } while (0)
#define smc_special_unlock(lock) do { } while (0)
#endif
/* /*
* This is called to actually send a packet to the chip. * This is called to actually send a packet to the chip.
* Returns non-zero when successful. * Returns non-zero when successful.
...@@ -550,14 +586,18 @@ static void smc_hardware_send_pkt(unsigned long data) ...@@ -550,14 +586,18 @@ static void smc_hardware_send_pkt(unsigned long data)
DBG(3, "%s: %s\n", dev->name, __FUNCTION__); DBG(3, "%s: %s\n", dev->name, __FUNCTION__);
if (!smc_special_trylock(&lp->lock)) {
tasklet_schedule(&lp->tx_task);
return;
}
lp->saved_skb = NULL; lp->saved_skb = NULL;
packet_no = SMC_GET_AR(); packet_no = SMC_GET_AR();
if (unlikely(packet_no & AR_FAILED)) { if (unlikely(packet_no & AR_FAILED)) {
printk("%s: Memory allocation failed.\n", dev->name); printk("%s: Memory allocation failed.\n", dev->name);
lp->stats.tx_errors++; lp->stats.tx_errors++;
lp->stats.tx_fifo_errors++; lp->stats.tx_fifo_errors++;
dev_kfree_skb(skb); goto done;
return;
} }
/* point to the beginning of the packet */ /* point to the beginning of the packet */
...@@ -597,16 +637,18 @@ static void smc_hardware_send_pkt(unsigned long data) ...@@ -597,16 +637,18 @@ static void smc_hardware_send_pkt(unsigned long data)
/* queue the packet for TX */ /* queue the packet for TX */
SMC_SET_MMU_CMD(MC_ENQUEUE); SMC_SET_MMU_CMD(MC_ENQUEUE);
SMC_ACK_INT(IM_TX_EMPTY_INT); SMC_ACK_INT(IM_TX_EMPTY_INT);
if (!THROTTLE_TX_PKTS)
netif_wake_queue(dev);
SMC_ENABLE_INT(IM_TX_INT | IM_TX_EMPTY_INT); SMC_ENABLE_INT(IM_TX_INT | IM_TX_EMPTY_INT);
dev_kfree_skb(skb);
dev->trans_start = jiffies; dev->trans_start = jiffies;
lp->stats.tx_packets++; lp->stats.tx_packets++;
lp->stats.tx_bytes += len; lp->stats.tx_bytes += len;
done: smc_special_unlock(&lp->lock);
if (!THROTTLE_TX_PKTS)
netif_wake_queue(dev);
dev_kfree_skb(skb);
} }
/* /*
...@@ -647,6 +689,8 @@ static int smc_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -647,6 +689,8 @@ static int smc_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
return 0; return 0;
} }
smc_special_lock(&lp->lock);
/* now, try to allocate the memory */ /* now, try to allocate the memory */
SMC_SET_MMU_CMD(MC_ALLOC | numPages); SMC_SET_MMU_CMD(MC_ALLOC | numPages);
...@@ -663,6 +707,8 @@ static int smc_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -663,6 +707,8 @@ static int smc_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
} }
} while (--poll_count); } while (--poll_count);
smc_special_unlock(&lp->lock);
if (!poll_count) { if (!poll_count) {
/* oh well, wait until the chip finds memory later */ /* oh well, wait until the chip finds memory later */
netif_stop_queue(dev); netif_stop_queue(dev);
...@@ -1175,6 +1221,8 @@ static irqreturn_t smc_interrupt(int irq, void *dev_id, struct pt_regs *regs) ...@@ -1175,6 +1221,8 @@ static irqreturn_t smc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
DBG(3, "%s: %s\n", dev->name, __FUNCTION__); DBG(3, "%s: %s\n", dev->name, __FUNCTION__);
spin_lock(&lp->lock);
saved_pointer = SMC_GET_PTR(); saved_pointer = SMC_GET_PTR();
mask = SMC_GET_INT_MASK(); mask = SMC_GET_INT_MASK();
SMC_SET_INT_MASK(0); SMC_SET_INT_MASK(0);
...@@ -1196,8 +1244,6 @@ static irqreturn_t smc_interrupt(int irq, void *dev_id, struct pt_regs *regs) ...@@ -1196,8 +1244,6 @@ static irqreturn_t smc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
if (!status) if (!status)
break; break;
spin_lock(&lp->lock);
if (status & IM_RCV_INT) { if (status & IM_RCV_INT) {
DBG(3, "%s: RX irq\n", dev->name); DBG(3, "%s: RX irq\n", dev->name);
smc_rcv(dev); smc_rcv(dev);
...@@ -1240,14 +1286,14 @@ static irqreturn_t smc_interrupt(int irq, void *dev_id, struct pt_regs *regs) ...@@ -1240,14 +1286,14 @@ static irqreturn_t smc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
SMC_ACK_INT(IM_ERCV_INT); SMC_ACK_INT(IM_ERCV_INT);
PRINTK("%s: UNSUPPORTED: ERCV INTERRUPT \n", dev->name); PRINTK("%s: UNSUPPORTED: ERCV INTERRUPT \n", dev->name);
} }
spin_unlock(&lp->lock);
} while (--timeout); } while (--timeout);
/* restore register states */ /* restore register states */
SMC_SET_PTR(saved_pointer); SMC_SET_PTR(saved_pointer);
SMC_SET_INT_MASK(mask); SMC_SET_INT_MASK(mask);
spin_unlock(&lp->lock);
DBG(3, "%s: Interrupt done (%d loops)\n", dev->name, 8-timeout); DBG(3, "%s: Interrupt done (%d loops)\n", dev->name, 8-timeout);
/* /*
...@@ -1471,7 +1517,7 @@ static int smc_close(struct net_device *dev) ...@@ -1471,7 +1517,7 @@ static int smc_close(struct net_device *dev)
netif_carrier_off(dev); netif_carrier_off(dev);
/* clear everything */ /* clear everything */
smc_shutdown(dev->base_addr); smc_shutdown(dev);
if (lp->phy_type != 0) { if (lp->phy_type != 0) {
flush_scheduled_work(); flush_scheduled_work();
...@@ -2093,7 +2139,7 @@ static int smc_drv_suspend(struct device *dev, u32 state, u32 level) ...@@ -2093,7 +2139,7 @@ static int smc_drv_suspend(struct device *dev, u32 state, u32 level)
if (ndev && level == SUSPEND_DISABLE) { if (ndev && level == SUSPEND_DISABLE) {
if (netif_running(ndev)) { if (netif_running(ndev)) {
netif_device_detach(ndev); netif_device_detach(ndev);
smc_shutdown(ndev->base_addr); smc_shutdown(ndev);
} }
} }
return 0; return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment