mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
ed7c9dccef
PD#SWPL-145324 Problem: cma alloc too slow. Solution: let the idle cpu allocate more. reduce drain lru in cma task. Verify: local. Change-Id: I5f46112dcc4d5eb8fe7b21526d09a4078edc2752 Signed-off-by: Jianxiong Pan <jianxiong.pan@amlogic.com>
2047 lines
51 KiB
C
2047 lines
51 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* drivers/amlogic/memory_ext/aml_cma.c
|
|
*
|
|
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
*/
|
|
|
|
#include <internal.h>
|
|
#include <linux/stddef.h>
|
|
#include <linux/compiler.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/rmap.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/sched/rt.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/module.h>
|
|
#include <linux/swap.h>
|
|
#include <linux/migrate.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/page-isolation.h>
|
|
#include <linux/spinlock_types.h>
|
|
#include <linux/amlogic/aml_cma.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/hugetlb.h>
|
|
#include <linux/cma.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/oom.h>
|
|
#include <linux/of.h>
|
|
#include <linux/shrinker.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <asm/system_misc.h>
|
|
#include <asm/pgtable.h>
|
|
#include <linux/page_pinner.h>
|
|
#include <trace/events/page_isolation.h>
|
|
#if IS_MODULE(CONFIG_AMLOGIC_CMA)
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/jump_label.h>
|
|
#include <linux/types.h>
|
|
#endif
|
|
#ifdef CONFIG_PAGE_OWNER
|
|
#include <linux/page_owner.h>
|
|
#endif
|
|
|
|
/* from mm/ path */
|
|
#include <internal.h>
|
|
|
|
#if IS_ENABLED(CONFIG_AMLOGIC_PAGE_TRACE)
|
|
#include <linux/amlogic/page_trace.h>
|
|
#endif /* CONFIG_AMLOGIC_PAGE_TRACE */
|
|
|
|
#if IS_ENABLED(CONFIG_AMLOGIC_USER_FAULT)
|
|
#include <linux/amlogic/user_fault.h>
|
|
#endif
|
|
|
|
#include <trace/hooks/mm.h>
|
|
#include <linux/amlogic/gki_module.h>
|
|
#include <linux/swap.h>
|
|
|
|
#define MAX_DEBUG_LEVEL 5
|
|
#define MAX_JOB_NUM 40
|
|
|
|
struct work_cma {
|
|
struct list_head list;
|
|
unsigned long pfn;
|
|
unsigned long count;
|
|
struct task_struct *host;
|
|
int ret;
|
|
};
|
|
|
|
struct cma_pcp {
|
|
struct completion start;
|
|
struct completion end;
|
|
struct task_struct *task;
|
|
int cpu;
|
|
};
|
|
|
|
static DEFINE_MUTEX(cma_mutex);
|
|
static atomic_long_t nr_cma_allocated;
|
|
static bool can_boost;
|
|
static DEFINE_PER_CPU(struct cma_pcp, cma_pcp_thread);
|
|
static struct proc_dir_entry *dentry;
|
|
static int cma_alloc_trace;
|
|
int cma_debug_level;
|
|
static int allow_cma_tasks;
|
|
static unsigned long cma_isolated;
|
|
static struct list_head work_list;
|
|
static spinlock_t work_list_lock; /* protect job list */
|
|
|
|
static atomic_t cma_allocate;
|
|
|
|
#ifdef CONFIG_AMLOGIC_CMA_DIS
|
|
unsigned long ion_cma_allocated;
|
|
#endif
|
|
|
|
#if IS_MODULE(CONFIG_AMLOGIC_CMA)
|
|
struct dummy_cma {
|
|
unsigned long base_pfn;
|
|
unsigned long count;
|
|
unsigned long *bitmap;
|
|
unsigned int order_per_bit; /* Order of pages represented by one bit */
|
|
spinlock_t lock;
|
|
#ifdef CONFIG_CMA_DEBUGFS
|
|
struct hlist_head mem_head;
|
|
spinlock_t mem_head_lock; /* for cma debugfs */
|
|
struct debugfs_u32_array dfs_bitmap;
|
|
#endif
|
|
char name[CMA_MAX_NAME];
|
|
#ifdef CONFIG_CMA_SYSFS
|
|
/* the number of CMA page successful allocations */
|
|
atomic64_t nr_pages_succeeded;
|
|
/* the number of CMA page allocation failures */
|
|
atomic64_t nr_pages_failed;
|
|
/* kobject requires dynamic object */
|
|
struct cma_kobject *cma_kobj;
|
|
#endif
|
|
};
|
|
|
|
static inline unsigned long cma_bitmap_maxno(struct dummy_cma *cma)
|
|
{
|
|
return cma->count >> cma->order_per_bit;
|
|
}
|
|
|
|
#ifdef CONFIG_CMA_SYSFS
|
|
static void cma_sysfs_account_success_pages(struct dummy_cma *cma, unsigned long nr_pages)
|
|
{
|
|
atomic64_add(nr_pages, &cma->nr_pages_succeeded);
|
|
}
|
|
|
|
static void cma_sysfs_account_fail_pages(struct dummy_cma *cma, unsigned long nr_pages)
|
|
{
|
|
atomic64_add(nr_pages, &cma->nr_pages_failed);
|
|
}
|
|
#else
|
|
static inline void cma_sysfs_account_success_pages(struct dummy_cma *cma,
|
|
unsigned long nr_pages) {};
|
|
static inline void cma_sysfs_account_fail_pages(struct dummy_cma *cma,
|
|
unsigned long nr_pages) {};
|
|
#endif
|
|
|
|
static unsigned long cma_bitmap_aligned_mask(const struct dummy_cma *cma,
|
|
unsigned int align_order)
|
|
{
|
|
if (align_order <= cma->order_per_bit)
|
|
return 0;
|
|
return (1UL << (align_order - cma->order_per_bit)) - 1;
|
|
}
|
|
|
|
/*
|
|
* Find the offset of the base PFN from the specified align_order.
|
|
* The value returned is represented in order_per_bits.
|
|
*/
|
|
static unsigned long cma_bitmap_aligned_offset(const struct dummy_cma *cma,
|
|
unsigned int align_order)
|
|
{
|
|
return (cma->base_pfn & ((1UL << align_order) - 1))
|
|
>> cma->order_per_bit;
|
|
}
|
|
|
|
static unsigned long cma_bitmap_pages_to_bits(const struct dummy_cma *cma,
|
|
unsigned long pages)
|
|
{
|
|
return ALIGN(pages, 1UL << cma->order_per_bit) >> cma->order_per_bit;
|
|
}
|
|
|
|
static void cma_clear_bitmap(struct dummy_cma *cma, unsigned long pfn,
|
|
unsigned long count)
|
|
{
|
|
unsigned long bitmap_no, bitmap_count;
|
|
unsigned long flags;
|
|
|
|
bitmap_no = (pfn - cma->base_pfn) >> cma->order_per_bit;
|
|
bitmap_count = cma_bitmap_pages_to_bits(cma, count);
|
|
|
|
spin_lock_irqsave(&cma->lock, flags);
|
|
bitmap_clear(cma->bitmap, bitmap_no, bitmap_count);
|
|
spin_unlock_irqrestore(&cma->lock, flags);
|
|
}
|
|
|
|
unsigned long aml_totalcma_pages;
|
|
|
|
#ifdef CONFIG_PAGE_PINNER
|
|
static void __nocfi aml_page_pinner_failure_detect(struct page *page)
|
|
{
|
|
/*
|
|
*if (!static_branch_unlikely(&page_pinner_inited))
|
|
* return;
|
|
*
|
|
*if (!static_branch_unlikely(&failure_tracking))
|
|
* return;
|
|
*/
|
|
|
|
//__page_pinner_failure_detect(page);
|
|
}
|
|
#else
|
|
static void aml_page_pinner_failure_detect(struct page *page)
|
|
{
|
|
}
|
|
#endif /* CONFIG_PAGE_PINNER */
|
|
|
|
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
|
void (*aml_prep_huge_page)(struct page *page);
|
|
#else /* CONFIG_TRANSPARENT_HUGEPAGE */
|
|
static inline void aml_prep_huge_page(struct page *page) {}
|
|
#endif
|
|
|
|
unsigned int (*aml_reclaim_clean_pages_from_list)(struct zone *zone,
|
|
struct list_head *page_list);
|
|
int (*aml_isolate_pages_range)(struct compact_control *cc, unsigned long start_pfn,
|
|
unsigned long end_pfn);
|
|
void (*aml_rmap_walk)(struct page *page, struct rmap_walk_control *rwc);
|
|
void (*aml_undo_isolate_page_range)(unsigned long start_pfn, unsigned long end_pfn,
|
|
unsigned int migratetype);
|
|
unsigned long (*aml_iso_free_range)(struct compact_control *cc,
|
|
unsigned long start_pfn, unsigned long end_pfn);
|
|
void (*aml_drain_all_pages)(struct zone *zone);
|
|
void (*aml_lru_add_drain_all)(void);
|
|
#if CONFIG_AMLOGIC_KERNEL_VERSION >= 14515
|
|
int (*aml_start_isolate_page_range)(unsigned long start_pfn, unsigned long end_pfn,
|
|
unsigned int migratetype, int flags,
|
|
unsigned long *failed_pfn);
|
|
#else
|
|
int (*aml_start_isolate_page_range)(unsigned long start_pfn, unsigned long end_pfn,
|
|
unsigned int migratetype, int flags);
|
|
#endif
|
|
|
|
unsigned long (*aml_kallsyms_lookup_name)(const char *name);
|
|
|
|
/* For each probe you need to allocate a kprobe structure */
|
|
static struct kprobe kp_lookup_name = {
|
|
.symbol_name = "kallsyms_lookup_name",
|
|
};
|
|
#endif
|
|
|
|
#ifdef CONFIG_PAGE_OWNER
|
|
#if IS_MODULE(CONFIG_AMLOGIC_CMA)
|
|
void (*aml__dump_owner)(const struct page *page);
|
|
#else
|
|
static void aml__dump_owner(const struct page *page)
|
|
{
|
|
__dump_page_owner(page);
|
|
}
|
|
#endif
|
|
#else
|
|
static void aml__dump_owner(const struct page *page)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* We insert a none-mapping vm area to vmalloc space
|
|
* and dynamic adjust it's size according nr_cma_allocated.
|
|
* Just in order to let all driver allocated cma size counted
|
|
* into KernelUsed item for dumpsys meminfo command on Android
|
|
* layer
|
|
*/
|
|
|
|
static void get_cma_alloc_ref(void)
|
|
{
|
|
atomic_inc(&cma_allocate);
|
|
}
|
|
|
|
static void put_cma_alloc_ref(void)
|
|
{
|
|
atomic_dec(&cma_allocate);
|
|
}
|
|
|
|
unsigned long get_cma_allocated(void)
|
|
{
|
|
return atomic_long_read(&nr_cma_allocated);
|
|
}
|
|
EXPORT_SYMBOL(get_cma_allocated);
|
|
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
static int cma_alloc_ref(void)
|
|
{
|
|
return atomic_read(&cma_allocate);
|
|
}
|
|
|
|
static bool cma_first_wm_low __read_mostly;
|
|
|
|
static int __init early_cma_first_wm_low_param(char *buf)
|
|
{
|
|
if (!buf)
|
|
return -EINVAL;
|
|
|
|
if (strcmp(buf, "off") == 0)
|
|
cma_first_wm_low = false;
|
|
else if (strcmp(buf, "on") == 0)
|
|
cma_first_wm_low = true;
|
|
|
|
pr_info("cma_first_wm_low %sabled\n", cma_first_wm_low ? "en" : "dis");
|
|
|
|
return 0;
|
|
}
|
|
|
|
early_param("cma_first_wm_low", early_cma_first_wm_low_param);
|
|
|
|
void check_cma_isolated(unsigned long *isolate,
|
|
unsigned long active, unsigned long inactive)
|
|
{
|
|
long tmp;
|
|
unsigned long raw = *isolate;
|
|
|
|
tmp = *isolate - cma_isolated;
|
|
if (tmp < 0)
|
|
*isolate = 0;
|
|
else
|
|
*isolate = tmp;
|
|
|
|
WARN_ONCE(*isolate > (inactive + active) / 2,
|
|
"isolated:%ld, cma:%ld, inactive:%ld, active:%ld\n",
|
|
raw, cma_isolated, inactive, active);
|
|
}
|
|
|
|
bool can_use_cma(gfp_t gfp_flags)
|
|
{
|
|
if (unlikely(!cma_first_wm_low))
|
|
return false;
|
|
|
|
if (cma_forbidden_mask(gfp_flags))
|
|
return false;
|
|
|
|
if (cma_alloc_ref())
|
|
return false;
|
|
|
|
if (task_nice(current) > 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(can_use_cma);
|
|
|
|
void update_gfp_flags(gfp_t *gfp)
|
|
{
|
|
/*
|
|
* There are 2 bit flags in gfp:
|
|
* __GFP_CMA: indicate to get CMA page from buddy system
|
|
* __GFP_NO_CMA: indicate not get CMA page from buddy system
|
|
*
|
|
* Kernel only allow ZRAM/ANON pages use cma, but cma pool usually
|
|
* very large, if file cache can't use cma(movable) then it will
|
|
* cause system hung. So we should use cma with all movable page but
|
|
* filter some special case which may cause CMA allocation fail by
|
|
* __GFP_NO_CMA.
|
|
*/
|
|
if (can_use_cma(*gfp))
|
|
*gfp |= __GFP_CMA;
|
|
else
|
|
*gfp &= ~__GFP_CMA;
|
|
}
|
|
|
|
#define ACTIVE_MIGRATE 3
|
|
#define INACTIVE_MIGRATE (ACTIVE_MIGRATE * 4)
|
|
static int filecache_need_migrate(struct page *page)
|
|
{
|
|
if (PageActive(page) && page_mapcount(page) >= ACTIVE_MIGRATE)
|
|
return 1;
|
|
|
|
if (!PageActive(page) && page_mapcount(page) >= INACTIVE_MIGRATE)
|
|
return 1;
|
|
|
|
if (PageUnevictable(page))
|
|
return 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void cma_keep_high_active(struct page *page, struct list_head *high,
|
|
struct list_head *clean)
|
|
{
|
|
if (filecache_need_migrate(page)) {
|
|
/*
|
|
* leaving pages with high map count to migrate
|
|
* instead of reclaimed. This can help to avoid
|
|
* file cache jolt if reclaim large cma size
|
|
*/
|
|
list_move(&page->lru, high);
|
|
} else {
|
|
ClearPageActive(page);
|
|
list_move(&page->lru, clean);
|
|
}
|
|
}
|
|
|
|
bool cma_page(struct page *page)
|
|
{
|
|
int migrate_type = 0;
|
|
|
|
if (!page)
|
|
return false;
|
|
migrate_type = get_pageblock_migratetype(page);
|
|
if (is_migrate_cma(migrate_type) ||
|
|
is_migrate_isolate(migrate_type)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL(cma_page);
|
|
#endif
|
|
|
|
#if (IS_MODULE(CONFIG_AMLOGIC_PAGE_TRACE) && IS_MODULE(CONFIG_AMLOGIC_CMA)) || \
|
|
(IS_BUILTIN(CONFIG_AMLOGIC_PAGE_TRACE) && IS_BUILTIN(CONFIG_AMLOGIC_CMA))
|
|
static void update_cma_page_trace(struct page *page, unsigned long cnt)
|
|
{
|
|
long i;
|
|
unsigned long fun;
|
|
|
|
if (!page)
|
|
return;
|
|
|
|
fun = find_back_trace();
|
|
if (cma_alloc_trace)
|
|
pr_info("c a p:%lx, c:%ld, f:%ps\n",
|
|
page_to_pfn(page), cnt, (void *)fun);
|
|
for (i = 0; i < cnt; i++) {
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
set_page_trace(page, 0, __GFP_NO_CMA, (void *)fun);
|
|
#else
|
|
set_page_trace(page, 0, 0, (void *)fun);
|
|
#endif
|
|
page++;
|
|
}
|
|
}
|
|
#endif /* CONFIG_AMLOGIC_PAGE_TRACE */
|
|
|
|
void aml_cma_alloc_pre_hook(int *dummy, int count, unsigned long *tick)
|
|
{
|
|
get_cma_alloc_ref();
|
|
|
|
/* temporary increase task priority if allocate many pages */
|
|
*dummy = task_nice(current);
|
|
*tick = sched_clock();
|
|
if (count >= (pageblock_nr_pages / 2))
|
|
set_user_nice(current, -18);
|
|
}
|
|
|
|
void aml_cma_alloc_post_hook(int *dummy, int count, struct page *page,
|
|
unsigned long tick, int ret)
|
|
{
|
|
put_cma_alloc_ref();
|
|
if (page)
|
|
atomic_long_add(count, &nr_cma_allocated);
|
|
|
|
if (count >= (pageblock_nr_pages / 2))
|
|
set_user_nice(current, *dummy);
|
|
cma_debug(0, NULL, "return page:%lx, tick:%16ld, ret:%d\n",
|
|
page ? page_to_pfn(page) : 0, (unsigned long)sched_clock() - tick, ret);
|
|
#if (IS_MODULE(CONFIG_AMLOGIC_PAGE_TRACE) && IS_MODULE(CONFIG_AMLOGIC_CMA)) || \
|
|
(IS_BUILTIN(CONFIG_AMLOGIC_PAGE_TRACE) && IS_BUILTIN(CONFIG_AMLOGIC_CMA))
|
|
update_cma_page_trace(page, count);
|
|
#endif /* CONFIG_AMLOGIC_PAGE_TRACE */
|
|
}
|
|
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
void check_water_mark(long free_pages, unsigned long mark)
|
|
{
|
|
/* already set */
|
|
if (likely(cma_first_wm_low))
|
|
return;
|
|
|
|
/* cma first after system boot, dont care the watermark when:
|
|
* 1) total ram is smaller than 1G or
|
|
* 2) cma pages is more than 30% of totalram (total - reserved)
|
|
*/
|
|
if ((totalcma_pages * 10 > 3 * totalram_pages() ||
|
|
totalram_pages() < 262144) && system_state == SYSTEM_RUNNING) {
|
|
cma_first_wm_low = true;
|
|
pr_info("Now can use cma1, free:%ld, wm:%ld, cma:%ld, total:%ld\n",
|
|
free_pages, mark, totalcma_pages, totalram_pages());
|
|
}
|
|
if (free_pages <= mark && free_pages > 0) {
|
|
/* do not using cma until water mark is low */
|
|
cma_first_wm_low = true;
|
|
pr_info("Now can use cma, free:%ld, wm:%ld\n",
|
|
free_pages, mark);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_ARM64
|
|
static int clear_cma_pagemap(unsigned long pfn, unsigned long count)
|
|
{
|
|
pgd_t *pgd;
|
|
p4d_t *p4d;
|
|
pud_t *pud;
|
|
pmd_t *pmd;
|
|
unsigned long addr, end;
|
|
struct mm_struct *mm;
|
|
|
|
addr = (unsigned long)pfn_to_kaddr(pfn);
|
|
end = addr + count * PAGE_SIZE;
|
|
mm = &init_mm;
|
|
for (; addr < end; addr += PMD_SIZE) {
|
|
pgd = pgd_offset(mm, addr);
|
|
if (pgd_none(*pgd) || pgd_bad(*pgd))
|
|
break;
|
|
|
|
p4d = p4d_offset(pgd, addr);
|
|
if (p4d_none(*p4d) || p4d_bad(*p4d))
|
|
break;
|
|
|
|
pud = pud_offset(p4d, addr);
|
|
if (pud_none(*pud) || pud_bad(*pud))
|
|
break;
|
|
|
|
pmd = pmd_offset(pud, addr);
|
|
if (pmd_none(*pmd))
|
|
break;
|
|
|
|
pr_debug("%s, addr:%lx, pgd:%p %llx, pmd:%p %llx\n",
|
|
__func__, addr, pgd,
|
|
pgd_val(*pgd), pmd, pmd_val(*pmd));
|
|
pmd_clear(pmd);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int setup_cma_full_pagemap(unsigned long pfn, unsigned long count)
|
|
{
|
|
#ifdef CONFIG_ARM
|
|
/*
|
|
* arm already create level 3 mmu mapping for lowmem cma.
|
|
* And if high mem cma, there is no mapping. So nothing to
|
|
* do for arch arm.
|
|
*/
|
|
return 0;
|
|
#elif defined(CONFIG_ARM64)
|
|
struct vm_area_struct vma = {};
|
|
unsigned long addr, size;
|
|
int ret;
|
|
|
|
clear_cma_pagemap(pfn, count);
|
|
addr = (unsigned long)pfn_to_kaddr(pfn);
|
|
size = count * PAGE_SIZE;
|
|
vma.vm_mm = &init_mm;
|
|
vma.vm_start = addr;
|
|
vma.vm_end = addr + size;
|
|
vma.vm_page_prot = PAGE_KERNEL;
|
|
ret = remap_pfn_range(&vma, addr, pfn,
|
|
size, vma.vm_page_prot);
|
|
if (ret < 0)
|
|
pr_info("%s, remap pte failed:%d, cma:%lx\n",
|
|
__func__, ret, pfn);
|
|
return 0;
|
|
#else
|
|
#error "NOT supported ARCH"
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(setup_cma_full_pagemap);
|
|
|
|
int cma_mmu_op(struct page *page, int count, bool set)
|
|
{
|
|
pgd_t *pgd;
|
|
p4d_t *p4d;
|
|
pud_t *pud;
|
|
pmd_t *pmd;
|
|
pte_t *pte;
|
|
unsigned long addr, end;
|
|
struct mm_struct *mm;
|
|
struct cma *cma;
|
|
|
|
if (!page || PageHighMem(page))
|
|
return -EINVAL;
|
|
|
|
/* TODO: owner must make sure this cma pool have called
|
|
* setup_cma_full_pagemap before call this function
|
|
*/
|
|
if (!cma_page(page)) {
|
|
pr_debug("%s, page:%lx is not cma or no clear-map, cma:%px\n",
|
|
__func__, page_to_pfn(page), cma);
|
|
return -EINVAL;
|
|
}
|
|
|
|
addr = (unsigned long)page_address(page);
|
|
end = addr + count * PAGE_SIZE;
|
|
mm = &init_mm;
|
|
for (; addr < end; addr += PAGE_SIZE) {
|
|
pgd = pgd_offset(mm, addr);
|
|
if (pgd_none(*pgd) || pgd_bad(*pgd))
|
|
break;
|
|
|
|
p4d = p4d_offset(pgd, addr);
|
|
if (p4d_none(*p4d) || p4d_bad(*p4d))
|
|
break;
|
|
|
|
pud = pud_offset(p4d, addr);
|
|
if (pud_none(*pud) || pud_bad(*pud))
|
|
break;
|
|
|
|
pmd = pmd_offset(pud, addr);
|
|
if (pmd_none(*pmd))
|
|
break;
|
|
|
|
pte = pte_offset_map(pmd, addr);
|
|
if (set)
|
|
set_pte_at(mm, addr, pte, mk_pte(page, PAGE_KERNEL));
|
|
else
|
|
pte_clear(mm, addr, pte);
|
|
pte_unmap(pte);
|
|
#ifdef CONFIG_ARM
|
|
pr_debug("%s, add:%lx, pgd:%p %x, pmd:%p %x, pte:%p %x\n",
|
|
__func__, addr, pgd, (int)pgd_val(*pgd),
|
|
pmd, (int)pmd_val(*pmd), pte, (int)pte_val(*pte));
|
|
#elif defined(CONFIG_ARM64)
|
|
pr_debug("%s, add:%lx, pgd:%p %llx, pmd:%p %llx, pte:%p %llx\n",
|
|
__func__, addr, pgd, pgd_val(*pgd),
|
|
pmd, pmd_val(*pmd), pte, pte_val(*pte));
|
|
#endif
|
|
page++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void check_page_to_cma(struct compact_control *cc,
|
|
struct address_space *mapping,
|
|
struct page *page)
|
|
{
|
|
/* no need check once it is true */
|
|
if (test_bit(FORBID_TO_CMA_BIT, &cc->total_migrate_scanned))
|
|
return;
|
|
|
|
if ((unsigned long)mapping & PAGE_MAPPING_ANON)
|
|
mapping = NULL;
|
|
|
|
#ifdef CONFIG_KSM
|
|
if ((((unsigned long)page->mapping & PAGE_MAPPING_FLAGS) == PAGE_MAPPING_KSM) &&
|
|
!PageSlab(page))
|
|
__set_bit(FORBID_TO_CMA_BIT, &cc->total_migrate_scanned);
|
|
#endif
|
|
|
|
if (mapping && cma_forbidden_mask(mapping_gfp_mask(mapping)))
|
|
__set_bit(FORBID_TO_CMA_BIT, &cc->total_migrate_scanned);
|
|
}
|
|
|
|
static int can_migrate_to_cma(struct page *page)
|
|
{
|
|
struct address_space *mapping;
|
|
|
|
mapping = page_mapping(page);
|
|
if ((unsigned long)mapping & PAGE_MAPPING_ANON)
|
|
mapping = NULL;
|
|
|
|
if (PageKsm(page) && !PageSlab(page))
|
|
return 0;
|
|
|
|
if (mapping && cma_forbidden_mask(mapping_gfp_mask(mapping)))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
struct page *get_compact_page(struct page *migratepage,
|
|
struct compact_control *cc)
|
|
{
|
|
int can_to_cma, find = 0;
|
|
struct page *page, *next;
|
|
|
|
can_to_cma = can_migrate_to_cma(migratepage);
|
|
if (!can_to_cma) {
|
|
list_for_each_entry_safe(page, next, &cc->freepages, lru) {
|
|
if (!cma_page(page)) {
|
|
list_del(&page->lru);
|
|
cc->nr_freepages--;
|
|
find = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!find)
|
|
return NULL;
|
|
} else {
|
|
page = list_entry(cc->freepages.next, struct page, lru);
|
|
list_del(&page->lru);
|
|
cc->nr_freepages--;
|
|
}
|
|
return page;
|
|
}
|
|
#endif
|
|
|
|
/* cma alloc/free interface */
|
|
|
|
static unsigned long pfn_max_align_down(unsigned long pfn)
|
|
{
|
|
return pfn & ~(max_t(unsigned long, MAX_ORDER_NR_PAGES,
|
|
pageblock_nr_pages) - 1);
|
|
}
|
|
|
|
static unsigned long aml_pfn_max_align_up(unsigned long pfn)
|
|
{
|
|
return ALIGN(pfn, max_t(unsigned long, MAX_ORDER_NR_PAGES,
|
|
pageblock_nr_pages));
|
|
}
|
|
|
|
static struct page *get_migrate_page(struct page *page, unsigned long private)
|
|
{
|
|
struct migration_target_control *mtc;
|
|
gfp_t gfp_mask;
|
|
unsigned int order = 0;
|
|
struct page *new_page = NULL;
|
|
int nid;
|
|
int zidx;
|
|
|
|
mtc = (struct migration_target_control *)private;
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
gfp_mask = mtc->gfp_mask | __GFP_NO_CMA;
|
|
#else
|
|
gfp_mask = mtc->gfp_mask;
|
|
#endif
|
|
nid = mtc->nid;
|
|
if (nid == NUMA_NO_NODE)
|
|
nid = page_to_nid(page);
|
|
|
|
/*
|
|
* TODO: allocate a destination hugepage from a nearest neighbor node,
|
|
* accordance with memory policy of the user process if possible. For
|
|
* now as a simple work-around, we use the next node for destination.
|
|
*/
|
|
if (PageHuge(page)) {
|
|
struct hstate *h = page_hstate(compound_head(page));
|
|
|
|
gfp_mask |= htlb_modify_alloc_mask(h, gfp_mask);
|
|
new_page = alloc_huge_page_nodemask(h,
|
|
page_to_nid(page),
|
|
0, gfp_mask);
|
|
#if (IS_MODULE(CONFIG_AMLOGIC_PAGE_TRACE) && IS_MODULE(CONFIG_AMLOGIC_CMA)) || \
|
|
(IS_BUILTIN(CONFIG_AMLOGIC_PAGE_TRACE) && IS_BUILTIN(CONFIG_AMLOGIC_CMA))
|
|
#ifdef CONFIG_HUGETLB_PAGE
|
|
replace_page_trace(new_page, page);
|
|
#endif
|
|
#endif
|
|
return new_page;
|
|
}
|
|
|
|
if (PageTransHuge(page)) {
|
|
/*
|
|
* clear __GFP_RECLAIM to make the migration callback
|
|
* consistent with regular THP allocations.
|
|
*/
|
|
gfp_mask &= ~__GFP_RECLAIM;
|
|
gfp_mask |= GFP_TRANSHUGE;
|
|
order = HPAGE_PMD_ORDER;
|
|
}
|
|
zidx = zone_idx(page_zone(page));
|
|
if (is_highmem_idx(zidx) || zidx == ZONE_MOVABLE)
|
|
gfp_mask |= __GFP_HIGHMEM;
|
|
|
|
new_page = __alloc_pages(gfp_mask, order, nid, mtc->nmask);
|
|
|
|
if (new_page && PageTransHuge(new_page))
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
prep_transhuge_page(new_page);
|
|
#else
|
|
aml_prep_huge_page(new_page);
|
|
#endif
|
|
|
|
#if (IS_MODULE(CONFIG_AMLOGIC_PAGE_TRACE) && IS_MODULE(CONFIG_AMLOGIC_CMA)) || \
|
|
(IS_BUILTIN(CONFIG_AMLOGIC_PAGE_TRACE) && IS_BUILTIN(CONFIG_AMLOGIC_CMA))
|
|
replace_page_trace(new_page, page);
|
|
#endif
|
|
return new_page;
|
|
}
|
|
|
|
#if defined(CONFIG_DYNAMIC_DEBUG) || \
|
|
(defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
|
|
/* Usage: See admin-guide/dynamic-debug-howto.rst */
|
|
static void alloc_contig_dump_pages(struct list_head *page_list)
|
|
{
|
|
DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, "migrate failure");
|
|
|
|
if (DYNAMIC_DEBUG_BRANCH(descriptor)) {
|
|
struct page *page;
|
|
|
|
dump_stack();
|
|
list_for_each_entry(page, page_list, lru)
|
|
dump_page(page, "migration failure");
|
|
}
|
|
}
|
|
#else
|
|
static inline void alloc_contig_dump_pages(struct list_head *page_list)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
/* [start, end) must belong to a single zone. */
|
|
static int __nocfi aml_alloc_contig_migrate_range(struct compact_control *cc,
|
|
unsigned long start,
|
|
unsigned long end, bool boost,
|
|
struct task_struct *host)
|
|
{
|
|
/* This function is based on compact_zone() from compaction.c. */
|
|
unsigned long nr_reclaimed;
|
|
unsigned long pfn = start;
|
|
unsigned int tries = 0;
|
|
int ret = 0;
|
|
struct migration_target_control mtc = {
|
|
.nid = zone_to_nid(cc->zone),
|
|
.gfp_mask = GFP_USER | __GFP_MOVABLE | __GFP_RETRY_MAYFAIL,
|
|
};
|
|
|
|
while (pfn < end || !list_empty(&cc->migratepages)) {
|
|
if (fatal_signal_pending(host)) {
|
|
ret = -EINTR;
|
|
break;
|
|
}
|
|
|
|
if (list_empty(&cc->migratepages)) {
|
|
cc->nr_migratepages = 0;
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
ret = isolate_migratepages_range(cc, pfn, end);
|
|
#else
|
|
ret = aml_isolate_pages_range(cc, pfn, end);
|
|
#endif
|
|
if (ret && ret != -EAGAIN) {
|
|
cma_debug(1, NULL, " iso migrate page fail, ret:%d\n",
|
|
ret);
|
|
break;
|
|
}
|
|
pfn = cc->migrate_pfn;
|
|
tries = 0;
|
|
} else if (++tries == 5) {
|
|
ret = -EBUSY;
|
|
break;
|
|
}
|
|
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
nr_reclaimed = reclaim_clean_pages_from_list(cc->zone,
|
|
&cc->migratepages);
|
|
#else
|
|
nr_reclaimed = aml_reclaim_clean_pages_from_list(cc->zone,
|
|
&cc->migratepages);
|
|
#endif
|
|
cc->nr_migratepages -= nr_reclaimed;
|
|
|
|
ret = migrate_pages(&cc->migratepages, get_migrate_page,
|
|
NULL, (unsigned long)&mtc, cc->mode, MR_CONTIG_RANGE, NULL);
|
|
|
|
/*
|
|
* On -ENOMEM, migrate_pages() bails out right away. It is pointless
|
|
* to retry again over this error, so do the same here.
|
|
*/
|
|
if (ret == -ENOMEM)
|
|
break;
|
|
}
|
|
|
|
if (ret < 0) {
|
|
if (ret == -EBUSY) {
|
|
struct page *page;
|
|
|
|
alloc_contig_dump_pages(&cc->migratepages);
|
|
list_for_each_entry(page, &cc->migratepages, lru) {
|
|
/* The page will be freed by putback_movable_pages soon */
|
|
if (page_count(page) == 1)
|
|
continue;
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
page_pinner_failure_detect(page);
|
|
#else
|
|
aml_page_pinner_failure_detect(page);
|
|
#endif
|
|
}
|
|
}
|
|
putback_movable_pages(&cc->migratepages);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __nocfi cma_boost_work_func(void *cma_data)
|
|
{
|
|
struct cma_pcp *c_work;
|
|
struct work_cma *job;
|
|
unsigned long pfn, end;
|
|
int ret = -1;
|
|
int this_cpu;
|
|
struct compact_control cc = {
|
|
.nr_migratepages = 0,
|
|
.order = -1,
|
|
.mode = MIGRATE_SYNC,
|
|
.ignore_skip_hint = true,
|
|
.no_set_skip_hint = true,
|
|
.gfp_mask = GFP_KERNEL,
|
|
.alloc_contig = true,
|
|
};
|
|
|
|
c_work = (struct cma_pcp *)cma_data;
|
|
for (;;) {
|
|
ret = wait_for_completion_interruptible(&c_work->start);
|
|
if (ret < 0) {
|
|
pr_err("%s wait for task %d is %d\n",
|
|
__func__, c_work->cpu, ret);
|
|
continue;
|
|
}
|
|
this_cpu = get_cpu();
|
|
put_cpu();
|
|
if (this_cpu != c_work->cpu) {
|
|
pr_err("%s, cpu %d is not work cpu:%d\n",
|
|
__func__, this_cpu, c_work->cpu);
|
|
}
|
|
again:
|
|
spin_lock(&work_list_lock);
|
|
if (list_empty(&work_list)) {
|
|
/* NO job todo ? */
|
|
spin_unlock(&work_list_lock);
|
|
cma_debug(1, NULL, "%s,%d, list empty\n", __func__, __LINE__);
|
|
goto next;
|
|
}
|
|
job = list_first_entry(&work_list, struct work_cma, list);
|
|
list_del(&job->list);
|
|
spin_unlock(&work_list_lock);
|
|
|
|
INIT_LIST_HEAD(&cc.migratepages);
|
|
pfn = job->pfn;
|
|
cc.zone = page_zone(pfn_to_page(pfn));
|
|
end = pfn + job->count;
|
|
ret = aml_alloc_contig_migrate_range(&cc, pfn, end,
|
|
1, job->host);
|
|
job->ret = ret;
|
|
if (!ret) {
|
|
goto again;
|
|
} else if (ret == -EBUSY) {
|
|
spin_lock(&work_list_lock);
|
|
job->ret = 0;
|
|
list_add(&job->list, &work_list);
|
|
spin_unlock(&work_list_lock);
|
|
cma_debug(1, pfn_to_page(pfn), "contig migrate failed\n");
|
|
goto again;
|
|
} else {
|
|
pr_err("failed, ret:%d\n", ret);
|
|
}
|
|
next:
|
|
complete(&c_work->end);
|
|
if (kthread_should_stop()) {
|
|
pr_err("%s task exit\n", __func__);
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __init init_cma_boost_task(void)
|
|
{
|
|
int cpu;
|
|
struct task_struct *task;
|
|
struct cma_pcp *work;
|
|
char task_name[20] = {};
|
|
INIT_LIST_HEAD(&work_list);
|
|
spin_lock_init(&work_list_lock);
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
memset(task_name, 0, sizeof(task_name));
|
|
sprintf(task_name, "cma_task%d", cpu);
|
|
work = &per_cpu(cma_pcp_thread, cpu);
|
|
init_completion(&work->start);
|
|
init_completion(&work->end);
|
|
work->cpu = cpu;
|
|
task = kthread_create(cma_boost_work_func, work, task_name);
|
|
if (!IS_ERR(task)) {
|
|
kthread_bind(task, cpu);
|
|
set_user_nice(task, -17);
|
|
work->task = task;
|
|
pr_debug("create cma task%p, for cpu %d\n", task, cpu);
|
|
wake_up_process(task);
|
|
} else {
|
|
can_boost = 0;
|
|
pr_err("create task for cpu %d fail:%p\n", cpu, task);
|
|
return -1;
|
|
}
|
|
}
|
|
can_boost = 1;
|
|
return 0;
|
|
}
|
|
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
module_init(init_cma_boost_task);
|
|
#endif
|
|
|
|
int cma_alloc_contig_boost(unsigned long start_pfn, unsigned long count)
|
|
{
|
|
struct cpumask has_work;
|
|
int cpu, cpus, i = 0, ret = 0, ebusy = 0, einv = 0;
|
|
atomic_t ok;
|
|
unsigned long delta;
|
|
unsigned long cnt;
|
|
unsigned long flags;
|
|
struct cma_pcp *work;
|
|
struct work_cma job[MAX_JOB_NUM] = {};
|
|
|
|
cpumask_clear(&has_work);
|
|
|
|
if (allow_cma_tasks)
|
|
cpus = allow_cma_tasks;
|
|
else
|
|
cpus = num_online_cpus() - 1;
|
|
|
|
/* cnt = count; */
|
|
if (count < pageblock_nr_pages) {
|
|
delta = count / cpus;
|
|
cnt = cpus;
|
|
} else if (count <= pageblock_nr_pages * 10) {
|
|
delta = 256;
|
|
cnt = count / delta;
|
|
} else if (count <= pageblock_nr_pages * 20) {
|
|
delta = 512;
|
|
cnt = count / delta;
|
|
} else {
|
|
delta = count / MAX_JOB_NUM;
|
|
cnt = MAX_JOB_NUM;
|
|
}
|
|
if (cnt > MAX_JOB_NUM) {
|
|
pr_err("cnt too large: %ld, delta: %ld, count: %ld\n", cnt, delta, count);
|
|
return -ENOMEM;
|
|
}
|
|
spin_lock(&work_list_lock);
|
|
if (!list_empty(&work_list))
|
|
list_del_init(&work_list);
|
|
for (i = 0; i < cnt; i++) {
|
|
INIT_LIST_HEAD(&job[i].list);
|
|
job[i].pfn = start_pfn + i * delta;
|
|
job[i].count = delta;
|
|
job[i].ret = 0;
|
|
job[i].host = current;
|
|
if (i == cnt - 1)
|
|
job[i].count = count - i * delta;
|
|
list_add(&job[i].list, &work_list);
|
|
}
|
|
spin_unlock(&work_list_lock);
|
|
i = 0;
|
|
|
|
atomic_set(&ok, 0);
|
|
local_irq_save(flags);
|
|
for_each_online_cpu(cpu) {
|
|
work = &per_cpu(cma_pcp_thread, cpu);
|
|
cpumask_set_cpu(cpu, &has_work);
|
|
complete(&work->start);
|
|
i++;
|
|
if (i == cpus) {
|
|
cma_debug(1, NULL, "sched work to %d cpu\n", i);
|
|
break;
|
|
}
|
|
}
|
|
local_irq_restore(flags);
|
|
|
|
for_each_cpu(cpu, &has_work) {
|
|
work = &per_cpu(cma_pcp_thread, cpu);
|
|
wait_for_completion(&work->end);
|
|
if (job[cpu].ret) {
|
|
if (job[cpu].ret != -EBUSY)
|
|
einv++;
|
|
else
|
|
ebusy++;
|
|
}
|
|
}
|
|
|
|
if (einv)
|
|
ret = -EINVAL;
|
|
else if (ebusy)
|
|
ret = -EBUSY;
|
|
else
|
|
ret = 0;
|
|
|
|
if (ret < 0 && ret != -EBUSY) {
|
|
pr_err("%s, failed, ret:%d, ok:%d\n",
|
|
__func__, ret, atomic_read(&ok));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __nocfi __aml_check_pageblock_isolate(unsigned long pfn,
|
|
unsigned long end_pfn,
|
|
int flags)
|
|
{
|
|
struct page *page;
|
|
|
|
while (pfn < end_pfn) {
|
|
page = pfn_to_page(pfn);
|
|
if (PageBuddy(page)) {
|
|
/*
|
|
* If the page is on a free list, it has to be on
|
|
* the correct MIGRATE_ISOLATE freelist. There is no
|
|
* simple way to verify that as VM_BUG_ON(), though.
|
|
*/
|
|
pfn += 1 << buddy_order(page);
|
|
} else if ((flags & MEMORY_OFFLINE) && PageHWPoison(page)) {
|
|
/* A HWPoisoned page cannot be also PageBuddy */
|
|
pfn++;
|
|
} else if ((flags & MEMORY_OFFLINE) && PageOffline(page) &&
|
|
!page_count(page)) {
|
|
/*
|
|
* The responsible driver agreed to skip PageOffline()
|
|
* pages when offlining memory by dropping its
|
|
* reference in MEM_GOING_OFFLINE.
|
|
*/
|
|
pfn++;
|
|
} else {
|
|
cma_debug(1, page, " isolate failed\n");
|
|
aml__dump_owner(page);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pfn;
|
|
}
|
|
|
|
#if IS_MODULE(CONFIG_AMLOGIC_CMA)
|
|
#define aml_pfn_to_online_page(pfn) \
|
|
({ \
|
|
struct page *___page = NULL; \
|
|
if (pfn_valid(pfn)) \
|
|
___page = pfn_to_page(pfn); \
|
|
___page; \
|
|
})
|
|
#endif
|
|
|
|
static inline struct page * __nocfi
|
|
check_page_valid(unsigned long pfn, unsigned long nr_pages)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nr_pages; i++) {
|
|
struct page *page;
|
|
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
page = pfn_to_online_page(pfn + i);
|
|
#else
|
|
page = aml_pfn_to_online_page(pfn + i);
|
|
#endif
|
|
if (!page)
|
|
continue;
|
|
return page;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int __nocfi aml_check_pages_isolated(unsigned long start_pfn, unsigned long end_pfn,
|
|
int isol_flags)
|
|
{
|
|
unsigned long pfn, flags;
|
|
struct page *page;
|
|
struct zone *zone;
|
|
int ret;
|
|
|
|
/*
|
|
* Note: pageblock_nr_pages != MAX_ORDER. Then, chunks of free pages
|
|
* are not aligned to pageblock_nr_pages.
|
|
* Then we just check migratetype first.
|
|
*/
|
|
for (pfn = start_pfn; pfn < end_pfn; pfn += pageblock_nr_pages) {
|
|
page = check_page_valid(pfn, pageblock_nr_pages);
|
|
if (page && !is_migrate_isolate_page(page))
|
|
break;
|
|
}
|
|
page = check_page_valid(start_pfn, end_pfn - start_pfn);
|
|
if (pfn < end_pfn || !page) {
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
/* Check all pages are free or marked as ISOLATED */
|
|
zone = page_zone(page);
|
|
spin_lock_irqsave(&zone->lock, flags);
|
|
pfn = __aml_check_pageblock_isolate(start_pfn, end_pfn, isol_flags);
|
|
spin_unlock_irqrestore(&zone->lock, flags);
|
|
|
|
ret = pfn < end_pfn ? -EBUSY : 0;
|
|
|
|
out:
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
trace_test_pages_isolated(start_pfn, end_pfn, pfn);
|
|
#endif
|
|
if (pfn < end_pfn)
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
page_pinner_failure_detect(pfn_to_page(pfn));
|
|
#else
|
|
aml_page_pinner_failure_detect(pfn_to_page(pfn));
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static unsigned long cur_alloc_start;
|
|
static unsigned long cur_alloc_end;
|
|
|
|
int in_cma_allocating(struct page *page)
|
|
{
|
|
unsigned long pfn;
|
|
|
|
if (!page)
|
|
return 0;
|
|
pfn = page_to_pfn(page);
|
|
if (pfn >= cur_alloc_start && pfn <= cur_alloc_end)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int __nocfi aml_cma_alloc_range(unsigned long start, unsigned long end,
|
|
unsigned int migrate_type, gfp_t gfp_mask)
|
|
{
|
|
unsigned long outer_start, outer_end;
|
|
int ret = 0, order;
|
|
int try_times = 0;
|
|
int boost_ok = 0;
|
|
#if CONFIG_AMLOGIC_KERNEL_VERSION >= 14515
|
|
unsigned long failed_pfn;
|
|
#endif
|
|
|
|
struct compact_control cc = {
|
|
.nr_migratepages = 0,
|
|
.order = -1,
|
|
.zone = page_zone(pfn_to_page(start)),
|
|
.mode = MIGRATE_SYNC,
|
|
.ignore_skip_hint = true,
|
|
.no_set_skip_hint = true,
|
|
.gfp_mask = current_gfp_context(gfp_mask),
|
|
.alloc_contig = true,
|
|
};
|
|
INIT_LIST_HEAD(&cc.migratepages);
|
|
|
|
cma_debug(0, NULL, " range [%lx-%lx]\n", start, end);
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
mutex_lock(&cma_mutex);
|
|
#if CONFIG_AMLOGIC_KERNEL_VERSION >= 14515
|
|
ret = start_isolate_page_range(pfn_max_align_down(start),
|
|
aml_pfn_max_align_up(end), migrate_type,
|
|
0, &failed_pfn);
|
|
#else
|
|
ret = start_isolate_page_range(pfn_max_align_down(start),
|
|
aml_pfn_max_align_up(end), migrate_type, 0);
|
|
#endif
|
|
#else
|
|
#if CONFIG_AMLOGIC_KERNEL_VERSION >= 14515
|
|
ret = aml_start_isolate_page_range(pfn_max_align_down(start),
|
|
aml_pfn_max_align_up(end), migrate_type,
|
|
0, &failed_pfn);
|
|
#else
|
|
ret = aml_start_isolate_page_range(pfn_max_align_down(start),
|
|
aml_pfn_max_align_up(end), migrate_type, 0);
|
|
#endif
|
|
#endif
|
|
if (ret < 0) {
|
|
cma_debug(1, NULL, "ret:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
cur_alloc_start = start;
|
|
cur_alloc_end = end;
|
|
cma_isolated += (aml_pfn_max_align_up(end) - pfn_max_align_down(start));
|
|
try_again:
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
lru_add_drain_all();
|
|
drain_all_pages(cc.zone);
|
|
#else
|
|
aml_lru_add_drain_all();
|
|
aml_drain_all_pages(cc.zone);
|
|
#endif
|
|
/*
|
|
* try to use more cpu to do this job when alloc count is large
|
|
*/
|
|
cpus_read_lock();
|
|
if ((num_online_cpus() > 1) && can_boost &&
|
|
((end - start) >= pageblock_nr_pages / 2)) {
|
|
ret = cma_alloc_contig_boost(start, end - start);
|
|
boost_ok = !ret ? 1 : 0;
|
|
} else {
|
|
ret = aml_alloc_contig_migrate_range(&cc, start,
|
|
end, 0, current);
|
|
}
|
|
cpus_read_unlock();
|
|
|
|
if (ret && ret != -EBUSY) {
|
|
cma_debug(1, NULL, "ret:%d\n", ret);
|
|
goto done;
|
|
}
|
|
|
|
ret = 0;
|
|
order = 0;
|
|
outer_start = start;
|
|
while (!PageBuddy(pfn_to_page(outer_start))) {
|
|
if (++order >= MAX_ORDER) {
|
|
outer_start = start;
|
|
break;
|
|
}
|
|
outer_start &= ~0UL << order;
|
|
}
|
|
|
|
if (outer_start != start) {
|
|
order = buddy_order(pfn_to_page(outer_start));
|
|
|
|
/*
|
|
* outer_start page could be small order buddy page and
|
|
* it doesn't include start page. Adjust outer_start
|
|
* in this case to report failed page properly
|
|
* on tracepoint in test_pages_isolated()
|
|
*/
|
|
if (outer_start + (1UL << order) <= start)
|
|
outer_start = start;
|
|
}
|
|
|
|
/* Make sure the range is really isolated. */
|
|
if (aml_check_pages_isolated(outer_start, end, false)) {
|
|
cma_debug(1, NULL, "check page isolate(%lx, %lx) failed\n",
|
|
outer_start, end);
|
|
try_times++;
|
|
if (try_times < 10)
|
|
goto try_again;
|
|
ret = -EBUSY;
|
|
goto done;
|
|
}
|
|
|
|
/* Grab isolated pages from freelists. */
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
outer_end = isolate_freepages_range(&cc, outer_start, end);
|
|
#else
|
|
outer_end = aml_iso_free_range(&cc, outer_start, end);
|
|
#endif
|
|
if (!outer_end) {
|
|
ret = -EBUSY;
|
|
cma_debug(1, NULL, "iso free range(%lx, %lx) failed\n",
|
|
outer_start, end);
|
|
goto done;
|
|
}
|
|
|
|
/* Free head and tail (if any) */
|
|
if (start != outer_start)
|
|
aml_cma_free(outer_start, start - outer_start, 0);
|
|
if (end != outer_end)
|
|
aml_cma_free(end, outer_end - end, 0);
|
|
|
|
done:
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
undo_isolate_page_range(pfn_max_align_down(start),
|
|
aml_pfn_max_align_up(end), migrate_type);
|
|
#else
|
|
aml_undo_isolate_page_range(pfn_max_align_down(start),
|
|
aml_pfn_max_align_up(end), migrate_type);
|
|
#endif
|
|
cma_isolated -= (aml_pfn_max_align_up(end) - pfn_max_align_down(start));
|
|
cur_alloc_start = 0;
|
|
cur_alloc_end = 0;
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
mutex_unlock(&cma_mutex);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __aml_cma_free_check(struct page *page, int order, unsigned int *cnt)
|
|
{
|
|
int i;
|
|
int ref = 0;
|
|
|
|
/*
|
|
* clear ref count, head page should avoid this operation.
|
|
* ref count of head page will be cleared when __free_pages
|
|
* is called.
|
|
*/
|
|
for (i = 1; i < (1 << order); i++) {
|
|
if (!put_page_testzero(page + i))
|
|
ref++;
|
|
}
|
|
if (ref) {
|
|
pr_info("%s, %d pages are still in use\n", __func__, ref);
|
|
*cnt += ref;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int aml_cma_get_page_order(unsigned long pfn)
|
|
{
|
|
int i, mask = 1;
|
|
|
|
for (i = 0; i < (MAX_ORDER - 1); i++) {
|
|
if (pfn & (mask << i))
|
|
break;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
void aml_cma_free(unsigned long pfn, unsigned int nr_pages, int update)
|
|
{
|
|
unsigned int count = 0;
|
|
struct page *page;
|
|
int free_order, start_order = 0;
|
|
int batch;
|
|
unsigned int orig_nr_pages = nr_pages;
|
|
|
|
while (nr_pages) {
|
|
page = pfn_to_page(pfn);
|
|
free_order = aml_cma_get_page_order(pfn);
|
|
if (nr_pages >= (1 << free_order)) {
|
|
start_order = free_order;
|
|
} else {
|
|
/* remain pages is not enough */
|
|
start_order = 0;
|
|
while (nr_pages >= (1 << start_order))
|
|
start_order++;
|
|
start_order--;
|
|
}
|
|
batch = (1 << start_order);
|
|
if (__aml_cma_free_check(page, start_order, &count))
|
|
break;
|
|
__free_pages(page, start_order);
|
|
pr_debug("pages:%4d, free:%2d, start:%2d, batch:%4d, pfn:%lx\n",
|
|
nr_pages, free_order,
|
|
start_order, batch, pfn);
|
|
nr_pages -= batch;
|
|
pfn += batch;
|
|
}
|
|
WARN(count != 0, "%d pages are still in use!\n", count);
|
|
if (update) {
|
|
#if (IS_MODULE(CONFIG_AMLOGIC_PAGE_TRACE) && IS_MODULE(CONFIG_AMLOGIC_CMA)) || \
|
|
(IS_BUILTIN(CONFIG_AMLOGIC_PAGE_TRACE) && IS_BUILTIN(CONFIG_AMLOGIC_CMA))
|
|
if (cma_alloc_trace)
|
|
pr_info("c f p:%lx, c:%d, f:%ps\n",
|
|
pfn, count, (void *)find_back_trace());
|
|
#endif /* CONFIG_AMLOGIC_PAGE_TRACE */
|
|
atomic_long_sub(orig_nr_pages, &nr_cma_allocated);
|
|
}
|
|
}
|
|
|
|
static bool cma_vma_show(struct page *page, struct vm_area_struct *vma,
|
|
unsigned long addr, void *arg)
|
|
{
|
|
#if defined(CONFIG_AMLOGIC_USER_FAULT) && IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
struct mm_struct *mm = vma->vm_mm;
|
|
|
|
show_vma(mm, addr);
|
|
#endif
|
|
return false; /* keep loop */
|
|
}
|
|
|
|
void rmap_walk_vma(struct page *page)
|
|
{
|
|
struct rmap_walk_control rwc = {
|
|
.rmap_one = cma_vma_show,
|
|
};
|
|
|
|
pr_info("%s, show map for page:%lx,f:%lx, m:%px, p:%d\n",
|
|
__func__, page_to_pfn(page), page->flags,
|
|
page->mapping, page_count(page));
|
|
if (!page_mapping(page))
|
|
return;
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
rmap_walk(page, &rwc);
|
|
#else
|
|
aml_rmap_walk(page, &rwc);
|
|
#endif
|
|
}
|
|
|
|
void __nocfi show_page(struct page *page)
|
|
{
|
|
unsigned long trace = 0;
|
|
unsigned long map_flag = -1UL;
|
|
|
|
if (!page)
|
|
return;
|
|
#if (IS_MODULE(CONFIG_AMLOGIC_PAGE_TRACE) && IS_MODULE(CONFIG_AMLOGIC_CMA)) || \
|
|
(IS_BUILTIN(CONFIG_AMLOGIC_PAGE_TRACE) && IS_BUILTIN(CONFIG_AMLOGIC_CMA))
|
|
trace = get_page_trace(page);
|
|
#endif
|
|
if (page->mapping && !((unsigned long)page->mapping & 0x3))
|
|
map_flag = page->mapping->flags;
|
|
pr_info("page:%lx, map:%lx, mf:%lx, pf:%lx, m:%d, c:%d, o:%lx, pt:%lx, f:%ps\n",
|
|
page_to_pfn(page), (unsigned long)page->mapping, map_flag,
|
|
page->flags & 0xffffffff,
|
|
page_mapcount(page), page_count(page), page->private, page->index,
|
|
(void *)trace);
|
|
aml__dump_owner(page);
|
|
if (cma_debug_level > 4 && !irqs_disabled())
|
|
rmap_walk_vma(page);
|
|
}
|
|
|
|
static int cma_debug_show(struct seq_file *m, void *arg)
|
|
{
|
|
seq_printf(m, "level=%d, alloc trace:%d, allow task:%d\n",
|
|
cma_debug_level, cma_alloc_trace, allow_cma_tasks);
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
seq_printf(m, "driver used:%lu isolated:%d total:%lu\n",
|
|
get_cma_allocated(), 0, totalcma_pages);
|
|
#else
|
|
seq_printf(m, "driver used:%lu isolated:%d total:%lu\n",
|
|
get_cma_allocated(), 0, aml_totalcma_pages);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t cma_debug_write(struct file *file, const char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int arg = 0;
|
|
int ok = 0;
|
|
int cpu;
|
|
struct cma_pcp *work;
|
|
char *buf;
|
|
|
|
buf = kmalloc(count, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(buf, buffer, count))
|
|
goto exit;
|
|
|
|
if (!strncmp(buf, "cma_task=", 9)) { /* option for 'cma_task=' */
|
|
if (sscanf(buf, "cma_task=%d", &arg) < 0)
|
|
goto exit;
|
|
if (arg <= num_online_cpus() && arg >= 1) {
|
|
ok = 1;
|
|
allow_cma_tasks = arg;
|
|
pr_info("set allow_cma_tasks to %d\n", allow_cma_tasks);
|
|
}
|
|
goto exit;
|
|
}
|
|
|
|
if (!strncmp(buf, "cma_prio=", 9)) { /* option for 'cma_prio=' */
|
|
if (sscanf(buf, "cma_prio=%d", &arg) < 0)
|
|
goto exit;
|
|
if (arg >= MIN_NICE && arg < MAX_NICE) {
|
|
for_each_possible_cpu(cpu) {
|
|
work = &per_cpu(cma_pcp_thread, cpu);
|
|
set_user_nice(work->task, arg);
|
|
}
|
|
ok = 1;
|
|
pr_info("renice cma task to %d\n", arg);
|
|
}
|
|
goto exit;
|
|
}
|
|
|
|
if (!strncmp(buf, "cma_trace=", 9)) { /* option for 'cma_trace=' */
|
|
if (sscanf(buf, "cma_trace=%d", &arg) < 0)
|
|
goto exit;
|
|
|
|
cma_alloc_trace = arg ? 1 : 0;
|
|
ok = 1;
|
|
goto exit;
|
|
}
|
|
|
|
if (!kstrtoint(buf, 10, &arg)) {
|
|
if (arg > MAX_DEBUG_LEVEL)
|
|
goto exit;
|
|
|
|
ok = 1;
|
|
cma_debug_level = arg;
|
|
goto exit;
|
|
}
|
|
exit:
|
|
kfree(buf);
|
|
if (ok)
|
|
return count;
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int cma_debug_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, cma_debug_show, NULL);
|
|
}
|
|
|
|
static const struct proc_ops cma_dbg_file_ops = {
|
|
.proc_open = cma_debug_open,
|
|
.proc_read = seq_read,
|
|
.proc_lseek = seq_lseek,
|
|
.proc_write = cma_debug_write,
|
|
.proc_release = single_release,
|
|
};
|
|
|
|
#if IS_BUILTIN(CONFIG_AMLOGIC_CMA)
|
|
static int __init aml_cma_init(void)
|
|
{
|
|
atomic_set(&cma_allocate, 0);
|
|
atomic_long_set(&nr_cma_allocated, 0);
|
|
|
|
dentry = proc_create("cma_debug", 0644, NULL, &cma_dbg_file_ops);
|
|
if (IS_ERR_OR_NULL(dentry)) {
|
|
pr_err("%s, create sysfs failed\n", __func__);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
arch_initcall(aml_cma_init);
|
|
#else
|
|
/**
|
|
* cma_alloc() - allocate pages from contiguous area
|
|
* @cma: Contiguous memory region for which the allocation is performed.
|
|
* @count: Requested number of pages.
|
|
* @align: Requested alignment of pages (in PAGE_SIZE order).
|
|
* @no_warn: Avoid printing message about failed allocation
|
|
*
|
|
* This function allocates part of contiguous memory on specific
|
|
* contiguous memory area.
|
|
*/
|
|
struct page *aml_cma_alloc(struct dummy_cma *cma, unsigned long count,
|
|
unsigned int align, bool no_warn)
|
|
{
|
|
unsigned long mask, offset;
|
|
unsigned long pfn = -1;
|
|
unsigned long start = 0;
|
|
unsigned long bitmap_maxno, bitmap_no, bitmap_count;
|
|
unsigned long i;
|
|
struct page *page = NULL;
|
|
int ret = -ENOMEM;
|
|
int num_attempts = 0;
|
|
int max_retries = 5;
|
|
int dummy;
|
|
unsigned long tick = 0;
|
|
unsigned long long in_tick, timeout;
|
|
|
|
in_tick = sched_clock();
|
|
|
|
if (!cma || !cma->count || !cma->bitmap)
|
|
goto out;
|
|
|
|
cma_debug(0, NULL, "(cma %p, count %lu, align %d)\n",
|
|
(void *)cma, count, align);
|
|
in_tick = sched_clock();
|
|
timeout = 2ULL * 1000000 * (1 + ((count * PAGE_SIZE) >> 20));
|
|
|
|
if (!count)
|
|
goto out;
|
|
|
|
//trace_cma_alloc_start(cma->name, count, align);
|
|
|
|
mask = cma_bitmap_aligned_mask(cma, align);
|
|
offset = cma_bitmap_aligned_offset(cma, align);
|
|
bitmap_maxno = cma_bitmap_maxno(cma);
|
|
bitmap_count = cma_bitmap_pages_to_bits(cma, count);
|
|
|
|
if (bitmap_count > bitmap_maxno)
|
|
goto out;
|
|
|
|
aml_cma_alloc_pre_hook(&dummy, count, &tick);
|
|
//trace_android_vh_cma_alloc_retry(cma->name, &max_retries);
|
|
for (;;) {
|
|
spin_lock_irq(&cma->lock);
|
|
bitmap_no = bitmap_find_next_zero_area_off(cma->bitmap,
|
|
bitmap_maxno, start, bitmap_count, mask,
|
|
offset);
|
|
if (bitmap_no >= bitmap_maxno) {
|
|
if ((num_attempts < max_retries) && (ret == -EBUSY)) {
|
|
spin_unlock_irq(&cma->lock);
|
|
|
|
if (fatal_signal_pending(current)) {
|
|
ret = -EINTR;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Page may be momentarily pinned by some other
|
|
* process which has been scheduled out, e.g.
|
|
* in exit path, during unmap call, or process
|
|
* fork and so cannot be freed there. Sleep
|
|
* for 100ms and retry the allocation.
|
|
*/
|
|
start = 0;
|
|
ret = -ENOMEM;
|
|
schedule_timeout_killable(msecs_to_jiffies(100));
|
|
num_attempts++;
|
|
continue;
|
|
} else {
|
|
spin_unlock_irq(&cma->lock);
|
|
break;
|
|
}
|
|
}
|
|
bitmap_set(cma->bitmap, bitmap_no, bitmap_count);
|
|
/*
|
|
* It's safe to drop the lock here. We've marked this region for
|
|
* our exclusive use. If the migration fails we will take the
|
|
* lock again and unmark it.
|
|
*/
|
|
spin_unlock_irq(&cma->lock);
|
|
|
|
pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit);
|
|
mutex_lock(&cma_mutex);
|
|
ret = aml_cma_alloc_range(pfn, pfn + count, MIGRATE_CMA,
|
|
GFP_KERNEL | (no_warn ? __GFP_NOWARN : 0));
|
|
mutex_unlock(&cma_mutex);
|
|
if (ret == 0) {
|
|
page = pfn_to_page(pfn);
|
|
break;
|
|
}
|
|
|
|
cma_clear_bitmap(cma, pfn, count);
|
|
if (ret != -EBUSY)
|
|
break;
|
|
|
|
cma_debug(0, NULL, "memory range at %p is busy, retrying\n",
|
|
pfn_to_page(pfn));
|
|
|
|
//trace_cma_alloc_busy_retry(cma->name, pfn, pfn_to_page(pfn),
|
|
// count, align);
|
|
/* try again with a bit different memory target */
|
|
//start = bitmap_no + mask + 1;
|
|
/*
|
|
* CMA allocation time out, for example:
|
|
* 1. set isolation failed.
|
|
* 2. refcout and mapcount mismatch.
|
|
* may blocked on some pages, relax CPU and try later.
|
|
*/
|
|
if ((sched_clock() - in_tick) >= timeout)
|
|
usleep_range(1000, 2000);
|
|
}
|
|
|
|
//trace_cma_alloc_finish(cma->name, pfn, page, count, align);
|
|
|
|
/*
|
|
* CMA can allocate multiple page blocks, which results in different
|
|
* blocks being marked with different tags. Reset the tags to ignore
|
|
* those page blocks.
|
|
*/
|
|
if (page) {
|
|
for (i = 0; i < count; i++)
|
|
page_kasan_tag_reset(page + i);
|
|
}
|
|
|
|
if (ret && !no_warn) {
|
|
pr_err_ratelimited("%s: %s: alloc failed, req-size: %lu pages, ret: %d\n",
|
|
__func__, cma->name, count, ret);
|
|
//cma_debug_show_areas(cma);
|
|
}
|
|
|
|
pr_debug("%s(): returned %p\n", __func__, page);
|
|
out:
|
|
if (page) {
|
|
count_vm_event(CMA_ALLOC_SUCCESS);
|
|
cma_sysfs_account_success_pages(cma, count);
|
|
} else {
|
|
count_vm_event(CMA_ALLOC_FAIL);
|
|
if (cma)
|
|
cma_sysfs_account_fail_pages(cma, count);
|
|
}
|
|
aml_cma_alloc_post_hook(&dummy, count, page, tick, ret);
|
|
|
|
return page;
|
|
}
|
|
|
|
/**
|
|
* cma_release() - release allocated pages
|
|
* @cma: Contiguous memory region for which the allocation is performed.
|
|
* @pages: Allocated pages.
|
|
* @count: Number of allocated pages.
|
|
*
|
|
* This function releases memory allocated by cma_alloc().
|
|
* It returns false when provided pages do not belong to contiguous area and
|
|
* true otherwise.
|
|
*/
|
|
bool aml_cma_release(struct dummy_cma *cma, const struct page *pages,
|
|
unsigned long count)
|
|
{
|
|
unsigned long pfn;
|
|
|
|
if (!cma || !pages)
|
|
return false;
|
|
|
|
pr_debug("%s(page %p, count %lu)\n", __func__, (void *)pages, count);
|
|
|
|
pfn = page_to_pfn(pages);
|
|
|
|
if (pfn < cma->base_pfn || pfn >= cma->base_pfn + cma->count)
|
|
return false;
|
|
|
|
VM_BUG_ON(pfn + count > cma->base_pfn + cma->count);
|
|
|
|
aml_cma_free(pfn, count, 1);
|
|
cma_clear_bitmap(cma, pfn, count);
|
|
//trace_cma_release(cma->name, pfn, pages, count);
|
|
|
|
return true;
|
|
}
|
|
|
|
static int __nocfi __kprobes cma_alloc_pre_handler(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
//restore to origin context
|
|
instruction_pointer_set(regs, (unsigned long)aml_cma_alloc);
|
|
|
|
//no need continue do single-step
|
|
return 1;
|
|
}
|
|
|
|
struct kprobe kp_cma_alloc = {
|
|
.symbol_name = "cma_alloc",
|
|
.pre_handler = cma_alloc_pre_handler,
|
|
};
|
|
|
|
static int __nocfi __kprobes cma_release_pre_handler(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
//restore to origin context
|
|
instruction_pointer_set(regs, (unsigned long)aml_cma_release);
|
|
|
|
//no need continue do single-step
|
|
return 1;
|
|
}
|
|
|
|
struct kprobe kp_cma_release = {
|
|
.symbol_name = "cma_release",
|
|
.pre_handler = cma_release_pre_handler,
|
|
};
|
|
|
|
static int compaction_alloc_debug;
|
|
module_param(compaction_alloc_debug, int, 0644);
|
|
|
|
static unsigned long low_cma_pfn = -1UL; //max
|
|
module_param(low_cma_pfn, ulong, 0644);
|
|
|
|
struct cma;
|
|
static int low_cma_func(struct cma *cma, void *data)
|
|
{
|
|
struct dummy_cma *d_cma = (struct dummy_cma *)cma;
|
|
|
|
if (d_cma->base_pfn < low_cma_pfn)
|
|
low_cma_pfn = d_cma->base_pfn;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static __nocfi void low_cma_init(void)
|
|
{
|
|
cma_for_each_area(low_cma_func, NULL);
|
|
pr_info("low_cma_pfn=%lx\n", low_cma_pfn);
|
|
|
|
if (low_cma_pfn < 500 * 1024 / 4) {
|
|
pr_err("low_cma_pfn less than 500M ignore\n");
|
|
low_cma_pfn = -1UL;
|
|
}
|
|
}
|
|
|
|
static int __nocfi __kprobes compaction_alloc_pre_handler(struct kprobe *p, struct pt_regs *regs)
|
|
{
|
|
#ifdef CONFIG_ARM64
|
|
struct compact_control *cc = (struct compact_control *)regs->regs[1];
|
|
#elif CONFIG_ARM
|
|
struct compact_control *cc = (struct compact_control *)regs->ARM_r1;
|
|
#endif
|
|
|
|
if (list_empty(&cc->freepages)) {
|
|
if (cc->free_pfn >= low_cma_pfn) {
|
|
if (compaction_alloc_debug)
|
|
pr_info("compaction_alloc: set free_pfn %lx->%lx\n",
|
|
cc->free_pfn, low_cma_pfn - pageblock_nr_pages);
|
|
|
|
cc->free_pfn = low_cma_pfn - pageblock_nr_pages;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct kprobe kp_compaction_alloc = {
|
|
.symbol_name = "compaction_alloc",
|
|
.pre_handler = compaction_alloc_pre_handler,
|
|
};
|
|
|
|
static void *get_symbol_addr(const char *symbol_name)
|
|
{
|
|
struct kprobe kp;
|
|
int ret;
|
|
|
|
kp.symbol_name = symbol_name;
|
|
|
|
ret = register_kprobe(&kp);
|
|
if (ret < 0) {
|
|
pr_err("register_kprobe:%s failed, returned %d\n", symbol_name, ret);
|
|
return NULL;
|
|
}
|
|
pr_debug("symbol_name:%s addr=%px\n", symbol_name, kp.addr);
|
|
unregister_kprobe(&kp);
|
|
|
|
return kp.addr;
|
|
}
|
|
|
|
static int __nocfi common_symbol_init(void *data)
|
|
{
|
|
int ret;
|
|
|
|
ret = register_kprobe(&kp_lookup_name);
|
|
if (ret < 0) {
|
|
pr_err("register_kprobe failed, returned %d\n", ret);
|
|
return ret;
|
|
}
|
|
pr_debug("kprobe lookup offset at %px\n", kp_lookup_name.addr);
|
|
|
|
aml_kallsyms_lookup_name = (unsigned long (*)(const char *name))kp_lookup_name.addr;
|
|
|
|
aml_totalcma_pages = *(unsigned long *)aml_kallsyms_lookup_name("totalcma_pages");
|
|
aml_prep_huge_page = (void (*)(struct page *page))get_symbol_addr("prep_transhuge_page");
|
|
aml_reclaim_clean_pages_from_list = (unsigned int (*)(struct zone *zone,
|
|
struct list_head *page_list))get_symbol_addr("reclaim_clean_pages_from_list");
|
|
aml_isolate_pages_range = (int (*)(struct compact_control *cc, unsigned long start_pfn,
|
|
unsigned long end_pfn))get_symbol_addr("isolate_migratepages_range");
|
|
aml_rmap_walk = (void (*)(struct page *page,
|
|
struct rmap_walk_control *rwc))get_symbol_addr("rmap_walk");
|
|
aml_undo_isolate_page_range = (void (*)(unsigned long start_pfn, unsigned long end_pfn,
|
|
unsigned int migratetype))get_symbol_addr("undo_isolate_page_range");
|
|
aml_iso_free_range = (unsigned long (*)(struct compact_control *cc, unsigned long start_pfn,
|
|
unsigned long end_pfn))get_symbol_addr("isolate_freepages_range");
|
|
aml_drain_all_pages = (void (*)(struct zone *zone))get_symbol_addr("drain_all_pages");
|
|
aml_lru_add_drain_all = (void (*)(void))get_symbol_addr("lru_add_drain_all");
|
|
#if CONFIG_AMLOGIC_KERNEL_VERSION >= 14515
|
|
aml_start_isolate_page_range = (int (*)(unsigned long start_pfn, unsigned long end_pfn,
|
|
unsigned int migratetype, int flags,
|
|
unsigned long *failed_pfn))get_symbol_addr("start_isolate_page_range");
|
|
#else
|
|
aml_start_isolate_page_range = (int (*)(unsigned long start_pfn, unsigned long end_pfn,
|
|
unsigned int migratetype, int flags))get_symbol_addr("start_isolate_page_range");
|
|
#endif
|
|
|
|
#ifdef CONFIG_PAGE_OWNER
|
|
aml__dump_owner = (void (*)(const struct page *page))get_symbol_addr("__dump_page_owner");
|
|
#endif
|
|
ret = register_kprobe(&kp_cma_alloc);
|
|
if (ret < 0) {
|
|
pr_err("register_kprobe:%s failed, returned %d\n",
|
|
kp_cma_alloc.symbol_name, ret);
|
|
return 1;
|
|
}
|
|
|
|
ret = register_kprobe(&kp_cma_release);
|
|
if (ret < 0) {
|
|
pr_err("register_kprobe:%s failed, returned %d\n",
|
|
kp_cma_release.symbol_name, ret);
|
|
return 1;
|
|
}
|
|
|
|
ret = register_kprobe(&kp_compaction_alloc);
|
|
if (ret < 0) {
|
|
pr_err("register_kprobe:%s failed, returned %d\n",
|
|
kp_compaction_alloc.symbol_name, ret);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if CONFIG_AMLOGIC_KERNEL_VERSION >= 14515
|
|
static int cma_enabled;
|
|
module_param(cma_enabled, int, 0644);
|
|
|
|
static int cma_enabled_setup(char *str)
|
|
{
|
|
if (kstrtoint(str, 0, &cma_enabled)) {
|
|
pr_err("cma_enabled: bad arg:%s\n", str);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
__setup("cma_enabled=", cma_enabled_setup);
|
|
|
|
static int cma_enabled_swap_ratio = 70;
|
|
module_param(cma_enabled_swap_ratio, int, 0644);
|
|
|
|
static int cma_enabled_swap_ratio_setup(char *str)
|
|
{
|
|
if (kstrtoint(str, 0, &cma_enabled_swap_ratio)) {
|
|
pr_err("cma_enabled_swap_ratio: bad arg:%s\n", str);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
__setup("cma_enabled_swap_ratio=", cma_enabled_swap_ratio_setup);
|
|
|
|
static struct timer_list cma_enabled_timer;
|
|
|
|
static void cma_enabled_timer_fn(struct timer_list *timer)
|
|
{
|
|
struct sysinfo i;
|
|
|
|
if (cma_enabled)
|
|
return;
|
|
|
|
si_swapinfo(&i);
|
|
|
|
if (i.totalswap && i.freeswap * 100 < i.totalswap * cma_enabled_swap_ratio) {
|
|
pr_info("swap free low(MB):%lu/%lu, ratio=%d, enable cma now!\n",
|
|
i.freeswap * 4 / 1024,
|
|
i.totalswap * 4 / 1024,
|
|
cma_enabled_swap_ratio);
|
|
cma_enabled = 1;
|
|
} else {
|
|
mod_timer(timer, jiffies + HZ / 10);
|
|
}
|
|
}
|
|
|
|
static void __maybe_unused delay_enable_cma_hook(void *data,
|
|
gfp_t gfp_mask, unsigned int *alloc_flags, bool *bypass)
|
|
{
|
|
if (!cma_enabled)
|
|
*bypass = 1;
|
|
}
|
|
|
|
static void delay_enable_cma_init(void)
|
|
{
|
|
register_trace_android_vh_calc_alloc_flags(delay_enable_cma_hook, NULL);
|
|
|
|
timer_setup(&cma_enabled_timer, cma_enabled_timer_fn, 0);
|
|
mod_timer(&cma_enabled_timer, jiffies + HZ);
|
|
}
|
|
#endif
|
|
|
|
static int __init aml_cma_module_init(void)
|
|
{
|
|
atomic_set(&cma_allocate, 0);
|
|
atomic_long_set(&nr_cma_allocated, 0);
|
|
|
|
dentry = proc_create("cma_debug", 0644, NULL, &cma_dbg_file_ops);
|
|
if (IS_ERR_OR_NULL(dentry)) {
|
|
pr_err("%s, create sysfs failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
init_cma_boost_task();
|
|
kthread_run(common_symbol_init, NULL, "AML_CMA_TASK");
|
|
|
|
low_cma_init();
|
|
|
|
#if CONFIG_AMLOGIC_KERNEL_VERSION >= 14515
|
|
delay_enable_cma_init();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit aml_cma_module_exit(void)
|
|
{
|
|
}
|
|
|
|
module_init(aml_cma_module_init);
|
|
module_exit(aml_cma_module_exit);
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_IMPORT_NS(MINIDUMP);
|
|
#endif
|
|
|