V4L/DVB (9720): cx18: Major rewrite of interrupt handling for incoming mailbox processing
authorAndy Walls <awalls@radix.net>
Sun, 16 Nov 2008 04:38:19 +0000 (01:38 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Tue, 30 Dec 2008 11:38:07 +0000 (09:38 -0200)
A major rewrite of interrupt handling for incoming mailbox processing, to split
the timing critical steps from the the deferrable steps as the sending XPU on
the CX23418 will time out and overwrite our incoming mailboxes rather quickly.
Setup a pool of work "order forms" for the irq handler to send jobs to the new
work handler routine which uses the kernel default work queue to do the
deferrable work.  Started optimizing some of the cx18-io calls as they are now
the low hanging fruit for recoving microseconds back from the timeline.
Future optimizations will get rid of mmio read retries, mmio stats logging, and
combine smaller functions in the irq path into the larger ones to save ~2 us
each.

Signed-off-by: Andy Walls <awalls@radix.net>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
16 files changed:
drivers/media/video/cx18/cx18-driver.c
drivers/media/video/cx18/cx18-driver.h
drivers/media/video/cx18/cx18-dvb.c
drivers/media/video/cx18/cx18-dvb.h
drivers/media/video/cx18/cx18-firmware.c
drivers/media/video/cx18/cx18-io.c
drivers/media/video/cx18/cx18-io.h
drivers/media/video/cx18/cx18-irq.c
drivers/media/video/cx18/cx18-irq.h
drivers/media/video/cx18/cx18-mailbox.c
drivers/media/video/cx18/cx18-mailbox.h
drivers/media/video/cx18/cx18-queue.c
drivers/media/video/cx18/cx18-queue.h
drivers/media/video/cx18/cx18-scb.h
drivers/media/video/cx18/cx18-streams.c
drivers/media/video/cx18/cx18-streams.h

index 8ef11d578b8d3b0f31605e16b9e77d4e221fc9a3..434cc7fdee3673f7bcf94f0fbfd3dce2d52bcb09 100644 (file)
@@ -440,6 +440,8 @@ done:
  */
 static int __devinit cx18_init_struct1(struct cx18 *cx)
 {
+       int i;
+
        cx->base_addr = pci_resource_start(cx->dev, 0);
 
        mutex_init(&cx->serialize_lock);
@@ -451,7 +453,11 @@ static int __devinit cx18_init_struct1(struct cx18 *cx)
 
        spin_lock_init(&cx->lock);
 
-       INIT_WORK(&cx->work, cx18_work_handler);
+       for (i = 0; i < CX18_MAX_EPU_WORK_ORDERS; i++) {
+               cx->epu_work_order[i].cx = cx;
+               cx->epu_work_order[i].str = cx->epu_debug_str;
+               INIT_WORK(&cx->epu_work_order[i].work, cx18_epu_work_handler);
+       }
 
        /* start counting open_id at 1 */
        cx->open_id = 1;
index ce76806759ec5cf04e41418d4c8772420a53a251..5ebf84b78ca808bd12924b66bfa55df53e0d9390 100644 (file)
@@ -203,8 +203,6 @@ struct cx18_options {
 #define CX18_F_I_EOS                   4       /* End of encoder stream */
 #define CX18_F_I_RADIO_USER            5       /* radio tuner is selected */
 #define CX18_F_I_ENC_PAUSED            13      /* the encoder is paused */
-#define CX18_F_I_HAVE_WORK             15      /* there is work to be done */
-#define CX18_F_I_WORK_HANDLER_DVB      18      /* work to be done for DVB */
 #define CX18_F_I_INITED                        21      /* set after first open */
 #define CX18_F_I_FAILED                        22      /* set if first open failed */
 
@@ -247,6 +245,19 @@ struct cx18_dvb {
 struct cx18;    /* forward reference */
 struct cx18_scb; /* forward reference */
 
+#define CX18_MAX_MDL_ACKS 2
+#define CX18_MAX_EPU_WORK_ORDERS 70 /* CPU_DE_RELEASE_MDL bursts 63 commands */
+
+struct cx18_epu_work_order {
+       struct work_struct work;
+       atomic_t pending;
+       struct cx18 *cx;
+       int rpu;
+       struct cx18_mailbox mb;
+       struct cx18_mdl_ack mdl_ack[CX18_MAX_MDL_ACKS];
+       char *str;
+};
+
 #define CX18_INVALID_TASK_HANDLE 0xffffffff
 
 struct cx18_stream {
@@ -388,7 +399,6 @@ struct cx18 {
        struct mutex epu2apu_mb_lock; /* protect driver to chip mailbox in SCB*/
        struct mutex epu2cpu_mb_lock; /* protect driver to chip mailbox in SCB*/
 
-
        struct cx18_av_state av_state;
 
        /* codec settings */
@@ -441,7 +451,8 @@ struct cx18 {
        /* when the current DMA is finished this queue is woken up */
        wait_queue_head_t dma_waitq;
 
-       struct work_struct work;
+       struct cx18_epu_work_order epu_work_order[CX18_MAX_EPU_WORK_ORDERS];
+       char epu_debug_str[256]; /* CX18_EPU_DEBUG is rare: use shared space */
 
        /* i2c */
        struct i2c_adapter i2c_adap[2];
index 4845f732ef486a12cd249506f754e4af594eaa27..b74253b1377efbdb5a5406d15a664d9e7f06a985 100644 (file)
@@ -23,8 +23,6 @@
 #include "cx18-dvb.h"
 #include "cx18-io.h"
 #include "cx18-streams.h"
-#include "cx18-queue.h"
-#include "cx18-scb.h"
 #include "cx18-cards.h"
 #include "s5h1409.h"
 #include "mxl5005s.h"
@@ -305,26 +303,3 @@ static int dvb_register(struct cx18_stream *stream)
 
        return ret;
 }
-
-void cx18_dvb_work_handler(struct cx18 *cx)
-{
-       struct cx18_buffer *buf;
-       struct cx18_stream *s = &cx->streams[CX18_ENC_STREAM_TYPE_TS];
-
-       while ((buf = cx18_dequeue(s, &s->q_full)) != NULL) {
-               if (s->dvb.enabled)
-                       dvb_dmx_swfilter(&s->dvb.demux, buf->buf,
-                                        buf->bytesused);
-
-               cx18_buf_sync_for_device(s, buf);
-               cx18_enqueue(s, buf, &s->q_free);
-
-               if (s->handle == CX18_INVALID_TASK_HANDLE ||
-                   !test_bit(CX18_F_S_STREAMING, &s->s_flags))
-                       continue;
-
-               cx18_vapi(cx, CX18_CPU_DE_SET_MDL, 5, s->handle,
-                      (void __iomem *)&cx->scb->cpu_mdl[buf->id] - cx->enc_mem,
-                      1, buf->id, s->buf_size);
-       }
-}
index bbdcefc87f288c4fa92b70cf68e56d572df17b47..bf8d8f6f54558ab3264dd783ad13dc9246632b40 100644 (file)
@@ -23,4 +23,3 @@
 
 int cx18_dvb_register(struct cx18_stream *stream);
 void cx18_dvb_unregister(struct cx18_stream *stream);
-void cx18_dvb_work_handler(struct cx18 *cx);
index d9c5f55ab17cc6f7f1a0885dbd7e8c9855729f46..fdea4b75204d895bc5f03e80244471ceac316475 100644 (file)
@@ -121,6 +121,7 @@ static int load_cpu_fw_direct(const char *fn, u8 __iomem *mem, struct cx18 *cx)
                        if (cx18_raw_readl(cx, dst) != *src) {
                                CX18_ERR("Mismatch at offset %x\n", i);
                                release_firmware(fw);
+                               cx18_setup_page(cx, 0);
                                return -EIO;
                        }
                        dst++;
@@ -131,6 +132,7 @@ static int load_cpu_fw_direct(const char *fn, u8 __iomem *mem, struct cx18 *cx)
                CX18_INFO("loaded %s firmware (%zd bytes)\n", fn, fw->size);
        size = fw->size;
        release_firmware(fw);
+       cx18_setup_page(cx, SCB_OFFSET);
        return size;
 }
 
@@ -150,6 +152,7 @@ static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx,
        if (request_firmware(&fw, fn, &cx->dev->dev)) {
                CX18_ERR("unable to open firmware %s\n", fn);
                CX18_ERR("did you put the firmware in the hotplug firmware directory?\n");
+               cx18_setup_page(cx, 0);
                return -ENOMEM;
        }
 
@@ -185,6 +188,7 @@ static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx,
                                        CX18_ERR("Mismatch at offset %x\n",
                                                 offset + j);
                                        release_firmware(fw);
+                                       cx18_setup_page(cx, 0);
                                        return -EIO;
                                }
                        }
@@ -196,6 +200,7 @@ static int load_apu_fw_direct(const char *fn, u8 __iomem *dst, struct cx18 *cx,
                                fn, apu_version, fw->size);
        size = fw->size;
        release_firmware(fw);
+       cx18_setup_page(cx, 0);
        return size;
 }
 
index 3c6485fceea06ca8db66fa5d3267a3a890560545..c67694f63d0e5a31d8ddeda4276dccfe6bfbfbc7 100644 (file)
@@ -166,41 +166,6 @@ u8 cx18_readb_retry(struct cx18 *cx, const void __iomem *addr)
        return val;
 }
 
-void cx18_memcpy_fromio(struct cx18 *cx, void *to,
-                       const void __iomem *from, unsigned int len)
-{
-       const u8 __iomem *src = from;
-       u8 *dst = to;
-
-       /* Align reads on the CX23418's addresses */
-       if ((len > 0) && ((unsigned long) src & 1)) {
-               *dst = cx18_readb(cx, src);
-               len--;
-               dst++;
-               src++;
-       }
-       if ((len > 1) && ((unsigned long) src & 2)) {
-               *((u16 *)dst) = cx18_raw_readw(cx, src);
-               len -= 2;
-               dst += 2;
-               src += 2;
-       }
-       while (len > 3) {
-               *((u32 *)dst) = cx18_raw_readl(cx, src);
-               len -= 4;
-               dst += 4;
-               src += 4;
-       }
-       if (len > 1) {
-               *((u16 *)dst) = cx18_raw_readw(cx, src);
-               len -= 2;
-               dst += 2;
-               src += 2;
-       }
-       if (len > 0)
-               *dst = cx18_readb(cx, src);
-}
-
 void cx18_memset_io(struct cx18 *cx, void __iomem *addr, int val, size_t count)
 {
        u8 __iomem *dst = addr;
index 4486b73faf5b52f08b00df55511d937ba31e65fa..fdc2bcc92fca23ac7dbef65338c12a09d123f3c6 100644 (file)
@@ -249,8 +249,13 @@ static inline u32 cx18_write_sync(struct cx18 *cx, u32 val, void __iomem *addr)
 }
 
 
+static inline
 void cx18_memcpy_fromio(struct cx18 *cx, void *to,
-                       const void __iomem *from, unsigned int len);
+                       const void __iomem *from, unsigned int len)
+{
+       memcpy_fromio(to, from, len);
+}
+
 void cx18_memset_io(struct cx18 *cx, void __iomem *addr, int val, size_t count);
 
 
