Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 95f3c4eb authored by Ingo Molnar's avatar Ingo Molnar
Browse files

Merge branch 'dma-api/debug' of...

Merge branch 'dma-api/debug' of git://git.kernel.org/pub/scm/linux/kernel/git/joro/linux-2.6-iommu into core/iommu
parents 04dfcfcb 86f31952
Loading
Loading
Loading
Loading
+106 −0
Original line number Diff line number Diff line
@@ -609,3 +609,109 @@ size is the size (and should be a page-sized multiple).
The return value will be either a pointer to the processor virtual
address of the memory, or an error (via PTR_ERR()) if any part of the
region is occupied.

Part III - Debug drivers use of the DMA-API
-------------------------------------------

The DMA-API as described above as some constraints. DMA addresses must be
released with the corresponding function with the same size for example. With
the advent of hardware IOMMUs it becomes more and more important that drivers
do not violate those constraints. In the worst case such a violation can
result in data corruption up to destroyed filesystems.

To debug drivers and find bugs in the usage of the DMA-API checking code can
be compiled into the kernel which will tell the developer about those
violations. If your architecture supports it you can select the "Enable
debugging of DMA-API usage" option in your kernel configuration. Enabling this
option has a performance impact. Do not enable it in production kernels.

If you boot the resulting kernel will contain code which does some bookkeeping
about what DMA memory was allocated for which device. If this code detects an
error it prints a warning message with some details into your kernel log. An
example warning message may look like this:

------------[ cut here ]------------
WARNING: at /data2/repos/linux-2.6-iommu/lib/dma-debug.c:448
	check_unmap+0x203/0x490()
Hardware name:
forcedeth 0000:00:08.0: DMA-API: device driver frees DMA memory with wrong
	function [device address=0x00000000640444be] [size=66 bytes] [mapped as
single] [unmapped as page]
Modules linked in: nfsd exportfs bridge stp llc r8169
Pid: 0, comm: swapper Tainted: G        W  2.6.28-dmatest-09289-g8bb99c0 #1
Call Trace:
 <IRQ>  [<ffffffff80240b22>] warn_slowpath+0xf2/0x130
 [<ffffffff80647b70>] _spin_unlock+0x10/0x30
 [<ffffffff80537e75>] usb_hcd_link_urb_to_ep+0x75/0xc0
 [<ffffffff80647c22>] _spin_unlock_irqrestore+0x12/0x40
 [<ffffffff8055347f>] ohci_urb_enqueue+0x19f/0x7c0
 [<ffffffff80252f96>] queue_work+0x56/0x60
 [<ffffffff80237e10>] enqueue_task_fair+0x20/0x50
 [<ffffffff80539279>] usb_hcd_submit_urb+0x379/0xbc0
 [<ffffffff803b78c3>] cpumask_next_and+0x23/0x40
 [<ffffffff80235177>] find_busiest_group+0x207/0x8a0
 [<ffffffff8064784f>] _spin_lock_irqsave+0x1f/0x50
 [<ffffffff803c7ea3>] check_unmap+0x203/0x490
 [<ffffffff803c8259>] debug_dma_unmap_page+0x49/0x50
 [<ffffffff80485f26>] nv_tx_done_optimized+0xc6/0x2c0
 [<ffffffff80486c13>] nv_nic_irq_optimized+0x73/0x2b0
 [<ffffffff8026df84>] handle_IRQ_event+0x34/0x70
 [<ffffffff8026ffe9>] handle_edge_irq+0xc9/0x150
 [<ffffffff8020e3ab>] do_IRQ+0xcb/0x1c0
 [<ffffffff8020c093>] ret_from_intr+0x0/0xa
 <EOI> <4>---[ end trace f6435a98e2a38c0e ]---

The driver developer can find the driver and the device including a stacktrace
of the DMA-API call which caused this warning.

Per default only the first error will result in a warning message. All other
errors will only silently counted. This limitation exist to prevent the code
from flooding your kernel log. To support debugging a device driver this can
be disabled via debugfs. See the debugfs interface documentation below for
details.

