Commit eaf51ced authored by Jean Tourrilhes's avatar Jean Tourrilhes Committed by Linus Torvalds

[PATCH] irport fixes

ir259_irport-6.diff :
	o [CORRECT] fix module ownership in irport
	o [CORRECT] Properly initialise dev->trans_start in irport
	o [CORRECT] Add delay to drain the Tx fifo to avoid corrupting
		last outgoing Tx byte when changing speed
	o [FEATURE] Safer locking around speed change in irport_hard_xmit()
	o [FEATURE] Enforce half duplex operation in interrupt handler
	o [FEATURE] Optimise interrupt handler for latency and I/O ops
	o [FEATURE] Optimise Tx path in irport_write()
	o [FEATURE] Add ZeroCopy Rx
	o [FEATURE] Better debugging in watchdog timeout
	o [FEATURE] Various other cleanups and comments
parent bb17ce49
......@@ -11,6 +11,7 @@
* Sources: serial.c by Linus Torvalds
*
* Copyright (c) 1997, 1998, 1999-2000 Dag Brattli, All Rights Reserved.
* Copyright (c) 2000-2003 Jean Tourrilhes, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
......@@ -48,6 +49,7 @@
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/rtnetlink.h>
#include <asm/system.h>
......@@ -72,14 +74,14 @@ static unsigned int qos_mtt_bits = 0x03;
static struct irport_cb *dev_self[] = { NULL, NULL, NULL, NULL};
static char *driver_name = "irport";
static void irport_write_wakeup(struct irport_cb *self);
static int irport_write(int iobase, int fifo_size, __u8 *buf, int len);
static void irport_receive(struct irport_cb *self);
static inline void irport_write_wakeup(struct irport_cb *self);
static inline int irport_write(int iobase, int fifo_size, __u8 *buf, int len);
static inline void irport_receive(struct irport_cb *self);
static int irport_net_init(struct net_device *dev);
static int irport_net_ioctl(struct net_device *dev, struct ifreq *rq,
int cmd);
static int irport_is_receiving(struct irport_cb *self);
static inline int irport_is_receiving(struct irport_cb *self);
static int irport_set_dtr_rts(struct net_device *dev, int dtr, int rts);
static int irport_raw_write(struct net_device *dev, __u8 *buf, int len);
static struct net_device_stats *irport_net_get_stats(struct net_device *dev);
......@@ -169,7 +171,7 @@ irport_open(int i, unsigned int iobase, unsigned int irq)
self->io.sir_base = iobase;
self->io.sir_ext = IO_EXTENT;
self->io.irq = irq;
self->io.fifo_size = 16;
self->io.fifo_size = 16; /* 16550A and compatible */
/* Initialize QoS for this device */
irda_init_max_qos_capabilies(&self->qos);
......@@ -181,39 +183,47 @@ irport_open(int i, unsigned int iobase, unsigned int irq)
irda_qos_bits_to_value(&self->qos);
self->flags = IFF_SIR|IFF_PIO;
self->mode = IRDA_IRLAP;
/* Bootstrap ZeroCopy Rx */
self->rx_buff.truesize = IRDA_SKB_MAX_MTU;
self->rx_buff.skb = __dev_alloc_skb(self->rx_buff.truesize,
GFP_KERNEL);
if (self->rx_buff.skb == NULL)
return NULL;
skb_reserve(self->rx_buff.skb, 1);
self->rx_buff.head = self->rx_buff.skb->data;
/* No need to memset the buffer, unless you are really pedantic */
/* Finish setup the Rx buffer descriptor */
self->rx_buff.in_frame = FALSE;
self->rx_buff.state = OUTSIDE_FRAME;
self->rx_buff.data = self->rx_buff.head;
/* Specify how much memory we want */
self->rx_buff.truesize = 4000;
self->tx_buff.truesize = 4000;
/* Allocate memory if needed */
if (self->rx_buff.truesize > 0) {
self->rx_buff.head = (__u8 *) kmalloc(self->rx_buff.truesize,
GFP_KERNEL);
if (self->rx_buff.head == NULL)
return NULL;
memset(self->rx_buff.head, 0, self->rx_buff.truesize);
}
if (self->tx_buff.truesize > 0) {
self->tx_buff.head = (__u8 *) kmalloc(self->tx_buff.truesize,
GFP_KERNEL);
if (self->tx_buff.head == NULL) {
kfree(self->rx_buff.head);
kfree_skb(self->rx_buff.skb);
self->rx_buff.skb = NULL;
self->rx_buff.head = NULL;
return NULL;
}
memset(self->tx_buff.head, 0, self->tx_buff.truesize);
}
self->rx_buff.in_frame = FALSE;
self->rx_buff.state = OUTSIDE_FRAME;
self->tx_buff.data = self->tx_buff.head;
self->rx_buff.data = self->rx_buff.head;
self->mode = IRDA_IRLAP;
if (!(dev = dev_alloc("irda%d", &err))) {
ERROR("%s(), dev_alloc() failed!\n", __FUNCTION__);
return NULL;
}
self->netdev = dev;
/* Keep track of module usage */
SET_MODULE_OWNER(dev);
/* May be overridden by piggyback drivers */
dev->priv = (void *) self;
......@@ -241,7 +251,8 @@ irport_open(int i, unsigned int iobase, unsigned int irq)
ERROR("%s(), register_netdev() failed!\n", __FUNCTION__);
return NULL;
}
MESSAGE("IrDA: Registered device %s\n", dev->name);
MESSAGE("IrDA: Registered device %s (irport io=0x%X irq=%d)\n",
dev->name, iobase, irq);
return self;
}
......@@ -270,8 +281,9 @@ int irport_close(struct irport_cb *self)
if (self->tx_buff.head)
kfree(self->tx_buff.head);
if (self->rx_buff.head)
kfree(self->rx_buff.head);
if (self->rx_buff.skb)
kfree_skb(self->rx_buff.skb);
self->rx_buff.skb = NULL;
/* Remove ourselves */
dev_self[self->index] = NULL;
......@@ -306,6 +318,9 @@ void irport_stop(struct irport_cb *self)
/* We can't lock, we may be called from a FIR driver - Jean II */
/* We are not transmitting any more */
self->transmitting = 0;
/* Reset UART */
outb(0, iobase+UART_MCR);
......@@ -326,6 +341,33 @@ int irport_probe(int iobase)
return 0;
}
/*
* Function irport_get_fcr (speed)
*
* Compute value of fcr
*
*/
static inline unsigned int irport_get_fcr(__u32 speed)
{
unsigned int fcr; /* FIFO control reg */
/* Enable fifos */
fcr = UART_FCR_ENABLE_FIFO;
/*
* Use trigger level 1 to avoid 3 ms. timeout delay at 9600 bps, and
* almost 1,7 ms at 19200 bps. At speeds above that we can just forget
* about this timeout since it will always be fast enough.
*/
if (speed < 38400)
fcr |= UART_FCR_TRIGGER_1;
else
//fcr |= UART_FCR_TRIGGER_14;
fcr |= UART_FCR_TRIGGER_8;
return(fcr);
}
/*
* Function irport_change_speed (self, speed)
*
......@@ -337,11 +379,12 @@ void irport_change_speed(void *priv, __u32 speed)
{
struct irport_cb *self = (struct irport_cb *) priv;
int iobase;
int fcr; /* FIFO control reg */
int lcr; /* Line control reg */
unsigned int fcr; /* FIFO control reg */
unsigned int lcr; /* Line control reg */
int divisor;
ASSERT(self != NULL, return;);
ASSERT(speed != 0, return;);
IRDA_DEBUG(1, "%s(), Setting speed to: %d - iobase=%#x\n",
__FUNCTION__, speed, self->io.sir_base);
......@@ -358,18 +401,9 @@ void irport_change_speed(void *priv, __u32 speed)
divisor = SPEED_MAX/speed;
fcr = UART_FCR_ENABLE_FIFO;
/* Get proper fifo configuration */
fcr = irport_get_fcr(speed);
/*
* Use trigger level 1 to avoid 3 ms. timeout delay at 9600 bps, and
* almost 1,7 ms at 19200 bps. At speeds above that we can just forget
* about this timeout since it will always be fast enough.
*/
if (self->io.speed < 38400)
fcr |= UART_FCR_TRIGGER_1;
else
fcr |= UART_FCR_TRIGGER_14;
/* IrDA ports use 8N1 */
lcr = UART_LCR_WLEN8;
......@@ -380,7 +414,7 @@ void irport_change_speed(void *priv, __u32 speed)
outb(fcr, iobase+UART_FCR); /* Enable FIFO's */
/* Turn on interrups */
/* This will generate a fata interrupt storm.
/* This will generate a fatal interrupt storm.
* People calling us will do that properly - Jean II */
//outb(/*UART_IER_RLSI|*/UART_IER_RDI/*|UART_IER_THRI*/, iobase+UART_IER);
}
......@@ -467,8 +501,8 @@ int __irport_change_speed(struct irda_task *task)
irda_task_next_state(task, IRDA_TASK_DONE);
ret = -1;
break;
}
/* Put stuff in the sate we found them - Jean II */
}
/* Put stuff in the state we found them - Jean II */
if(wasunlocked) {
spin_unlock_irqrestore(&self->lock, flags);
}
......@@ -476,98 +510,6 @@ int __irport_change_speed(struct irda_task *task)
return ret;
}
/*
* Function irport_write_wakeup (tty)
*
* Called by the driver when there's room for more data. If we have
* more packets to send, we send them here.
*
*/
static void irport_write_wakeup(struct irport_cb *self)
{
int actual = 0;
int iobase;
int fcr;
ASSERT(self != NULL, return;);
IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
iobase = self->io.sir_base;
/* Finished with frame? */
if (self->tx_buff.len > 0) {
/* Write data left in transmit buffer */
actual = irport_write(iobase, self->io.fifo_size,
self->tx_buff.data, self->tx_buff.len);
self->tx_buff.data += actual;
self->tx_buff.len -= actual;
/* Turn on transmit finished interrupt. */
outb(UART_IER_THRI, iobase+UART_IER);
} else {
/*
* Now serial buffer is almost free & we can start
* transmission of another packet. But first we must check
* if we need to change the speed of the hardware
*/
if (self->new_speed) {
IRDA_DEBUG(5, "%s(), Changing speed!\n", __FUNCTION__);
irda_task_execute(self, __irport_change_speed,
irport_change_speed_complete,
NULL, (void *) self->new_speed);
self->new_speed = 0;
IRDA_DEBUG(5, "%s(), Speed changed!\n", __FUNCTION__ );
} else {
/* Tell network layer that we want more frames */
netif_wake_queue(self->netdev);
}
self->stats.tx_packets++;
/*
* Reset Rx FIFO to make sure that all reflected transmit data
* is discarded. This is needed for half duplex operation
*/
fcr = UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR;
if (self->io.speed < 38400)
fcr |= UART_FCR_TRIGGER_1;
else
fcr |= UART_FCR_TRIGGER_14;
outb(fcr, iobase+UART_FCR);
/* Turn on receive interrupts */
outb(UART_IER_RDI, iobase+UART_IER);
}
}
/*
* Function irport_write (driver)
*
* Fill Tx FIFO with transmit data
*
*/
static int irport_write(int iobase, int fifo_size, __u8 *buf, int len)
{
int actual = 0;
/* Tx FIFO should be empty! */
if (!(inb(iobase+UART_LSR) & UART_LSR_THRE)) {
IRDA_DEBUG(0, "%s(), failed, fifo not empty!\n", __FUNCTION__);
return 0;
}
/* Fill FIFO with current frame */
while ((fifo_size-- > 0) && (actual < len)) {
/* Transmit next byte */
outb(buf[actual], iobase+UART_TX);
actual++;
}
return actual;
}
/*
* Function irport_change_speed_complete (task)
*
......@@ -604,23 +546,79 @@ static void irport_timeout(struct net_device *dev)
{
struct irport_cb *self;
int iobase;
int iir, lsr;
unsigned long flags;
self = (struct irport_cb *) dev->priv;
ASSERT(self != NULL, return;);
iobase = self->io.sir_base;
WARNING("%s: transmit timed out\n", dev->name);
WARNING("%s: transmit timed out, jiffies = %ld, trans_start = %ld\n",
dev->name, jiffies, dev->trans_start);
spin_lock_irqsave(&self->lock, flags);
/* Debug what's happening... */
/* Get interrupt status */
lsr = inb(iobase+UART_LSR);
/* Read interrupt register */
iir = inb(iobase+UART_IIR);
IRDA_DEBUG(0, "%s(), iir=%02x, lsr=%02x, iobase=%#x\n",
__FUNCTION__, iir, lsr, iobase);
IRDA_DEBUG(0, "%s(), transmitting=%d, remain=%d, done=%d\n",
__FUNCTION__, self->transmitting, self->tx_buff.len,
self->tx_buff.data - self->tx_buff.head);
/* Now, restart the port */
irport_start(self);
self->change_speed(self->priv, self->io.speed);
/* This will re-enable irqs */
outb(/*UART_IER_RLSI|*/UART_IER_RDI/*|UART_IER_THRI*/, iobase+UART_IER);
dev->trans_start = jiffies;
spin_unlock_irqrestore(&self->lock, flags);
dev->trans_start = jiffies;
netif_wake_queue(dev);
}
/*
* Function irport_wait_hw_transmitter_finish ()
*
* Wait for the real end of HW transmission
*
* The UART is a strict FIFO, and we get called only when we have finished
* pushing data to the FIFO, so the maximum amount of time we must wait
* is only for the FIFO to drain out.
*
* We use a simple calibrated loop. We may need to adjust the loop
* delay (udelay) to balance I/O traffic and latency. And we also need to
* adjust the maximum timeout.
* It would probably be better to wait for the proper interrupt,
* but it doesn't seem to be available.
*
* We can't use jiffies or kernel timers because :
* 1) We are called from the interrupt handler, which disable softirqs,
* so jiffies won't be increased
* 2) Jiffies granularity is usually very coarse (10ms), and we don't
* want to wait that long to detect stuck hardware.
* Jean II
*/
static void irport_wait_hw_transmitter_finish(struct irport_cb *self)
{
int iobase;
int count = 1000; /* 1 ms */
iobase = self->io.sir_base;
/* Calibrated busy loop */
while((count-- > 0) && !(inb(iobase+UART_LSR) & UART_LSR_TEMT))
udelay(1);
if(count == 0)
IRDA_DEBUG(0, "%s(): stuck transmitter\n", __FUNCTION__);
}
/*
* Function irport_hard_start_xmit (struct sk_buff *skb, struct net_device *dev)
*
......@@ -645,8 +643,8 @@ int irport_hard_xmit(struct sk_buff *skb, struct net_device *dev)
iobase = self->io.sir_base;
netif_stop_queue(dev);
/* Make sure tests *& speed change are atomic */
/* Make sure tests & speed change are atomic */
spin_lock_irqsave(&self->lock, flags);
/* Check if we need to change the speed */
......@@ -654,10 +652,21 @@ int irport_hard_xmit(struct sk_buff *skb, struct net_device *dev)
if ((speed != self->io.speed) && (speed != -1)) {
/* Check for empty frame */
if (!skb->len) {
/*
* We send frames one by one in SIR mode (no
* pipelining), so at this point, if we were sending
* a previous frame, we just received the interrupt
* telling us it is finished (UART_IIR_THRI).
* Therefore, waiting for the transmitter to really
* finish draining the fifo won't take too long.
* And the interrupt handler is not expected to run.
* - Jean II */
irport_wait_hw_transmitter_finish(self);
/* Better go there already locked - Jean II */
irda_task_execute(self, __irport_change_speed,
irport_change_speed_complete,
NULL, (void *) speed);
dev->trans_start = jiffies;
spin_unlock_irqrestore(&self->lock, flags);
dev_kfree_skb(skb);
return 0;
......@@ -674,9 +683,13 @@ int irport_hard_xmit(struct sk_buff *skb, struct net_device *dev)
self->stats.tx_bytes += self->tx_buff.len;
/* We are transmitting */
self->transmitting = 1;
/* Turn on transmit finished interrupt. Will fire immediately! */
outb(UART_IER_THRI, iobase+UART_IER);
dev->trans_start = jiffies;
spin_unlock_irqrestore(&self->lock, flags);
dev_kfree_skb(skb);
......@@ -684,13 +697,101 @@ int irport_hard_xmit(struct sk_buff *skb, struct net_device *dev)
return 0;
}
/*
* Function irport_write (driver)
*
* Fill Tx FIFO with transmit data
*
* Called only from irport_write_wakeup()
*/
static inline int irport_write(int iobase, int fifo_size, __u8 *buf, int len)
{
int actual = 0;
/* Fill FIFO with current frame */
while ((actual < fifo_size) && (actual < len)) {
/* Transmit next byte */
outb(buf[actual], iobase+UART_TX);
actual++;
}
return actual;
}
/*
* Function irport_write_wakeup (tty)
*
* Called by the driver when there's room for more data. If we have
* more packets to send, we send them here.
*
* Called only from irport_interrupt()
* Make sure this function is *not* called while we are receiving,
* otherwise we will reset fifo and loose data :-(
*/
static inline void irport_write_wakeup(struct irport_cb *self)
{
int actual = 0;
int iobase;
unsigned int fcr;
ASSERT(self != NULL, return;);
IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
iobase = self->io.sir_base;
/* Finished with frame? */
if (self->tx_buff.len > 0) {
/* Write data left in transmit buffer */
actual = irport_write(iobase, self->io.fifo_size,
self->tx_buff.data, self->tx_buff.len);
self->tx_buff.data += actual;
self->tx_buff.len -= actual;
} else {
/*
* Now serial buffer is almost free & we can start
* transmission of another packet. But first we must check
* if we need to change the speed of the hardware
*/
if (self->new_speed) {
irport_wait_hw_transmitter_finish(self);
irda_task_execute(self, __irport_change_speed,
irport_change_speed_complete,
NULL, (void *) self->new_speed);
self->new_speed = 0;
} else {
/* Tell network layer that we want more frames */
netif_wake_queue(self->netdev);
}
self->stats.tx_packets++;
/*
* Reset Rx FIFO to make sure that all reflected transmit data
* is discarded. This is needed for half duplex operation
*/
fcr = irport_get_fcr(self->io.speed);
fcr |= UART_FCR_CLEAR_RCVR;
outb(fcr, iobase+UART_FCR);
/* Finished transmitting */
self->transmitting = 0;
/* Turn on receive interrupts */
outb(UART_IER_RDI, iobase+UART_IER);
IRDA_DEBUG(1, "%s() : finished Tx\n", __FUNCTION__);
}
}
/*
* Function irport_receive (self)
*
* Receive one frame from the infrared port
*
* Called only from irport_interrupt()
*/
static void irport_receive(struct irport_cb *self)
static inline void irport_receive(struct irport_cb *self)
{
int boguscount = 0;
int iobase;
......@@ -739,40 +840,51 @@ irqreturn_t irport_interrupt(int irq, void *dev_id, struct pt_regs *regs)
iobase = self->io.sir_base;
iir = inb(iobase+UART_IIR) & UART_IIR_ID;
while (iir) {
handled = 1;
/* Cut'n'paste interrupt routine from serial.c
* This version try to minimise latency and I/O operations.
* Simplified and modified to enforce half duplex operation.
* - Jean II */
/* Clear interrupt */
/* Check status even is iir reg is cleared, more robust and
* eliminate a read on the I/O bus - Jean II */
do {
/* Get interrupt status ; Clear interrupt */
lsr = inb(iobase+UART_LSR);
/* Are we receiving or transmitting ? */
if(!self->transmitting) {
/* Received something ? */
if (lsr & UART_LSR_DR)
irport_receive(self);
} else {
/* Room in Tx fifo ? */
if (lsr & (UART_LSR_THRE | UART_LSR_TEMT))
irport_write_wakeup(self);
}
IRDA_DEBUG(4, "%s(), iir=%02x, lsr=%02x, iobase=%#x\n",
__FUNCTION__, iir, lsr, iobase);
/* A bit hackish, but working as expected... Jean II */
if(lsr & (UART_LSR_THRE | UART_LSR_TEMT | UART_LSR_DR))
handled = 1;
switch (iir) {
case UART_IIR_RLSI:
IRDA_DEBUG(2, "%s(), RLSI\n", __FUNCTION__);
break;
case UART_IIR_RDI:
/* Receive interrupt */
irport_receive(self);
break;
case UART_IIR_THRI:
if (lsr & UART_LSR_THRE)
/* Transmitter ready for data */
irport_write_wakeup(self);
break;
default:
IRDA_DEBUG(0, "%s(), unhandled IIR=%#x\n", __FUNCTION__, iir);
break;
}
/* Make sure we don't stay here too long */
if (boguscount++ > 100)
/* Make sure we don't stay here to long */
if (boguscount++ > 10) {
WARNING("%s() irq handler looping : lsr=%02x\n",
__FUNCTION__, lsr);
break;
}
/* Read interrupt register */
iir = inb(iobase+UART_IIR);
/* Enable this debug only when no other options and at low
* bit rates, otherwise it may cause Rx overruns (lsr=63).
* - Jean II */
IRDA_DEBUG(6, "%s(), iir=%02x, lsr=%02x, iobase=%#x\n",
__FUNCTION__, iir, lsr, iobase);
/* As long as interrupt pending... */
} while ((iir & UART_IIR_NO_INT) == 0);
iir = inb(iobase + UART_IIR) & UART_IIR_ID;
}
spin_unlock(&self->lock);
return IRQ_RETVAL(handled);
}
......@@ -800,8 +912,8 @@ int irport_net_open(struct net_device *dev)
char hwname[16];
unsigned long flags;
IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
ASSERT(dev != NULL, return -1;);
self = (struct irport_cb *) dev->priv;
......@@ -815,7 +927,12 @@ int irport_net_open(struct net_device *dev)
}
spin_lock_irqsave(&self->lock, flags);
/* Init uart */
irport_start(self);
/* Set 9600 bauds per default, including at the dongle */
irda_task_execute(self, __irport_change_speed,
irport_change_speed_complete,
NULL, (void *) 9600);
spin_unlock_irqrestore(&self->lock, flags);
......@@ -828,12 +945,9 @@ int irport_net_open(struct net_device *dev)
*/
self->irlap = irlap_open(dev, &self->qos, hwname);
/* FIXME: change speed of dongle */
/* Ready to play! */
netif_start_queue(dev);
MOD_INC_USE_COUNT;
return 0;
}
......@@ -873,40 +987,16 @@ int irport_net_close(struct net_device *dev)
free_irq(self->io.irq, dev);
MOD_DEC_USE_COUNT;
return 0;
}
/*
* Function irport_wait_until_sent (self)
*
* Delay exectution until finished transmitting
*
*/
#if 0
void irport_wait_until_sent(struct irport_cb *self)
{
int iobase;
iobase = self->io.sir_base;
/* Wait until Tx FIFO is empty */
while (!(inb(iobase+UART_LSR) & UART_LSR_THRE)) {
IRDA_DEBUG(2, "%s(), waiting!\n", __FUNCTION__);
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(MSECS_TO_JIFFIES(60));
}
}
#endif
/*
* Function irport_is_receiving (self)
*
* Returns true is we are currently receiving data
*
*/
static int irport_is_receiving(struct irport_cb *self)
static inline int irport_is_receiving(struct irport_cb *self)
{
return (self->rx_buff.state != OUTSIDE_FRAME);
}
......@@ -998,6 +1088,12 @@ static int irport_net_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
break;
}
/* Locking :
* irda_device_dongle_init() can't be locked.
* irda_task_execute() doesn't need to be locked.
* Jean II
*/
/* Initialize dongle */
dongle = irda_device_dongle_init(dev, irq->ifr_dongle);
if (!dongle)
......
......@@ -67,6 +67,7 @@ struct irport_cb {
__u32 new_speed;
int mode;
int index; /* Instance index */
int transmitting; /* Are we transmitting ? */
spinlock_t lock; /* For serializing operations */
......
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