// SPDX-License-Identifier: GPL-2.0-only#include <linux/alloc_tag.h>#include <linux/execmem.h>#include <linux/fs.h>#include <linux/gfp.h>#include <linux/kallsyms.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/compat.h>#include <linux/page_ext.h>#include <linux/pgalloc_tag.h>#include <linux/proc_fs.h>#include <linux/rcupdate.h>#include <linux/seq_buf.h>#include <linux/seq_file.h>#include <linux/string_choices.h>#include <linux/vmalloc.h>#include <linux/kmemleak.h>#include <uapi/linux/alloc_tag.h> #include "internal.h"#include "page_alloc.h" #define ALLOCINFO_FILE_NAME "allocinfo"#define MODULE_ALLOC_TAG_VMAP_SIZE (100000UL * sizeof(struct alloc_tag))#define SECTION_START(NAME) (CODETAG_SECTION_START_PREFIX NAME)#define SECTION_STOP(NAME) (CODETAG_SECTION_STOP_PREFIX NAME) #ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULTstatic bool mem_profiling_support = true;#elsestatic bool mem_profiling_support;#endif /* * Memory allocation profiling is permanently disabled and cannot be enabled. * Must be called after setup_early_mem_profiling(). */bool mem_alloc_profiling_permanently_disabled(void){ return !mem_profiling_support;} static struct codetag_type *alloc_tag_cttype; #ifdef CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPUDEFINE_PER_CPU(struct alloc_tag_counters, _shared_alloc_tag);EXPORT_SYMBOL(_shared_alloc_tag);#endif DEFINE_STATIC_KEY_MAYBE(CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT, mem_alloc_profiling_key);EXPORT_SYMBOL(mem_alloc_profiling_key); DEFINE_STATIC_KEY_FALSE(mem_profiling_compressed); struct alloc_tag_kernel_section kernel_tags = { NULL, 0 };unsigned long alloc_tag_ref_mask;int alloc_tag_ref_offs; struct allocinfo_private { struct codetag_iterator iter; struct codetag_iterator reported_iter; bool print_header; struct allocinfo_filter filter; /* ioctl uses a separate iterator not to interfere with reads */ struct codetag_iterator ioctl_iter; bool positioned; /* seq_open_private() sets to 0 */ struct mutex ioctl_lock;}; static void *allocinfo_start(struct seq_file *m, loff_t *pos){ struct allocinfo_private *priv; loff_t node = *pos; priv = (struct allocinfo_private *)m->private; codetag_lock_module_list(alloc_tag_cttype); if (node == 0) { priv->print_header = true; priv->iter = codetag_get_ct_iter(alloc_tag_cttype); } else { priv->iter = priv->reported_iter; } codetag_next_ct(&priv->iter); return priv->iter.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; priv->reported_iter = priv->iter; ct = codetag_next_ct(&priv->iter); (*pos)++; if (!ct) return NULL; return priv;} static void allocinfo_stop(struct seq_file *m, void *arg){ codetag_unlock_module_list(alloc_tag_cttype);} static void print_allocinfo_header(struct seq_buf *buf){ /* Output format version, so we can change it. */ seq_buf_printf(buf, "allocinfo - version: 2.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); if (unlikely(alloc_tag_is_inaccurate(tag))) seq_buf_printf(out, " accurate:no"); 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,}; /* * Initializes seq_file operations and allocates private state when opening * the /proc/allocinfo procfs entry. */static int allocinfo_open(struct inode *inode, struct file *file){ int ret; ret = seq_open_private(file, &allocinfo_seq_op, sizeof(struct allocinfo_private)); if (!ret) { struct seq_file *m = file->private_data; struct allocinfo_private *priv = m->private; mutex_init(&priv->ioctl_lock); } return ret;} /* * Cleans up the seq_file state and frees up the private state allocated in * allocinfo_open() when closing the /proc/allocinfo file descriptor. */static int allocinfo_release(struct inode *inode, struct file *file){ struct seq_file *m = file->private_data; struct allocinfo_private *priv = m->private; mutex_destroy(&priv->ioctl_lock); return seq_release_private(inode, file);} /* * Returns a pointer to the suffix of a string so that its length fits within * ALLOCINFO_STR_SIZE, preserving the trailing characters. * Function, file and module names often have the same prefixes, therefore * when filtering by these criteria, we compare the last 64 characters to * minimize the chances of name collisions */static const char *allocinfo_str(const char *str){ size_t len = strlen(str); /* Keep an extra space for the trailing NULL. */ if (len >= ALLOCINFO_STR_SIZE) str += (len - ALLOCINFO_STR_SIZE) + 1; return str;} /* Copy a string and trim from the beginning if it's too long */static void allocinfo_copy_str(char *dest, const char *src){ strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE);} /* Compare two strings and only consider the trimmed suffix if s1 is too long */static int allocinfo_cmp_str(const char *str, const char *template){ return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE);} /* Fetch the per-CPU counters */static inline struct alloc_tag_counters allocinfo_prefetch_counters(struct codetag *ct){ return alloc_tag_read(ct_to_alloc_tag(ct));} /* * Populates the UAPI allocinfo_tag_data structure with active runtime * profiling counters extracted from the given kernel codetag. */static void allocinfo_to_params(struct codetag *ct, struct allocinfo_tag_data *data, struct alloc_tag_counters *counters){ if (ct->modname) allocinfo_copy_str(data->tag.modname, ct->modname); else data->tag.modname[0] = '\0'; allocinfo_copy_str(data->tag.function, ct->function); allocinfo_copy_str(data->tag.filename, ct->filename); data->tag.lineno = ct->lineno; data->counter.bytes = counters->bytes; data->counter.calls = counters->calls; data->counter.accurate = !alloc_tag_is_inaccurate(ct_to_alloc_tag(ct));} /* * Retrieves the unique content ID representing the current allocation tag module * layout, allowing userspace to detect if modules were loaded / unloaded. */static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg){ struct allocinfo_content_id params; codetag_lock_module_list(alloc_tag_cttype); params.id = codetag_get_content_id(alloc_tag_cttype); codetag_unlock_module_list(alloc_tag_cttype); if (copy_to_user(arg, ¶ms, sizeof(params))) return -EFAULT; return 0;} /* * Verifies whether a given codetag satisfies the active filtering criteria by * matching its characteristics against the specified filter. */static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter, struct alloc_tag_counters *counters, bool *fetched_counters){ bool inaccurate; if (!filter || !filter->mask) return true; if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) { /* user wants to filter by modname but ct->modname is NULL */ if (!ct->modname) { /* validate if user was attempting to filter for built-in allocations */ if (filter->fields.modname[0] != '\0') return false; } else if (allocinfo_cmp_str(ct->modname, filter->fields.modname)) return false; } if ((filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) && ct->function && allocinfo_cmp_str(ct->function, filter->fields.function)) return false; if ((filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) && ct->filename && allocinfo_cmp_str(ct->filename, filter->fields.filename)) return false; if ((filter->mask & ALLOCINFO_FILTER_MASK_LINENO) && ct->lineno != filter->fields.lineno) return false; if (filter->mask & ALLOCINFO_FILTER_MASK_INACCURATE) { inaccurate = !!(ct->flags & CODETAG_FLAG_INACCURATE); if (inaccurate != !!(filter->inaccurate)) return false; } if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) { if (!*fetched_counters) { *counters = allocinfo_prefetch_counters(ct); *fetched_counters = true; } if ((filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) && counters->bytes < filter->min_size) return false; if ((filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) && counters->bytes > filter->max_size) return false; } return true;} /* * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its * profiling data and returns it to userspace. */static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg){ struct allocinfo_private *priv; struct codetag *ct; struct allocinfo_get_at params = {0}; __u64 skip_count; struct alloc_tag_counters counters; bool fetched_counters; if (copy_from_user(¶ms, arg, sizeof(params))) return -EFAULT; if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS) return -EINVAL; if ((params.filter.mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) && (params.filter.mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) && params.filter.min_size > params.filter.max_size) return -EINVAL; priv = m->private; mutex_lock(&priv->ioctl_lock); codetag_lock_module_list(alloc_tag_cttype); if (params.pos >= codetag_get_count(alloc_tag_cttype)) { codetag_unlock_module_list(alloc_tag_cttype); mutex_unlock(&priv->ioctl_lock); return -ENOENT; } skip_count = params.pos; if (params.filter.mask) priv->filter = params.filter; else priv->filter.mask = 0; /* Find the codetag */ priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); ct = codetag_next_ct(&priv->ioctl_iter); while (ct) { fetched_counters = false; if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) { if (skip_count == 0) break; skip_count--; } ct = codetag_next_ct(&priv->ioctl_iter); } if (ct) { if (!fetched_counters) counters = allocinfo_prefetch_counters(ct); allocinfo_to_params(ct, ¶ms.data, &counters); priv->positioned = true; } codetag_unlock_module_list(alloc_tag_cttype); mutex_unlock(&priv->ioctl_lock); if (!ct) return -ENOENT; if (copy_to_user(arg, ¶ms, sizeof(params))) return -EFAULT; return 0;} /* * Advances the ioctl iterator to the next allocation tag in the sequence and * returns its profiling data to userspace. */static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg){ struct allocinfo_private *priv; struct codetag *ct; struct allocinfo_tag_data params; int ret = 0; struct alloc_tag_counters counters; bool fetched_counters; memset(¶ms, 0, sizeof(params)); priv = m->private; mutex_lock(&priv->ioctl_lock); codetag_lock_module_list(alloc_tag_cttype); if (!priv->positioned) { priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); priv->positioned = true; } ct = codetag_next_ct(&priv->ioctl_iter); while (ct) { fetched_counters = false; if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) break; ct = codetag_next_ct(&priv->ioctl_iter); } if (ct) { if (!fetched_counters) counters = allocinfo_prefetch_counters(ct); allocinfo_to_params(ct, ¶ms, &counters); } if (!ct) { priv->positioned = false; ret = -ENOENT; } codetag_unlock_module_list(alloc_tag_cttype); mutex_unlock(&priv->ioctl_lock); if (ret == 0) { if (copy_to_user(arg, ¶ms, sizeof(params))) return -EFAULT; } return ret;} /* * Entry point ioctl function for /proc/allocinfo routing requests to fetch the * layout content ID, seek to a specific tag, or read sequential tags. */static long allocinfo_ioctl(struct file *file, unsigned int cmd, unsigned long __arg){ void __user *arg = (void __user *)__arg; int ret; switch (cmd) { case ALLOCINFO_IOC_CONTENT_ID: ret = allocinfo_ioctl_get_content_id(file->private_data, arg); break; case ALLOCINFO_IOC_GET_AT: ret = allocinfo_ioctl_get_at(file->private_data, arg); break; case ALLOCINFO_IOC_GET_NEXT: ret = allocinfo_ioctl_get_next(file->private_data, arg); break; default: ret = -ENOIOCTLCMD; break; } return ret;} #ifdef CONFIG_COMPATstatic long allocinfo_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ return allocinfo_ioctl(file, cmd, (unsigned long)compat_ptr(arg));}#endif static const struct proc_ops allocinfo_proc_ops = { .proc_open = allocinfo_open, .proc_read_iter = seq_read_iter, .proc_lseek = seq_lseek, .proc_release = allocinfo_release, .proc_ioctl = allocinfo_ioctl,#ifdef CONFIG_COMPAT .proc_compat_ioctl = allocinfo_compat_ioctl,#endif}; 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 (IS_ERR_OR_NULL(alloc_tag_cttype)) return 0; if (can_sleep) codetag_lock_module_list(alloc_tag_cttype); else if (!codetag_trylock_module_list(alloc_tag_cttype)) return 0;Lines 1–500 of 1367. Load more
// SPDX-License-Identifier: GPL-2.0-only #include <linux/alloc_tag.h> #include <linux/execmem.h> #include <linux/fs.h> #include <linux/gfp.h> #include <linux/kallsyms.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/compat.h> #include <linux/page_ext.h> #include <linux/pgalloc_tag.h> #include <linux/proc_fs.h> #include <linux/rcupdate.h> #include <linux/seq_buf.h> #include <linux/seq_file.h> #include <linux/string_choices.h> #include <linux/vmalloc.h> #include <linux/kmemleak.h> #include <uapi/linux/alloc_tag.h> #include "internal.h" #include "page_alloc.h" #define ALLOCINFO_FILE_NAME "allocinfo" #define MODULE_ALLOC_TAG_VMAP_SIZE (100000UL * sizeof(struct alloc_tag)) #define SECTION_START(NAME) (CODETAG_SECTION_START_PREFIX NAME) #define SECTION_STOP(NAME) (CODETAG_SECTION_STOP_PREFIX NAME) #ifdef CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT static bool mem_profiling_support = true; #else static bool mem_profiling_support; #endif /* * Memory allocation profiling is permanently disabled and cannot be enabled. * Must be called after setup_early_mem_profiling(). */ bool mem_alloc_profiling_permanently_disabled(void) { return !mem_profiling_support; } static struct codetag_type *alloc_tag_cttype; #ifdef CONFIG_ARCH_MODULE_NEEDS_WEAK_PER_CPU DEFINE_PER_CPU(struct alloc_tag_counters, _shared_alloc_tag); EXPORT_SYMBOL(_shared_alloc_tag); #endif DEFINE_STATIC_KEY_MAYBE(CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT, mem_alloc_profiling_key); EXPORT_SYMBOL(mem_alloc_profiling_key); DEFINE_STATIC_KEY_FALSE(mem_profiling_compressed); struct alloc_tag_kernel_section kernel_tags = { NULL, 0 }; unsigned long alloc_tag_ref_mask; int alloc_tag_ref_offs; struct allocinfo_private { struct codetag_iterator iter; struct codetag_iterator reported_iter; bool print_header; struct allocinfo_filter filter; /* ioctl uses a separate iterator not to interfere with reads */ struct codetag_iterator ioctl_iter; bool positioned; /* seq_open_private() sets to 0 */ struct mutex ioctl_lock; }; static void *allocinfo_start(struct seq_file *m, loff_t *pos) { struct allocinfo_private *priv; loff_t node = *pos; priv = (struct allocinfo_private *)m->private; codetag_lock_module_list(alloc_tag_cttype); if (node == 0) { priv->print_header = true; priv->iter = codetag_get_ct_iter(alloc_tag_cttype); } else { priv->iter = priv->reported_iter; } codetag_next_ct(&priv->iter); return priv->iter.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; priv->reported_iter = priv->iter; ct = codetag_next_ct(&priv->iter); (*pos)++; if (!ct) return NULL; return priv; } static void allocinfo_stop(struct seq_file *m, void *arg) { codetag_unlock_module_list(alloc_tag_cttype); } static void print_allocinfo_header(struct seq_buf *buf) { /* Output format version, so we can change it. */ seq_buf_printf(buf, "allocinfo - version: 2.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); if (unlikely(alloc_tag_is_inaccurate(tag))) seq_buf_printf(out, " accurate:no"); 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, }; /* * Initializes seq_file operations and allocates private state when opening * the /proc/allocinfo procfs entry. */ static int allocinfo_open(struct inode *inode, struct file *file) { int ret; ret = seq_open_private(file, &allocinfo_seq_op, sizeof(struct allocinfo_private)); if (!ret) { struct seq_file *m = file->private_data; struct allocinfo_private *priv = m->private; mutex_init(&priv->ioctl_lock); } return ret; } /* * Cleans up the seq_file state and frees up the private state allocated in * allocinfo_open() when closing the /proc/allocinfo file descriptor. */ static int allocinfo_release(struct inode *inode, struct file *file) { struct seq_file *m = file->private_data; struct allocinfo_private *priv = m->private; mutex_destroy(&priv->ioctl_lock); return seq_release_private(inode, file); } /* * Returns a pointer to the suffix of a string so that its length fits within * ALLOCINFO_STR_SIZE, preserving the trailing characters. * Function, file and module names often have the same prefixes, therefore * when filtering by these criteria, we compare the last 64 characters to * minimize the chances of name collisions */ static const char *allocinfo_str(const char *str) { size_t len = strlen(str); /* Keep an extra space for the trailing NULL. */ if (len >= ALLOCINFO_STR_SIZE) str += (len - ALLOCINFO_STR_SIZE) + 1; return str; } /* Copy a string and trim from the beginning if it's too long */ static void allocinfo_copy_str(char *dest, const char *src) { strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE); } /* Compare two strings and only consider the trimmed suffix if s1 is too long */ static int allocinfo_cmp_str(const char *str, const char *template) { return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE); } /* Fetch the per-CPU counters */ static inline struct alloc_tag_counters allocinfo_prefetch_counters(struct codetag *ct) { return alloc_tag_read(ct_to_alloc_tag(ct)); } /* * Populates the UAPI allocinfo_tag_data structure with active runtime * profiling counters extracted from the given kernel codetag. */ static void allocinfo_to_params(struct codetag *ct, struct allocinfo_tag_data *data, struct alloc_tag_counters *counters) { if (ct->modname) allocinfo_copy_str(data->tag.modname, ct->modname); else data->tag.modname[0] = '\0'; allocinfo_copy_str(data->tag.function, ct->function); allocinfo_copy_str(data->tag.filename, ct->filename); data->tag.lineno = ct->lineno; data->counter.bytes = counters->bytes; data->counter.calls = counters->calls; data->counter.accurate = !alloc_tag_is_inaccurate(ct_to_alloc_tag(ct)); } /* * Retrieves the unique content ID representing the current allocation tag module * layout, allowing userspace to detect if modules were loaded / unloaded. */ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg) { struct allocinfo_content_id params; codetag_lock_module_list(alloc_tag_cttype); params.id = codetag_get_content_id(alloc_tag_cttype); codetag_unlock_module_list(alloc_tag_cttype); if (copy_to_user(arg, ¶ms, sizeof(params))) return -EFAULT; return 0; } /* * Verifies whether a given codetag satisfies the active filtering criteria by * matching its characteristics against the specified filter. */ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter, struct alloc_tag_counters *counters, bool *fetched_counters) { bool inaccurate; if (!filter || !filter->mask) return true; if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) { /* user wants to filter by modname but ct->modname is NULL */ if (!ct->modname) { /* validate if user was attempting to filter for built-in allocations */ if (filter->fields.modname[0] != '\0') return false; } else if (allocinfo_cmp_str(ct->modname, filter->fields.modname)) return false; } if ((filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) && ct->function && allocinfo_cmp_str(ct->function, filter->fields.function)) return false; if ((filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) && ct->filename && allocinfo_cmp_str(ct->filename, filter->fields.filename)) return false; if ((filter->mask & ALLOCINFO_FILTER_MASK_LINENO) && ct->lineno != filter->fields.lineno) return false; if (filter->mask & ALLOCINFO_FILTER_MASK_INACCURATE) { inaccurate = !!(ct->flags & CODETAG_FLAG_INACCURATE); if (inaccurate != !!(filter->inaccurate)) return false; } if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) { if (!*fetched_counters) { *counters = allocinfo_prefetch_counters(ct); *fetched_counters = true; } if ((filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) && counters->bytes < filter->min_size) return false; if ((filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) && counters->bytes > filter->max_size) return false; } return true; } /* * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its * profiling data and returns it to userspace. */ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) { struct allocinfo_private *priv; struct codetag *ct; struct allocinfo_get_at params = {0}; __u64 skip_count; struct alloc_tag_counters counters; bool fetched_counters; if (copy_from_user(¶ms, arg, sizeof(params))) return -EFAULT; if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS) return -EINVAL; if ((params.filter.mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) && (params.filter.mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) && params.filter.min_size > params.filter.max_size) return -EINVAL; priv = m->private; mutex_lock(&priv->ioctl_lock); codetag_lock_module_list(alloc_tag_cttype); if (params.pos >= codetag_get_count(alloc_tag_cttype)) { codetag_unlock_module_list(alloc_tag_cttype); mutex_unlock(&priv->ioctl_lock); return -ENOENT; } skip_count = params.pos; if (params.filter.mask) priv->filter = params.filter; else priv->filter.mask = 0; /* Find the codetag */ priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); ct = codetag_next_ct(&priv->ioctl_iter); while (ct) { fetched_counters = false; if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) { if (skip_count == 0) break; skip_count--; } ct = codetag_next_ct(&priv->ioctl_iter); } if (ct) { if (!fetched_counters) counters = allocinfo_prefetch_counters(ct); allocinfo_to_params(ct, ¶ms.data, &counters); priv->positioned = true; } codetag_unlock_module_list(alloc_tag_cttype); mutex_unlock(&priv->ioctl_lock); if (!ct) return -ENOENT; if (copy_to_user(arg, ¶ms, sizeof(params))) return -EFAULT; return 0; } /* * Advances the ioctl iterator to the next allocation tag in the sequence and * returns its profiling data to userspace. */ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg) { struct allocinfo_private *priv; struct codetag *ct; struct allocinfo_tag_data params; int ret = 0; struct alloc_tag_counters counters; bool fetched_counters; memset(¶ms, 0, sizeof(params)); priv = m->private; mutex_lock(&priv->ioctl_lock); codetag_lock_module_list(alloc_tag_cttype); if (!priv->positioned) { priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); priv->positioned = true; } ct = codetag_next_ct(&priv->ioctl_iter); while (ct) { fetched_counters = false; if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) break; ct = codetag_next_ct(&priv->ioctl_iter); } if (ct) { if (!fetched_counters) counters = allocinfo_prefetch_counters(ct); allocinfo_to_params(ct, ¶ms, &counters); } if (!ct) { priv->positioned = false; ret = -ENOENT; } codetag_unlock_module_list(alloc_tag_cttype); mutex_unlock(&priv->ioctl_lock); if (ret == 0) { if (copy_to_user(arg, ¶ms, sizeof(params))) return -EFAULT; } return ret; } /* * Entry point ioctl function for /proc/allocinfo routing requests to fetch the * layout content ID, seek to a specific tag, or read sequential tags. */ static long allocinfo_ioctl(struct file *file, unsigned int cmd, unsigned long __arg) { void __user *arg = (void __user *)__arg; int ret; switch (cmd) { case ALLOCINFO_IOC_CONTENT_ID: ret = allocinfo_ioctl_get_content_id(file->private_data, arg); break; case ALLOCINFO_IOC_GET_AT: ret = allocinfo_ioctl_get_at(file->private_data, arg); break; case ALLOCINFO_IOC_GET_NEXT: ret = allocinfo_ioctl_get_next(file->private_data, arg); break; default: ret = -ENOIOCTLCMD; break; } return ret; } #ifdef CONFIG_COMPAT static long allocinfo_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { return allocinfo_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); } #endif static const struct proc_ops allocinfo_proc_ops = { .proc_open = allocinfo_open, .proc_read_iter = seq_read_iter, .proc_lseek = seq_lseek, .proc_release = allocinfo_release, .proc_ioctl = allocinfo_ioctl, #ifdef CONFIG_COMPAT .proc_compat_ioctl = allocinfo_compat_ioctl, #endif }; 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 (IS_ERR_OR_NULL(alloc_tag_cttype)) return 0; if (can_sleep) codetag_lock_module_list(alloc_tag_cttype); else if (!codetag_trylock_module_list(alloc_tag_cttype)) return 0;