summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShaun Ruffell <sruffell@digium.com>2010-02-10 22:36:03 +0000
committerShaun Ruffell <sruffell@digium.com>2010-02-10 22:36:03 +0000
commit8d01aa4ca33ff7e0ffc44d473ac99e61e195d86a (patch)
tree8813691073bf5c2ccf44a69444a6ad524a10e30d
parent36183ddd5d9980d98c80b0196e10702ac3b21fb1 (diff)
voicebus: Improve hard underrun handling.
When there is a hard underrun, or in other words interrupts are held off for so long that the board completely runs out of transmit descriptors, it was too easy for the driver to be in a 'confused' state about the descriptors. This change has the driver cleanup and reset the descriptor ring in this case so that we're not racing against the interface while we resume normal operation. git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@8026 a0bf4364-ded3-4de4-8d8a-66a801d63aff
-rw-r--r--drivers/dahdi/voicebus/voicebus.c304
-rw-r--r--drivers/dahdi/voicebus/voicebus.h2
2 files changed, 143 insertions, 163 deletions
diff --git a/drivers/dahdi/voicebus/voicebus.c b/drivers/dahdi/voicebus/voicebus.c
index d68c1f3..8853093 100644
--- a/drivers/dahdi/voicebus/voicebus.c
+++ b/drivers/dahdi/voicebus/voicebus.c
@@ -141,12 +141,10 @@ static inline void handle_transmit(struct voicebus *vb, void *vbb)
#endif
/* Bit definitions for struct voicebus.flags */
-#define TX_UNDERRUN 1
-#define RX_UNDERRUN 2
-#define IN_DEFERRED_PROCESSING 3
-#define STOP 4
-#define STOPPED 5
-#define LATENCY_LOCKED 6
+#define IN_DEFERRED_PROCESSING 1
+#define STOP 2
+#define STOPPED 3
+#define LATENCY_LOCKED 4
#if VOICEBUS_DEFERRED == WORKQUEUE
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
@@ -629,13 +627,74 @@ vb_reset_interface(struct voicebus *vb)
return 0;
}
+/*!
+ * \brief Give a frame to the hardware to use for receiving.
+ *
+ */
+static inline int
+vb_submit_rxb(struct voicebus *vb, void *vbb)
+{
+ struct voicebus_descriptor *d;
+ struct voicebus_descriptor_list *dl = &vb->rxd;
+ unsigned int tail = dl->tail;
+
+ d = vb_descriptor(dl, tail);
+
+ if (unlikely(d->buffer1)) {
+ /* Do not overwrite a buffer that is still in progress. */
+ WARN_ON(1);
+ voicebus_free(vb, vbb);
+ return -EBUSY;
+ }
+
+ dl->pending[tail] = vbb;
+ dl->tail = (++tail) & DRING_MASK;
+ d->buffer1 = dma_map_single(&vb->pdev->dev, vbb,
+ VOICEBUS_SFRAME_SIZE, DMA_FROM_DEVICE);
+ SET_OWNED(d); /* That's it until the hardware is done with it. */
+ atomic_inc(&dl->count);
+ return 0;
+}
+
+static void setup_descriptors(struct voicebus *vb)
+{
+ int i;
+ void *vbb;
+
+ vb_cleanup_tx_descriptors(vb);
+ vb_cleanup_rx_descriptors(vb);
+
+ /* Tell the card where the descriptors are in host memory. */
+ vb_setctl(vb, 0x0020, (u32)vb->txd.desc_dma);
+ vb_setctl(vb, 0x0018, (u32)vb->rxd.desc_dma);
+
+ for (i = 0; i < DRING_SIZE; ++i) {
+ vbb = voicebus_alloc(vb);
+ if (unlikely(NULL == vbb)) {
+ BUG_ON(1);
+ /* \todo I need to make sure the driver can recover
+ * from this condition. .... */
+ } else {
+ vb_submit_rxb(vb, vbb);
+ }
+ }
+
+ for (i = 0; i < vb->min_tx_buffer_count; ++i) {
+ vbb = voicebus_alloc(vb);
+
+ if (unlikely(NULL == vbb))
+ BUG_ON(1);
+ else
+ handle_transmit(vb, vbb);
+ }
+}
+
static int
vb_initialize_interface(struct voicebus *vb)
{
u32 reg;
- vb_cleanup_tx_descriptors(vb);
- vb_cleanup_rx_descriptors(vb);
+ setup_descriptors(vb);
/* Pass bad packets, runt packets, disable SQE function,
* store-and-forward */
@@ -643,10 +702,6 @@ vb_initialize_interface(struct voicebus *vb)
/* ...disable jabber and the receive watchdog. */
vb_setctl(vb, 0x0078, 0x00000013);
- /* Tell the card where the descriptors are in host memory. */
- vb_setctl(vb, 0x0020, (u32)vb->txd.desc_dma);
- vb_setctl(vb, 0x0018, (u32)vb->rxd.desc_dma);
-
reg = vb_getctl(vb, 0x00fc);
vb_setctl(vb, 0x00fc, (reg & ~0x7) | 0x7);
vb_setsdi(vb, 0x00, 0x0100);
@@ -725,35 +780,6 @@ int voicebus_transmit(struct voicebus *vb, void *vbb)
EXPORT_SYMBOL(voicebus_transmit);
/*!
- * \brief Give a frame to the hardware to use for receiving.
- *
- */
-static inline int
-vb_submit_rxb(struct voicebus *vb, void *vbb)
-{
- struct voicebus_descriptor *d;
- struct voicebus_descriptor_list *dl = &vb->rxd;
- unsigned int tail = dl->tail;
-
- d = vb_descriptor(dl, tail);
-
- if (unlikely(d->buffer1)) {
- /* Do not overwrite a buffer that is still in progress. */
- WARN_ON(1);
- voicebus_free(vb, vbb);
- return -EBUSY;
- }
-
- dl->pending[tail] = vbb;
- dl->tail = (++tail) & DRING_MASK;
- d->buffer1 = dma_map_single(&vb->pdev->dev, vbb,
- VOICEBUS_SFRAME_SIZE, DMA_FROM_DEVICE);
- SET_OWNED(d); /* That's it until the hardware is done with it. */
- atomic_inc(&dl->count);
- return 0;
-}
-
-/*!
* \brief Remove the next completed transmit buffer (txb) from the tx
* descriptor ring.
*
@@ -832,19 +858,6 @@ __vb_tx_demand_poll(struct voicebus *vb)
}
/*!
- * \brief Command the hardware to check if it owns the next transmit
- * descriptor.
- */
-static void
-vb_tx_demand_poll(struct voicebus *vb)
-{
- LOCKS_VOICEBUS;
- VBLOCK(vb);
- __vb_tx_demand_poll(vb);
- VBUNLOCK(vb);
-}
-
-/*!
* \brief Command the hardware to check if it owns the next receive
* descriptor.
*/
@@ -855,15 +868,6 @@ __vb_rx_demand_poll(struct voicebus *vb)
}
static void
-vb_rx_demand_poll(struct voicebus *vb)
-{
- LOCKS_VOICEBUS;
- VBLOCK(vb);
- __vb_rx_demand_poll(vb);
- VBUNLOCK(vb);
-}
-
-static void
__vb_enable_interrupts(struct voicebus *vb)
{
__vb_setctl(vb, IER_CSR7, DEFAULT_INTERRUPTS);
@@ -884,6 +888,31 @@ vb_disable_interrupts(struct voicebus *vb)
VBUNLOCK(vb);
}
+static void start_packet_processing(struct voicebus *vb)
+{
+ LOCKS_VOICEBUS;
+ u32 reg;
+
+ VBLOCK(vb);
+ clear_bit(STOP, &vb->flags);
+ clear_bit(STOPPED, &vb->flags);
+#if VOICEBUS_DEFERRED == TIMER
+ vb->timer.expires = jiffies + HZ/1000;
+ add_timer(&vb->timer);
+#else
+ /* Clear the interrupt status register. */
+ __vb_setctl(vb, SR_CSR5, 0xffffffff);
+ __vb_enable_interrupts(vb);
+#endif
+ /* Start the transmit and receive processors. */
+ reg = __vb_getctl(vb, 0x0030);
+ __vb_setctl(vb, 0x0030, reg|0x00002002);
+ /* Tell the interface to poll the tx and rx descriptors. */
+ __vb_rx_demand_poll(vb);
+ __vb_tx_demand_poll(vb);
+ VBUNLOCK(vb);
+}
+
/*!
* \brief Starts the VoiceBus interface.
*
@@ -899,10 +928,6 @@ vb_disable_interrupts(struct voicebus *vb)
int
voicebus_start(struct voicebus *vb)
{
- LOCKS_VOICEBUS;
- u32 reg;
- int i;
- void *vbb;
int ret;
if (!vb_is_stopped(vb))
@@ -915,57 +940,7 @@ voicebus_start(struct voicebus *vb)
if (ret)
return ret;
- /* We must set up a minimum of three buffers to start with, since two
- * are immediately read into the TX FIFO, and the descriptor of the
- * third is read as soon as the first buffer is done.
- */
-
- /*
- * NOTE: handle_transmit is normally only called in the context of the
- * deferred processing thread. Since the deferred processing thread
- * is known to not be running at this point, it is safe to call the
- * handle transmit as if it were.
- */
- /* Ensure that all the rx slots are ready for a buffer. */
- for (i = 0; i < DRING_SIZE; ++i) {
- vbb = voicebus_alloc(vb);
- if (unlikely(NULL == vbb)) {
- BUG_ON(1);
- /* \todo I need to make sure the driver can recover
- * from this condition. .... */
- } else {
- vb_submit_rxb(vb, vbb);
- }
- }
-
- for (i = 0; i < vb->min_tx_buffer_count; ++i) {
- vbb = voicebus_alloc(vb);
-
- if (unlikely(NULL == vbb))
- BUG_ON(1);
- else
- handle_transmit(vb, vbb);
-
- }
-
- VBLOCK(vb);
- clear_bit(STOP, &vb->flags);
- clear_bit(STOPPED, &vb->flags);
-#if VOICEBUS_DEFERRED == TIMER
- vb->timer.expires = jiffies + HZ/1000;
- add_timer(&vb->timer);
-#else
- /* Clear the interrupt status register. */
- __vb_setctl(vb, SR_CSR5, 0xffffffff);
- __vb_enable_interrupts(vb);
-#endif
- /* Start the transmit and receive processors. */
- reg = __vb_getctl(vb, 0x0030);
- __vb_setctl(vb, 0x0030, reg|0x00002002);
- /* Tell the interface to poll the tx and rx descriptors. */
- __vb_rx_demand_poll(vb);
- __vb_tx_demand_poll(vb);
- VBUNLOCK(vb);
+ start_packet_processing(vb);
BUG_ON(vb_is_stopped(vb));
@@ -1053,6 +1028,14 @@ voicebus_release(struct voicebus *vb)
{
/* quiesce the hardware */
voicebus_stop(vb);
+
+ /* Make sure the underrun_work isn't running or going to run. */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22)
+ flush_scheduled_work();
+#else
+ cancel_work_sync(&vb->underrun_work);
+#endif
+
#if VOICEBUS_DEFERRED == WORKQUEUE
destroy_workqueue(vb->workqueue);
#elif VOICEBUS_DEFERRED == TASKLET
@@ -1269,8 +1252,6 @@ static void vb_deferred(struct voicebus *vb)
unsigned int idle_buffers;
int softunderrun;
- int underrun = test_bit(TX_UNDERRUN, &vb->flags);
-
buffer_count = 0;
/* First, temporarily store any non-idle buffers that the hardware has
@@ -1335,24 +1316,6 @@ static void vb_deferred(struct voicebus *vb)
for (i = 0; i < buffer_count; ++i)
handle_transmit(vb, vb->vbb_stash[i]);
- /* If underrun is set, it means that the hardware signalled that it
- * completely ran out of transmit descriptors. This is what we are
- * trying to avoid with all this racy softunderun business, but alas,
- * it's still possible to happen if interrupts are locked longer than
- * DRING_SIZE milliseconds for some reason. We should have already fixed
- * up the descriptor ring in this case, so let's just tell the hardware
- * to reread what it believes the next descriptor is. */
- if (unlikely(underrun)) {
- if (printk_ratelimit()) {
- dev_info(&vb->pdev->dev, "Host failed to service "
- "card interrupt within %d ms which is a "
- "hardunderun.\n", DRING_SIZE);
- }
- vb_rx_demand_poll(vb);
- vb_tx_demand_poll(vb);
- clear_bit(TX_UNDERRUN, &vb->flags);
- }
-
/* Print any messages about soft latency bumps after we fix the transmit
* descriptor ring. Otherwise it's possible to take so much time
* printing the dmesg output that we lose the lead that we got on the
@@ -1387,6 +1350,35 @@ static void vb_deferred(struct voicebus *vb)
}
}
+/**
+ * handle_hardunderrun() - reset the AN983 after experiencing a hardunderrun.
+ * @work: The work_struct used to queue this function.
+ *
+ */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+static void handle_hardunderrun(void *data)
+{
+ struct voicebus *vb = data;
+#else
+static void handle_hardunderrun(struct work_struct *work)
+{
+ struct voicebus *vb = container_of(work, struct voicebus,
+ underrun_work);
+#endif
+ if (printk_ratelimit()) {
+ dev_info(&vb->pdev->dev, "Host failed to service "
+ "card interrupt within %d ms which is a "
+ "hardunderun.\n", DRING_SIZE);
+ }
+ voicebus_stop(vb);
+
+ if (vb->ops->handle_error)
+ vb->ops->handle_error(vb);
+
+ setup_descriptors(vb);
+ start_packet_processing(vb);
+}
+
/*!
* \brief Interrupt handler for VoiceBus interface.
*
@@ -1414,7 +1406,11 @@ vb_isr(int irq, void *dev_id)
if (!int_status)
return IRQ_NONE;
- if (likely(int_status & TX_COMPLETE_INTERRUPT)) {
+ if (unlikely((int_status & TX_UNAVAILABLE_INTERRUPT) &&
+ !test_bit(STOP, &vb->flags))) {
+ schedule_work(&vb->underrun_work);
+ __vb_setctl(vb, SR_CSR5, int_status);
+ } else if (likely(int_status & TX_COMPLETE_INTERRUPT)) {
/* ******************************************************** */
/* NORMAL INTERRUPT CASE */
/* ******************************************************** */
@@ -1427,30 +1423,6 @@ vb_isr(int irq, void *dev_id)
# endif
__vb_setctl(vb, SR_CSR5, TX_COMPLETE_INTERRUPT);
} else {
- /* ******************************************************** */
- /* ABNORMAL / ERROR CONDITIONS */
- /* ******************************************************** */
- if ((int_status & TX_UNAVAILABLE_INTERRUPT)) {
- /* This can happen if the host fails to service the
- * interrupt within the required time interval (1ms
- * for each buffer on the queue). Increasing the
- * depth of the tx queue (up to a maximum of
- * DRING_SIZE) can make the driver / system more
- * tolerant of interrupt latency under periods of
- * heavy system load, but also increases the general
- * latency that the driver adds to the voice
- * conversations.
- */
- set_bit(TX_UNDERRUN, &vb->flags);
-# if VOICEBUS_DEFERRED == WORKQUEUE
- queue_work(vb->workqueue, &vb->workitem);
-# elif VOICEBUS_DEFERRED == TASKLET
- tasklet_schedule(&vb->tasklet);
-# else
- vb_deferred(vb);
-# endif
- }
-
if (int_status & FATAL_BUS_ERROR_INTERRUPT)
dev_err(&vb->pdev->dev, "Fatal Bus Error detected!\n");
@@ -1567,6 +1539,12 @@ voicebus_init(struct voicebus *vb, const char *board_name)
vb->timer.data = (unsigned long)vb;
#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20)
+ INIT_WORK(&vb->underrun_work, handle_hardunderrun, vb);
+#else
+ INIT_WORK(&vb->underrun_work, handle_hardunderrun);
+#endif
+
/* ----------------------------------------------------------------
Configure the hardware / kernel module interfaces.
---------------------------------------------------------------- */
diff --git a/drivers/dahdi/voicebus/voicebus.h b/drivers/dahdi/voicebus/voicebus.h
index a038b91..be713c6 100644
--- a/drivers/dahdi/voicebus/voicebus.h
+++ b/drivers/dahdi/voicebus/voicebus.h
@@ -58,6 +58,7 @@ struct voicebus;
struct voicebus_operations {
void (*handle_receive)(struct voicebus *vb, void *vbb);
void (*handle_transmit)(struct voicebus *vb, void *vbb);
+ void (*handle_error)(struct voicebus *vb);
};
/**
@@ -94,6 +95,7 @@ struct voicebus {
#elif VOICEBUS_DEFERRED == TIMER
struct timer_list timer;
#endif
+ struct work_struct underrun_work;
const struct voicebus_operations *ops;
struct completion stopped_completion;
unsigned long flags;