Re: [PULL] http://mercurial.intuxication.org/hg/v4l-dvb-commits

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sat, 28 Feb 2009, Andy Walls wrote:
> On Fri, 2009-02-27 at 21:05 +0200, Igor M. Liplianin wrote:
> > On 27 февраля 2009, "Igor M. Liplianin" <liplianin@xxxxxx> wrote:
> > > On Fri, 27 Feb 2009, Igor M. Liplianin wrote:
> > > > 01/02: dm1105: not demuxing from interrupt context.
> > > > http://mercurial.intuxication.org/hg/v4l-dvb-commits?cmd=changeset;node=6
> > > >faf0753950b
> > >
> > > I'm not sure if you considered this, but the default work queue is
> > > multi-threaded with a kernel thread for each CPU.  This means that if the
> > > IRQ handler calls schedule_work() while your work function is running then
> > > you can get a second copy running of your work function running at the same
> > > time.  It doesn't look like your work function uses atomit_t or locking, so
> > > I'm guessing it's not safe to run concurrently.
> > >
> > > For the pluto2 patch, I created a single threaded work queue.  This avoids
> > > the concurrency problem and it's not like the demuxer can run in parallel
> > > anyway.  Having your own work queue also means that you don't have to worry
> > > about latency from whatever other tasks might be in the default work queue.
> > >
> > > Also consider that the work function is queued mutliple times before it
> > > runs, it will only run once.  I.e.  queuing a work func that's already in
> > > the queue does nothing (one the function starts to run, it's no longer in
> > > the queue and can be added again before it's finished).  The pluto2 patch I
> > > posted didn't take this into account, but I've since fixed it.
> >
> > I'll return to this :)
> > But it complicates things more and more :(
>
> Yes it does complicate things.  Here are some excerpts from what I had
> to do for cx18.  Perhaps it will help you.  (Or perhaps it will not help
> at all.  The CX23418 chip put alot of requirements on me that drove my
> solution.)

Here's the latest patch for pluto2.  It's a much simpler chip than cx18.
I've used atomic operations to design a lockless system.

If the driver runs out of DMA buffers it will set a "stalled" flag and not
re-enable DMA by acking the IRQ.  When the work function has run and freed
up some buffers, it will clear the stalled flag and re-enable DMA.  The
device's internal buffer will have to do while the driver is stalled to
avoid an overrun.

diff -r 960985ba30c6 -r 92fadfbb8880 linux/drivers/media/dvb/pluto2/pluto2.c
--- a/linux/drivers/media/dvb/pluto2/pluto2.c	Tue Feb 10 21:31:59 2009 +0100
+++ b/linux/drivers/media/dvb/pluto2/pluto2.c	Fri Feb 27 11:10:56 2009 -0800
@@ -23,6 +23,25 @@
  *
  */

+/*
+ * Internal Buffer:
+ * The device has an internal 4kB buffer than can hold up to 21 TS packets.  If
+ * MSKO is cleared, the device will set OVR and generate an irq if the internal
+ * buffer overflows.
+ *
+ * DMA:
+ * Data will be DMAed to the address in PCAR.  The register will auto-increment
+ * after each transfer and should not be written to during DMA.  NBPACKETS
+ * stores the number of packets transferred during the last DMA operation.  If
+ * DEM is cleared an irq will be generated after DMA and DE (and NBPACKETS)
+ * will be set.  A new DMA transfer will not start until IACK is set, so this
+ * is the time to set PCAR if needed.
+ *
+ * The AFULL "almost full" function is not supported.  ADEF * 2 is the almost
+ * full trigger point.  Maybe this is also the point at which a DMA operation
+ * is triggered?
+ */
+
 #include <linux/i2c.h>
 #include <linux/i2c-algo-bit.h>
 #include <linux/init.h>
@@ -87,13 +106,28 @@ DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr
 #define SLCS_SWC		(0x0001 <<  0)

 #define TS_DMA_PACKETS		(8)
-#define TS_DMA_BYTES		(188 * TS_DMA_PACKETS)
+#define TS_DMA_BYTES		(4096)
+#define TS_DMA_BUFFERS		(2)

 #define I2C_ADDR_TDA10046	0x10
 #define I2C_ADDR_TUA6034	0xc2
 #define NHWFILTERS		8

 struct pluto {
+	/* DMA buffer, at start so it's page aligned for streaming DMA.  Each
+	 * buf gets its own page, as streaming DMA operates on page
+	 * granularity.  We only need 4k (which is usually what page size is)
+	 * to hold the maximum DMA size of 21 packets.  */
+	u8 dma_buf[TS_DMA_BUFFERS][PAGE_SIZE];
+
+	dma_addr_t dma_addr[TS_DMA_BUFFERS];
+	atomic_t npackets[TS_DMA_BUFFERS];
+	atomic_t dma_buf_num, dmx_buf_num;
+	unsigned long stalled;
+	struct work_struct work;
+	struct workqueue_struct *dmaq;
+	char wqname[16];
+
 	/* pci */
 	struct pci_dev *pdev;
 	u8 __iomem *io_mem;
@@ -116,11 +150,6 @@ struct pluto {

 	/* irq */
 	unsigned int overflow;
-
-	/* dma */
-	dma_addr_t dma_addr;
-	u8 dma_buf[TS_DMA_BYTES];
-	u8 dummy[4096];
 };

 static inline struct pluto *feed_to_pluto(struct dvb_demux_feed *feed)
@@ -232,26 +261,36 @@ static void pluto_reset_ts(struct pluto
 	}
 }

-static void pluto_set_dma_addr(struct pluto *pluto)
-{
-	pluto_writereg(pluto, REG_PCAR, pluto->dma_addr);
+static void pluto_set_dma_addr(struct pluto *pluto, unsigned int num)
+{
+	pluto_writereg(pluto, REG_PCAR, pluto->dma_addr[num]);
 }

 static int __devinit pluto_dma_map(struct pluto *pluto)
 {
-	pluto->dma_addr = pci_map_single(pluto->pdev, pluto->dma_buf,
+	int r;
+
+	pluto->dma_addr[0] = pci_map_single(pluto->pdev, pluto->dma_buf[0],
 			TS_DMA_BYTES, PCI_DMA_FROMDEVICE);
-
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
-	return pci_dma_mapping_error(pluto->dma_addr);
-#else
-	return pci_dma_mapping_error(pluto->pdev, pluto->dma_addr);
-#endif
+	r = pci_dma_mapping_error(pluto->pdev, pluto->dma_addr[0]);
+	if (r)
+		return r;
+
+	pluto->dma_addr[1] = pci_map_single(pluto->pdev, pluto->dma_buf[1],
+			TS_DMA_BYTES, PCI_DMA_FROMDEVICE);
+	r = pci_dma_mapping_error(pluto->pdev, pluto->dma_addr[1]);
+	if (r)
+		pci_unmap_single(pluto->pdev, pluto->dma_addr[0],
+				 TS_DMA_BYTES, PCI_DMA_FROMDEVICE);
+
+	return r;
 }

 static void pluto_dma_unmap(struct pluto *pluto)
 {
-	pci_unmap_single(pluto->pdev, pluto->dma_addr,
+	pci_unmap_single(pluto->pdev, pluto->dma_addr[0],
+			TS_DMA_BYTES, PCI_DMA_FROMDEVICE);
+	pci_unmap_single(pluto->pdev, pluto->dma_addr[1],
 			TS_DMA_BYTES, PCI_DMA_FROMDEVICE);
 }

@@ -287,11 +326,18 @@ static int pluto_stop_feed(struct dvb_de
 	return 0;
 }

-static void pluto_dma_end(struct pluto *pluto, unsigned int nbpackets)
-{
-	/* synchronize the DMA transfer with the CPU
-	 * first so that we see updated contents. */
-	pci_dma_sync_single_for_cpu(pluto->pdev, pluto->dma_addr,
+static int pluto_dma_end(struct pluto *pluto, unsigned int nbpackets)
+{
+	unsigned int bnum = atomic_read(&pluto->dma_buf_num) % TS_DMA_BUFFERS;
+	unsigned int newbnum;
+	int ret = 1;
+
+	if (test_bit(0, &pluto->stalled))
+		dev_err(&pluto->pdev->dev, "DMA while driver stalled?\n");
+
+	/* synchronize the DMA transfer with the CPU first so that we see
+	 * updated contents.  */
+	pci_dma_sync_single_for_cpu(pluto->pdev, pluto->dma_addr[bnum],
 			TS_DMA_BYTES, PCI_DMA_FROMDEVICE);

 	/* Workaround for broken hardware:
@@ -305,28 +351,105 @@ static void pluto_dma_end(struct pluto *
 	 *     has been transfered. Only a reset seems to solve this
 	 */
 	if ((nbpackets == 0) || (nbpackets > TS_DMA_PACKETS)) {
-		unsigned int i = 0;
-		while (pluto->dma_buf[i] == 0x47)
-			i += 188;
-		nbpackets = i / 188;
-		if (i == 0) {
+		unsigned int i;
+
+		for (i = 0, nbpackets = 0;
+		     i < sizeof(pluto->dma_buf[bnum]) && pluto->dma_buf[bnum][i] == 0x47;
+		     i += 188, nbpackets++)
+			;
+
+		if (nbpackets == 0) {
 			pluto_reset_ts(pluto, 1);
-			dev_printk(KERN_DEBUG, &pluto->pdev->dev, "resetting TS because of invalid packet counter\n");
+			dev_dbg(&pluto->pdev->dev,
+				"resetting TS because of invalid packet counter\n");
+
+			/* Just re-use old buffer */
+			pci_dma_sync_single_for_device(pluto->pdev,
+						       pluto->dma_addr[bnum],
+						       TS_DMA_BYTES,
+						       PCI_DMA_FROMDEVICE);
+			pluto_set_dma_addr(pluto, bnum);
+			return 1;
 		}
 	}

-	dvb_dmx_swfilter_packets(&pluto->demux, pluto->dma_buf, nbpackets);
-
-	/* clear the dma buffer. this is needed to be able to identify
-	 * new valid ts packets above */
-	memset(pluto->dma_buf, 0, nbpackets * 188);
+	/* Mark buffer as used */
+	atomic_set(&pluto->npackets[bnum], nbpackets);
+
+	/* Switch to the next buffer */
+	newbnum = atomic_inc_return(&pluto->dma_buf_num) % TS_DMA_BUFFERS;

 	/* reset the dma address */
-	pluto_set_dma_addr(pluto);
-
-	/* sync the buffer and give it back to the card */
-	pci_dma_sync_single_for_device(pluto->pdev, pluto->dma_addr,
-			TS_DMA_BYTES, PCI_DMA_FROMDEVICE);
+	pluto_set_dma_addr(pluto, newbnum);
+
+	if (atomic_read(&pluto->npackets[newbnum])) {
+		dev_dbg(&pluto->pdev->dev, "buffer not ready\n");
+		/* Buffer isn't empty.  We want to hold off setting IACK until
+		 * it is, so that it won't get DMAed into.  */
+		ret = 0;
+
+		/* Setting stalled will tell the demux work function that it
+		 * needs to do the IACK when it has freed up a buffer.  */
+		set_bit(0, &pluto->stalled);
+		mb();
+	}
+
+	/* queue demuxer work function to processes buffer(s) */
+	queue_work(pluto->dmaq, &pluto->work);
+
+	return ret;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+static void pluto_dmx_buffers(void *_pluto)
+#else
+static void pluto_dmx_buffers(struct work_struct *work)
+#endif
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	struct pluto *pluto = _pluto;
+#else
+	struct pluto *pluto = container_of(work, struct pluto, work);
+#endif
+
+	/* If this function is queued multiple times by multiple IRQs before it
+	 * runs, it will still only be run once.  So we must process all buffers
+	 * available and not just one.  It also possible that it will get queued
+	 * again while it is already running, in which it will run again but
+	 * there will be no buffers to process, but we still must check if the
+	 * driver is stalled.  */
+	while (atomic_read(&pluto->dmx_buf_num) !=
+	       atomic_read(&pluto->dma_buf_num)) {
+		unsigned int bnum = atomic_read(&pluto->dmx_buf_num) % TS_DMA_BUFFERS;
+		unsigned int npackets = atomic_read(&pluto->npackets[bnum]);
+		unsigned int i;
+
+		atomic_inc(&pluto->dmx_buf_num);
+
+		if (!npackets) {
+			dev_warn(&pluto->pdev->dev,
+				 "Empty buffer queued to demuxer?\n");
+			continue;
+		}
+
+		dvb_dmx_swfilter_packets(&pluto->demux, pluto->dma_buf[bnum],
+					 npackets);
+
+		/* Clear the MPEG start codes in the DMA buffer.  This is
+		 * needed to be able to identify new valid TS packets.  */
+		for (i = 0; npackets; npackets--, i += 188)
+			pluto->dma_buf[bnum][i] = 0;
+
+		/* Give buffer back to device */
+		pci_dma_sync_single_for_device(pluto->pdev, pluto->dma_addr[bnum],
+					       TS_DMA_BYTES, PCI_DMA_FROMDEVICE);
+
+		/* Mark buffer as empty */
+		atomic_set(&pluto->npackets[bnum], 0);
+	}
+	/* Need IACK to enable DMA again? */
+	if (test_and_clear_bit(0, &pluto->stalled))
+		pluto_write_tscr(pluto, TSCR_IACK);
 }

 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
@@ -337,6 +460,7 @@ static irqreturn_t pluto_irq(int irq, vo
 {
 	struct pluto *pluto = dev_id;
 	u32 tscr;
+	int ack = 1;

 	/* check whether an interrupt occured on this device */
 	tscr = pluto_readreg(pluto, REG_TSCR);
@@ -349,24 +473,24 @@ static irqreturn_t pluto_irq(int irq, vo
 		return IRQ_HANDLED;
 	}

+	/* overflow interrupt */
+	if (tscr & TSCR_OVR)
+		pluto->overflow++;
+
 	/* dma end interrupt */
 	if (tscr & TSCR_DE) {
-		pluto_dma_end(pluto, (tscr & TSCR_NBPACKETS) >> 24);
-		/* overflow interrupt */
-		if (tscr & TSCR_OVR)
-			pluto->overflow++;
+		ack = pluto_dma_end(pluto, (tscr & TSCR_NBPACKETS) >> 24);
 		if (pluto->overflow) {
 			dev_err(&pluto->pdev->dev, "overflow irq (%d)\n",
 					pluto->overflow);
 			pluto_reset_ts(pluto, 1);
 			pluto->overflow = 0;
 		}
-	} else if (tscr & TSCR_OVR) {
-		pluto->overflow++;
 	}

 	/* ACK the interrupt */
-	pluto_write_tscr(pluto, tscr | TSCR_IACK);
+	if (ack)
+		pluto_write_tscr(pluto, tscr | TSCR_IACK);

 	return IRQ_HANDLED;
 }
@@ -412,7 +536,7 @@ static int __devinit pluto_hw_init(struc
 #endif
 	/* map DMA and set address */
 	pluto_dma_map(pluto);
-	pluto_set_dma_addr(pluto);
+	pluto_set_dma_addr(pluto, 0);

 	/* enable interrupts */
 	pluto_enable_irqs(pluto);
@@ -610,7 +734,7 @@ static int __devinit pluto2_probe(struct

 	pluto = kzalloc(sizeof(struct pluto), GFP_KERNEL);
 	if (!pluto)
-		goto out;
+		return -ENOMEM;

 	pluto->pdev = pdev;

@@ -639,13 +763,9 @@ static int __devinit pluto2_probe(struct

 	pci_set_drvdata(pdev, pluto);

-	ret = request_irq(pdev->irq, pluto_irq, IRQF_SHARED, DRIVER_NAME, pluto);
+	ret = pluto_hw_init(pluto);
 	if (ret < 0)
 		goto err_pci_iounmap;
-
-	ret = pluto_hw_init(pluto);
-	if (ret < 0)
-		goto err_free_irq;

 	/* i2c */
 	i2c_set_adapdata(&pluto->i2c_adap, pluto);
@@ -721,9 +841,27 @@ static int __devinit pluto2_probe(struct
 		goto err_disconnect_frontend;

 	dvb_net_init(dvb_adapter, &pluto->dvbnet, dmx);
-out:
-	return ret;
-
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	INIT_WORK(&pluto->work, pluto_dmx_buffers, pluto);
+#else
+	INIT_WORK(&pluto->work, pluto_dmx_buffers);
+#endif
+	sprintf(pluto->wqname, "%s[%d] dvb", dvb_adapter->name, dvb_adapter->num);
+	pluto->dmaq = create_singlethread_workqueue(pluto->wqname);
+	if (!pluto->dmaq)
+		goto err_dvb_net;
+
+	ret = request_irq(pdev->irq, pluto_irq, IRQF_SHARED, DRIVER_NAME, pluto);
+	if (ret < 0)
+		goto err_workqueue;
+
+	return 0;
+
+err_workqueue:
+	destroy_workqueue(pluto->dmaq);
+err_dvb_net:
+	dvb_net_release(&pluto->dvbnet);
 err_disconnect_frontend:
 	dmx->disconnect_frontend(dmx);
 err_remove_mem_frontend:
@@ -740,8 +878,6 @@ err_i2c_del_adapter:
 	i2c_del_adapter(&pluto->i2c_adap);
 err_pluto_hw_exit:
 	pluto_hw_exit(pluto);
-err_free_irq:
-	free_irq(pdev->irq, pluto);
 err_pci_iounmap:
 	pci_iounmap(pdev, pluto->io_mem);
 err_pci_release_regions:
@@ -751,7 +887,7 @@ err_kfree:
 err_kfree:
 	pci_set_drvdata(pdev, NULL);
 	kfree(pluto);
-	goto out;
+	return ret;
 }

 static void __devexit pluto2_remove(struct pci_dev *pdev)
diff -r 960985ba30c6 -r 92fadfbb8880 v4l/compat.h
--- a/v4l/compat.h	Tue Feb 10 21:31:59 2009 +0100
+++ b/v4l/compat.h	Fri Feb 27 11:10:56 2009 -0800
@@ -421,4 +421,8 @@ static inline int usb_endpoint_type(cons
 	} while (0)
 #endif

-#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27)
+#define pci_dma_mapping_error(dev, addr)	pci_dma_mapping_error(addr)
+#endif
+
+#endif
--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux