223 lines
5 KiB
C
223 lines
5 KiB
C
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
||
|
|
/*
|
||
|
|
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
|
||
|
|
*
|
||
|
|
* Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
||
|
|
*
|
||
|
|
* Took is_el1_instruction_abort() from arch/arm64/mm/fault.c
|
||
|
|
* Copyright (C) 2012 ARM Ltd
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <linux/module.h>
|
||
|
|
#include <linux/oom.h>
|
||
|
|
#include <trace/hooks/mm.h>
|
||
|
|
#include <trace/hooks/vmscan.h>
|
||
|
|
#include <linux/printk.h>
|
||
|
|
#include <linux/nodemask.h>
|
||
|
|
#include <linux/kthread.h>
|
||
|
|
#include <linux/swap.h>
|
||
|
|
#include <trace/hooks/fault.h>
|
||
|
|
#include <asm/esr.h>
|
||
|
|
#include <asm/ptrace.h>
|
||
|
|
|
||
|
|
static unsigned long panic_on_oom_timeout;
|
||
|
|
struct task_struct *saved_tsk;
|
||
|
|
static uint kswapd_threads;
|
||
|
|
module_param_named(kswapd_threads, kswapd_threads, uint, 0644);
|
||
|
|
|
||
|
|
#define PANIC_ON_OOM_DEFER_TIMEOUT (5*HZ)
|
||
|
|
|
||
|
|
static void readahead_set(void *data, gfp_t *flag)
|
||
|
|
{
|
||
|
|
if (*flag & __GFP_MOVABLE) {
|
||
|
|
*flag |= __GFP_CMA;
|
||
|
|
*flag &= ~__GFP_HIGHMEM;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void gfp_zone_set(void *data, gfp_t *flag)
|
||
|
|
{
|
||
|
|
if (!IS_ENABLED(CONFIG_HIGHMEM)) {
|
||
|
|
if ((*flag & __GFP_MOVABLE) && !(*flag & __GFP_CMA))
|
||
|
|
*flag &= ~__GFP_HIGHMEM;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void set_swap_cache(void *data, gfp_t *flag)
|
||
|
|
{
|
||
|
|
*flag |= __GFP_CMA;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void __maybe_unused __oom_panic_defer(void *data, struct oom_control *oc, int *val)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
struct task_struct *p;
|
||
|
|
|
||
|
|
if (oc->chosen)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
rcu_read_lock();
|
||
|
|
for_each_process(p) {
|
||
|
|
if (tsk_is_oom_victim(p))
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
rcu_read_unlock();
|
||
|
|
|
||
|
|
if (p == &init_task)
|
||
|
|
goto out;
|
||
|
|
|
||
|
|
if (p != saved_tsk) {
|
||
|
|
panic_on_oom_timeout = jiffies + PANIC_ON_OOM_DEFER_TIMEOUT;
|
||
|
|
saved_tsk = p;
|
||
|
|
ret = -1;
|
||
|
|
} else if (time_before_eq(jiffies, panic_on_oom_timeout)) {
|
||
|
|
ret = -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
out:
|
||
|
|
*val = ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void balance_reclaim(void *unused, bool *balance_anon_file_reclaim)
|
||
|
|
{
|
||
|
|
*balance_anon_file_reclaim = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int kswapd_per_node_run(int nid, unsigned int kswapd_threads)
|
||
|
|
{
|
||
|
|
pg_data_t *pgdat = NODE_DATA(nid);
|
||
|
|
unsigned int hid, start = 0;
|
||
|
|
int ret = 0;
|
||
|
|
|
||
|
|
if (pgdat->kswapd) {
|
||
|
|
start = 1;
|
||
|
|
pgdat->mkswapd[0] = pgdat->kswapd;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (hid = start; hid < kswapd_threads; ++hid) {
|
||
|
|
pgdat->mkswapd[hid] = kthread_run(kswapd, pgdat, "kswapd%d:%d",
|
||
|
|
nid, hid);
|
||
|
|
if (IS_ERR(pgdat->mkswapd[hid])) {
|
||
|
|
/* failure at boot is fatal */
|
||
|
|
WARN_ON(system_state < SYSTEM_RUNNING);
|
||
|
|
pr_err("Failed to start kswapd%d on node %d\n",
|
||
|
|
hid, nid);
|
||
|
|
ret = PTR_ERR(pgdat->mkswapd[hid]);
|
||
|
|
pgdat->mkswapd[hid] = NULL;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (!pgdat->kswapd)
|
||
|
|
pgdat->kswapd = pgdat->mkswapd[hid];
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kswapd_per_node_stop(int nid, unsigned int kswapd_threads)
|
||
|
|
{
|
||
|
|
int hid = 0;
|
||
|
|
struct task_struct *kswapd;
|
||
|
|
|
||
|
|
for (hid = 0; hid < kswapd_threads; hid++) {
|
||
|
|
kswapd = NODE_DATA(nid)->mkswapd[hid];
|
||
|
|
if (kswapd) {
|
||
|
|
kthread_stop(kswapd);
|
||
|
|
NODE_DATA(nid)->mkswapd[hid] = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
NODE_DATA(nid)->kswapd = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void kswapd_threads_set(void *unused, int nid, bool *skip, bool run)
|
||
|
|
{
|
||
|
|
*skip = true;
|
||
|
|
if (run)
|
||
|
|
kswapd_per_node_run(nid, kswapd_threads);
|
||
|
|
else
|
||
|
|
kswapd_per_node_stop(nid, kswapd_threads);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
static int init_kswapd_per_node_hook(void)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
int nid;
|
||
|
|
|
||
|
|
if (kswapd_threads > MAX_KSWAPD_THREADS) {
|
||
|
|
pr_err("Failed to set kswapd_threads to %d ,Max limit is %d\n",
|
||
|
|
kswapd_threads, MAX_KSWAPD_THREADS);
|
||
|
|
return ret;
|
||
|
|
} else if (kswapd_threads > 1) {
|
||
|
|
ret = register_trace_android_vh_kswapd_per_node(kswapd_threads_set, NULL);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("Failed to register kswapd_per_node hooks\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
for_each_node_state(nid, N_MEMORY)
|
||
|
|
kswapd_per_node_run(nid, kswapd_threads);
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool is_el1_instruction_abort(unsigned long esr)
|
||
|
|
{
|
||
|
|
return ESR_ELx_EC(esr) == ESR_ELx_EC_IABT_CUR;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void can_fixup_sea(void *unused, unsigned long addr, unsigned long esr,
|
||
|
|
struct pt_regs *regs, bool *can_fixup)
|
||
|
|
{
|
||
|
|
if (!user_mode(regs) && !is_el1_instruction_abort(esr))
|
||
|
|
*can_fixup = true;
|
||
|
|
else
|
||
|
|
*can_fixup = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int __init init_mem_hooks(void)
|
||
|
|
{
|
||
|
|
int ret;
|
||
|
|
|
||
|
|
ret = init_kswapd_per_node_hook();
|
||
|
|
if (ret)
|
||
|
|
return ret;
|
||
|
|
|
||
|
|
ret = register_trace_android_rvh_set_readahead_gfp_mask(readahead_set, NULL);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("Failed to register readahead_gfp_mask hooks\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = register_trace_android_rvh_set_gfp_zone_flags(gfp_zone_set, NULL);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("Failed to register gfp_zone_flags hooks\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = register_trace_android_rvh_set_skip_swapcache_flags(set_swap_cache, NULL);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("Failed to register skip_swapcache_flags hooks\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (IS_ENABLED(CONFIG_QCOM_BALANCE_ANON_FILE_RECLAIM)) {
|
||
|
|
ret = register_trace_android_rvh_set_balance_anon_file_reclaim(balance_reclaim,
|
||
|
|
NULL);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("Failed to register balance_anon_file_reclaim hooks\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = register_trace_android_vh_try_fixup_sea(can_fixup_sea, NULL);
|
||
|
|
if (ret) {
|
||
|
|
pr_err("Failed to register try_fixup_sea\n");
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
module_init(init_mem_hooks);
|
||
|
|
|
||
|
|
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Memory Trace Hook Call-Back Registration");
|
||
|
|
MODULE_LICENSE("GPL v2");
|