index 4912b3c8e6a58d9251c2331c24ddc065de499b18..bc36a6b8f775ac08780c2b5c9f3e6f90ad62e30c 100644 (file)
 
 #include "cx18-driver.h"
 #include "cx18-io.h"
-#include "cx18-firmware.h"
-#include "cx18-fileops.h"
-#include "cx18-queue.h"
 #include "cx18-irq.h"
-#include "cx18-ioctl.h"
 #include "cx18-mailbox.h"
-#include "cx18-vbi.h"
 #include "cx18-scb.h"
-#include "cx18-dvb.h"
-
-void cx18_work_handler(struct work_struct *work)
-{
-       struct cx18 *cx = container_of(work, struct cx18, work);
-       if (test_and_clear_bit(CX18_F_I_WORK_HANDLER_DVB, &cx->i_flags))
-               cx18_dvb_work_handler(cx);
-}
-
-static void epu_dma_done(struct cx18 *cx, struct cx18_mailbox *mb, int rpu)
-{
-       u32 handle = mb->args[0];
-       struct cx18_stream *s = NULL;
-       struct cx18_buffer *buf;
-       u32 off;
-       int i;
-       int id;
-
-       for (i = 0; i < CX18_MAX_STREAMS; i++) {
-               s = &cx->streams[i];
-               if ((handle == s->handle) && (s->dvb.enabled))
-                       break;
-               if (s->v4l2dev && handle == s->handle)
-                       break;
-       }
-       if (i == CX18_MAX_STREAMS) {
-               CX18_WARN("Got DMA done notification for unknown/inactive"
-                         " handle %d\n", handle);
-               mb->error = CXERR_NOT_OPEN;
-               mb->cmd = 0;
-               cx18_mb_ack(cx, mb, rpu);
-               return;
-       }
-
-       off = mb->args[1];
-       if (mb->args[2] != 1)
-               CX18_WARN("Ack struct = %d for %s\n",
-                       mb->args[2], s->name);
-       id = cx18_read_enc(cx, off);
-       buf = cx18_queue_get_buf_irq(s, id, cx18_read_enc(cx, off + 4));
-       CX18_DEBUG_HI_DMA("DMA DONE for %s (buffer %d)\n", s->name, id);
-       if (buf) {
-               cx18_buf_sync_for_cpu(s, buf);
-               if (s->type == CX18_ENC_STREAM_TYPE_TS && s->dvb.enabled) {
-                       CX18_DEBUG_HI_DMA("TS recv bytesused = %d\n",
-                                       buf->bytesused);
-
-                       set_bit(CX18_F_I_WORK_HANDLER_DVB, &cx->i_flags);
-                       set_bit(CX18_F_I_HAVE_WORK, &cx->i_flags);
-               } else
-                       set_bit(CX18_F_B_NEED_BUF_SWAP, &buf->b_flags);
-       } else {
-               CX18_WARN("Could not find buf %d for stream %s\n",
-                               cx18_read_enc(cx, off), s->name);
-       }
-       mb->error = 0;
-       mb->cmd = 0;
-       cx18_mb_ack(cx, mb, rpu);
-       wake_up(&cx->dma_waitq);
-       if (s->id != -1)
-               wake_up(&s->waitq);
-}
-
-static void epu_debug(struct cx18 *cx, struct cx18_mailbox *mb, int rpu)
-{
-       char str[256] = { 0 };
-       char *p;
-
-       if (mb->args[1]) {
-               cx18_setup_page(cx, mb->args[1]);
-               cx18_memcpy_fromio(cx, str, cx->enc_mem + mb->args[1], 252);
-               str[252] = 0;
-       }
-       cx18_mb_ack(cx, mb, rpu);
-       CX18_DEBUG_INFO("%x %s\n", mb->args[0], str);
-       p = strchr(str, '.');
-       if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags) && p && p > str)
-               CX18_INFO("FW version: %s\n", p - 1);
-}
-
-static void epu_cmd(struct cx18 *cx, u32 sw1)
-{
-       struct cx18_mailbox mb;
-
-       if (sw1 & IRQ_CPU_TO_EPU) {
-               cx18_memcpy_fromio(cx, &mb, &cx->scb->cpu2epu_mb, sizeof(mb));
-               mb.error = 0;
-
-               switch (mb.cmd) {
-               case CX18_EPU_DMA_DONE:
-                       epu_dma_done(cx, &mb, CPU);
-                       break;
-               case CX18_EPU_DEBUG:
-                       epu_debug(cx, &mb, CPU);
-                       break;
-               default:
-                       CX18_WARN("Unknown CPU_TO_EPU mailbox command %#08x\n",
-                                 mb.cmd);
-                       break;
-               }
-       }
-
-       if (sw1 & IRQ_APU_TO_EPU) {
-               cx18_memcpy_fromio(cx, &mb, &cx->scb->apu2epu_mb, sizeof(mb));
-               CX18_WARN("Unknown APU_TO_EPU mailbox command %#08x\n", mb.cmd);
-       }
-}
 
 static void xpu_ack(struct cx18 *cx, u32 sw2)
 {
@@ -145,6 +33,14 @@ static void xpu_ack(struct cx18 *cx, u32 sw2)
                wake_up(&cx->mb_apu_waitq);
 }
 
