Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ARM: 8505/1: dma-mapping: Optimize allocation

The __iommu_alloc_buffer() is expected to be called to allocate pretty
sizeable buffers. Upon simple tests of video I saw it trying to
allocate 4,194,304 bytes. The function tries to allocate large chunks
in order to optimize IOMMU TLB usage.

The current function is very, very slow.

One problem is the way it keeps trying and trying to allocate big
chunks. Imagine a very fragmented memory that has 4M free but no
contiguous pages at all. Further imagine allocating 4M (1024 pages).
We'll do the following memory allocations:
- For page 1:
- Try to allocate order 10 (no retry)
- Try to allocate order 9 (no retry)
- ...
- Try to allocate order 0 (with retry, but not needed)
- For page 2:
- Try to allocate order 9 (no retry)
- Try to allocate order 8 (no retry)
- ...
- Try to allocate order 0 (with retry, but not needed)
- ...
- ...

Total number of calls to alloc() calls for this case is:
sum(int(math.log(i, 2)) + 1 for i in range(1, 1025))
=> 9228

The above is obviously worse case, but given how slow alloc can be we
really want to try to avoid even somewhat bad cases. I timed the old
code with a device under memory pressure and it wasn't hard to see it
take more than 120 seconds to allocate 4 megs of memory! (NOTE: testing
was done on kernel 3.14, so possibly mainline would behave
differently).

A second problem is that allocating big chunks under memory pressure
when we don't need them is just not a great idea anyway unless we really
need them. We can make due pretty well with smaller chunks so it's
probably wise to leave bigger chunks for other users once memory
pressure is on.

Let's adjust the allocation like this:

1. If a big chunk fails, stop trying to hard and bump down to lower
order allocations.
2. Don't try useless orders. The whole point of big chunks is to
optimize the TLB and it can really only make use of 2M, 1M, 64K and
4K sizes.

We'll still tend to eat up a bunch of big chunks, but that might be the
right answer for some users. A future patch could possibly add a new
DMA_ATTR that would let the caller decide that TLB optimization isn't
important and that we should use smaller chunks. Presumably this would
be a sane strategy for some callers.

Signed-off-by: Douglas Anderson <dianders@chromium.org>
Acked-by: Marek Szyprowski <m.szyprowski@samsung.com>
Reviewed-by: Robin Murphy <robin.murphy@arm.com>
Reviewed-by: Tomasz Figa <tfiga@chromium.org>
Tested-by: Javier Martinez Canillas <javier@osg.samsung.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

authored by

Doug Anderson and committed by
Russell King
33298ef6 73e592f3

+20 -14
+20 -14
arch/arm/mm/dma-mapping.c
··· 1122 1122 spin_unlock_irqrestore(&mapping->lock, flags); 1123 1123 } 1124 1124 1125 + /* We'll try 2M, 1M, 64K, and finally 4K; array must end with 0! */ 1126 + static const int iommu_order_array[] = { 9, 8, 4, 0 }; 1127 + 1125 1128 static struct page **__iommu_alloc_buffer(struct device *dev, size_t size, 1126 1129 gfp_t gfp, struct dma_attrs *attrs) 1127 1130 { ··· 1132 1129 int count = size >> PAGE_SHIFT; 1133 1130 int array_size = count * sizeof(struct page *); 1134 1131 int i = 0; 1132 + int order_idx = 0; 1135 1133 1136 1134 if (array_size <= PAGE_SIZE) 1137 1135 pages = kzalloc(array_size, GFP_KERNEL); ··· 1166 1162 while (count) { 1167 1163 int j, order; 1168 1164 1169 - for (order = __fls(count); order > 0; --order) { 1170 - /* 1171 - * We do not want OOM killer to be invoked as long 1172 - * as we can fall back to single pages, so we force 1173 - * __GFP_NORETRY for orders higher than zero. 1174 - */ 1175 - pages[i] = alloc_pages(gfp | __GFP_NORETRY, order); 1176 - if (pages[i]) 1177 - break; 1165 + order = iommu_order_array[order_idx]; 1166 + 1167 + /* Drop down when we get small */ 1168 + if (__fls(count) < order) { 1169 + order_idx++; 1170 + continue; 1178 1171 } 1179 1172 1180 - if (!pages[i]) { 1181 - /* 1182 - * Fall back to single page allocation. 1183 - * Might invoke OOM killer as last resort. 1184 - */ 1173 + if (order) { 1174 + /* See if it's easy to allocate a high-order chunk */ 1175 + pages[i] = alloc_pages(gfp | __GFP_NORETRY, order); 1176 + 1177 + /* Go down a notch at first sign of pressure */ 1178 + if (!pages[i]) { 1179 + order_idx++; 1180 + continue; 1181 + } 1182 + } else { 1185 1183 pages[i] = alloc_pages(gfp, 0); 1186 1184 if (!pages[i]) 1187 1185 goto error;