mm: cma: Ensure that reservations never cross the low/high mem boundary

Commit 95b0e655f9 ("ARM: mm: don't limit default CMA region only to
low memory") extended CMA memory reservation to allow usage of high
memory. It relied on commit f7426b983a ("mm: cma: adjust address limit
to avoid hitting low/high memory boundary") to ensure that the reserved
block never crossed the low/high memory boundary. While the
implementation correctly lowered the limit, it failed to consider the
case where the base..limit range crossed the low/high memory boundary
with enough space on each side to reserve the requested size on either
low or high memory.

Rework the base and limit adjustment to fix the problem. The function
now starts by rejecting the reservation altogether for fixed
reservations that cross the boundary, tries to reserve from high memory
first and then falls back to low memory.

Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
This commit is contained in:
Laurent Pinchart 2014-10-24 13:18:41 +03:00 committed by Marek Szyprowski
parent 800a85d3d2
commit 16195ddd4e

View File

@ -253,23 +253,24 @@ int __init cma_declare_contiguous(phys_addr_t base,
return -EINVAL; return -EINVAL;
/* /*
* adjust limit to avoid crossing low/high memory boundary for * If allocating at a fixed base the request region must not cross the
* automatically allocated regions * low/high memory boundary.
*/ */
if (((limit == 0 || limit > memblock_end) && if (fixed && base < highmem_start && base + size > highmem_start) {
(memblock_end - size < highmem_start &&
memblock_end > highmem_start)) ||
(!fixed && limit > highmem_start && limit - size < highmem_start)) {
limit = highmem_start;
}
if (fixed && base < highmem_start && base+size > highmem_start) {
ret = -EINVAL; ret = -EINVAL;
pr_err("Region at %08lx defined on low/high memory boundary (%08lx)\n", pr_err("Region at %08lx defined on low/high memory boundary (%08lx)\n",
(unsigned long)base, (unsigned long)highmem_start); (unsigned long)base, (unsigned long)highmem_start);
goto err; goto err;
} }
/*
* If the limit is unspecified or above the memblock end, its effective
* value will be the memblock end. Set it explicitly to simplify further
* checks.
*/
if (limit == 0 || limit > memblock_end)
limit = memblock_end;
/* Reserve memory */ /* Reserve memory */
if (fixed) { if (fixed) {
if (memblock_is_region_reserved(base, size) || if (memblock_is_region_reserved(base, size) ||
@ -278,14 +279,30 @@ int __init cma_declare_contiguous(phys_addr_t base,
goto err; goto err;
} }
} else { } else {
phys_addr_t addr = memblock_alloc_range(size, alignment, base, phys_addr_t addr = 0;
limit);
if (!addr) { /*
ret = -ENOMEM; * All pages in the reserved area must come from the same zone.
goto err; * If the requested region crosses the low/high memory boundary,
} else { * try allocating from high memory first and fall back to low
base = addr; * memory in case of failure.
*/
if (base < highmem_start && limit > highmem_start) {
addr = memblock_alloc_range(size, alignment,
highmem_start, limit);
limit = highmem_start;
} }
if (!addr) {
addr = memblock_alloc_range(size, alignment, base,
limit);
if (!addr) {
ret = -ENOMEM;
goto err;
}
}
base = addr;
} }
ret = cma_init_reserved_mem(base, size, order_per_bit, res_cma); ret = cma_init_reserved_mem(base, size, order_per_bit, res_cma);