+static void epu_cmd(struct cx18 *cx, u32 sw1)
+{
+       if (sw1 & IRQ_CPU_TO_EPU)
+               cx18_api_epu_cmd_irq(cx, CPU);
+       if (sw1 & IRQ_APU_TO_EPU)
+               cx18_api_epu_cmd_irq(cx, APU);
+}
+
 irqreturn_t cx18_irq_handler(int irq, void *dev_id)
 {
        struct cx18 *cx = (struct cx18 *)dev_id;
@@ -170,6 +66,13 @@ irqreturn_t cx18_irq_handler(int irq, void *dev_id)
                CX18_DEBUG_HI_IRQ("received interrupts "
                                  "SW1: %x  SW2: %x  HW2: %x\n", sw1, sw2, hw2);
 
+       /*
+        * SW1 responses have to happen first.  The sending XPU times out the
+        * incoming mailboxes on us rather rapidly.
+        */
+       if (sw1)
+               epu_cmd(cx, sw1);
+
        /* To do: interrupt-based I2C handling
        if (hw2 & (HW2_I2C1_INT|HW2_I2C2_INT)) {
        }
@@ -178,11 +81,5 @@ irqreturn_t cx18_irq_handler(int irq, void *dev_id)
        if (sw2)
                xpu_ack(cx, sw2);
 
-       if (sw1)
-               epu_cmd(cx, sw1);
-
-       if (test_and_clear_bit(CX18_F_I_HAVE_WORK, &cx->i_flags))
-               schedule_work(&cx->work);
-
        return (sw1 || sw2 || hw2) ? IRQ_HANDLED : IRQ_NONE;
 }
index 6f3ec89637626fd9c93c3d96f14fd721d304a160..cd8f548b7ee818370e3be26921a43a674f21c2fc 100644 (file)
@@ -32,5 +32,3 @@
 #define SW2_INT_ENABLE_PCI              0xc7315c
 
 irqreturn_t cx18_irq_handler(int irq, void *dev_id);
-
-void cx18_work_handler(struct work_struct *work);
index 35f7188d4d3bccdaeefd53b6bd9866b670194a0f..3d210d2ffdad540210f943def8ad5e509c2cfdc9 100644 (file)
 #include "cx18-scb.h"
 #include "cx18-irq.h"
 #include "cx18-mailbox.h"
+#include "cx18-queue.h"
+#include "cx18-streams.h"
+
+static const char *rpu_str[] = { "APU", "CPU", "EPU", "HPU" };
 
 #define API_FAST (1 << 2) /* Short timeout */
 #define API_SLOW (1 << 3) /* Additional 300ms timeout */
@@ -92,12 +96,149 @@ static const struct cx18_api_info *find_api_info(u32 cmd)
        return NULL;
 }
 
