author | tao zeng <tao.zeng@amlogic.com> | 2017-10-23 02:38:16 (GMT) |
---|---|---|
committer | Jianxin Pan <jianxin.pan@amlogic.com> | 2017-10-23 07:11:33 (GMT) |
commit | a998ca2c01b417ca59a8b4af68f283179e256184 (patch) | |
tree | 842a2be7a5e00a49ca846ce04e852e0f259320a3 | |
parent | 8c990449e2c2646af71e64a6c4cb0376848cb12b (diff) | |
download | common-a998ca2c01b417ca59a8b4af68f283179e256184.zip common-a998ca2c01b417ca59a8b4af68f283179e256184.tar.gz common-a998ca2c01b417ca59a8b4af68f283179e256184.tar.bz2 |
mm: add pagetrace function
PD#151104: mm: add pagetrace function
1. implement pagetrace as a driver; you can get information
of how many pages allocated by each function by read:
cat /proc/pagetrace
2. fix wrong statistics of free memory of each migrate type.
Change-Id: Ib2dff4bb5b3dd288ee188007352fc7b353eda100
Signed-off-by: tao zeng <tao.zeng@amlogic.com>
-rw-r--r-- | MAINTAINERS | 4 | ||||
-rw-r--r-- | drivers/amlogic/Kconfig | 3 | ||||
-rw-r--r-- | drivers/amlogic/Makefile | 2 | ||||
-rw-r--r-- | drivers/amlogic/memory_ext/Kconfig | 20 | ||||
-rw-r--r-- | drivers/amlogic/memory_ext/Makefile | 2 | ||||
-rw-r--r-- | drivers/amlogic/memory_ext/page_trace.c | 847 | ||||
-rw-r--r-- | include/linux/amlogic/page_trace.h | 98 | ||||
-rw-r--r-- | init/main.c | 7 | ||||
-rw-r--r-- | mm/cma.c | 20 | ||||
-rw-r--r-- | mm/page_alloc.c | 30 |
10 files changed, 1031 insertions, 2 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 84c35ce..437da7a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13476,6 +13476,10 @@ AMLOGIC driver for cpufreq M: Tao Zeng <tao.zeng@amlogic.com> F: drivers/amlogic/cpufreq/* +AMLOGIC driver for memory extend +M: Tao Zeng <tao.zeng@amlogic.com> +F: drivers/amlogic/memory_ext/* + AMLOGIC driver for pmu M: Tao Zeng <tao.zeng@amlogic.com> F: drivers/amlogic/power/* diff --git a/drivers/amlogic/Kconfig b/drivers/amlogic/Kconfig index 2bcd011..35cd9a3 100644 --- a/drivers/amlogic/Kconfig +++ b/drivers/amlogic/Kconfig @@ -115,5 +115,8 @@ source "drivers/amlogic/drm/Kconfig" source "drivers/amlogic/secure_monitor/Kconfig" source "drivers/amlogic/tee/Kconfig" + +source "drivers/amlogic/memory_ext/Kconfig" + endmenu endif diff --git a/drivers/amlogic/Makefile b/drivers/amlogic/Makefile index 53f5104..98bbf90 100644 --- a/drivers/amlogic/Makefile +++ b/drivers/amlogic/Makefile @@ -106,3 +106,5 @@ obj-$(CONFIG_DRM_MESON) += drm/ obj-$(CONFIG_AMLOGIC_M8B_SM) += secure_monitor/ obj-$(CONFIG_AMLOGIC_TEE) += tee/ + +obj-$(CONFIG_AMLOGIC_MEMORY_EXTEND) += memory_ext/ diff --git a/drivers/amlogic/memory_ext/Kconfig b/drivers/amlogic/memory_ext/Kconfig new file mode 100644 index 0000000..4ea17c1 --- a/dev/null +++ b/drivers/amlogic/memory_ext/Kconfig @@ -0,0 +1,20 @@ +menuconfig AMLOGIC_MEMORY_EXTEND + bool "AMLOGIC memory extend support" + depends on AMLOGIC_DRIVER + depends on AMLOGIC_MODIFY + default y + help + Amlogic memory extend is drivers which extend for memory management + functions, can be used for memory debug or other features. + +config AMLOGIC_PAGE_TRACE + bool "Amlogic trace for page allocate" + depends on AMLOGIC_MEMORY_EXTEND + depends on KALLSYMS + default y + help + Amlogic page trace will record function address of caller for page + allocate/free, according with allocate flags and order. trace + information is stored in a pre-allocated memory block. And can be shown + with allocate page count information of each caller functions from + /proc/pagetrace diff --git a/drivers/amlogic/memory_ext/Makefile b/drivers/amlogic/memory_ext/Makefile new file mode 100644 index 0000000..673d8b3 --- a/dev/null +++ b/drivers/amlogic/memory_ext/Makefile @@ -0,0 +1,2 @@ + +obj-$(CONFIG_AMLOGIC_PAGE_TRACE) += page_trace.o diff --git a/drivers/amlogic/memory_ext/page_trace.c b/drivers/amlogic/memory_ext/page_trace.c new file mode 100644 index 0000000..4fa3e3c --- a/dev/null +++ b/drivers/amlogic/memory_ext/page_trace.c @@ -0,0 +1,847 @@ +/* + * drivers/amlogic/memory_ext/page_trace.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 <linux/amlogic/page_trace.h> +#include <linux/gfp.h> +#include <linux/proc_fs.h> +#include <linux/kallsyms.h> +#include <linux/mmzone.h> +#include <linux/memblock.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/sort.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/uaccess.h> +#include <asm/stacktrace.h> + +#define DEBUG_PAGE_TRACE 0 + +#define COMMON_CALLER_SIZE 20 + +/* + * this is a driver which will hook during page alloc/free and + * record caller of each page to a buffer. Detailed information + * of page allocate statistics can be find in /proc/pagetrace + * + */ +static unsigned int trace_step = 1; +static bool merge_function; +struct page_trace *trace_buffer; +static unsigned long ptrace_size; +static struct proc_dir_entry *dentry; +static bool page_trace_disable __initdata; + +struct alloc_caller { + unsigned long func_start_addr; + unsigned long size; +}; + +struct fun_symbol { + const char *name; + int full_match; +}; + +static struct alloc_caller common_caller[COMMON_CALLER_SIZE]; + +/* + * following functions are common API from page allocate, they should not + * be record in page trace, parse for back trace should keep from these + * functions + */ +static struct fun_symbol common_func[] __initdata = { + {"__alloc_pages_nodemask", 1}, + {"kmem_cache_alloc", 1}, + {"__get_free_pages", 1}, + {"__kmalloc", 1}, + {"cma_alloc", 1}, + {"dma_alloc_from_contiguous", 1}, + {"__dma_alloc", 1}, + {"__kmalloc_track_caller", 1}, + {"kmem_cache_alloc_trace", 1}, + {"alloc_pages_exact", 1}, + {"__alloc_page_frag", 1}, + {"kmalloc_order", 0}, +#ifdef CONFIG_SLUB /* for some static symbols not exported in headfile */ + {"new_slab", 0}, + {"slab_alloc", 0}, +#endif + {} /* tail */ +}; + +static int early_page_trace_param(char *buf) +{ + if (!buf) + return -EINVAL; + + if (strcmp(buf, "off") == 0) { + page_trace_disable = true; + pr_info("page_trace_disable disabled\n"); + } + + return 0; +} +early_param("page_trace", early_page_trace_param); + +static int early_page_trace_step(char *buf) +{ + if (!buf) + return -EINVAL; + + if (!kstrtouint(buf, 10, &trace_step)) + pr_info("page trace_step:%d\n", trace_step); + + return 0; +} +early_param("page_trace_step", early_page_trace_step); + +static inline bool page_trace_invalid(struct page_trace *trace) +{ + return trace->order == IP_INVALID; +} + +#if DEBUG_PAGE_TRACE +static inline bool range_ok(struct page_trace *trace) +{ + unsigned long offset; + + offset = (trace->ret_ip << 2); + if (trace->module_flag) { + if (offset >= MODULES_END) + return false; + } else { + if (offset >= ((unsigned long)_end - (unsigned long)_text)) + return false; + } + return true; +} + +static bool check_trace_valid(struct page_trace *trace) +{ + unsigned long offset; + + if (trace->order == IP_INVALID) + return true; + + if (trace->order >= 10 || + trace->migrate_type >= MIGRATE_TYPES || + !range_ok(trace)) { + offset = (unsigned long)((trace - trace_buffer)); + pr_err("bad trace:%p, %x, pfn:%ld, ip:%pf\n", trace, + *((unsigned int *)trace), + offset / sizeof(struct page_trace), + (void *)_RET_IP_); + return false; + } + return true; +} +#else +static inline bool check_trace_valid(struct page_trace *trace) +{ + return true; +} +#endif /* DEBUG_PAGE_TRACE */ + +static void push_ip(struct page_trace *base, struct page_trace *ip) +{ + int i; + unsigned long end; + + for (i = trace_step - 1; i > 0; i--) + base[i] = base[i - 1]; + + /* debug check */ + check_trace_valid(base); + end = ((unsigned long)trace_buffer + ptrace_size); + WARN_ON(((unsigned long)base) >= end); + + base[0] = *ip; +} + +static inline int is_module_addr(unsigned long ip) +{ + if (ip >= MODULES_VADDR && ip < MODULES_END) + return 1; + return 0; +} + +/* + * following 3 functions are modify from kernel/kallsyms.c + */ +static unsigned int __init kallsyms_expand_symbol(unsigned int off, + char *result, size_t maxlen) +{ + int len, skipped_first = 0; + const u8 *tptr, *data; + + /* Get the compressed symbol length from the first symbol byte. */ + data = &kallsyms_names[off]; + len = *data; + data++; + + /* + * Update the offset to return the offset for the next symbol on + * the compressed stream. + */ + off += len + 1; + + /* + * For every byte on the compressed symbol data, copy the table + * entry for that byte. + */ + while (len) { + tptr = &kallsyms_token_table[kallsyms_token_index[*data]]; + data++; + len--; + + while (*tptr) { + if (skipped_first) { + if (maxlen <= 1) + goto tail; + *result = *tptr; + result++; + maxlen--; + } else + skipped_first = 1; + tptr++; + } + } + +tail: + if (maxlen) + *result = '\0'; + + /* Return to offset to the next symbol. */ + return off; +} + +static unsigned long __init kallsyms_sym_address(int idx) +{ + if (!IS_ENABLED(CONFIG_KALLSYMS_BASE_RELATIVE)) + return kallsyms_addresses[idx]; + + /* values are unsigned offsets if --absolute-percpu is not in effect */ + if (!IS_ENABLED(CONFIG_KALLSYMS_ABSOLUTE_PERCPU)) + return kallsyms_relative_base + (u32)kallsyms_offsets[idx]; + + /* ...otherwise, positive offsets are absolute values */ + if (kallsyms_offsets[idx] >= 0) + return kallsyms_offsets[idx]; + + /* ...and negative offsets are relative to kallsyms_relative_base - 1 */ + return kallsyms_relative_base - 1 - kallsyms_offsets[idx]; +} + +/* Lookup the address for this symbol. Returns 0 if not found. */ +static unsigned long __init kallsyms_contain_name(const char *name, long full, + unsigned int *offset) +{ + char namebuf[KSYM_NAME_LEN]; + unsigned long i; + unsigned int off = 0; + + for (i = 0; i < kallsyms_num_syms; i++) { + off = kallsyms_expand_symbol(off, namebuf, ARRAY_SIZE(namebuf)); + + if (full && strcmp(namebuf, name) == 0) + return kallsyms_sym_address(i); + if (!full && strstr(namebuf, name) && (off > *offset)) { + *offset = off; /* update offset for next loop */ + return kallsyms_sym_address(i); + } + } + return 0; +} + +/* + * set up information for common caller in memory allocate API + */ +static void __init setup_common_caller(unsigned long kaddr) +{ + unsigned long size, offset; + int i = 0; + const char *name; + char str[KSYM_NAME_LEN]; + + for (i = 0; i < COMMON_CALLER_SIZE; i++) { + /* find a empty caller */ + if (!common_caller[i].func_start_addr) + break; + } + if (i >= COMMON_CALLER_SIZE) { + pr_err("%s, out of memory\n", __func__); + return; + } + + name = kallsyms_lookup(kaddr, &size, &offset, NULL, str); + if (name) { + common_caller[i].func_start_addr = kaddr; + common_caller[i].size = size; + pr_debug("setup %d caller:%lx + %lx, %pf\n", + i, kaddr, size, (void *)kaddr); + } else + pr_err("can't find symbol %pf\n", (void *)kaddr); +} + +static int __init fuzzy_match(const char *name) +{ + unsigned int off = 0; + unsigned long addr; + int find = 0; + + while (1) { + addr = kallsyms_contain_name(name, 0, &off); + if (addr) { + setup_common_caller(addr); + find++; + } else + break; + } + return find; +} + +static void __init dump_common_caller(void) +{ + int i; + + for (i = 0; i < COMMON_CALLER_SIZE; i++) { + if (common_caller[i].func_start_addr) + pr_debug("%2d, addr:%lx + %4lx, %pf\n", i, + common_caller[i].func_start_addr, + common_caller[i].size, + (void *)common_caller[i].func_start_addr); + else + break; + } +} + +static void __init find_static_common_symbol(void) +{ + int i; + unsigned long addr; + struct fun_symbol *s; + + for (i = 0; i < COMMON_CALLER_SIZE; i++) { + s = &common_func[i]; + if (!s->name) + break; /* end */ + if (s->full_match) { + addr = kallsyms_contain_name(s->name, 1, NULL); + if (addr) + setup_common_caller(addr); + else + pr_info("can't find symbol:%s\n", s->name); + } else { + if (!fuzzy_match(s->name)) + pr_info("can't fuzzy match:%s\n", s->name); + } + } + dump_common_caller(); +} + +static int is_common_caller(struct alloc_caller *caller, unsigned long pc) +{ + int i, ret = 0; + + for (i = 0; i < COMMON_CALLER_SIZE; i++) { + if (!caller[i].func_start_addr) /* end if this table */ + break; + + /* pc is in one of common caller */ + if ((pc >= caller[i].func_start_addr) && + (pc <= (caller[i].func_start_addr + caller[i].size))) { + ret = 1; + break; + } + } + return ret; +} + +unsigned long unpack_ip(struct page_trace *trace) +{ + unsigned long text; + + if (trace->module_flag) + text = MODULES_VADDR; + else + text = (unsigned long)_text; + return text + ((trace->ret_ip) << 2); +} +EXPORT_SYMBOL(unpack_ip); + +static inline unsigned long find_back_trace(void) +{ + struct stackframe frame; + int ret, step = 0; + + frame.fp = (unsigned long)__builtin_frame_address(0); + frame.sp = current_stack_pointer; + frame.pc = _RET_IP_; + while (1) { + #ifdef CONFIG_ARM64 + ret = unwind_frame(current, &frame); + #elif defined(CONFIG_ARM) + ret = unwind_frame(&frame); + #else /* not supported */ + ret = -1; + #endif + if (ret < 0) { + pr_err("%s, can't find back trace\n", __func__); + return 0; + } + step++; + if (!is_common_caller(common_caller, frame.pc) && step > 1) + return frame.pc; + } + pr_info("can't get pc\n"); + dump_stack(); + return 0; +} + +struct page_trace *find_page_base(struct page *page) +{ + unsigned long pfn, zone_offset = 0, offset; + struct zone *zone; + struct page_trace *p; + + pfn = page_to_pfn(page); + for_each_populated_zone(zone) { + /* pfn is in this zone */ + if (pfn <= zone_end_pfn(zone) && + pfn >= zone->zone_start_pfn) { + offset = pfn - zone->zone_start_pfn; + p = trace_buffer; + return p + ((offset + zone_offset) * trace_step); + } + /* next zone */ + zone_offset += zone->spanned_pages; + } + return NULL; +} + +static void __init set_init_page_trace(struct page *page, int order, gfp_t flag) +{ + unsigned long text, ip; + struct page_trace trace = {}, *base; + + if (page && trace_buffer) { + ip = (unsigned long)set_page_trace; + text = (unsigned long)_text; + + trace.ret_ip = (ip - text) >> 2; + WARN_ON(trace.ret_ip > IP_RANGE_MASK); + trace.migrate_type = gfpflags_to_migratetype(flag); + trace.order = order; + base = find_page_base(page); + push_ip(base, &trace); + } + +} + +void set_page_trace(struct page *page, int order, gfp_t gfp_flags) +{ + unsigned long text, ip; + struct page_trace trace = {}, *base; + + if (page && trace_buffer) { + ip = find_back_trace(); + if (!ip) { + pr_err("can't find backtrace for page:%lx\n", + page_to_pfn(page)); + dump_stack(); + return; + } + text = (unsigned long)_text; + if (ip >= (unsigned long)_text) + text = (unsigned long)_text; + else if (is_module_addr(ip)) { + text = MODULES_VADDR; + trace.module_flag = 1; + } + + trace.ret_ip = (ip - text) >> 2; + WARN_ON(trace.ret_ip > IP_RANGE_MASK); + if (gfp_flags == __GFP_BDEV) + trace.migrate_type = MIGRATE_CMA; + else + trace.migrate_type = gfpflags_to_migratetype(gfp_flags); + trace.order = order; + base = find_page_base(page); + pr_debug("%s, base:%p, page:%lx, _ip:%x, o:%d, f:%x, ip:%lx\n", + __func__, base, page_to_pfn(page), + (*((unsigned int *)&trace)), order, + gfp_flags, ip); + push_ip(base, &trace); + } +} +EXPORT_SYMBOL(set_page_trace); + +void reset_page_trace(struct page *page, int order) +{ + struct page_trace *base; + int i; + + if (page && trace_buffer) { + base = find_page_base(page); + check_trace_valid(base); + for (i = 0; i < (1 << order); i++) { + base->order = IP_INVALID; + base += (trace_step); + } + } +} +EXPORT_SYMBOL(reset_page_trace); + +/* + * move page out of buddy and make sure they are not malloced by + * other module + * + * Note: + * before call this functions, memory for page trace buffer are + * freed to buddy. + */ +static void __init page_trace_pre_work(struct page *start_page, + unsigned long size) +{ + int i, order, j, tailed = 0; + struct page *page, *last_page; + + size = PAGE_ALIGN(size); + + page = start_page; + last_page = page + (size >> PAGE_SHIFT); + for (i = 0; i < size >> PAGE_SHIFT;) { + order = page_private(page); + pr_debug("page:%p, order:%d\n", page, order); + list_del(&page->lru); /* del from buddy */; + set_page_private(page, 0); + for (j = 0; j < (1 << order); j++) { + set_init_page_trace(page, 0, GFP_KERNEL); + page++; + i++; + } + } + + /* free tailed pages */ + while (page > last_page) { + __free_pages(page, 0); + page--; + tailed++; + } + pr_info("%s, %d, tailed:%d\n", __func__, __LINE__, tailed); +} + +#define SHOW_CNT 1024 +struct page_summary { + unsigned long ip; + unsigned int cnt; +}; + +static unsigned long find_ip_base(unsigned long ip) +{ + unsigned long size, offset; + const char *name; + char str[KSYM_NAME_LEN]; + + name = kallsyms_lookup(ip, &size, &offset, NULL, str); + if (name) /* find */ + return ip - offset; + else /* not find */ + return ip; +} + +static int find_page_ip(struct page_trace *trace, + struct page_summary *sum, int *o) +{ + int i = 0; + int order; + unsigned long ip; + + *o = 0; + ip = unpack_ip(trace); + order = trace->order; + if (merge_function) + ip = find_ip_base(ip); + for (i = 0; i < SHOW_CNT; i++) { + if (sum[i].ip == ip) { + /* find */ + sum[i].cnt += (1 << order); + *o = order; + return 0; + } + if (sum[i].ip == 0) { /* empty */ + sum[i].cnt += (1 << order); + sum[i].ip = ip; + *o = order; + return 1; + } + } + return 0; +} + +#define K(x) ((x) << (PAGE_SHIFT - 10)) +static int trace_cmp(const void *x1, const void *x2) +{ + struct page_summary *s1, *s2; + + s1 = (struct page_summary *)x1; + s2 = (struct page_summary *)x2; + return s2->cnt - s1->cnt; +} + +static void show_page_trace(struct seq_file *m, + struct page_summary *sum, int cnt, int type) +{ + int i; + unsigned long total = 0; + + if (!cnt) + return; + sort(sum, cnt, sizeof(*sum), trace_cmp, NULL); + for (i = 0; i < cnt; i++) { + seq_printf(m, "%8d, %16lx, %pf\n", + K(sum[i].cnt), sum[i].ip, (void *)sum[i].ip); + total += sum[i].cnt; + } + seq_puts(m, "------------------------------\n"); + seq_printf(m, "total pages:%ld, %ld kB, type:%s\n", + total, K(total), migratetype_names[type]); +} + +static inline int type_match(struct page_trace *trace, int type) +{ + return (trace->migrate_type) == type; +} + +static int update_page_trace(struct seq_file *m, struct zone *zone, + struct page_summary *sum, int type) +{ + unsigned long pfn, flags; + unsigned long start_pfn = zone->zone_start_pfn; + unsigned long end_pfn = zone_end_pfn(zone); + int max_trace = 0, ret; + int order; + unsigned long ip; + struct page_trace *trace; + + spin_lock_irqsave(&zone->lock, flags); + for (pfn = start_pfn; pfn < end_pfn; pfn++) { + struct page *page; + + if (!pfn_valid(pfn)) + continue; + + page = pfn_to_page(pfn); + + /* Watch for unexpected holes punched in the memmap */ + if (!memmap_valid_within(pfn, page, zone)) + continue; + + trace = find_page_base(page); + check_trace_valid(trace); + if (page_trace_invalid(trace)) /* free pages */ + continue; + + if (!(*(unsigned int *)trace)) /* empty */ + continue; + + if (type_match(trace, type)) { + ret = find_page_ip(trace, sum, &order); + if (max_trace == SHOW_CNT && ret) { + ip = unpack_ip(trace); + pr_err("MAX sum cnt, pfn:%ld, lr:%lx, %pf\n", + pfn, ip, (void *)ip); + } else + max_trace += ret; + if (order) + pfn += ((1 << order) - 1); + } + } + spin_unlock_irqrestore(&zone->lock, flags); + return max_trace; +} +/* + * This prints out statistics in relation to grouping pages by mobility. + * It is expensive to collect so do not constantly read the file. + */ +static int pagetrace_show(struct seq_file *m, void *arg) +{ + pg_data_t *p = (pg_data_t *)arg; + struct zone *zone; + int mtype, ret, print_flag; + struct page_summary *sum; + + /* check memoryless node */ + if (!node_state(p->node_id, N_MEMORY)) + return 0; + + sum = vmalloc(sizeof(struct page_summary) * SHOW_CNT); + if (!sum) + return -ENOMEM; + + for_each_populated_zone(zone) { + print_flag = 0; + seq_printf(m, "Node %d, zone %8s\n", p->node_id, zone->name); + for (mtype = 0; mtype < MIGRATE_TYPES; mtype++) { + memset(sum, 0, sizeof(struct page_summary) * SHOW_CNT); + ret = update_page_trace(m, zone, sum, mtype); + if (ret > 0) { + seq_printf(m, "%s %s %s\n", + "count(KB)", "kaddr", "function"); + seq_puts(m, "------------------------------\n"); + show_page_trace(m, sum, ret, mtype); + seq_puts(m, "\n"); + print_flag = 1; + } + } + if (print_flag) + seq_puts(m, "------------------------------\n"); + } + vfree(sum); + return 0; +} + +static void *frag_start(struct seq_file *m, loff_t *pos) +{ + pg_data_t *pgdat; + loff_t node = *pos; + + for (pgdat = first_online_pgdat(); + pgdat && node; + pgdat = next_online_pgdat(pgdat)) + --node; + + return pgdat; +} + +static void *frag_next(struct seq_file *m, void *arg, loff_t *pos) +{ + pg_data_t *pgdat = (pg_data_t *)arg; + + (*pos)++; + return next_online_pgdat(pgdat); +} + +static void frag_stop(struct seq_file *m, void *arg) +{ +} +static const struct seq_operations pagetrace_op = { + .start = frag_start, + .next = frag_next, + .stop = frag_stop, + .show = pagetrace_show, +}; + +static int pagetrace_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &pagetrace_op); +} + +static ssize_t pagetrace_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char *buf; + unsigned long arg = 0; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, buffer, count)) { + kfree(buf); + return -EINVAL; + } + + if (!strncmp(buf, "merge=", 6)) { /* option for 'merge=' */ + if (sscanf(buf, "merge=%ld", &arg) < 0) { + kfree(buf); + return -EINVAL; + } + merge_function = arg ? 1 : 0; + pr_info("set merge_function to %d\n", merge_function); + } + + kfree(buf); + + return count; +} + +static const struct file_operations pagetrace_file_ops = { + .open = pagetrace_open, + .read = seq_read, + .llseek = seq_lseek, + .write = pagetrace_write, + .release = seq_release, +}; + +static int __init page_trace_module_init(void) +{ + if (!trace_buffer) + return -ENOMEM; + + dentry = proc_create("pagetrace", 0444, NULL, &pagetrace_file_ops); + if (IS_ERR_OR_NULL(dentry)) { + pr_err("%s, create sysfs failed\n", __func__); + return -1; + } + + return 0; +} + +static void __exit page_trace_module_exit(void) +{ + if (dentry) + proc_remove(dentry); +} +module_init(page_trace_module_init); +module_exit(page_trace_module_exit); + +void __init page_trace_mem_init(void) +{ + struct zone *zone; + unsigned long total_page = 0; + phys_addr_t mem_add; + struct page *page; + + if (page_trace_disable) + return; + + for_each_populated_zone(zone) { + total_page += zone->spanned_pages; + pr_debug("zone:%s, spaned pages:%ld, total:%ld\n", + zone->name, zone->spanned_pages, total_page); + } + ptrace_size = total_page * sizeof(struct page_trace) * trace_step; + ptrace_size = PAGE_ALIGN(ptrace_size); + mem_add = memblock_alloc(ptrace_size, (pageblock_nr_pages) * PAGE_SIZE); + if (mem_add != 0) { + page = pfn_to_page(__phys_to_pfn(mem_add)); + trace_buffer = (struct page_trace *)page_address(page); + pr_info("%s, ptrace len:%ld, mem_add:%p, step:%d, page:%ld\n", + __func__, ptrace_size, trace_buffer, + trace_step, sizeof(struct page)); + memset(trace_buffer, 0, ptrace_size); + page_trace_pre_work(page, ptrace_size); + find_static_common_symbol(); + } else { + trace_buffer = NULL; + ptrace_size = 0; + pr_err("%s reserve memory failed\n", __func__); + } +} + diff --git a/include/linux/amlogic/page_trace.h b/include/linux/amlogic/page_trace.h new file mode 100644 index 0000000..d3b0aea --- a/dev/null +++ b/include/linux/amlogic/page_trace.h @@ -0,0 +1,98 @@ +/* + * include/linux/amlogic/page_trace.h + * + * 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. + * + */ + +#ifndef __PAGE_TRACE_H__ +#define __PAGE_TRACE_H__ + +#include <asm/memory.h> + +/* + * bit map lay out for _ret_ip table + * + * 31 28 27 26 24 23 0 + * +------+---+----------------------+ + * | | | | | + * +------+---+----------------------+ + * | | | | + * | | | +-------- offset of ip in base address + * | | +------------------ MIGRATE_TYPE + * | +------------------------ base address select + * | 0: ip base is in kernel address + * | 1: ip base is in module address + * +----------------------------- allocate order + * + * Note: + * offset in ip address is Logical shift right by 2 bits, + * because kernel code are always compiled by ARM instruction + * set, so pc is aligned by 2. There are 24 bytes used for store + * offset in kernel/module, plus these 2 shift bits, You must + * make sure your kernel image size is not larger than 2^26 = 64MB + */ +#define IP_ORDER_MASK (0xf0000000) +#define IP_MODULE_BIT (1 << 27) +#define IP_MIGRATE_MASK (0x07000000) +#define IP_RANGE_MASK (0x00ffffff) + /* max order usually should not be 15 */ +#define IP_INVALID (0xf) + +/* this struct should not larger than 32 bit */ +struct page_trace { + unsigned int ret_ip :24; + unsigned int migrate_type : 3; + unsigned int module_flag : 1; + unsigned int order : 4; +}; + +#ifdef CONFIG_AMLOGIC_PAGE_TRACE +extern unsigned long unpack_ip(struct page_trace *trace); +extern void set_page_trace(struct page *page, int order, gfp_t gfp_flags); +extern void reset_page_trace(struct page *page, int order); +extern void page_trace_mem_init(void); +extern struct page_trace *find_page_base(struct page *page); +#else +static inline unsigned long unpack_ip(struct page_trace *trace) +{ + return 0; +} +static inline void set_page_trace(struct page *page, int order, gfp_t gfp_flags) +{ +} +static inline void reset_page_trace(struct page *page, int order) +{ +} +static inline void page_trace_mem_init(void) +{ +} +static inline struct page_trace *find_page_base(struct page *page) +{ + return NULL; +} +#endif + +#ifdef CONFIG_KALLSYMS +extern const unsigned long kallsyms_addresses[] __weak; +extern const int kallsyms_offsets[] __weak; +extern const u8 kallsyms_names[] __weak; +extern const unsigned long kallsyms_num_syms + __attribute__((weak, section(".rodata"))); +extern const unsigned long kallsyms_relative_base + __attribute__((weak, section(".rodata"))); +extern const u8 kallsyms_token_table[] __weak; +extern const u16 kallsyms_token_index[] __weak; +#endif /* CONFIG_KALLSYMS */ + +#endif /* __PAGE_TRACE_H__ */ diff --git a/init/main.c b/init/main.c index ae3996a..7828946 100644 --- a/init/main.c +++ b/init/main.c @@ -81,6 +81,9 @@ #include <linux/integrity.h> #include <linux/proc_ns.h> #include <linux/io.h> +#ifdef CONFIG_AMLOGIC_PAGE_TRACE +#include <linux/amlogic/page_trace.h> +#endif #include <asm/io.h> #include <asm/bugs.h> @@ -469,6 +472,10 @@ static void __init mm_init(void) */ page_ext_init_flatmem(); mem_init(); +#ifdef CONFIG_AMLOGIC_PAGE_TRACE + /* allocate memory before first page allocated */ + page_trace_mem_init(); +#endif kmem_cache_init(); percpu_init_late(); pgtable_init(); @@ -36,6 +36,9 @@ #include <linux/highmem.h> #include <linux/io.h> #include <trace/events/cma.h> +#ifdef CONFIG_AMLOGIC_MODIFY +#include <linux/amlogic/page_trace.h> +#endif #include "cma.h" @@ -50,6 +53,19 @@ unsigned long get_driver_alloc_cma(void) { return atomic_long_read(&driver_alloc_cma); } + +static void update_cma_page_trace(struct page *page, unsigned long cnt) +{ + long i; + + if (page == NULL) + return; + + for (i = 0; i < cnt; i++) { + set_page_trace(page, 0, __GFP_BDEV); + page++; + } +} #endif /* CONFIG_AMLOGIC_MODIFY */ phys_addr_t cma_get_base(const struct cma *cma) @@ -448,8 +464,10 @@ struct page *cma_alloc(struct cma *cma, size_t count, unsigned int align) #ifdef CONFIG_AMLOGIC_MODIFY atomic_dec(&cma_alloc_ref); - if (page) + if (page) { atomic_long_add(count, &driver_alloc_cma); + update_cma_page_trace(page, count); + } WARN_ONCE(!page, "can't alloc from %lx with size:%ld, ret:%d\n", cma->base_pfn, count, ret); #endif /* CONFIG_AMLOGIC_MODIFY */ diff --git a/mm/page_alloc.c b/mm/page_alloc.c index ceceeaa..999bdc9 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -66,6 +66,7 @@ #include <linux/memcontrol.h> #ifdef CONFIG_AMLOGIC_MODIFY #include <linux/cma.h> +#include <linux/amlogic/page_trace.h> #endif /* CONFIG_AMLOGIC_MODIFY */ #include <asm/sections.h> @@ -822,6 +823,8 @@ static inline void __free_one_page(struct page *page, unsigned int max_order; #ifdef CONFIG_AMLOGIC_MODIFY int buddy_mg; + + migratetype = get_pageblock_migratetype(page); #endif /* CONFIG_AMLOGIC_MODIFY */ max_order = min_t(unsigned int, MAX_ORDER, pageblock_order + 1); @@ -1094,6 +1097,9 @@ static __always_inline bool free_pages_prepare(struct page *page, kernel_poison_pages(page, 1 << order, 0); kernel_map_pages(page, 1 << order, 0); kasan_free_pages(page, order); +#ifdef CONFIG_AMLOGIC_MODIFY + reset_page_trace(page, order); +#endif return true; } @@ -2067,8 +2073,13 @@ static bool can_steal_fallback(unsigned int order, int start_mt) * pages are moved, we can change migratetype of pageblock and permanently * use it's pages as requested migratetype in the future. */ +#ifdef CONFIG_AMLOGIC_MODIFY +static void steal_suitable_fallback(struct zone *zone, struct page *page, + int start_type, int *list_type) +#else static void steal_suitable_fallback(struct zone *zone, struct page *page, int start_type) +#endif /* CONFIG_AMLOGIC_MODIFY */ { unsigned int current_order = page_order(page); int pages; @@ -2080,6 +2091,9 @@ static void steal_suitable_fallback(struct zone *zone, struct page *page, } pages = move_freepages_block(zone, page, start_type); +#ifdef CONFIG_AMLOGIC_MODIFY + *list_type = start_type; +#endif /* CONFIG_AMLOGIC_MODIFY */ /* Claim the whole block if over half of it is free */ if (pages >= (1 << (pageblock_order-1)) || @@ -2240,6 +2254,9 @@ __rmqueue_fallback(struct zone *zone, unsigned int order, int start_migratetype) struct page *page; int fallback_mt; bool can_steal; +#ifdef CONFIG_AMLOGIC_MODIFY + int list_type; +#endif /* CONFIG_AMLOGIC_MODIFY */ /* Find the largest possible block of pages in the other list */ for (current_order = MAX_ORDER-1; @@ -2253,14 +2270,22 @@ __rmqueue_fallback(struct zone *zone, unsigned int order, int start_migratetype) page = list_first_entry(&area->free_list[fallback_mt], struct page, lru); + #ifdef CONFIG_AMLOGIC_MODIFY + /* list_type may change after try_to_steal_freepages */ + list_type = fallback_mt; + if (can_steal) + steal_suitable_fallback(zone, page, start_migratetype, + &list_type); + #else if (can_steal) steal_suitable_fallback(zone, page, start_migratetype); + #endif /* CONFIG_AMLOGIC_MODIFY */ /* Remove the page from the freelists */ area->nr_free--; #ifdef CONFIG_AMLOGIC_MODIFY __mod_zone_migrate_state(zone, -(1 << current_order), - fallback_mt); + list_type); #endif /* CONFIG_AMLOGIC_MODIFY */ list_del(&page->lru); rmv_page_order(page); @@ -4082,6 +4107,9 @@ out: kmemcheck_pagealloc_alloc(page, order, gfp_mask); trace_mm_page_alloc(page, order, alloc_mask, ac.migratetype); +#ifdef CONFIG_AMLOGIC_MODIFY + set_page_trace(page, order, gfp_mask); +#endif return page; } |