mirror of
https://github.com/hardkernel/kernel_common_drivers.git
synced 2026-06-25 12:03:48 +09:00
deecc3a6ac
PD#SWPL-101826 Problem: CIDs ignored by the owner need to be commented. Solution: Add comment. Verify: ./scripts/amlogic/coverity_check.sh Change-Id: I98ce05e6a1e20f6d17bbb5dbab161cc832b4686d Signed-off-by: shufei.zhao <shufei.zhao@amlogic.com>
462 lines
12 KiB
C
462 lines
12 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/gfp.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/mmzone.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/sort.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/list.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/kernel_stat.h>
|
|
#include <linux/memcontrol.h>
|
|
#include <linux/mm_inline.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/jhash.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <linux/amlogic/file_cache.h>
|
|
#include <linux/tick.h>
|
|
#include <trace/hooks/mm.h>
|
|
#include <linux/amlogic/pin_file.h>
|
|
|
|
static int file_cache_filter = 64; /* not print size < file_cache_filter, kb */
|
|
|
|
static struct proc_dir_entry *d_filecache;
|
|
#if defined(CONFIG_TRACEPOINTS) && defined(CONFIG_ANDROID_VENDOR_HOOKS)
|
|
static int record_fct(struct page *page, struct file_cache_trace *fct,
|
|
int *used, struct rb_root *root, int mc,
|
|
int active)
|
|
{
|
|
struct address_space *mapping;
|
|
struct file_cache_trace *tmp;
|
|
struct rb_node **link, *parent = NULL;
|
|
|
|
mapping = page_mapping(page);
|
|
if (!mapping)
|
|
return -1;
|
|
|
|
link = &root->rb_node;
|
|
while (*link) {
|
|
parent = *link;
|
|
tmp = rb_entry(parent, struct file_cache_trace, entry);
|
|
if (mapping == tmp->mapping) { /* match */
|
|
tmp->count++;
|
|
tmp->mapcnt += mc;
|
|
if (active)
|
|
tmp->active_count++;
|
|
else
|
|
tmp->inactive_count++;
|
|
return 0;
|
|
} else if (mapping < tmp->mapping) {
|
|
link = &tmp->entry.rb_left;
|
|
} else {
|
|
link = &tmp->entry.rb_right;
|
|
}
|
|
}
|
|
/* not found, get a new page summary */
|
|
if (*used >= MAX_FCT) {
|
|
pr_err("file out of range\n");
|
|
return -ERANGE;
|
|
}
|
|
tmp = &fct[*used];
|
|
*used = (*used) + 1;
|
|
tmp->mapping = mapping;
|
|
tmp->count++;
|
|
tmp->mapcnt += mc;
|
|
tmp->off = page_to_pgoff(page);
|
|
if (active)
|
|
tmp->active_count++;
|
|
else
|
|
tmp->inactive_count++;
|
|
rb_link_node(&tmp->entry, parent, link);
|
|
rb_insert_color(&tmp->entry, root);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
struct filecache_stat {
|
|
unsigned int total;
|
|
unsigned int nomap[3]; /* include active/inactive */
|
|
unsigned int files;
|
|
};
|
|
|
|
static struct filecache_stat fs;
|
|
|
|
static inline unsigned long vma_start_pgoff(struct vm_area_struct *v)
|
|
{
|
|
return v->vm_pgoff;
|
|
}
|
|
|
|
static inline unsigned long vma_last_pgoff(struct vm_area_struct *v)
|
|
{
|
|
return v->vm_pgoff + vma_pages(v) - 1;
|
|
}
|
|
|
|
/*
|
|
* Iterate over intervals intersecting [start;last]
|
|
*
|
|
* Note that a node's interval intersects [start;last] iff:
|
|
* Cond1: vma_start_pgoff(node) <= last
|
|
* and
|
|
* Cond2: start <= vma_last_pgoff(node)
|
|
*/
|
|
|
|
static struct vm_area_struct *
|
|
aml_vma_subtree_search(struct vm_area_struct *node, unsigned long start, unsigned long last)
|
|
{
|
|
while (true) {
|
|
/*
|
|
* Loop invariant: start <= node->shared.rb_subtree_last
|
|
* (Cond2 is satisfied by one of the subtree nodes)
|
|
*/
|
|
if (node->shared.rb.rb_left) {
|
|
struct vm_area_struct *left = rb_entry(node->shared.rb.rb_left,
|
|
struct vm_area_struct, shared.rb);
|
|
if (start <= left->shared.rb_subtree_last) {
|
|
/*
|
|
* Some nodes in left subtree satisfy Cond2.
|
|
* Iterate to find the leftmost such node N.
|
|
* If it also satisfies Cond1, that's the
|
|
* match we are looking for. Otherwise, there
|
|
* is no matching interval as nodes to the
|
|
* right of N can't satisfy Cond1 either.
|
|
*/
|
|
node = left;
|
|
continue;
|
|
}
|
|
}
|
|
if (vma_start_pgoff(node) <= last) { /* Cond1 */
|
|
if (start <= vma_last_pgoff(node)) /* Cond2 */
|
|
return node; /* node is leftmost match */
|
|
if (node->shared.rb.rb_right) {
|
|
node = rb_entry(node->shared.rb.rb_right,
|
|
struct vm_area_struct, shared.rb);
|
|
if (start <= node->shared.rb_subtree_last)
|
|
continue;
|
|
}
|
|
}
|
|
return NULL; /* No match */
|
|
}
|
|
}
|
|
|
|
static struct vm_area_struct *
|
|
aml_vma_iter_first(struct rb_root_cached *root,
|
|
unsigned long start, unsigned long last)
|
|
{
|
|
struct vm_area_struct *node, *leftmost;
|
|
|
|
if (!root->rb_root.rb_node)
|
|
return NULL;
|
|
|
|
/*
|
|
* Fastpath range intersection/overlap between A: [a0, a1] and
|
|
* B: [b0, b1] is given by:
|
|
*
|
|
* a0 <= b1 && b0 <= a1
|
|
*
|
|
* ... where A holds the lock range and B holds the smallest
|
|
* 'start' and largest 'last' in the tree. For the later, we
|
|
* rely on the root node, which by augmented interval tree
|
|
* property, holds the largest value in its last-in-subtree.
|
|
* This allows mitigating some of the tree walk overhead for
|
|
* non-intersecting ranges, maintained and consulted in O(1).
|
|
*/
|
|
node = rb_entry(root->rb_root.rb_node, struct vm_area_struct, shared.rb);
|
|
if (node->shared.rb_subtree_last < start)
|
|
return NULL;
|
|
|
|
leftmost = rb_entry(root->rb_leftmost, struct vm_area_struct, shared.rb);
|
|
if (vma_start_pgoff(leftmost) > last)
|
|
return NULL;
|
|
|
|
return aml_vma_subtree_search(node, start, last);
|
|
}
|
|
|
|
#if defined(CONFIG_TRACEPOINTS) && defined(CONFIG_ANDROID_VENDOR_HOOKS)
|
|
void get_file_cache_hook(void *data, struct lruvec *lruvec)
|
|
{
|
|
struct page *page, *next;
|
|
struct list_head *list;
|
|
struct rb_root fct_root = RB_ROOT;
|
|
unsigned int t = 0, in = 0, an = 0;
|
|
int r, mc, lru = 0, a = 0;
|
|
struct file_cache_trace *fct = (struct file_cache_trace *)data;
|
|
|
|
for_each_lru(lru) {
|
|
/* only count for filecache */
|
|
if (!is_file_lru(lru) &&
|
|
lru != LRU_UNEVICTABLE)
|
|
continue;
|
|
|
|
if (lru == LRU_ACTIVE_FILE)
|
|
a = 1;
|
|
else
|
|
a = 0;
|
|
|
|
list = &lruvec->lists[lru];
|
|
spin_lock_irq(&lruvec->lru_lock);
|
|
list_for_each_entry_safe(page, next,
|
|
list, lru) {
|
|
if (!page_is_file_lru(page))
|
|
continue;
|
|
|
|
t++;
|
|
mc = page_mapcount(page);
|
|
if (mc <= 0) {
|
|
if (a)
|
|
an++;
|
|
else
|
|
in++;
|
|
continue;
|
|
}
|
|
r = record_fct(page, fct, &fs.files,
|
|
&fct_root, mc, a);
|
|
/* some data may lost */
|
|
if (r == -ERANGE) {
|
|
spin_unlock_irq(&lruvec->lru_lock);
|
|
goto out;
|
|
}
|
|
if (r) {
|
|
if (a)
|
|
an++;
|
|
else
|
|
in++;
|
|
}
|
|
}
|
|
spin_unlock_irq(&lruvec->lru_lock);
|
|
}
|
|
out: /* update final statistics */
|
|
fs.total += t;
|
|
fs.nomap[0] += an + in;
|
|
fs.nomap[1] += in;
|
|
fs.nomap[2] += an;
|
|
}
|
|
#endif
|
|
|
|
static int fcmp(const void *x1, const void *x2)
|
|
{
|
|
struct file_cache_trace *s1, *s2;
|
|
|
|
s1 = (struct file_cache_trace *)x1;
|
|
s2 = (struct file_cache_trace *)x2;
|
|
return s2->count - s1->count;
|
|
}
|
|
|
|
static char *parse_fct_name(struct file_cache_trace *fct, char *buf)
|
|
{
|
|
struct address_space *mapping = fct->mapping;
|
|
pgoff_t pgoff;
|
|
struct vm_area_struct *vma;
|
|
struct file *file;
|
|
|
|
pgoff = fct->off;
|
|
vma = aml_vma_iter_first(&mapping->i_mmap, pgoff, pgoff);
|
|
if (!vma) {
|
|
pr_err("%s, can't find vma for mapping:%p\n",
|
|
__func__, mapping);
|
|
return NULL;
|
|
}
|
|
memset(buf, 0, 256);
|
|
file = vma->vm_file;
|
|
if (file) {
|
|
char *p = d_path(&file->f_path, buf, 256);
|
|
|
|
if (!IS_ERR(p))
|
|
mangle_path(buf, p, "\n");
|
|
else
|
|
return NULL;
|
|
}
|
|
if (mapping->flags & (1 << AS_LOCK_MAPPING))
|
|
strncat(buf, " [pin]", 255);
|
|
|
|
return buf;
|
|
}
|
|
|
|
#ifdef arch_idle_time
|
|
static u64 get_iowait_time(struct kernel_cpustat *kcs, int cpu)
|
|
{
|
|
u64 iowait;
|
|
|
|
iowait = kcs->cpustat[CPUTIME_IOWAIT];
|
|
if (cpu_online(cpu) && nr_iowait_cpu(cpu))
|
|
iowait += arch_idle_time(cpu);
|
|
return iowait;
|
|
}
|
|
#else
|
|
static u64 aml_get_idle_time(struct kernel_cpustat *kcs, int cpu)
|
|
{
|
|
u64 idle, idle_usecs = -1ULL;
|
|
|
|
if (cpu_online(cpu))
|
|
idle_usecs = get_cpu_idle_time_us(cpu, NULL);
|
|
|
|
if (idle_usecs == -1ULL)
|
|
/* !NO_HZ or cpu offline so we can rely on cpustat.idle */
|
|
idle = kcs->cpustat[CPUTIME_IDLE];
|
|
else
|
|
idle = idle_usecs * NSEC_PER_USEC;
|
|
|
|
return idle;
|
|
}
|
|
|
|
static u64 get_iowait_time(struct kernel_cpustat *kcs, int cpu)
|
|
{
|
|
u64 iowait, iowait_usecs = -1ULL;
|
|
|
|
if (cpu_online(cpu))
|
|
iowait_usecs = get_cpu_iowait_time_us(cpu, NULL);
|
|
|
|
if (iowait_usecs == -1ULL)
|
|
/* !NO_HZ or cpu offline so we can rely on cpustat.iowait */
|
|
iowait = kcs->cpustat[CPUTIME_IOWAIT];
|
|
else
|
|
iowait = iowait_usecs * NSEC_PER_USEC;
|
|
|
|
return iowait;
|
|
}
|
|
#endif
|
|
|
|
static u64 get_iow_time(u64 *cpu)
|
|
{
|
|
u64 user, nice, system, idle, iowait, irq, softirq, steal;
|
|
u64 guest, guest_nice;
|
|
int i;
|
|
|
|
user = 0;
|
|
nice = 0;
|
|
system = 0;
|
|
idle = 0;
|
|
iowait = 0;
|
|
irq = 0;
|
|
softirq = 0;
|
|
steal = 0;
|
|
guest = 0;
|
|
guest_nice = 0;
|
|
|
|
for_each_possible_cpu(i) {
|
|
/*
|
|
* When runtime, it will reallocate a
|
|
* new buffer based on the number of CPUs.
|
|
*/
|
|
/* coverity[overrun-call:SUPPRESS] */
|
|
struct kernel_cpustat *kcs = &kcpustat_cpu(i);
|
|
|
|
user += kcpustat_cpu(i).cpustat[CPUTIME_USER];
|
|
nice += kcpustat_cpu(i).cpustat[CPUTIME_NICE];
|
|
system += kcpustat_cpu(i).cpustat[CPUTIME_SYSTEM];
|
|
idle += aml_get_idle_time(kcs, i);
|
|
iowait += get_iowait_time(kcs, i);
|
|
irq += kcpustat_cpu(i).cpustat[CPUTIME_IRQ];
|
|
softirq += kcpustat_cpu(i).cpustat[CPUTIME_SOFTIRQ];
|
|
steal += kcpustat_cpu(i).cpustat[CPUTIME_STEAL];
|
|
guest += kcpustat_cpu(i).cpustat[CPUTIME_GUEST];
|
|
guest_nice += kcpustat_cpu(i).cpustat[CPUTIME_GUEST_NICE];
|
|
}
|
|
*cpu = user + nice + system + idle + iowait + irq + softirq + steal +
|
|
guest + guest_nice;
|
|
return iowait;
|
|
}
|
|
|
|
#define K(x) ((unsigned long)(x) << (PAGE_SHIFT - 10))
|
|
static int filecache_show(struct seq_file *m, void *arg)
|
|
{
|
|
int i;
|
|
unsigned long tick = 0;
|
|
unsigned long long now;
|
|
u64 iow = 0, cputime = 0;
|
|
char fname[256];
|
|
struct file_cache_trace *fct;
|
|
unsigned int small_files = 0, small_fcache = 0;
|
|
unsigned int small_active = 0, small_inactive = 0;
|
|
|
|
fct = vzalloc(sizeof(*fct) * MAX_FCT);
|
|
if (!fct)
|
|
return -ENOMEM;
|
|
|
|
tick = sched_clock();
|
|
/* update_file_cache(&fs, fct); */
|
|
memset(&fs, 0, sizeof(struct filecache_stat));
|
|
#if defined(CONFIG_TRACEPOINTS) && defined(CONFIG_ANDROID_VENDOR_HOOKS)
|
|
register_trace_android_vh_do_traversal_lruvec(get_file_cache_hook, (void *)fct);
|
|
do_traversal_all_lruvec();
|
|
unregister_trace_android_vh_do_traversal_lruvec(get_file_cache_hook, (void *)fct);
|
|
#endif
|
|
|
|
now = sched_clock();
|
|
tick = now - tick;
|
|
|
|
sort(fct, fs.files, sizeof(struct file_cache_trace), fcmp, NULL);
|
|
seq_puts(m, "------------------------------\n");
|
|
seq_puts(m, "count(KB), active, inactive, mc, lc, amc, file name\n");
|
|
for (i = 0; i < fs.files; i++) {
|
|
if (K(fct[i].count) < file_cache_filter) {
|
|
small_files++;
|
|
small_fcache += fct[i].count;
|
|
small_active += fct[i].active_count;
|
|
small_inactive += fct[i].inactive_count;
|
|
continue;
|
|
}
|
|
seq_printf(m, " %6lu, %6lu, %6lu, %6u, %4ld, %3u, %s\n",
|
|
K(fct[i].count), K(fct[i].active_count),
|
|
K(fct[i].inactive_count), fct[i].mapcnt,
|
|
K(fct[i].lock_count),
|
|
fct[i].mapcnt / fct[i].count,
|
|
parse_fct_name(&fct[i], fname));
|
|
}
|
|
iow = get_iow_time(&cputime);
|
|
seq_printf(m, "small files:%u, cache:%lu [%lu/%lu] KB, time:%ld\n",
|
|
small_files, K(small_fcache),
|
|
K(small_inactive), K(small_active), tick);
|
|
seq_printf(m, "total:%lu KB, nomap[I/A]:%lu [%lu/%lu] KB, files:%u\n",
|
|
K(fs.total), K(fs.nomap[0]),
|
|
K(fs.nomap[1]), K(fs.nomap[2]),
|
|
fs.files);
|
|
seq_printf(m, "ktime:%12lld, iow:%12lld, cputime:%12lld\n",
|
|
now, iow, cputime);
|
|
seq_puts(m, "------------------------------\n");
|
|
vfree(fct);
|
|
return 0;
|
|
}
|
|
|
|
static int filecache_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, filecache_show, NULL);
|
|
}
|
|
|
|
static const struct proc_ops filecache_ops = {
|
|
.proc_open = filecache_open,
|
|
.proc_read = seq_read,
|
|
.proc_lseek = seq_lseek,
|
|
.proc_release = single_release,
|
|
};
|
|
|
|
static int __init filecache_module_init(void)
|
|
{
|
|
d_filecache = proc_create("filecache", 0444,
|
|
NULL, &filecache_ops);
|
|
if (IS_ERR_OR_NULL(d_filecache)) {
|
|
pr_err("%s, create filecache failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit filecache_module_exit(void)
|
|
{
|
|
if (d_filecache)
|
|
proc_remove(d_filecache);
|
|
}
|
|
module_init(filecache_module_init);
|
|
module_exit(filecache_module_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|