-long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb, int rpu)
+static void dump_mb(struct cx18 *cx, struct cx18_mailbox *mb, char *name)
+{
+       char argstr[MAX_MB_ARGUMENTS*11+1];
+       char *p;
+       int i;
+
+       if (!(cx18_debug & CX18_DBGFLG_API))
+               return;
+
+       for (i = 0, p = argstr; i < MAX_MB_ARGUMENTS; i++, p += 11) {
+               /* kernel snprintf() appends '\0' always */
+               snprintf(p, 12, " %#010x", mb->args[i]);
+       }
+       CX18_DEBUG_API("%s: req %#010x ack %#010x cmd %#010x err %#010x args%s"
+               "\n", name, mb->request, mb->ack, mb->cmd, mb->error, argstr);
+}
+
+
+/*
+ * Functions that run in a work_queue work handling context
+ */
+
+static void epu_dma_done(struct cx18 *cx, struct cx18_epu_work_order *order)
+{
+       u32 handle, mdl_ack_count;
+       struct cx18_mailbox *mb;
+       struct cx18_mdl_ack *mdl_ack;
+       struct cx18_stream *s;
+       struct cx18_buffer *buf;
+       int i;
+
+       mb = &order->mb;
+       handle = mb->args[0];
+       s = cx18_handle_to_stream(cx, handle);
+
+       if (s == NULL) {
+               CX18_WARN("Got DMA done notification for unknown/inactive"
+                         " handle %d\n", handle);
+               return;
+       }
+
+       mdl_ack_count = mb->args[2];
+       mdl_ack = order->mdl_ack;
+       for (i = 0; i < mdl_ack_count; i++, mdl_ack++) {
+               buf = cx18_queue_get_buf(s, mdl_ack->id, mdl_ack->data_used);
+               CX18_DEBUG_HI_DMA("DMA DONE for %s (buffer %d)\n", s->name,
+                                 mdl_ack->id);
+               if (buf == NULL) {
+                       CX18_WARN("Could not find buf %d for stream %s\n",
+                                 mdl_ack->id, s->name);
+                       continue;
+               }
+
+               cx18_buf_sync_for_cpu(s, buf);
+               if (s->type == CX18_ENC_STREAM_TYPE_TS && s->dvb.enabled) {
+                       CX18_DEBUG_HI_DMA("TS recv bytesused = %d\n",
+                                         buf->bytesused);
+
+                       dvb_dmx_swfilter(&s->dvb.demux, buf->buf,
+                                        buf->bytesused);
+
+                       cx18_buf_sync_for_device(s, buf);
+
+                       if (s->handle != CX18_INVALID_TASK_HANDLE &&
+                           test_bit(CX18_F_S_STREAMING, &s->s_flags))
+                               cx18_vapi(cx,
+                                      CX18_CPU_DE_SET_MDL, 5, s->handle,
+                                      (void __iomem *)
+                                      &cx->scb->cpu_mdl[buf->id] - cx->enc_mem,
+                                      1, buf->id, s->buf_size);
+               } else
+                       set_bit(CX18_F_B_NEED_BUF_SWAP, &buf->b_flags);
+       }
+       wake_up(&cx->dma_waitq);
+       if (s->id != -1)
+               wake_up(&s->waitq);
+}
+
+static void epu_debug(struct cx18 *cx, struct cx18_epu_work_order *order)
+{
+       char *p;
+       char *str = order->str;
+
+       CX18_DEBUG_INFO("%x %s\n", order->mb.args[0], str);
+       p = strchr(str, '.');
+       if (!test_bit(CX18_F_I_LOADED_FW, &cx->i_flags) && p && p > str)
+               CX18_INFO("FW version: %s\n", p - 1);
+}
+
+static void epu_cmd(struct cx18 *cx, struct cx18_epu_work_order *order)
+{
+       switch (order->rpu) {
+       case CPU:
+       {
+               switch (order->mb.cmd) {
+               case CX18_EPU_DMA_DONE:
+                       epu_dma_done(cx, order);
+                       break;
+               case CX18_EPU_DEBUG:
+                       epu_debug(cx, order);
+                       break;
+               default:
+                       CX18_WARN("Unknown CPU to EPU mailbox command %#0x\n",
+                                 order->mb.cmd);
+                       break;
+               }
+               break;
+       }
+       case APU:
+               CX18_WARN("Unknown APU to EPU mailbox command %#0x\n",
+                         order->mb.cmd);
+               break;
+       default:
+               break;
+       }
+}
+
+static
+void free_epu_work_order(struct cx18 *cx, struct cx18_epu_work_order *order)
+{
+       atomic_set(&order->pending, 0);
+}
+
+void cx18_epu_work_handler(struct work_struct *work)
+{
+       struct cx18_epu_work_order *order =
+                       container_of(work, struct cx18_epu_work_order, work);
+       struct cx18 *cx = order->cx;
+       epu_cmd(cx, order);
+       free_epu_work_order(cx, order);
+}
+
+
+/*
+ * Functions that run in an interrupt handling context
+ */
+
+static void mb_ack_irq(struct cx18 *cx, const struct cx18_epu_work_order *order)
 {
        struct cx18_mailbox __iomem *ack_mb;
-       u32 ack_irq;
+       u32 ack_irq, req;
 
-       switch (rpu) {
+       switch (order->rpu) {
        case APU:
                ack_irq = IRQ_EPU_TO_APU_ACK;
                ack_mb = &cx->scb->apu2epu_mb;
@@ -108,16 +249,175 @@ long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb, int rpu)
                break;
        default:
                CX18_WARN("Unhandled RPU (%d) for command %x ack\n",
-                         rpu, mb->cmd);
-               return -EINVAL;
+                         order->rpu, order->mb.cmd);
+               return;
        }
 