The debugfs directory for the DMA-API debugging code is called dma-api/. In
this directory the following files can currently be found:

	dma-api/all_errors	This file contains a numeric value. If this
				value is not equal to zero the debugging code
				will print a warning for every error it finds
				into the kernel log. Be carefull with this
				option. It can easily flood your logs.

	dma-api/disabled	This read-only file contains the character 'Y'
				if the debugging code is disabled. This can
				happen when it runs out of memory or if it was
				disabled at boot time

	dma-api/error_count	This file is read-only and shows the total
				numbers of errors found.

	dma-api/num_errors	The number in this file shows how many
				warnings will be printed to the kernel log
				before it stops. This number is initialized to
				one at system boot and be set by writing into
				this file

	dma-api/min_free_entries
				This read-only file can be read to get the
				minimum number of free dma_debug_entries the
				allocator has ever seen. If this value goes
				down to zero the code will disable itself
				because it is not longer reliable.

	dma-api/num_free_entries
				The current number of free dma_debug_entries
				in the allocator.

If you have this code compiled into your kernel it will be enabled by default.
If you want to boot without the bookkeeping anyway you can provide
'dma_debug=off' as a boot parameter. This will disable DMA-API debugging.
Notice that you can not enable it again at runtime. You have to reboot to do
so.

When the code disables itself at runtime this is most likely because it ran
out of dma_debug_entries. These entries are preallocated at boot. The number
of preallocated entries is defined per architecture. If it is too low for you
boot with 'dma_debug_entries=<your_desired_number>' to overwrite the
architectural default.
+10 −0
Original line number Diff line number Diff line
@@ -491,6 +491,16 @@ and is between 256 and 4096 characters. It is defined in the file
			Range: 0 - 8192
			Default: 64

	dma_debug=off	If the kernel is compiled with DMA_API_DEBUG support
			this option disables the debugging code at boot.

	dma_debug_entries=<number>
			This option allows to tune the number of preallocated
			entries for DMA-API debugging code. One entry is
			required per DMA-API allocation. Use this if the
			DMA-API debugging code disables itself because the
			architectural default is too low.

	hpet=		[X86-32,HPET] option to control HPET usage
			Format: { enable (default) | disable | force }
			disable: disable HPET and use PIT instead
+2 −0
Original line number Diff line number Diff line
@@ -106,3 +106,5 @@ config HAVE_CLK
	  The <linux/clk.h> calls support software clock gating and
	  thus are a key power management tool on many systems.

config HAVE_DMA_API_DEBUG
	bool
+1 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ config X86
	select HAVE_GENERIC_DMA_COHERENT if X86_32
	select HAVE_EFFICIENT_UNALIGNED_ACCESS
	select USER_STACKTRACE_SUPPORT
	select HAVE_DMA_API_DEBUG

config ARCH_DEFCONFIG
	string
+39 −6
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
 */

#include <linux/scatterlist.h>
#include <linux/dma-debug.h>
#include <linux/dma-attrs.h>
#include <asm/io.h>
#include <asm/swiotlb.h>
@@ -56,11 +57,16 @@ dma_map_single(struct device *hwdev, void *ptr, size_t size,
	       enum dma_data_direction dir)
{
	struct dma_map_ops *ops = get_dma_ops(hwdev);
	dma_addr_t addr;

