blob: a895f0c78f407c11a4d6dffa503833f58ee673a3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2018-2020 Oplus. All rights reserved.
*/
#include <linux/cred.h>
#include <linux/debugfs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/cpu.h>
#include <linux/sched/signal.h>
#include <linux/sched/task.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/security.h>
#include <linux/seq_file.h>
#include <linux/vmalloc.h>
#include <linux/swap.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/mm_types.h>
#include <linux/mempolicy.h>
#include <linux/rmap.h>
#include <linux/userfaultfd_k.h>
#include <linux/hugetlb.h>
#include <linux/resmap_account.h>
#include <linux/uaccess.h>
#include <asm/cacheflush.h>
#include <asm/tlb.h>
#include <asm/mmu_context.h>
#include <linux/file.h>
#include <linux/sched/signal.h>
#include "internal.h"
#define THRIDPART_APP_UID_LOW_LIMIT 10000UL
const char *resmap_item_name[] = {
"resmap_action",
"resmap_success",
"resmap_fail",
"resmap_texit",
};
int svm_oom_pid = -1;
unsigned long svm_oom_jiffies = 0;
#ifdef CONFIG_OPLUS_SYSTEM_KERNEL_QCOM
unsigned long gpu_compat_high_limit_addr;
#endif
DEFINE_PER_CPU(struct resmap_event_state, resmap_event_states);
int rlimit_svm_log = 1;
module_param_named(rlimit_svm_log, rlimit_svm_log, int, 0644);
int reserved_area_enable = 1;
module_param_named(reserved_area_enable, reserved_area_enable, int, 0644);
int reserved_area_checking(struct mm_struct *mm, unsigned long *vm_flags, unsigned long flags, unsigned long addr, unsigned long len)
{
if (check_reserve_mmap_doing(mm)) {
*vm_flags |= (VM_BACKUP_CREATE|VM_DONTEXPAND|VM_SOFTDIRTY);
} else if (is_backed_addr(mm, addr, addr+len)) {
*vm_flags |= VM_BACKUP_ALLOC;
} else if (!check_general_addr(mm, addr, addr+len)) {
pr_err("%s mmap backed base:%#lx addr:%#lx flags %lx len:%#lx is invalid.\n",
current->comm, mm->reserve_vma->vm_start,
addr, flags,
mm->reserve_vma->vm_end - mm->reserve_vma->vm_start);
return -ENOMEM;
}
return 0;
}
EXPORT_SYMBOL(reserved_area_checking);
void init_reserve_mm(struct mm_struct* mm)
{
mm->reserve_vma = NULL;
mm->reserve_mmap = NULL;
mm->reserve_mm_rb = RB_ROOT;
mm->reserve_map_count = 0;
mm->do_reserve_mmap = 0;
mm->vm_search_two_way = false;
}
EXPORT_SYMBOL(init_reserve_mm);
static struct vm_area_struct *remove_vma(struct vm_area_struct *vma)
{
struct vm_area_struct *next = vma->vm_next;
might_sleep();
if (vma->vm_ops && vma->vm_ops->close)
vma->vm_ops->close(vma);
put_vma(vma);
return next;
}
#ifdef CONFIG_OPLUS_SYSTEM_KERNEL_QCOM
#define VM_RESERVED_SIZE SZ_512M
#else
#define VM_RESERVED_SIZE SZ_128M
#endif
static inline unsigned long vm_mmap_pgoff_with_check(struct file *file,
unsigned long addr, unsigned long len, unsigned long prot,
unsigned long flags, unsigned long pgoff)
{
struct task_struct *task = current;
struct mm_struct *mm = task->mm;
unsigned long retval;
if ((flags & MAP_BACKUP_CREATE) && (addr == RESERVE_VMAP_ADDR) &&
!mm->reserve_vma && (mm->do_reserve_mmap == 0)) {
if (!test_thread_flag(TIF_32BIT) ||
!reserved_area_enable ||
#ifdef CONFIG_OPLUS_SYSTEM_KERNEL_QCOM
!gpu_compat_high_limit_addr ||
#endif
(flags & MAP_FIXED)) {
return -EINVAL;
}
addr = 0;
reserve_mmap_doing(mm);
if (PAGE_ALIGN(len) > RESERVE_VMAP_AREA_SIZE)
len = RESERVE_VMAP_AREA_SIZE;
retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
reserve_mmap_done(mm);
} else
retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
return retval;
}
EXPORT_SYMBOL(vm_mmap_pgoff_with_check);
void exit_reserved_mmap(struct mm_struct *mm)
{
struct mmu_gather tlb;
struct vm_area_struct *vma;
unsigned long nr_accounted = 0;
unsigned long start, end;
if (!mm->reserve_vma || !mm->reserve_mmap)
return;
vma = mm->reserve_mmap;
start = mm->reserve_vma->vm_start;
end = mm->reserve_vma->vm_end;
lru_add_drain();
flush_cache_mm(mm);
tlb_gather_mmu(&tlb, mm, start, end);
unmap_vmas(&tlb, vma, start, end);
free_pgtables(&tlb, vma, start, end);
tlb_finish_mmu(&tlb, start, end);
while (vma) {
if (vma->vm_flags & VM_ACCOUNT)
nr_accounted += vma_pages(vma);
vma = remove_vma(vma);
}
vm_unacct_memory(nr_accounted);
mm->reserve_vma = NULL;
mm->reserve_mm_rb = RB_ROOT;
}
EXPORT_SYMBOL(exit_reserved_mmap);
int dup_reserved_mmap(struct mm_struct *mm, struct mm_struct *oldmm,
struct kmem_cache *vm_area_cachep)
{
struct vm_area_struct *mpnt, *tmp, *prev, **pprev;
struct rb_node **rb_link, *rb_parent;
int retval = 0;
unsigned long charge;
LIST_HEAD(uf);
if (!mm->reserve_vma)
return 0;
rb_link = &mm->reserve_mm_rb.rb_node;
rb_parent = NULL;
pprev = &mm->reserve_mmap;
prev = NULL;
for (mpnt = oldmm->reserve_mmap; mpnt; mpnt = mpnt->vm_next) {
struct file *file;
if (mpnt->vm_flags & VM_DONTCOPY) {
vm_stat_account(mm, mpnt->vm_flags, -vma_pages(mpnt));
continue;
}
charge = 0;
if (mpnt->vm_flags & VM_ACCOUNT) {
unsigned long len = vma_pages(mpnt);
if (security_vm_enough_memory_mm(oldmm, len)) /* sic */
goto fail_nomem;
charge = len;
}
tmp = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
if (!tmp)
goto fail_nomem;
*tmp = *mpnt;
INIT_VMA(tmp);
retval = vma_dup_policy(mpnt, tmp);
if (retval)
goto fail_nomem_policy;
tmp->vm_mm = mm;
retval = dup_userfaultfd(tmp, &uf);
if (retval)
goto fail_nomem_anon_vma_fork;
if (tmp->vm_flags & VM_WIPEONFORK) {
/* VM_WIPEONFORK gets a clean slate in the child. */
tmp->anon_vma = NULL;
if (anon_vma_prepare(tmp))
goto fail_nomem_anon_vma_fork;
} else if (anon_vma_fork(tmp, mpnt))
goto fail_nomem_anon_vma_fork;
tmp->vm_flags &= ~(VM_LOCKED | VM_LOCKONFAULT);
tmp->vm_next = tmp->vm_prev = NULL;
file = tmp->vm_file;
if (file) {
struct inode *inode = file_inode(file);
struct address_space *mapping = file->f_mapping;
get_file(file);
if (tmp->vm_flags & VM_DENYWRITE)
atomic_dec(&inode->i_writecount);
i_mmap_lock_write(mapping);
if (tmp->vm_flags & VM_SHARED)
atomic_inc(&mapping->i_mmap_writable);
flush_dcache_mmap_lock(mapping);
/* insert tmp into the share list, just after mpnt */
vma_interval_tree_insert_after(tmp, mpnt,
&mapping->i_mmap);
flush_dcache_mmap_unlock(mapping);
i_mmap_unlock_write(mapping);
}
/*
* Clear hugetlb-related page reserves for children. This only
* affects MAP_PRIVATE mappings. Faults generated by the child
* are not guaranteed to succeed, even if read-only
*/
if (is_vm_hugetlb_page(tmp))
reset_vma_resv_huge_pages(tmp);
/*
* Link in the new vma and copy the page table entries.
*/
*pprev = tmp;
pprev = &tmp->vm_next;
tmp->vm_prev = prev;
prev = tmp;
__vma_link_rb(mm, tmp, rb_link, rb_parent);
rb_link = &tmp->vm_rb.rb_right;
rb_parent = &tmp->vm_rb;
mm->reserve_map_count++;
if (!(tmp->vm_flags & VM_WIPEONFORK))
retval = copy_page_range(mm, oldmm, mpnt);
if (tmp->vm_ops && tmp->vm_ops->open)
tmp->vm_ops->open(tmp);
if (retval)
goto out;
}
out:
return retval;
fail_nomem_anon_vma_fork:
mpol_put(vma_policy(tmp));
fail_nomem_policy:
kmem_cache_free(vm_area_cachep, tmp);
fail_nomem:
retval = -ENOMEM;
vm_unacct_memory(charge);
goto out;
}
EXPORT_SYMBOL(dup_reserved_mmap);
static inline bool check_parent_is_zygote(struct task_struct *tsk)
{
struct task_struct *t;
bool ret = false;
rcu_read_lock();
t = rcu_dereference(tsk->real_parent);
if (t) {
const struct cred *tcred = __task_cred(t);
if (!strcmp(t->comm, "main") && (tcred->uid.val == 0) &&
(t->parent != NULL) && !strcmp(t->parent->comm,"init"))
ret = true;
}
rcu_read_unlock();
return ret;
}
#ifdef CONFIG_OPPO_HEALTHINFO
void trigger_stack_limit_changed(long value)
{
char msg[128] = {0};
snprintf(msg, 127, "{\"version\":1, \"pid\":%d, \"size\":%ld}",
current->tgid, value);
ohm_action_trig_with_msg(OHM_RLIMIT_MON, msg);
}
void trigger_svm_oom_event(struct mm_struct *mm, bool brk_risk, bool is_locked)
{
int len = 0;
int oom = 0;
int res = 0;
int over_time = 0;
int change_stack = 0;
struct rlimit *rlim;
unsigned long long current_time_ns;
char *svm_oom_msg = NULL;
unsigned int uid = (unsigned int)(current_uid().val);
unsigned long backed_vm_size = 0;
if (!(rlimit_svm_log && (current->pid == current->tgid) &&
is_compat_task() &&
check_parent_is_zygote(current) &&
(uid >= THRIDPART_APP_UID_LOW_LIMIT)))
return;
svm_oom_msg = (char*)kmalloc(128, GFP_KERNEL);
if (!svm_oom_msg)
return;
if (is_locked) {
if (mm->reserve_vma) {
backed_vm_size =
mm->reserve_vma->vm_end - mm->reserve_vma->vm_start;
res = 1;
}
} else {
down_read(&mm->mmap_sem);
if (mm->reserve_vma) {
backed_vm_size =
mm->reserve_vma->vm_end - mm->reserve_vma->vm_start;
res = 1;
}
up_read(&mm->mmap_sem);
}
if ((svm_oom_pid == current->pid) &&
time_after_eq((svm_oom_jiffies + 15*HZ), jiffies)) {
svm_oom_pid = -1;
oom = 1;
}
rlim = current->signal->rlim + RLIMIT_STACK;
if (rlim->rlim_cur > STACK_RLIMIT_OVERFFLOW || brk_risk)
change_stack = 1;
if (change_stack) {
len = snprintf(svm_oom_msg, 127,
"{\"version\":1, \"size\":%ld, \"uid\":%u, \"type\":\"%s,%s,%s\"}",
(long)rlim->rlim_cur, uid,
(oom ? "oom" : "no_oom"),
(res ? "res" : "no_res"),
(brk_risk ? "brk" : "no_brk"));
svm_oom_msg[len] = '\0';
ohm_action_trig_with_msg(OHM_RLIMIT_MON, svm_oom_msg);
kfree(svm_oom_msg);
return;
}
current_time_ns = ktime_get_boot_ns();
if ((current_time_ns > current->real_start_time) ||
(current_time_ns - current->real_start_time >= TRIGGER_TIME_NS))
over_time = 1;
if (oom || (!change_stack && !res && over_time)) {
len = snprintf(svm_oom_msg, 127,
"{\"version\":1, \"size\":%lu, \"uid\":%u, \"type\":\"%s,%s,%s\"}",
backed_vm_size, uid,
(oom ? "oom" : "no_oom"),
(res ? "res" : "no_res"),
(change_stack ? "stack" : "no_stack"));
svm_oom_msg[len] = '\0';
ohm_action_trig_with_msg(OHM_SVM_MON, svm_oom_msg);
}
kfree(svm_oom_msg);
}
#else
void trigger_stack_limit_changed(long value)
{
pr_warn("[gloom] CONFIG_OPLUS_HEALTHINFO is not enabled.\n");
}
void trigger_svm_oom_event(struct mm_struct *mm, bool brk_risk, bool is_locked)
{
pr_warn("[gloom] CONFIG_OPLUS_HEALTHINFO is not enabled.\n");
}
#endif
EXPORT_SYMBOL(trigger_stack_limit_changed);
EXPORT_SYMBOL(trigger_svm_oom_event);
static ssize_t reserved_area_enable_write(struct file *file,
const char __user *buff, size_t len, loff_t *ppos)
{
char kbuf[12] = {'0'};
long val;
int ret;
if (len > 11)
len = 11;
if (copy_from_user(&kbuf, buff, len))
return -EFAULT;
kbuf[len] = '\0';
ret = kstrtol(kbuf, 10, &val);
if (ret)
return -EINVAL;
reserved_area_enable = val ? 1 : 0;
return len;
}
static ssize_t reserved_area_enable_read(struct file *file,
char __user *buffer, size_t count, loff_t *off)
{
char kbuf[12] = {'0'};
int len;
len = snprintf(kbuf, 12, "%d\n", reserved_area_enable);
if (kbuf[len] != '\n')
kbuf[len] = '\n';
if (len > *off)
len -= *off;
else
len = 0;
if (copy_to_user(buffer, kbuf + *off, (len < count ? len : count)))
return -EFAULT;
*off += (len < count ? len : count);
return (len < count ? len : count);
}
static const struct file_operations proc_reserved_area_enable_ops = {
.write = reserved_area_enable_write,
.read = reserved_area_enable_read,
};
int create_reserved_area_enable_proc(struct proc_dir_entry *parent)
{
if (parent && proc_create("reserved_area_enable",
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH,
parent, &proc_reserved_area_enable_ops)) {
printk("Register reserved_area_enable interface passed.\n");
return 0;
}
pr_err("Register reserved_area_enable interface failed.\n");
return -ENOMEM;
}
static void sum_resmap_events(unsigned int *ret)
{
int cpu;
int i;
memset(ret, 0, RESMAP_TEXIT * sizeof(unsigned int));
for_each_online_cpu(cpu) {
struct resmap_event_state *this = &per_cpu(resmap_event_states,
cpu);
for (i = 0; i < RESMAP_TEXIT; i++)
ret[i] += this->event[i];
}
}
void all_resmap_events(unsigned int *ret)
{
get_online_cpus();
sum_resmap_events(ret);
put_online_cpus();
}
EXPORT_SYMBOL_GPL(all_resmap_events);
static int resmap_account_show(struct seq_file *s, void *unused)
{
int i;
unsigned int all_events[RESMAP_TEXIT];
all_resmap_events(all_events);
for (i = 0; i < RESMAP_TEXIT; i++)
seq_printf(s, "%-18s %u\n", resmap_item_name[i], all_events[i]);
return 0;
}
static int resmap_account_open(struct inode *inode, struct file *file)
{
return single_open(file, resmap_account_show, NULL);
}
static const struct file_operations resmap_account_fops = {
.owner = THIS_MODULE,
.open = resmap_account_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init resmap_account_init(void)
{
struct dentry *f = debugfs_create_file("resmap_account", 0444, NULL,
NULL, &resmap_account_fops);
if (!f)
pr_warn("%s create resmap_account failed\n", __func__);
else
pr_info("%s create resmap_account passed\n", __func__);
return 0;
}
device_initcall(resmap_account_init);
MODULE_LICENSE("GPL");