-       cx18_setup_page(cx, SCB_OFFSET);
-       cx18_write_sync(cx, mb->request, &ack_mb->ack);
+       req = order->mb.request;
+       /* Don't ack if the RPU has gotten impatient and timed us out */
+       if (req != cx18_readl(cx, &ack_mb->request) ||
+           req == cx18_readl(cx, &ack_mb->ack))
+               return;
+       cx18_writel(cx, req, &ack_mb->ack);
        cx18_write_reg_expect(cx, ack_irq, SW2_INT_SET, ack_irq, ack_irq);
-       return 0;
+       return;
+}
+
+static int epu_dma_done_irq(struct cx18 *cx, struct cx18_epu_work_order *order,
+                           int stale)
+{
+       u32 handle, mdl_ack_offset, mdl_ack_count;
+       struct cx18_mailbox *mb;
+
+       mb = &order->mb;
+       handle = mb->args[0];
+       mdl_ack_offset = mb->args[1];
+       mdl_ack_count = mb->args[2];
+
+       if (handle == CX18_INVALID_TASK_HANDLE ||
+           mdl_ack_count == 0 || mdl_ack_count > CX18_MAX_MDL_ACKS) {
+               if (!stale)
+                       mb_ack_irq(cx, order);
+               return -1;
+       }
+
+       cx18_memcpy_fromio(cx, order->mdl_ack, cx->enc_mem + mdl_ack_offset,
+                          sizeof(struct cx18_mdl_ack) * mdl_ack_count);
+       if (!stale)
+               mb_ack_irq(cx, order);
+       return 1;
+}
+
+static
+int epu_debug_irq(struct cx18 *cx, struct cx18_epu_work_order *order, int stale)
+{
+       u32 str_offset;
+       char *str = order->str;
+
+       str[0] = '\0';
+       str_offset = order->mb.args[1];
+       if (str_offset) {
+               cx18_setup_page(cx, str_offset);
+               cx18_memcpy_fromio(cx, str, cx->enc_mem + str_offset, 252);
+               str[252] = '\0';
+               cx18_setup_page(cx, SCB_OFFSET);
+       }
+
+       if (!stale)
+               mb_ack_irq(cx, order);
+
+       return str_offset ? 1 : 0;
+}
+
+static inline
+int epu_cmd_irq(struct cx18 *cx, struct cx18_epu_work_order *order, int stale)
+{
+       int ret = -1;
+
+       switch (order->rpu) {
+       case CPU:
+       {
+               switch (order->mb.cmd) {
+               case CX18_EPU_DMA_DONE:
+                       ret = epu_dma_done_irq(cx, order, stale);
+                       break;
+               case CX18_EPU_DEBUG:
+                       ret = epu_debug_irq(cx, order, stale);
+                       break;
+               default:
+                       CX18_WARN("Unknown CPU to EPU mailbox command %#0x\n",
+                                 order->mb.cmd);
+                       break;
+               }
+               break;
+       }
+       case APU:
+               CX18_WARN("Unknown APU to EPU mailbox command %#0x\n",
+                         order->mb.cmd);
+               break;
+       default:
+               break;
+       }
+       return ret;
+}
+
+static inline
+struct cx18_epu_work_order *alloc_epu_work_order_irq(struct cx18 *cx)
+{
+       int i;
+       struct cx18_epu_work_order *order = NULL;
+
+       for (i = 0; i < CX18_MAX_EPU_WORK_ORDERS; i++) {
+               /*
+                * We only need "pending" atomic to inspect its contents,
+                * and need not do a check and set because:
+                * 1. Any work handler thread only clears "pending" and only
+                * on one, particular work order at a time, per handler thread.
+                * 2. "pending" is only set here, and we're serialized because
+                * we're called in an IRQ handler context.
+                */
+               if (atomic_read(&cx->epu_work_order[i].pending) == 0) {
+                       order = &cx->epu_work_order[i];
+                       atomic_set(&order->pending, 1);
+                       break;
+               }
+       }
+       return order;
 }
 
