[PATCH] libertas: handle hardware events outside ISR
Holger Schurig
hs4233 at mail.mn-solutions.de
Fri Oct 17 03:36:59 EDT 2008
This is the code that I'm using since some months, but which I
didn't post so far because I see some obvious problems:
* it produces a considerable amount of latency on my device,
e.g. while scanning or associating
* it's not completely cleaned up, e.g. remove all lines in
#ifdef WITH_TASKLET, that is a remnant of a test I made
But on the other side it showed to be rock-solid in day-to-day
use, e.g. with a GUI application that calls the equivalent of
"iwconfig eth1" every second.
In case you want to massage this patch and put it into the
Kernel, here is my:
Signed-off-by: Holger Schurig <hs4233 at mail.mn-solutions.de>
--- linux.orig/drivers/net/wireless/libertas/dev.h
+++ linux/drivers/net/wireless/libertas/dev.h
@@ -154,6 +154,7 @@
/** Hardware access */
int (*hw_host_to_card) (struct lbs_private *priv, u8 type, u8
*payload, u16 nb);
void (*reset_card) (struct lbs_private *priv);
+ void (*poll_card) (struct lbs_private *priv);
/* Wake On LAN */
uint32_t wol_criteria;
@@ -195,6 +196,8 @@
/* Events sent from hardware to driver */
struct kfifo *event_fifo;
+#define LBS_EVENT_MASK 0xff0000
+#define LBS_EVENT_WAKE 0xff0001
/* nickname */
u8 nodename[16];
--- linux.orig/drivers/net/wireless/libertas/if_cs.c
+++ linux/drivers/net/wireless/libertas/if_cs.c
@@ -55,10 +55,17 @@
/* Data structures
*/
/********************************************************************/
+/* #define WITH_TASKLET */
+
struct if_cs_card {
struct pcmcia_device *p_dev;
struct lbs_private *priv;
void __iomem *iobase;
+ spinlock_t lock;
+ u16 int_cause;
+#ifdef WITH_TASKLET
+ struct tasklet_struct tasklet;
+#endif
};
@@ -238,6 +245,7 @@
*/
#define IF_CS_CARD_STATUS 0x00000020
#define IF_CS_CARD_STATUS_MASK 0x7f00
+#define IF_CS_CARD_STATUS_EVENT (IF_CS_CARD_STATUS_MASK |
IF_CS_BIT_EVENT)
/*
* The card int cause register is used by the card/firmware to
notify us
@@ -304,22 +312,8 @@
{
struct if_cs_card *card = (struct if_cs_card *)priv->card;
int ret = -1;
- int loops = 0;
lbs_deb_enter(LBS_DEB_CS);
- if_cs_disable_ints(card);
-
- /* Is hardware ready? */
- while (1) {
- u16 status = if_cs_read16(card, IF_CS_CARD_STATUS);
- if (status & IF_CS_BIT_COMMAND)
- break;
- if (++loops > 100) {
- lbs_pr_err("card not ready for commands\n");
- goto done;
- }
- mdelay(1);
- }
if_cs_write16(card, IF_CS_CMD_LEN, nb);
@@ -337,8 +331,6 @@
if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_COMMAND);
ret = 0;
-done:
- if_cs_enable_ints(card);
lbs_deb_leave_args(LBS_DEB_CS, "ret %d", ret);
return ret;
}
@@ -349,13 +341,8 @@
static void if_cs_send_data(struct lbs_private *priv, u8 *buf,
u16 nb)
{
struct if_cs_card *card = (struct if_cs_card *)priv->card;
- u16 status;
lbs_deb_enter(LBS_DEB_CS);
- if_cs_disable_ints(card);
-
- status = if_cs_read16(card, IF_CS_CARD_STATUS);
- BUG_ON((status & IF_CS_BIT_TX) == 0);
if_cs_write16(card, IF_CS_WRITE_LEN, nb);
@@ -366,30 +353,20 @@
if_cs_write16(card, IF_CS_HOST_STATUS, IF_CS_BIT_TX);
if_cs_write16(card, IF_CS_HOST_INT_CAUSE, IF_CS_BIT_TX);
- if_cs_enable_ints(card);
lbs_deb_leave(LBS_DEB_CS);
}
/*
- * Get the command result out of the card.
+ * Get the command response out of the card.
*/
-static int if_cs_receive_cmdres(struct lbs_private *priv, u8
*data, u32 *len)
+static int if_cs_receive_resp(struct lbs_private *priv, u8
*data, u32 *len)
{
unsigned long flags;
int ret = -1;
- u16 status;
lbs_deb_enter(LBS_DEB_CS);
- /* is hardware ready? */
- status = if_cs_read16(priv->card, IF_CS_CARD_STATUS);
- if ((status & IF_CS_BIT_RESP) == 0) {
- lbs_pr_err("no cmd response in card\n");
- *len = 0;
- goto out;
- }
-
*len = if_cs_read16(priv->card, IF_CS_RESP_LEN);
if ((*len == 0) || (*len > LBS_CMD_BUFFER_SIZE)) {
lbs_pr_err("card cmd buffer has invalid # of bytes (%d)\n",
*len);
@@ -425,6 +402,7 @@
lbs_deb_enter(LBS_DEB_CS);
len = if_cs_read16(priv->card, IF_CS_READ_LEN);
+ //lbs_deb_cs("rx %d\n", len);
if (len == 0 || len > MRVDRV_ETH_RX_PACKET_BUFFER_SIZE) {
lbs_pr_err("card data buffer has invalid # of bytes (%d)\n",
len);
priv->stats.rx_dropped++;
@@ -455,74 +433,138 @@
static irqreturn_t if_cs_interrupt(int irq, void *data)
{
struct if_cs_card *card = data;
- struct lbs_private *priv = card->priv;
- u16 cause;
+ //struct lbs_private *priv = card->priv;
+ unsigned long flags;
+ u16 cause, status;
lbs_deb_enter(LBS_DEB_CS);
- /* Ask card interrupt cause register if there is something for
us */
+ if_cs_disable_ints(card);
+
cause = if_cs_read16(card, IF_CS_CARD_INT_CAUSE);
lbs_deb_cs("cause 0x%04x\n", cause);
if (cause == 0) {
/* Not for us */
+ if_cs_enable_ints(card);
return IRQ_NONE;
}
if (cause == 0xffff) {
/* Read in junk, the card has probably been removed */
card->priv->surpriseremoved = 1;
+ if_cs_enable_ints(card);
return IRQ_HANDLED;
}
+ /* read card status to clear event bit */
+ status = if_cs_read16(card, IF_CS_CARD_STATUS);
+ lbs_deb_cs("status 0x%04x\n", status);
+
+ cause &= IF_CS_BIT_MASK;
+
+ /* Store the interupt cause in our shadow */
+ spin_lock_irqsave(&card->lock, flags);
+ card->int_cause |= cause;
+ if (!(cause && IF_CS_BIT_RX) && status & IF_CS_BIT_RX) {
+ /* We sometimes miss an RX interrupt, but we can deduce this
+ * fact from the status */
+ lbs_deb_cs("forcing IF_CS_BIT_RX\n");
+ card->int_cause |= IF_CS_BIT_RX;
+ }
+ if (status & IF_CS_BIT_EVENT) {
+ /* Store the event bit and the event code into our
+ * int_cause shadow */
+ lbs_deb_cs("transferring event\n");
+ card->int_cause |= (status & IF_CS_CARD_STATUS_EVENT);
+ }
+ spin_unlock_irqrestore(&card->lock, flags);
+
+ /* Clear interrupt cause */
+ if_cs_write16(card, IF_CS_CARD_INT_CAUSE, cause);
+
+#ifdef WITH_TASKLET
+ /* Simple way to keep the main thread from sleeping :-) */
+ tasklet_schedule(&card->tasklet);
+#else
+ lbs_queue_event(card->priv, LBS_EVENT_WAKE);
+#endif
+
+ if_cs_enable_ints(card);
+
+ return IRQ_HANDLED;
+}
+
+#ifdef WITH_TASKLET
+static void if_cs_poll_status(unsigned long data)
+{
+ struct if_cs_card *card = (struct if_cs_card *)data;
+ struct lbs_private *priv = card->priv;
+#else
+static void if_cs_poll_status(struct lbs_private *priv)
+{
+ struct if_cs_card *card = (struct if_cs_card *)priv->card;
+#endif
+ unsigned long flags;
+ u16 cause;
+
+ lbs_deb_enter(LBS_DEB_CS);
+
+ if (priv->surpriseremoved)
+ return;
+
+ spin_lock_irqsave(&card->lock, flags);
+ cause = card->int_cause;
+ card->int_cause = 0;
+ spin_unlock_irqrestore(&card->lock, flags);
+
+ lbs_deb_cs("int_cause 0x%04x\n", cause);
+
+ if (!cause)
+ return;
+
+ if (cause & IF_CS_BIT_TX) {
+ lbs_deb_cs("tx\n");
+ lbs_host_to_card_done(priv);
+ }
+
if (cause & IF_CS_BIT_RX) {
struct sk_buff *skb;
- lbs_deb_cs("rx packet\n");
skb = if_cs_receive_data(priv);
if (skb)
lbs_process_rxed_packet(priv, skb);
}
- if (cause & IF_CS_BIT_TX) {
- lbs_deb_cs("tx done\n");
- lbs_host_to_card_done(priv);
- }
-
if (cause & IF_CS_BIT_RESP) {
- unsigned long flags;
+ unsigned long driver_flags;
u8 i;
- lbs_deb_cs("cmd resp\n");
- spin_lock_irqsave(&priv->driver_lock, flags);
+ lbs_deb_cs("resp\n");
+ spin_lock_irqsave(&priv->driver_lock, driver_flags);
i = (priv->resp_idx == 0) ? 1 : 0;
- spin_unlock_irqrestore(&priv->driver_lock, flags);
+ spin_unlock_irqrestore(&priv->driver_lock, driver_flags);
BUG_ON(priv->resp_len[i]);
- if_cs_receive_cmdres(priv, priv->resp_buf[i],
+ if_cs_receive_resp(priv, priv->resp_buf[i],
&priv->resp_len[i]);
- spin_lock_irqsave(&priv->driver_lock, flags);
+ spin_lock_irqsave(&priv->driver_lock, driver_flags);
lbs_notify_command_response(priv, i);
- spin_unlock_irqrestore(&priv->driver_lock, flags);
+ spin_unlock_irqrestore(&priv->driver_lock, driver_flags);
}
if (cause & IF_CS_BIT_EVENT) {
- u16 status = if_cs_read16(priv->card, IF_CS_CARD_STATUS);
if_cs_write16(priv->card, IF_CS_HOST_INT_CAUSE,
IF_CS_BIT_EVENT);
- lbs_queue_event(priv, (status & IF_CS_CARD_STATUS_MASK) >> 8);
+ lbs_queue_event(priv, (cause & IF_CS_CARD_STATUS_MASK) >> 8);
}
- /* Clear interrupt cause */
- if_cs_write16(card, IF_CS_CARD_INT_CAUSE, cause &
IF_CS_BIT_MASK);
-
+ if_cs_enable_ints(card);
lbs_deb_leave(LBS_DEB_CS);
- return IRQ_HANDLED;
}
-
/********************************************************************/
/* Firmware
*/
/********************************************************************/
@@ -847,6 +889,11 @@
}
}
+ spin_lock_init(&card->lock);
+#ifdef WITH_TASKLET
+ tasklet_init(&card->tasklet, if_cs_poll_status, (unsigned
long)card);
+#endif
+
/* Initialize io access */
card->iobase = ioport_map(p_dev->io.BasePort1,
p_dev->io.NumPorts1);
if (!card->iobase) {
@@ -903,6 +950,9 @@
card->priv = priv;
priv->card = card;
priv->hw_host_to_card = if_cs_host_to_card;
+#ifndef WITH_TASKLET
+ priv->poll_card = if_cs_poll_status;
+#endif
priv->fw_ready = 1;
/* Now actually get the IRQ */
--- linux.orig/drivers/net/wireless/libertas/main.c
+++ linux/drivers/net/wireless/libertas/main.c
@@ -706,6 +706,10 @@
add_wait_queue(&priv->waitq, &wait);
set_current_state(TASK_INTERRUPTIBLE);
+
+ if (priv->poll_card)
+ priv->poll_card(priv);
+
spin_lock_irq(&priv->driver_lock);
if (kthread_should_stop())
@@ -812,9 +816,12 @@
__kfifo_get(priv->event_fifo, (unsigned char *) &event,
sizeof(event));
- spin_unlock_irq(&priv->driver_lock);
- lbs_process_event(priv, event);
- spin_lock_irq(&priv->driver_lock);
+ /* ignore LBS_EVENT_xxx events */
+ if (!(event & LBS_EVENT_MASK)) {
+ spin_unlock_irq(&priv->driver_lock);
+ lbs_process_event(priv, event);
+ spin_lock_irq(&priv->driver_lock);
+ }
}
spin_unlock_irq(&priv->driver_lock);
More information about the libertas-dev
mailing list