1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2023 Huawei Technologies Co., Ltd.
4 */
5
6 #include <linux/fs.h>
7 #include <linux/proc_fs.h>
8 #include <linux/seq_file.h>
9 #include <linux/fdtable.h>
10 #include <linux/sched/task.h>
11 #include <linux/sched/signal.h>
12 #include "../drivers/staging/android/ashmem.h"
13
14 #define PURGEABLE_ASHMEM_SHRINKALL_ARG 0
15
16 struct purgeable_ashmem_trigger_args {
17 struct seq_file *seq;
18 struct task_struct *tsk;
19 };
20
purgeable_ashmem_trigger_cb(const void * data,struct file * f,unsigned int fd)21 static int purgeable_ashmem_trigger_cb(const void *data,
22 struct file *f, unsigned int fd)
23 {
24 const struct purgeable_ashmem_trigger_args *args = data;
25 struct task_struct *tsk = args->tsk;
26 struct purgeable_ashmem_metadata pmdata;
27
28 if (!is_ashmem_file(f))
29 return 0;
30 if (!get_purgeable_ashmem_metadata(f, &pmdata))
31 return 0;
32 if (pmdata.is_purgeable) {
33 pmdata.name = pmdata.name == NULL ? "" : pmdata.name;
34 seq_printf(args->seq,
35 "%s,%u,%u,%ld,%s,%zu,%u,%u,%d,%d\n",
36 tsk->comm, tsk->pid, fd, (long)tsk->signal->oom_score_adj,
37 pmdata.name, pmdata.size, pmdata.id, pmdata.create_time,
38 pmdata.refc, pmdata.purged);
39 }
40 return 0;
41 }
42
purgeable_ashmem_trigger_write(struct file * file,const char __user * buffer,size_t count,loff_t * ppos)43 static ssize_t purgeable_ashmem_trigger_write(struct file *file,
44 const char __user *buffer, size_t count, loff_t *ppos)
45 {
46 char *buf;
47 unsigned int ashmem_id = 0;
48 unsigned int create_time = 0;
49 const unsigned int params_num = 2;
50 const struct cred *cred = current_cred();
51
52 if (!cred)
53 return 0;
54
55 if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) &&
56 !uid_eq(cred->euid, GLOBAL_ROOT_UID)) {
57 pr_err("no permission to shrink purgeable ashmem!\n");
58 return 0;
59 }
60 buf = memdup_user_nul(buffer, count);
61 buf = strstrip(buf);
62 if (sscanf(buf, "%u %u", &ashmem_id, &create_time) != params_num)
63 return -EINVAL;
64 if (ashmem_id == PURGEABLE_ASHMEM_SHRINKALL_ARG &&
65 create_time == PURGEABLE_ASHMEM_SHRINKALL_ARG)
66 ashmem_shrinkall();
67 else
68 ashmem_shrink_by_id(ashmem_id, create_time);
69 return count;
70 }
71
purgeable_ashmem_trigger_show(struct seq_file * s,void * d)72 static int purgeable_ashmem_trigger_show(struct seq_file *s, void *d)
73 {
74 struct task_struct *tsk = NULL;
75 struct purgeable_ashmem_trigger_args cb_args;
76 const struct cred *cred = current_cred();
77
78 if (!cred)
79 return -EINVAL;
80
81 if (!uid_eq(cred->euid, GLOBAL_MEMMGR_UID) &&
82 !uid_eq(cred->euid, GLOBAL_ROOT_UID)) {
83 pr_err("no permission to shrink purgeable ashmem!\n");
84 return -EINVAL;
85 }
86 seq_puts(s, "Process purgeable ashmem detail info:\n");
87 seq_puts(s, "----------------------------------------------------\n");
88 seq_printf(s, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
89 "process_name", "pid", "adj", "fd",
90 "ashmem_name", "size", "id", "time", "ref_count", "purged");
91
92 ashmem_mutex_lock();
93 rcu_read_lock();
94 for_each_process(tsk) {
95 if (tsk->flags & PF_KTHREAD)
96 continue;
97 cb_args.seq = s;
98 cb_args.tsk = tsk;
99
100 task_lock(tsk);
101 iterate_fd(tsk->files, 0,
102 purgeable_ashmem_trigger_cb, (void *)&cb_args);
103 task_unlock(tsk);
104 }
105 rcu_read_unlock();
106 ashmem_mutex_unlock();
107 seq_puts(s, "----------------------------------------------------\n");
108 return 0;
109 }
110
purgeable_ashmem_trigger_open(struct inode * inode,struct file * file)111 static int purgeable_ashmem_trigger_open(struct inode *inode,
112 struct file *file)
113 {
114 return single_open(file, purgeable_ashmem_trigger_show,
115 inode->i_private);
116 }
117
118 static const struct proc_ops purgeable_ashmem_trigger_fops = {
119 .proc_open = purgeable_ashmem_trigger_open,
120 .proc_write = purgeable_ashmem_trigger_write,
121 .proc_read = seq_read,
122 .proc_lseek = seq_lseek,
123 .proc_release = single_release,
124 };
125
init_purgeable_ashmem_trigger(void)126 void init_purgeable_ashmem_trigger(void)
127 {
128 struct proc_dir_entry *entry = NULL;
129
130 entry = proc_create_data("purgeable_ashmem_trigger", 0666,
131 NULL, &purgeable_ashmem_trigger_fops, NULL);
132 if (!entry)
133 pr_err("Failed to create purgeable ashmem trigger\n");
134 }
135