+void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu)
+{
+       struct cx18_mailbox __iomem *mb;
+       struct cx18_mailbox *order_mb;
+       struct cx18_epu_work_order *order;
+       int stale = 0;
+       int submit;
+
+       switch (rpu) {
+       case CPU:
+               mb = &cx->scb->cpu2epu_mb;
+               break;
+       case APU:
+               mb = &cx->scb->apu2epu_mb;
+               break;
+       default:
+               return;
+       }
+
+       order = alloc_epu_work_order_irq(cx);
+       if (order == NULL) {
+               CX18_WARN("Unable to find blank work order form to schedule "
+                         "incoming mailbox command processing\n");
+               return;
+       }
+
+       order->rpu = rpu;
+       order_mb = &order->mb;
+       cx18_memcpy_fromio(cx, order_mb, mb, sizeof(struct cx18_mailbox));
+
+       if (order_mb->request == order_mb->ack) {
+               CX18_WARN("Possibly falling behind: %s self-ack'ed our incoming"
+                         " %s to EPU mailbox (sequence no. %u)\n",
+                         rpu_str[rpu], rpu_str[rpu], order_mb->request);
+               dump_mb(cx, order_mb, "incoming");
+               stale = 1;
+       }
+
+       /*
+        * Individual EPU command processing is responsible for ack-ing
+        * a non-stale mailbox as soon as possible
+        */
+       submit = epu_cmd_irq(cx, order, stale);
+       if (submit > 0) {
+               schedule_work(&order->work);
+       }
+}
+
+
+/*
+ * Functions called from a non-interrupt, non work_queue context
+ */
+
 static void cx18_api_log_ack_delay(struct cx18 *cx, int msecs)
 {
        if (msecs > CX18_MAX_MB_ACK_DELAY)
@@ -167,8 +467,6 @@ static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[])
        }
 
        mutex_lock(mb_lock);
-       cx18_setup_page(cx, SCB_OFFSET);
-
        /*
         * Wait for an in-use mailbox to complete
         *
index 54758f32db1ec2ea9acc382e2412e528afb27fd7..002c0526afa0fe4eaed27b64206dce7dfda6c2f8 100644 (file)
 
 struct cx18;
 
+/*
+ * This structure is used by CPU to provide completed buffers information
+ * Its structure is dictrated by the layout of the SCB, required by the
+ * firmware, but its defintion needs to be here, instead of in cx18-scb.h,
+ * for mailbox work order scheduling
+ */
+struct cx18_mdl_ack {
+    u32 id;        /* ID of a completed MDL */
+    u32 data_used; /* Total data filled in the MDL for buffer 'id' */
+};
+
 /* The cx18_mailbox struct is the mailbox structure which is used for passing
    messages between processors */
 struct cx18_mailbox {
@@ -73,6 +84,9 @@ int cx18_vapi_result(struct cx18 *cx, u32 data[MAX_MB_ARGUMENTS], u32 cmd,
 int cx18_vapi(struct cx18 *cx, u32 cmd, int args, ...);
 int cx18_api_func(void *priv, u32 cmd, int in, int out,
                u32 data[CX2341X_MBOX_MAX_DATA]);
-long cx18_mb_ack(struct cx18 *cx, const struct cx18_mailbox *mb, int rpu);
+
+void cx18_api_epu_cmd_irq(struct cx18 *cx, int rpu);
+
+void cx18_epu_work_handler(struct work_struct *work);
 
 #endif
index 174682c2582f4ec1d3d48900f7ae15723d9e6234..c5a81aed61bbb51dcc489cc48d069757ab6cd5d4 100644 (file)
@@ -75,30 +75,37 @@ struct cx18_buffer *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q)
        return buf;
 }
 
