[PATCH] dmabounce: robustness on dma_unmap_single when irq disabled

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

I had a problem with dmabounce, I got repetitive WARN_ON(irqs_disabled()) from dma_free_coherent calls.
My platform is IXP420 (NSLU2), with a em28xx USB based DVB-T tuner plugged on.
The problem occurs when the USB stream begins.
I upgraded to a 2.6.22 kernel (debian sid) and got a stack trace (dumped when the WARN_ON was reached).
Here is a stack trace I obtained:
WARNING: at arch/arm/mm/consistent.c:364 dma_free_coherent()
[<c0025d8c>] (dump_stack+0x0/0x14) from [<c0027034>] (dma_free_coherent+0x38/0x200)
[<c0026ffc>] (dma_free_coherent+0x0/0x200) from [<c002b5bc>] (dma_unmap_single+0x144/0x164)
[<c002b478>] (dma_unmap_single+0x0/0x164) from [<bf003b9c>] (usb_hcd_giveback_urb+0xb0/0x108 [usbcore])
r7:c08633e0 r6:c1190800 r5:c08633e0 r4:00000001
[<bf003aec>] (usb_hcd_giveback_urb+0x0/0x108 [usbcore]) from [<bf02a6b4>] (ehci_urb_done+0x78/0x80 [ehci_hcd])
r6:c11908bc r5:c11908bc r4:c08633e0
[<bf02a63c>] (ehci_urb_done+0x0/0x80 [ehci_hcd]) from [<bf02c290>] (ehci_work+0x474/0x79c [ehci_hcd])
r5:c084e2a0 r4:ffc28180
[<bf02be1c>] (ehci_work+0x0/0x79c [ehci_hcd]) from [<bf02df50>] (ehci_watchdog+0x64/0x6c [ehci_hcd])
[<bf02deec>] (ehci_watchdog+0x0/0x6c [ehci_hcd]) from [<c003fcb0>] (run_timer_softirq+0x14c/0x1e0)
r5:00000100 r4:c02914a0
[<c003fb64>] (run_timer_softirq+0x0/0x1e0) from [<c003c028>] (__do_softirq+0x5c/0xd0)
r7:c02a7314 r6:0000000a r5:c0291284 r4:00000001
[<c003bfcc>] (__do_softirq+0x0/0xd0) from [<c003c3f4>] (irq_exit+0x44/0x4c)
r6:00000000 r5:c0278144 r4:00000005
[<c003c3b0>] (irq_exit+0x0/0x4c) from [<c0021048>] (__exception_text_start+0x48/0x60)
[<c0021000>] (__exception_text_start+0x0/0x60) from [<c0021a24>] (__irq_svc+0x24/0x80)
Exception stack(0xc0271f64 to 0xc0271fac)
1f60:          c0885580 00000002 c0270000 00000000 c0022f50 c0270000 c001ff50
1f80: c02a7314 0001e0ec 690541f1 0001dee4 c0271fc0 c0271fac c0271fac c0022d30
1fa0: c0022d38 60000013 ffffffff                                             r6:00000020 r5:0000001f r4:ffffffff
[<c0022cf8>] (cpu_idle+0x0/0x54) from [<c01e5ad8>] (rest_init+0x48/0x58)
r5:c028b828 r4:c029446c
[<c01e5a90>] (rest_init+0x0/0x58) from [<c0008bd8>] (start_kernel+0x280/0x2ec)
[<c0008958>] (start_kernel+0x0/0x2ec) from [<00008030>] (0x8030) 

So I corrected this by adding a tasklet that calls the dma_free_coherent, thanks to David Brownell who guided me.
Tested OK with the same configuration, no more irq disabled when calling dma_free_coherent.
The following patch is against the 2.6.22 version of dmabounce.c
Please comment and tell me if this breaks anything around, I do not have any other ARM based platform to test it.
Signed-off-by: Thierry MERLE <thierry.merle@xxxxxxx>

--- linux-source-2.6.22.orig/arch/arm/common/dmabounce.c	2007-10-12 21:51:54.000000000 +0200
+++ linux-source-2.6.22/arch/arm/common/dmabounce.c	2007-10-10 19:16:36.000000000 +0200
@@ -29,7 +29,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/dmapool.h>
 #include <linux/list.h>
+#include <linux/interrupt.h>
 #include <asm/cacheflush.h>
 #undef STATS
@@ -51,6 +51,7 @@ struct safe_buffer {
 	int		direction;
 	/* safe buffer info */
+	struct device *dev;
 	struct dmabounce_pool *pool;
 	void		*safe;
 	dma_addr_t	safe_dma_addr;
@@ -125,6 +126,7 @@ alloc_safe_buffer(struct dmabounce_devic
 		return NULL;
+	buf->dev = device_info->dev;
 	buf->ptr = ptr;
 	buf->size = size;
 	buf->direction = dir;
@@ -180,6 +182,39 @@ find_safe_buffer(struct dmabounce_device
 	return rb;
+/* The free safe buffer part. dma_free_coherent cannot be called irq disabled.
+   To cope with that, a tasklet (do_free) does the job upon request */
+static DEFINE_SPINLOCK(buflock);
+static LIST_HEAD(buffers);
+static void do_free(unsigned long ignored)
+       spin_lock_irq(&buflock);
+       while (!list_empty(&buffers)) {
+               struct safe_buffer *buf;
+               buf = list_entry(,
+				struct safe_buffer,
+				node);
+               list_del(&buf->node);
+               spin_unlock_irq(&buflock);
+	       if (buf->pool)
+		 dma_pool_free(buf->pool->pool, buf->safe, buf->safe_dma_addr);
+	       else
+		 dma_free_coherent(buf->dev, buf->size, buf->safe,
+				   buf->safe_dma_addr);
+	       kfree(buf);
+               spin_lock_irq(&buflock);
+       }
+       spin_unlock_irq(&buflock);
+static DECLARE_TASKLET(deferred_free, do_free, 0);
 static inline void
 free_safe_buffer(struct dmabounce_device_info *device_info, struct safe_buffer *buf)
@@ -193,13 +228,12 @@ free_safe_buffer(struct dmabounce_device
 	write_unlock_irqrestore(&device_info->lock, flags);
-	if (buf->pool)
-		dma_pool_free(buf->pool->pool, buf->safe, buf->safe_dma_addr);
-	else
-		dma_free_coherent(device_info->dev, buf->size, buf->safe,
-				    buf->safe_dma_addr);
+	/* pass the safe buffer to the tasklet */
+	spin_lock_irqsave(&buflock, flags);
+	list_add_tail(&buf->node, &buffers);
+	tasklet_schedule(&deferred_free);
+	spin_unlock_irqrestore(&buflock, flags);
-	kfree(buf);
 /* ************************************************** */

List admin:

[Linux ARM (vger)]     [Linux Omap]     [Linux Arm]     [Fedora ARM]     [eCos]     [Linux Fastboot]     [Gcc Help]     [Git]     [DCCP]     [IETF Announce]     [Security]     [PDAs]     [Linux]     [Linux Book List]     [Linux MIPS]     [Yosemite Campsites]     [Photos]

Add to Google Google PageRank Checking tool