	BUG_ON(!valid_dma_direction(dir));
	return ops->map_page(hwdev, virt_to_page(ptr),
	addr = ops->map_page(hwdev, virt_to_page(ptr),
			     (unsigned long)ptr & ~PAGE_MASK, size,
			     dir, NULL);
	debug_dma_map_page(hwdev, virt_to_page(ptr),
			   (unsigned long)ptr & ~PAGE_MASK, size,
			   dir, addr, true);
	return addr;
}

static inline void
@@ -72,6 +78,7 @@ dma_unmap_single(struct device *dev, dma_addr_t addr, size_t size,
	BUG_ON(!valid_dma_direction(dir));
	if (ops->unmap_page)
		ops->unmap_page(dev, addr, size, dir, NULL);
	debug_dma_unmap_page(dev, addr, size, dir, true);
}

static inline int
@@ -79,9 +86,13 @@ dma_map_sg(struct device *hwdev, struct scatterlist *sg,
	   int nents, enum dma_data_direction dir)
{
	struct dma_map_ops *ops = get_dma_ops(hwdev);
	int ents;

	BUG_ON(!valid_dma_direction(dir));
	return ops->map_sg(hwdev, sg, nents, dir, NULL);
	ents = ops->map_sg(hwdev, sg, nents, dir, NULL);
	debug_dma_map_sg(hwdev, sg, nents, ents, dir);

	return ents;
}

static inline void
@@ -91,6 +102,7 @@ dma_unmap_sg(struct device *hwdev, struct scatterlist *sg, int nents,
	struct dma_map_ops *ops = get_dma_ops(hwdev);

	BUG_ON(!valid_dma_direction(dir));
	debug_dma_unmap_sg(hwdev, sg, nents, dir);
	if (ops->unmap_sg)
		ops->unmap_sg(hwdev, sg, nents, dir, NULL);
}
@@ -104,6 +116,7 @@ dma_sync_single_for_cpu(struct device *hwdev, dma_addr_t dma_handle,
	BUG_ON(!valid_dma_direction(dir));
	if (ops->sync_single_for_cpu)
		ops->sync_single_for_cpu(hwdev, dma_handle, size, dir);
	debug_dma_sync_single_for_cpu(hwdev, dma_handle, size, dir);
	flush_write_buffers();
}

@@ -116,6 +129,7 @@ dma_sync_single_for_device(struct device *hwdev, dma_addr_t dma_handle,
	BUG_ON(!valid_dma_direction(dir));
	if (ops->sync_single_for_device)
		ops->sync_single_for_device(hwdev, dma_handle, size, dir);
	debug_dma_sync_single_for_device(hwdev, dma_handle, size, dir);
	flush_write_buffers();
}

@@ -130,6 +144,8 @@ dma_sync_single_range_for_cpu(struct device *hwdev, dma_addr_t dma_handle,
	if (ops->sync_single_range_for_cpu)
		ops->sync_single_range_for_cpu(hwdev, dma_handle, offset,
					       size, dir);
	debug_dma_sync_single_range_for_cpu(hwdev, dma_handle,
					    offset, size, dir);
	flush_write_buffers();
}

@@ -144,6 +160,8 @@ dma_sync_single_range_for_device(struct device *hwdev, dma_addr_t dma_handle,
	if (ops->sync_single_range_for_device)
		ops->sync_single_range_for_device(hwdev, dma_handle,
						  offset, size, dir);
	debug_dma_sync_single_range_for_device(hwdev, dma_handle,
					       offset, size, dir);
	flush_write_buffers();
}

@@ -156,6 +174,7 @@ dma_sync_sg_for_cpu(struct device *hwdev, struct scatterlist *sg,
	BUG_ON(!valid_dma_direction(dir));
	if (ops->sync_sg_for_cpu)
		ops->sync_sg_for_cpu(hwdev, sg, nelems, dir);
	debug_dma_sync_sg_for_cpu(hwdev, sg, nelems, dir);
	flush_write_buffers();
}

@@ -168,6 +187,7 @@ dma_sync_sg_for_device(struct device *hwdev, struct scatterlist *sg,
	BUG_ON(!valid_dma_direction(dir));
	if (ops->sync_sg_for_device)
		ops->sync_sg_for_device(hwdev, sg, nelems, dir);
	debug_dma_sync_sg_for_device(hwdev, sg, nelems, dir);

	flush_write_buffers();
}
@@ -177,15 +197,24 @@ static inline dma_addr_t dma_map_page(struct device *dev, struct page *page,
				      enum dma_data_direction dir)
{
	struct dma_map_ops *ops = get_dma_ops(dev);
	dma_addr_t addr;

	BUG_ON(!valid_dma_direction(dir));
	return ops->map_page(dev, page, offset, size, dir, NULL);
	addr = ops->map_page(dev, page, offset, size, dir, NULL);
	debug_dma_map_page(dev, page, offset, size, dir, addr, false);

	return addr;
}

static inline void dma_unmap_page(struct device *dev, dma_addr_t addr,
				  size_t size, enum dma_data_direction dir)
{
	dma_unmap_single(dev, addr, size, dir);
	struct dma_map_ops *ops = get_dma_ops(dev);

	BUG_ON(!valid_dma_direction(dir));
	if (ops->unmap_page)
		ops->unmap_page(dev, addr, size, dir, NULL);
	debug_dma_unmap_page(dev, addr, size, dir, false);
}

static inline void
@@ -250,8 +279,11 @@ dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle,
	if (!ops->alloc_coherent)
		return NULL;

	return ops->alloc_coherent(dev, size, dma_handle,
	memory = ops->alloc_coherent(dev, size, dma_handle,
				     dma_alloc_coherent_gfp_flags(dev, gfp));
	debug_dma_alloc_coherent(dev, size, *dma_handle, memory);

	return memory;
}

static inline void dma_free_coherent(struct device *dev, size_t size,
@@ -264,6 +296,7 @@ static inline void dma_free_coherent(struct device *dev, size_t size,
	if (dma_release_from_coherent(dev, get_order(size), vaddr))
		return;

	debug_dma_free_coherent(dev, size, vaddr, bus);
	if (ops->free_coherent)
		ops->free_coherent(dev, size, vaddr, bus);
}
Loading