162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2024 Huawei Technologies Co., Ltd.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/fs.h>
762306a36Sopenharmony_ci#include <linux/proc_fs.h>
862306a36Sopenharmony_ci#include <linux/seq_file.h>
962306a36Sopenharmony_ci#include <linux/fdtable.h>
1062306a36Sopenharmony_ci#include <linux/sched/task.h>
1162306a36Sopenharmony_ci#include <linux/sched/signal.h>
1262306a36Sopenharmony_ci#include "../drivers/staging/android/ashmem.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define PURGEABLE_ASHMEM_SHRINKALL_ARG 0
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistruct purgeable_ashmem_trigger_args {
1762306a36Sopenharmony_ci	struct seq_file *seq;
1862306a36Sopenharmony_ci	struct task_struct *tsk;
1962306a36Sopenharmony_ci};
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic int purgeable_ashmem_trigger_cb(const void *data,
2262306a36Sopenharmony_ci	struct file *f, unsigned int fd)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	const struct purgeable_ashmem_trigger_args *args = data;
2562306a36Sopenharmony_ci	struct task_struct *tsk = args->tsk;
2662306a36Sopenharmony_ci	struct purgeable_ashmem_metadata pmdata;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	if (!is_ashmem_file(f))
2962306a36Sopenharmony_ci		return 0;
3062306a36Sopenharmony_ci	if (!get_purgeable_ashmem_metadata(f, &pmdata))
3162306a36Sopenharmony_ci		return 0;
3262306a36Sopenharmony_ci	if (pmdata.is_purgeable) {
3362306a36Sopenharmony_ci		pmdata.name = pmdata.name == NULL ? "" : pmdata.name;
3462306a36Sopenharmony_ci		seq_printf(args->seq,
3562306a36Sopenharmony_ci			"%s,%u,%u,%ld,%s,%zu,%u,%u,%d,%d\n",
3662306a36Sopenharmony_ci			tsk->comm, tsk->pid, fd, (long)tsk->signal->oom_score_adj,
3762306a36Sopenharmony_ci			pmdata.name, pmdata.size, pmdata.id, pmdata.create_time,
3862306a36Sopenharmony_ci			pmdata.refc, pmdata.purged);
3962306a36Sopenharmony_ci	}
4062306a36Sopenharmony_ci	return 0;
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic ssize_t purgeable_ashmem_trigger_write(struct file *file,
4462306a36Sopenharmony_ci	const char __user *buffer, size_t count, loff_t *ppos)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	char *buf;
4762306a36Sopenharmony_ci	unsigned int ashmem_id = 0;
4862306a36Sopenharmony_ci	unsigned int create_time = 0;
4962306a36Sopenharmony_ci	const unsigned int params_num = 2;
5062306a36Sopenharmony_ci	const struct cred *cred = current_cred();
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	if (!cred)
5362306a36Sopenharmony_ci		return 0;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) &&
5662306a36Sopenharmony_ci	    !uid_eq(cred->euid, GLOBAL_ROOT_UID)) {
5762306a36Sopenharmony_ci		pr_err("no permission to shrink purgeable ashmem!\n");
5862306a36Sopenharmony_ci		return 0;
5962306a36Sopenharmony_ci	}
6062306a36Sopenharmony_ci	buf = memdup_user_nul(buffer, count);
6162306a36Sopenharmony_ci	buf = strstrip(buf);
6262306a36Sopenharmony_ci	if (sscanf(buf, "%u %u", &ashmem_id, &create_time) != params_num)
6362306a36Sopenharmony_ci		return -EINVAL;
6462306a36Sopenharmony_ci	if (ashmem_id == PURGEABLE_ASHMEM_SHRINKALL_ARG &&
6562306a36Sopenharmony_ci	    create_time == PURGEABLE_ASHMEM_SHRINKALL_ARG)
6662306a36Sopenharmony_ci		ashmem_shrinkall();
6762306a36Sopenharmony_ci	else
6862306a36Sopenharmony_ci		ashmem_shrink_by_id(ashmem_id, create_time);
6962306a36Sopenharmony_ci	return count;
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic int purgeable_ashmem_trigger_show(struct seq_file *s, void *d)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct task_struct *tsk = NULL;
7562306a36Sopenharmony_ci	struct purgeable_ashmem_trigger_args cb_args;
7662306a36Sopenharmony_ci	const struct cred *cred = current_cred();
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	if (!cred)
7962306a36Sopenharmony_ci		return -EINVAL;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) &&
8262306a36Sopenharmony_ci	    !uid_eq(cred->euid, GLOBAL_ROOT_UID)) {
8362306a36Sopenharmony_ci		pr_err("no permission to shrink purgeable ashmem!\n");
8462306a36Sopenharmony_ci		return -EINVAL;
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci	seq_puts(s, "Process purgeable ashmem detail info:\n");
8762306a36Sopenharmony_ci	seq_puts(s, "----------------------------------------------------\n");
8862306a36Sopenharmony_ci	seq_printf(s, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
8962306a36Sopenharmony_ci			"process_name", "pid", "adj", "fd",
9062306a36Sopenharmony_ci			"ashmem_name", "size", "id", "time", "ref_count", "purged");
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	ashmem_mutex_lock();
9362306a36Sopenharmony_ci	rcu_read_lock();
9462306a36Sopenharmony_ci	for_each_process(tsk) {
9562306a36Sopenharmony_ci		if (tsk->flags & PF_KTHREAD)
9662306a36Sopenharmony_ci			continue;
9762306a36Sopenharmony_ci		cb_args.seq = s;
9862306a36Sopenharmony_ci		cb_args.tsk = tsk;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci		task_lock(tsk);
10162306a36Sopenharmony_ci		iterate_fd(tsk->files, 0,
10262306a36Sopenharmony_ci			purgeable_ashmem_trigger_cb, (void *)&cb_args);
10362306a36Sopenharmony_ci		task_unlock(tsk);
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci	rcu_read_unlock();
10662306a36Sopenharmony_ci	ashmem_mutex_unlock();
10762306a36Sopenharmony_ci	seq_puts(s, "----------------------------------------------------\n");
10862306a36Sopenharmony_ci	return 0;
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic int purgeable_ashmem_trigger_open(struct inode *inode,
11262306a36Sopenharmony_ci	struct file *file)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	return single_open(file, purgeable_ashmem_trigger_show,
11562306a36Sopenharmony_ci					   inode->i_private);
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic const struct proc_ops purgeable_ashmem_trigger_fops = {
11962306a36Sopenharmony_ci	.proc_open = purgeable_ashmem_trigger_open,
12062306a36Sopenharmony_ci	.proc_write = purgeable_ashmem_trigger_write,
12162306a36Sopenharmony_ci	.proc_read = seq_read,
12262306a36Sopenharmony_ci	.proc_lseek = seq_lseek,
12362306a36Sopenharmony_ci	.proc_release = single_release,
12462306a36Sopenharmony_ci};
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_civoid init_purgeable_ashmem_trigger(void)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	struct proc_dir_entry *entry = NULL;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	entry = proc_create_data("purgeable_ashmem_trigger", 0660,
13162306a36Sopenharmony_ci			NULL, &purgeable_ashmem_trigger_fops, NULL);
13262306a36Sopenharmony_ci	if (!entry)
13362306a36Sopenharmony_ci		pr_err("Failed to create purgeable ashmem trigger\n");
13462306a36Sopenharmony_ci}
135