-struct cx18_buffer *cx18_queue_get_buf_irq(struct cx18_stream *s, u32 id,
+struct cx18_buffer *cx18_queue_get_buf(struct cx18_stream *s, u32 id,
        u32 bytesused)
 {
        struct cx18 *cx = s->cx;
        struct list_head *p;
+       unsigned long flags = 0;
 
-       spin_lock(&s->qlock);
+       spin_lock_irqsave(&s->qlock, flags);
        list_for_each(p, &s->q_free.list) {
                struct cx18_buffer *buf =
                        list_entry(p, struct cx18_buffer, list);
 
-               if (buf->id != id)
+               if (buf->id != id) {
+                       CX18_DEBUG_HI_DMA("Skipping buffer %d searching for %d "
+                                         "in stream %s q_free\n", buf->id, id,
+                                         s->name);
                        continue;
+               }
 
                buf->bytesused = bytesused;
-               atomic_dec(&s->q_free.buffers);
-               atomic_inc(&s->q_full.buffers);
-               s->q_full.bytesused += buf->bytesused;
-               list_move_tail(&buf->list, &s->q_full.list);
+               if (s->type != CX18_ENC_STREAM_TYPE_TS) {
+                       atomic_dec(&s->q_free.buffers);
+                       atomic_inc(&s->q_full.buffers);
+                       s->q_full.bytesused += buf->bytesused;
+                       list_move_tail(&buf->list, &s->q_full.list);
+               }
 
-               spin_unlock(&s->qlock);
+               spin_unlock_irqrestore(&s->qlock, flags);
                return buf;
        }
-       spin_unlock(&s->qlock);
+       spin_unlock_irqrestore(&s->qlock, flags);
        CX18_ERR("Cannot find buffer %d for stream %s\n", id, s->name);
        return NULL;
 }
index 7f93bb13c09f6b53de8848451475e00c5ea6b8c1..92edb5e3a627c79fb4aece80cbebce319afe47ee 100644 (file)
@@ -46,7 +46,7 @@ void cx18_queue_init(struct cx18_queue *q);
 void cx18_enqueue(struct cx18_stream *s, struct cx18_buffer *buf,
        struct cx18_queue *q);
 struct cx18_buffer *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q);
-struct cx18_buffer *cx18_queue_get_buf_irq(struct cx18_stream *s, u32 id,
+struct cx18_buffer *cx18_queue_get_buf(struct cx18_stream *s, u32 id,
        u32 bytesused);
 void cx18_flush_queues(struct cx18_stream *s);
 
index 594713bbed6892eb214cf16e867349e60f52b3f8..29866f02f0187292568df3438ffeefbcb5850610 100644 (file)
@@ -85,12 +85,6 @@ struct cx18_mdl {
     u32 length; /* Length of the buffer segment */
 };
 
-/* This structure is used by CPU to provide completed buffers information */
-struct cx18_mdl_ack {
-    u32 id;        /* ID of a completed MDL */
-    u32 data_used; /* Total data filled in the MDL for buffer 'id' */
-};
-
 struct cx18_scb {
        /* These fields form the System Control Block which is used at boot time
           for localizing the IPC data as well as the code positions for all
@@ -276,7 +270,7 @@ struct cx18_scb {
        struct cx18_mailbox  hpu2epu_mb;
        struct cx18_mailbox  ppu2epu_mb;
 
-       struct cx18_mdl_ack  cpu_mdl_ack[CX18_MAX_STREAMS][2];
+       struct cx18_mdl_ack  cpu_mdl_ack[CX18_MAX_STREAMS][CX18_MAX_MDL_ACKS];
        struct cx18_mdl      cpu_mdl[1];
 };
 
index c7d431f4923804ff1144ff79fef4b40d7ebdf48d..e6d808f7cc8f830c5f48979a08088bc78ad537cc 100644 (file)
@@ -595,3 +595,21 @@ u32 cx18_find_handle(struct cx18 *cx)
        }
        return CX18_INVALID_TASK_HANDLE;
 }
+
+struct cx18_stream *cx18_handle_to_stream(struct cx18 *cx, u32 handle)
+{
+       int i;
+       struct cx18_stream *s;
+
+       if (handle == CX18_INVALID_TASK_HANDLE)
+               return NULL;
+
+       for (i = 0; i < CX18_MAX_STREAMS; i++) {
+               s = &cx->streams[i];
+               if (s->handle != handle)
+                       continue;
+               if (s->v4l2dev || s->dvb.enabled)
+                       return s;
+       }
+       return NULL;
+}
index f327e947b24f069a9791e6b8e47e82156a1e34de..3fd578d0e6cea34992e57a7cbdfedb0fc6fd7934 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 u32 cx18_find_handle(struct cx18 *cx);
+struct cx18_stream *cx18_handle_to_stream(struct cx18 *cx, u32 handle);
 int cx18_streams_setup(struct cx18 *cx);
 int cx18_streams_register(struct cx18 *cx);
 void cx18_streams_cleanup(struct cx18 *cx, int unregister);