| // SPDX-License-Identifier: GPL-2.0-only | |
| #include <linux/alloc_tag.h> | |
| #include <linux/fs.h> | |
| #include <linux/gfp.h> | |
| #include <linux/module.h> | |
| #include <linux/page_ext.h> | |
| #include <linux/proc_fs.h> | |
| #include <linux/seq_buf.h> | |
| #include <linux/seq_file.h> | |
| static struct codetag_type *alloc_tag_cttype; | |
| DEFINE_PER_CPU(struct alloc_tag_counters, _shared_alloc_tag); | |
| EXPORT_SYMBOL(_shared_alloc_tag); | |
| DEFINE_STATIC_KEY_MAYBE(CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT, | |
| mem_alloc_profiling_key); | |
| struct allocinfo_private { | |
| struct codetag_iterator iter; | |
| bool print_header; | |
| }; | |
| static void *allocinfo_start(struct seq_file *m, loff_t *pos) | |
| { | |
| struct allocinfo_private *priv; | |
| struct codetag *ct; | |
| loff_t node = *pos; | |
| priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
| m->private = priv; | |
| if (!priv) | |
| return NULL; | |
| priv->print_header = (node == 0); | |
| codetag_lock_module_list(alloc_tag_cttype, true); | |
| priv->iter = codetag_get_ct_iter(alloc_tag_cttype); | |
| while ((ct = codetag_next_ct(&priv->iter)) != NULL && node) | |
| node--; | |
| return ct ? priv : NULL; | |
| } | |
| static void *allocinfo_next(struct seq_file *m, void *arg, loff_t *pos) | |
| { | |
| struct allocinfo_private *priv = (struct allocinfo_private *)arg; | |
| struct codetag *ct = codetag_next_ct(&priv->iter); | |
| (*pos)++; | |
| if (!ct) | |
| return NULL; | |
| return priv; | |
| } | |
| static void allocinfo_stop(struct seq_file *m, void *arg) | |
| { | |
| struct allocinfo_private *priv = (struct allocinfo_private *)m->private; | |
| if (priv) { | |
| codetag_lock_module_list(alloc_tag_cttype, false); | |
| kfree(priv); | |
| } | |
| } | |
| static void print_allocinfo_header(struct seq_buf *buf) | |
| { | |
| /* Output format version, so we can change it. */ | |
| seq_buf_printf(buf, "allocinfo - version: 1.0\n"); | |
| seq_buf_printf(buf, "# <size> <calls> <tag info>\n"); | |
| } | |
| static void alloc_tag_to_text(struct seq_buf *out, struct codetag *ct) | |
| { | |
| struct alloc_tag *tag = ct_to_alloc_tag(ct); | |
| struct alloc_tag_counters counter = alloc_tag_read(tag); | |
| s64 bytes = counter.bytes; | |
| seq_buf_printf(out, "%12lli %8llu ", bytes, counter.calls); | |
| codetag_to_text(out, ct); | |
| seq_buf_putc(out, ' '); | |
| seq_buf_putc(out, '\n'); | |
| } | |
| static int allocinfo_show(struct seq_file *m, void *arg) | |
| { | |
| struct allocinfo_private *priv = (struct allocinfo_private *)arg; | |
| char *bufp; | |
| size_t n = seq_get_buf(m, &bufp); | |
| struct seq_buf buf; | |
| seq_buf_init(&buf, bufp, n); | |
| if (priv->print_header) { | |
| print_allocinfo_header(&buf); | |
| priv->print_header = false; | |
| } | |
| alloc_tag_to_text(&buf, priv->iter.ct); | |
| seq_commit(m, seq_buf_used(&buf)); | |
| return 0; | |
| } | |
| static const struct seq_operations allocinfo_seq_op = { | |
| .start = allocinfo_start, | |
| .next = allocinfo_next, | |
| .stop = allocinfo_stop, | |
| .show = allocinfo_show, | |
| }; | |
| size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep) | |
| { | |
| struct codetag_iterator iter; | |
| struct codetag *ct; | |
| struct codetag_bytes n; | |
| unsigned int i, nr = 0; | |
| if (can_sleep) | |
| codetag_lock_module_list(alloc_tag_cttype, true); | |
| else if (!codetag_trylock_module_list(alloc_tag_cttype)) | |
| return 0; | |
| iter = codetag_get_ct_iter(alloc_tag_cttype); | |
| while ((ct = codetag_next_ct(&iter))) { | |
| struct alloc_tag_counters counter = alloc_tag_read(ct_to_alloc_tag(ct)); | |
| n.ct = ct; | |
| n.bytes = counter.bytes; | |
| for (i = 0; i < nr; i++) | |
| if (n.bytes > tags[i].bytes) | |
| break; | |
| if (i < count) { | |
| nr -= nr == count; | |
| memmove(&tags[i + 1], | |
| &tags[i], | |
| sizeof(tags[0]) * (nr - i)); | |
| nr++; | |
| tags[i] = n; | |
| } | |
| } | |
| codetag_lock_module_list(alloc_tag_cttype, false); | |
| return nr; | |
| } | |
| static void __init procfs_init(void) | |
| { | |
| proc_create_seq("allocinfo", 0400, NULL, &allocinfo_seq_op); | |
| } | |
| static bool alloc_tag_module_unload(struct codetag_type *cttype, | |
| struct codetag_module *cmod) | |
| { | |
| struct codetag_iterator iter = codetag_get_ct_iter(cttype); | |
| struct alloc_tag_counters counter; | |
| bool module_unused = true; | |
| struct alloc_tag *tag; | |
| struct codetag *ct; | |
| for (ct = codetag_next_ct(&iter); ct; ct = codetag_next_ct(&iter)) { | |
| if (iter.cmod != cmod) | |
| continue; | |
| tag = ct_to_alloc_tag(ct); | |
| counter = alloc_tag_read(tag); | |
| if (WARN(counter.bytes, | |
| "%s:%u module %s func:%s has %llu allocated at module unload", | |
| ct->filename, ct->lineno, ct->modname, ct->function, counter.bytes)) | |
| module_unused = false; | |
| } | |
| return module_unused; | |
| } | |
| #ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT | |
| static bool mem_profiling_support __meminitdata = true; | |
| #else | |
| static bool mem_profiling_support __meminitdata; | |
| #endif | |
| static int __init setup_early_mem_profiling(char *str) | |
| { | |
| bool enable; | |
| if (!str || !str[0]) | |
| return -EINVAL; | |
| if (!strncmp(str, "never", 5)) { | |
| enable = false; | |
| mem_profiling_support = false; | |
| } else { | |
| int res; | |
| res = kstrtobool(str, &enable); | |
| if (res) | |
| return res; | |
| mem_profiling_support = true; | |
| } | |
| if (enable != static_key_enabled(&mem_alloc_profiling_key)) { | |
| if (enable) | |
| static_branch_enable(&mem_alloc_profiling_key); | |
| else | |
| static_branch_disable(&mem_alloc_profiling_key); | |
| } | |
| return 0; | |
| } | |
| early_param("sysctl.vm.mem_profiling", setup_early_mem_profiling); | |
| static __init bool need_page_alloc_tagging(void) | |
| { | |
| return mem_profiling_support; | |
| } | |
| static __init void init_page_alloc_tagging(void) | |
| { | |
| } | |
| struct page_ext_operations page_alloc_tagging_ops = { | |
| .size = sizeof(union codetag_ref), | |
| .need = need_page_alloc_tagging, | |
| .init = init_page_alloc_tagging, | |
| }; | |
| EXPORT_SYMBOL(page_alloc_tagging_ops); | |
| #ifdef CONFIG_SYSCTL | |
| static struct ctl_table memory_allocation_profiling_sysctls[] = { | |
| { | |
| .procname = "mem_profiling", | |
| .data = &mem_alloc_profiling_key, | |
| #ifdef CONFIG_MEM_ALLOC_PROFILING_DEBUG | |
| .mode = 0444, | |
| #else | |
| .mode = 0644, | |
| #endif | |
| .proc_handler = proc_do_static_key, | |
| }, | |
| }; | |
| static void __init sysctl_init(void) | |
| { | |
| if (!mem_profiling_support) | |
| memory_allocation_profiling_sysctls[0].mode = 0444; | |
| register_sysctl_init("vm", memory_allocation_profiling_sysctls); | |
| } | |
| #else /* CONFIG_SYSCTL */ | |
| static inline void sysctl_init(void) {} | |
| #endif /* CONFIG_SYSCTL */ | |
| static int __init alloc_tag_init(void) | |
| { | |
| const struct codetag_type_desc desc = { | |
| .section = "alloc_tags", | |
| .tag_size = sizeof(struct alloc_tag), | |
| .module_unload = alloc_tag_module_unload, | |
| }; | |
| alloc_tag_cttype = codetag_register_type(&desc); | |
| if (IS_ERR(alloc_tag_cttype)) | |
| return PTR_ERR(alloc_tag_cttype); | |
| sysctl_init(); | |
| procfs_init(); | |
| return 0; | |
| } | |
| module_init(alloc_tag_init); |