@@ -310,10 +310,6 @@ struct cma_region {
const char *alloc_name;
void *private_data;
-#ifdef CONFIG_CMA_USE_MIGRATE_CMA
- unsigned short *isolation_map;
-#endif
-
unsigned users;
struct list_head list;
@@ -327,7 +323,9 @@ struct cma_region {
unsigned reserved:1;
unsigned copy_name:1;
unsigned free_alloc_name:1;
+#ifdef CONFIG_CMA_USE_MIGRATE_CMA
unsigned use_isolate:1;
+#endif
};
/**
@@ -449,6 +447,38 @@ struct cma_allocator {
*/
int cma_allocator_register(struct cma_allocator *alloc);
+/**
+ * __cma_grab() - migrates all pages from range and reserves them for CMA
+ * @reg: Region this call is made in context of. If the region is
+ * not marked needing grabbing the function does nothing.
+ * @start: Address in bytes of the first byte to free.
+ * @size: Size of the region to free.
+ *
+ * This function should be used when allocator wants to allocate some
+ * physical memory to make sure that it is not used for any movable or
+ * reclaimable pages (eg. page cache).
+ *
+ * In essence, this function migrates all movable and reclaimable
+ * pages from the range and then removes them from buddy system so
+ * page allocator won't consider them when allocating space.
+ *
+ * The allocator may assume that it is unlikely for this function to fail so
+ * if it fails allocator should just recover and return error.
+ */
+int __cma_grab(struct cma_region *reg, phys_addr_t start, size_t size);
+
+/**
+ * cma_ungrab_range() - frees pages from range back to buddy system.
+ * @reg: Region this call is made in context of. If the region is
+ * not marked needing grabbing the function does nothing.
+ * @start: Address in bytes of the first byte to free.
+ * @size: Size of the region to free.
+ *
+ * This is reverse of cma_grab_range(). Allocator should use it when
+ * physical memory is no longer used.
+ */
+void __cma_ungrab(struct cma_region *reg, phys_addr_t start, size_t size);
+
/**************************** Initialisation API ****************************/
@@ -363,6 +363,21 @@ config CMA
For more information see <Documentation/contiguous-memory.txt>.
If unsure, say "n".
+config CMA_USE_MIGRATE_CMA
+ bool "Use MIGRATE_CMA"
+ depends on CMA
+ default y
+ select MIGRATION
+ select MIGRATE_CMA
+ help
+ This makes CMA use MIGRATE_CMA migration type for regions
+ maintained by CMA. This makes it possible for standard page
+ allocator to use pages from such regions. This in turn may
+ make the whole system run faster as there will be more space
+ for page caches, etc.
+
+ If unsure, say "y".
+
config CMA_DEVELOPEMENT
bool "Include CMA developement features"
depends on CMA
@@ -145,6 +145,8 @@ static void cma_bf_cleanup(struct cma_region *reg)
kfree(prv);
}
+static void __cma_bf_free(struct cma_region *reg, union cma_bf_item *chunk);
+
struct cma *cma_bf_alloc(struct cma_region *reg,
size_t size, unsigned long alignment)
{
@@ -281,10 +283,17 @@ case_2:
item->chunk.phys = start;
item->chunk.size = size;
+
+ ret = __cma_grab(reg, start, size);
+ if (ret) {
+ __cma_bf_free(reg, item);
+ return ERR_PTR(ret);
+ }
+
return &item->chunk;
}
-static void cma_bf_free(struct cma_chunk *chunk)
+static void __cma_bf_free(struct cma_region *reg, union cma_bf_item *item)
{
struct cma_bf_private *prv = reg->private_data;
union cma_bf_item *prev;
@@ -350,6 +359,7 @@ next:
}
}
+static void cma_bf_free(struct cma *chunk)
{
__cma_ungrab(chunk->reg, chunk->phys, chunk->size);
__cma_bf_free(chunk->reg,
@@ -39,6 +39,13 @@
#include <linux/cma.h>
+#ifdef CONFIG_CMA_USE_MIGRATE_CMA
+#include <linux/page-isolation.h>
+
+#include <asm/page.h>
+
+#include "internal.h" /* __free_pageblock_cma() */
+#endif
/*
* Protects cma_regions, cma_allocators, cma_map, cma_map_length,
@@ -410,6 +417,222 @@ __cma_early_reserve(struct cma_region *reg)
return tried ? -ENOMEM : -EOPNOTSUPP;
}
+#ifdef CONFIG_CMA_USE_MIGRATE_CMA
+
+static struct cma_grabbed_early {
+ phys_addr_t start;
+ size_t size;
+} cma_grabbed_early[16] __initdata;
+static unsigned cma_grabbed_early_count __initdata;
+
+/* XXX Revisit */
+#ifdef phys_to_pfn
+/* nothing to do */
+#elif defined __phys_to_pfn
+# define phys_to_pfn __phys_to_pfn
+#else
+# warning correct phys_to_pfn implementation needed
+static unsigned long phys_to_pfn(phys_addr_t phys)
+{
+ return virt_to_pfn(phys_to_virt(phys));
+}
+#endif
+
+static unsigned long pfn_to_maxpage(unsigned long pfn)
+{
+ return pfn & ~(MAX_ORDER_NR_PAGES - 1);
+}
+
+static unsigned long pfn_to_maxpage_up(unsigned long pfn)
+{
+ return ALIGN(pfn, MAX_ORDER_NR_PAGES);
+}
+
+static int __init cma_free_grabbed(void)
+{
+ struct cma_grabbed_early *r = cma_grabbed_early;
+ unsigned i = cma_grabbed_early_count;
+
+ for (; i; --i, ++r) {
+ struct page *p = phys_to_page(r->start);
+ unsigned j = r->size >> (PAGE_SHIFT + pageblock_order);
+
+ pr_debug("feeding buddy with: %p + %u * %luM\n",
+ (void *)r->start,
+ j, 1ul << (PAGE_SHIFT + pageblock_order - 20));
+
+ do {
+ __free_pageblock_cma(p);
+ p += pageblock_nr_pages;
+ } while (--j);
+ }
+
+ return 0;
+}
+module_init(cma_free_grabbed);
+
+static phys_addr_t
+__cma_early_region_reserve_try_migrate_cma(struct cma_region *reg)
+{
+ int ret;
+
+ if (((reg->start | reg->size) & ((PAGE_SIZE << MAX_ORDER) - 1)))
+ return -EOPNOTSUPP;
+
+ /*
+ * XXX Revisit: Do we need to check if the region is
+ * consistent? For instance, are all pages valid and part of
+ * the same zone?
+ */
+
+ if (cma_grabbed_early_count >= ARRAY_SIZE(cma_grabbed_early)) {
+ static bool once = true;
+ if (once) {
+ pr_warn("grabbed too many ranges, not all will be MIGRATE_CMA");
+ once = false;
+ }
+ return -EOPNOTSUPP;
+ }
+
+ pr_debug("init: reserving region as MIGRATE_CMA\n");
+
+ reg->alignment = max(reg->alignment,
+ (unsigned long)PAGE_SHIFT << MAX_ORDER);
+
+ ret = __cma_early_reserve(reg);
+ if (ret)
+ return ret;
+
+ cma_grabbed_early[cma_grabbed_early_count].start = reg->start;
+ cma_grabbed_early[cma_grabbed_early_count].size = reg->size;
+ ++cma_grabbed_early_count;
+
+ reg->use_isolate = 1;
+
+ return 0;
+}
+
+int __cma_grab(struct cma_region *reg, phys_addr_t start_addr, size_t size)
+{
+ unsigned long start, end, _start, _end;
+ int ret;
+
+ if (!reg->use_isolate)
+ return 0;
+
+ pr_debug("%s\n", __func__);
+
+ /*
+ * What we do here is we mark all pageblocks in range as
+ * MIGRATE_ISOLATE. Because of the way page allocator work, we
+ * align the range to MAX_ORDER pages so that page allocator
+ * won't try to merge buddies from different pageblocks and
+ * change MIGRATE_ISOLATE to some other migration type.
+ *
+ * Once the pageblocks are marked as MIGRATE_ISOLATE, we
+ * migrate the pages from an unaligned range (ie. pages that
+ * we are interested in). This will put all the pages in
+ * range back to page allocator as MIGRATE_ISOLATE.
+ *
+ * When this is done, we take the pages in range from page
+ * allocator removing them from the buddy system. This way
+ * page allocator will never consider using them.
+ *
+ * This lets us mark the pageblocks back as MIGRATE_CMA so
+ * that free pages in the MAX_ORDER aligned range but not in
+ * the unaligned, original range are put back to page
+ * allocator so that buddy can use them.
+ */
+
+ start = phys_to_pfn(start_addr);
+ end = start + (size >> PAGE_SHIFT);
+
+ pr_debug("\tisolate range(%lx, %lx)\n",
+ pfn_to_maxpage(start), pfn_to_maxpage_up(end));
+ ret = __start_isolate_page_range(pfn_to_maxpage(start),
+ pfn_to_maxpage_up(end), MIGRATE_CMA);
+ if (ret)
+ goto done;
+
+ pr_debug("\tmigrate range(%lx, %lx)\n", start, end);
+ ret = do_migrate_range(start, end);
+ if (ret)
+ goto done;
+
+ /*
+ * Pages from [start, end) are within a MAX_ORDER aligned
+ * blocks that are marked as MIGRATE_ISOLATE. What's more,
+ * all pages in [start, end) are free in page allocator. What
+ * we are going to do is to allocate all pages from [start,
+ * end) (that is remove them from page allocater).
+ *
+ * The only problem is that pages at the beginning and at the
+ * end of interesting range may be not aligned with pages that
+ * page allocator holds, ie. they can be part of higher order
+ * pages. Because of this, we reserve the bigger range and
+ * once this is done free the pages we are not interested in.
+ */
+
+ pr_debug("\tfinding buddy\n");
+ ret = 0;
+ while (!PageBuddy(pfn_to_page(start & (~0UL << ret))))
+ if (WARN_ON(++ret > MAX_ORDER))
+ return -EINVAL;
+
+ _start = start & (~0UL << ret);
+ pr_debug("\talloc freed(%lx, %lx)\n", _start, end);
+ _end = alloc_contig_freed_pages(_start, end, 0);
+
+ /* Free head and tail (if any) */
+ pr_debug("\tfree contig(%lx, %lx)\n", _start, start);
+ free_contig_pages(pfn_to_page(_start), start - _start);
+ pr_debug("\tfree contig(%lx, %lx)\n", end, _end);
+ free_contig_pages(pfn_to_page(end), _end - end);
+
+ ret = 0;
+
+done:
+ pr_debug("\tundo isolate range(%lx, %lx)\n",
+ pfn_to_maxpage(start), pfn_to_maxpage_up(end));
+ __undo_isolate_page_range(pfn_to_maxpage(start),
+ pfn_to_maxpage_up(end), MIGRATE_CMA);
+
+ pr_debug("ret = %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(__cma_grab);
+
+void __cma_ungrab(struct cma_region *reg, phys_addr_t start, size_t size)
+{
+ if (reg->use_isolate)
+ free_contig_pages(pfn_to_page(phys_to_pfn(start)),
+ size >> PAGE_SHIFT);
+}
+EXPORT_SYMBOL_GPL(__cma_ungrab);
+
+#else
+
+static inline phys_addr_t
+__cma_early_region_reserve_try_migrate_cma(struct cma_region *reg)
+{
+ return -EOPNOTSUPP;
+}
+
+int __cma_grab(struct cma_region *reg, phys_addr_t start, size_t size)
+{
+ (void)reg; (void)start; (void)size;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__cma_grab);
+
+void __cma_ungrab(struct cma_region *reg, phys_addr_t start, size_t size)
+{
+ (void)reg; (void)start; (void)size;
+}
+EXPORT_SYMBOL_GPL(__cma_ungrab);
+
+#endif
+
int __init cma_early_region_reserve(struct cma_region *reg)
{
int ret;
@@ -420,6 +643,13 @@ int __init cma_early_region_reserve(struct cma_region *reg)
reg->reserved)
return -EINVAL;
+ /*
+ * Try using cma_early_grab_maxpages() if the requested
+ * region's start is aligned to PAGE_SIZE << MAX_ORDER and
+ * its size is PAGE_SIZE << MAX_ORDER multiple.
+ */
+ ret = __cma_early_region_reserve_try_migrate_cma(reg);
+ if (ret ==-EOPNOTSUPP)
ret = __cma_early_reserve(reg);
if (!ret)
reg->reserved = 1;
@@ -1393,6 +1623,7 @@ struct cma *cma_gen_alloc(struct cma_region *reg,
{
unsigned long start;
struct cma *chunk;
+ int ret;
chunk = kmalloc(sizeof *chunk, GFP_KERNEL);
if (unlikely(!chunk))
@@ -1405,6 +1636,13 @@ struct cma *cma_gen_alloc(struct cma_region *reg,
return ERR_PTR(-ENOMEM);
}
+ ret = __cma_grab(reg, start, size);
+ if (ret) {
+ gen_pool_free(reg->private_data, start, size);
+ kfree(chunk);
+ return ERR_PTR(ret);
+ }
+
chunk->phys = start;
chunk->size = size;
return chunk;
@@ -1413,6 +1651,7 @@ struct cma *cma_gen_alloc(struct cma_region *reg,
static void cma_gen_free(struct cma *chunk)
{
gen_pool_free(chunk->reg->private_data, chunk->phys, chunk->size);
+ __cma_ungrab(chunk->reg, chunk->phys, chunk->size);
kfree(chunk);
}