Merge "Merge scheduler changes from 5.15 into msm-pineapple"

This commit is contained in:
qctecmdr 2022-07-08 18:18:33 -07:00 committed by Gerrit - the friendly Code Review server
commit e092e6d615
32 changed files with 18817 additions and 0 deletions

View File

@ -33,3 +33,5 @@ source "Documentation/Kconfig"
# ANDROID: Set KCONFIG_EXT_PREFIX to decend into an external project.
source "$(KCONFIG_EXT_PREFIX)Kconfig.ext"
source "kernel/sched/walt/Kconfig"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -41,3 +41,14 @@ config QCOM_LMH
input from temperature and current sensors. On many newer Qualcomm SoCs
LMh is configured in the firmware and this feature need not be enabled.
However, on certain SoCs like sdm845 LMh has to be configured from kernel.
config QTI_CPU_PAUSE_COOLING_DEVICE
tristate "QTI CPU Pause cooling devices"
depends on THERMAL_OF && HOTPLUG_CPU
depends on ARCH_QCOM || COMPILE_TEST
help
This enables the QTI CPU Pause cooling device. These cooling
devices will be used by QTI chipset to pause a CPU from being
scheduled and hence will let the CPU to power collapse. Pausing
a CPU will be used when the CPU frequency mitigation
is not good enough to achieve the necessary cooling.

View File

@ -6,3 +6,4 @@ qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
obj-$(CONFIG_QCOM_LMH) += lmh.o
obj-$(CONFIG_QTI_CPU_PAUSE_COOLING_DEVICE) += thermal_pause.o

View File

@ -0,0 +1,473 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/module.h>
#include <linux/thermal.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/cpu.h>
#include <linux/of_device.h>
#include <linux/suspend.h>
#include <linux/cpumask.h>
#include <linux/sched/walt.h>
enum thermal_pause_levels {
THERMAL_NO_CPU_PAUSE,
THERMAL_GROUP_CPU_PAUSE,
/* define new pause levels above this line */
MAX_THERMAL_PAUSE_LEVEL
};
#define THERMAL_PAUSE_RETRY_COUNT 5
struct thermal_pause_cdev {
struct list_head node;
cpumask_t cpu_mask;
enum thermal_pause_levels thermal_pause_level;
enum thermal_pause_levels thermal_pause_req;
struct thermal_cooling_device *cdev;
struct device_node *np;
char cdev_name[THERMAL_NAME_LENGTH];
struct work_struct reg_work;
struct work_struct pause_update_work;
};
static DEFINE_MUTEX(cpus_pause_lock);
static LIST_HEAD(thermal_pause_cdev_list);
static struct cpumask cpus_in_max_cooling_level;
static enum cpuhp_state cpu_hp_online;
static BLOCKING_NOTIFIER_HEAD(thermal_pause_notifier);
void thermal_pause_notifier_register(struct notifier_block *n)
{
blocking_notifier_chain_register(&thermal_pause_notifier, n);
}
EXPORT_SYMBOL(thermal_pause_notifier_register);
void thermal_pause_notifier_unregister(struct notifier_block *n)
{
blocking_notifier_chain_unregister(&thermal_pause_notifier, n);
}
EXPORT_SYMBOL(thermal_pause_notifier_unregister);
const struct cpumask *thermal_paused_cpumask(void)
{
return &cpus_in_max_cooling_level;
}
EXPORT_SYMBOL(thermal_paused_cpumask);
static int thermal_pause_hp_online(unsigned int online_cpu)
{
struct thermal_pause_cdev *thermal_pause_cdev;
int ret = 0;
pr_debug("online entry CPU:%d\n", online_cpu);
mutex_lock(&cpus_pause_lock);
list_for_each_entry(thermal_pause_cdev, &thermal_pause_cdev_list, node) {
if (cpumask_test_cpu(online_cpu, &thermal_pause_cdev->cpu_mask)
&& !thermal_pause_cdev->cdev)
queue_work(system_highpri_wq,
&thermal_pause_cdev->reg_work);
}
mutex_unlock(&cpus_pause_lock);
pr_debug("online exit CPU:%d\n", online_cpu);
return ret;
}
/**
* thermal_pause_work - work function to pause a group of cpus at
* the specified level.
*
* @thermal_pasue_cdev: the cdev currently being processed
*
* Function to handle setting the current cpus paused by
* this driver for the mask specified in the device.
* it assumes the mutex is locked upon entrance.
*/
static int thermal_pause_work(struct thermal_pause_cdev *thermal_pause_cdev)
{
int cpu = 0;
int ret = -EBUSY;
cpumask_t cpus_to_pause, cpus_to_notify;
cpumask_copy(&cpus_to_pause, &thermal_pause_cdev->cpu_mask);
pr_debug("Pause:%*pbl\n", cpumask_pr_args(&thermal_pause_cdev->cpu_mask));
mutex_unlock(&cpus_pause_lock);
ret = walt_pause_cpus(&cpus_to_pause, PAUSE_THERMAL);
mutex_lock(&cpus_pause_lock);
if (ret == 0) {
/* remove CPUs that have already been notified */
cpumask_andnot(&cpus_to_notify, &thermal_pause_cdev->cpu_mask,
&cpus_in_max_cooling_level);
for_each_cpu(cpu, &cpus_to_notify)
blocking_notifier_call_chain(&thermal_pause_notifier, 1,
(void *)(long)cpu);
/* track CPUs currently in cooling level */
cpumask_or(&cpus_in_max_cooling_level,
&cpus_in_max_cooling_level,
&thermal_pause_cdev->cpu_mask);
} else {
/* Failure. These cpus not paused by thermal */
pr_err("Error pausing CPU:%*pbl. err:%d\n",
cpumask_pr_args(&thermal_pause_cdev->cpu_mask), ret);
}
return ret;
}
/**
* thermal_resume_work - work function to unpause a
* group of cpus in the mask for this cdev
*
* @thermal_pasue_cdev: the cdev currently being processed
*
* Function to handle enabling the group of cpus in the cdev.
* This is performed as a work function to avoid conflicts
* between a hotplug event invoking a pause cooling device,
* and a thermal event invoking a pause cooling device.
*/
static int thermal_resume_work(struct thermal_pause_cdev *thermal_pause_cdev)
{
int cpu = 0;
int ret = -ENODEV;
cpumask_t cpus_to_unpause, new_paused_cpus, cpus_to_notify;
struct thermal_pause_cdev *cdev;
cpumask_copy(&cpus_to_unpause, &thermal_pause_cdev->cpu_mask);
pr_debug("Unpause:%*pbl\n", cpumask_pr_args(&cpus_to_unpause));
mutex_unlock(&cpus_pause_lock);
ret = walt_resume_cpus(&cpus_to_unpause, PAUSE_THERMAL);
mutex_lock(&cpus_pause_lock);
if (ret == 0) {
/* gather up the cpus paused state from all the cdevs */
cpumask_clear(&new_paused_cpus);
list_for_each_entry(cdev, &thermal_pause_cdev_list, node) {
if (!cdev->thermal_pause_level || cdev == thermal_pause_cdev)
continue;
cpumask_or(&new_paused_cpus, &new_paused_cpus, &cdev->cpu_mask);
}
/* remove CPUs that will remain paused */
cpumask_andnot(&cpus_to_notify, &cpus_in_max_cooling_level, &new_paused_cpus);
/* Notify for each CPU that we intended to resume */
for_each_cpu(cpu, &cpus_to_notify)
blocking_notifier_call_chain(&thermal_pause_notifier, 0,
(void *)(long)cpu);
/* update the cpus cooling mask */
cpumask_copy(&cpus_in_max_cooling_level, &new_paused_cpus);
} else {
/* Failure. Ref-count for cpus controlled by thermal still set */
pr_err("Error resuming CPU:%*pbl. err:%d\n",
cpumask_pr_args(&thermal_pause_cdev->cpu_mask), ret);
}
return ret;
}
/**
* thermal_pause_set_update_work: Enforce Requested State
*
* @work: the work structure for this work
*
* Enforce the most recent requested cooling state, if
* it is a mismatch with the current state. Since the
* request is made in a different context from the
* enforcement, it is possible to have an updated request
* after completing the resume/pause request.
*
* Handle a post-operation mismatch between the cooling state
* and the requested state.
*
* This is performed as a work function to avoid conflicts
* between a hotplug event invoking a pause cooling device,
* and a thermal event invoking a pause cooling device.
*/
static void thermal_pause_update_work(struct work_struct *work)
{
int ret = 0;
int retcnt = THERMAL_PAUSE_RETRY_COUNT;
struct thermal_pause_cdev *thermal_pause_cdev =
container_of(work, struct thermal_pause_cdev, pause_update_work);
mutex_lock(&cpus_pause_lock);
retry:
if (thermal_pause_cdev->thermal_pause_req != thermal_pause_cdev->thermal_pause_level) {
if (thermal_pause_cdev->thermal_pause_req == THERMAL_NO_CPU_PAUSE) {
ret = thermal_resume_work(thermal_pause_cdev);
if (ret >= 0)
thermal_pause_cdev->thermal_pause_level = THERMAL_NO_CPU_PAUSE;
} else {
ret = thermal_pause_work(thermal_pause_cdev);
if (ret >= 0)
thermal_pause_cdev->thermal_pause_level = THERMAL_GROUP_CPU_PAUSE;
}
if (ret < 0 && retcnt > 0) {
retcnt--;
goto retry;
}
}
/*
* if the pause/resume operation itself failed (and failed THERMAL_PAUSE_RETRY_COUNT
* times) then ret will be negative here. Do not repeatedly retry if pause itself
* failed, because this can happen indefinitely.
*
* If instead the pause request has been toggled back and forth many times by
* the thermal framework, this can be handled here.
*/
if (ret >= 0 &&
thermal_pause_cdev->thermal_pause_req != thermal_pause_cdev->thermal_pause_level) {
pr_debug("Pause: requested state changed while workfn running\n");
retcnt = THERMAL_PAUSE_RETRY_COUNT;
goto retry;
}
mutex_unlock(&cpus_pause_lock);
}
/**
* thermal_pause_set_cur_state - callback function to set the current cooling
* level.
* @cdev: thermal cooling device pointer.
* @level: set this variable to the current cooling level.
*
* Callback for the thermal cooling device to change the cpu pause
* current cooling level, by making a requested and queueing the
* work to be done.
*
* Return: 0 on success, an error code otherwise.
*/
static int thermal_pause_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long level)
{
struct thermal_pause_cdev *thermal_pause_cdev = cdev->devdata;
if (level >= MAX_THERMAL_PAUSE_LEVEL)
return -EINVAL;
mutex_lock(&cpus_pause_lock);
if (level)
thermal_pause_cdev->thermal_pause_req = THERMAL_GROUP_CPU_PAUSE;
else
thermal_pause_cdev->thermal_pause_req = THERMAL_NO_CPU_PAUSE;
queue_work(system_highpri_wq, &thermal_pause_cdev->pause_update_work);
mutex_unlock(&cpus_pause_lock);
return 0;
}
/**
* thermal_pause_get_cur_state - callback function to get the current cooling
* state.
* @cdev: thermal cooling device pointer.
* @state: fill this variable with the current cooling state.
*
* Callback for the thermal cooling device to return the cpu pause
* current cooling level
*
* Return: 0 on success, an error code otherwise.
*/
static int thermal_pause_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *level)
{
struct thermal_pause_cdev *thermal_pause_cdev = cdev->devdata;
*level = thermal_pause_cdev->thermal_pause_level;
return 0;
}
/**
* thermal_pause_get_max_state - callback function to get the max cooling state.
* @cdev: thermal cooling device pointer.
* @level: fill this variable with the max cooling level
*
* Callback for the thermal cooling device to return the cpu
* pause max cooling state.
*
* Return: 0 on success, an error code otherwise.
*/
static int thermal_pause_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *level)
{
*level = MAX_THERMAL_PAUSE_LEVEL - 1;
return 0;
}
static struct thermal_cooling_device_ops thermal_pause_cooling_ops = {
.get_max_state = thermal_pause_get_max_state,
.get_cur_state = thermal_pause_get_cur_state,
.set_cur_state = thermal_pause_set_cur_state,
};
static void thermal_pause_register_cdev(struct work_struct *work)
{
struct thermal_pause_cdev *thermal_pause_cdev =
container_of(work, struct thermal_pause_cdev, reg_work);
int ret = 0;
cpumask_t cpus_online;
cpumask_and(&cpus_online,
&thermal_pause_cdev->cpu_mask,
cpu_online_mask);
if (!cpumask_equal(&thermal_pause_cdev->cpu_mask, &cpus_online))
return;
thermal_pause_cdev->cdev = thermal_of_cooling_device_register(
thermal_pause_cdev->np,
thermal_pause_cdev->cdev_name,
thermal_pause_cdev,
&thermal_pause_cooling_ops);
if (IS_ERR(thermal_pause_cdev->cdev)) {
ret = PTR_ERR(thermal_pause_cdev->cdev);
pr_err("Cooling register failed for %s, ret:%d\n",
thermal_pause_cdev->cdev_name, ret);
thermal_pause_cdev->cdev = NULL;
return;
}
pr_debug("Cooling device [%s] registered.\n",
thermal_pause_cdev->cdev_name);
}
static int thermal_pause_probe(struct platform_device *pdev)
{
int ret = 0, cpu = 0;
struct device_node *subsys_np = NULL, *cpu_phandle = NULL;
struct device *cpu_dev;
struct thermal_pause_cdev *thermal_pause_cdev = NULL;
struct device_node *np = pdev->dev.of_node;
struct of_phandle_iterator it;
cpumask_t cpu_mask;
unsigned long mask = 0;
const char *alias;
INIT_LIST_HEAD(&thermal_pause_cdev_list);
cpumask_clear(&cpus_in_max_cooling_level);
for_each_available_child_of_node(np, subsys_np) {
cpumask_clear(&cpu_mask);
mask = 0;
of_phandle_iterator_init(&it, subsys_np, "qcom,cpus", NULL, 0);
while (of_phandle_iterator_next(&it) == 0) {
cpu_phandle = it.node;
for_each_possible_cpu(cpu) {
cpu_dev = get_cpu_device(cpu);
if (cpu_dev && cpu_dev->of_node
== cpu_phandle) {
cpumask_set_cpu(cpu, &cpu_mask);
mask = mask | BIT(cpu);
break;
}
}
}
if (cpumask_empty(&cpu_mask))
continue;
thermal_pause_cdev = devm_kzalloc(&pdev->dev,
sizeof(*thermal_pause_cdev), GFP_KERNEL);
if (!thermal_pause_cdev) {
of_node_put(subsys_np);
return -ENOMEM;
}
ret = of_property_read_string(subsys_np,
"qcom,cdev-alias", &alias);
if (ret)
snprintf(thermal_pause_cdev->cdev_name, THERMAL_NAME_LENGTH,
"thermal-pause-%X", mask);
else
strscpy(thermal_pause_cdev->cdev_name, alias,
THERMAL_NAME_LENGTH);
thermal_pause_cdev->thermal_pause_level = false;
thermal_pause_cdev->cdev = NULL;
thermal_pause_cdev->np = subsys_np;
cpumask_copy(&thermal_pause_cdev->cpu_mask, &cpu_mask);
INIT_WORK(&thermal_pause_cdev->reg_work, thermal_pause_register_cdev);
INIT_WORK(&thermal_pause_cdev->pause_update_work, thermal_pause_update_work);
list_add(&thermal_pause_cdev->node, &thermal_pause_cdev_list);
}
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "thermal-pause/cdev:online",
thermal_pause_hp_online, NULL);
if (ret < 0)
return ret;
cpu_hp_online = ret;
return 0;
}
static int thermal_pause_remove(struct platform_device *pdev)
{
struct thermal_pause_cdev *thermal_pause_cdev = NULL, *next = NULL;
int ret = 0;
if (cpu_hp_online) {
cpuhp_remove_state_nocalls(cpu_hp_online);
cpu_hp_online = 0;
}
mutex_lock(&cpus_pause_lock);
list_for_each_entry_safe(thermal_pause_cdev, next,
&thermal_pause_cdev_list, node) {
/* for each asserted cooling device, resume the CPUs */
if (thermal_pause_cdev->thermal_pause_level) {
thermal_pause_cdev->thermal_pause_req = THERMAL_NO_CPU_PAUSE;
queue_work(system_highpri_wq, &thermal_pause_cdev->pause_update_work);
}
if (thermal_pause_cdev->cdev)
thermal_cooling_device_unregister(
thermal_pause_cdev->cdev);
list_del(&thermal_pause_cdev->node);
}
mutex_unlock(&cpus_pause_lock);
/* if the resume failed, thermal still controls the CPUs.
* ensure that the error is passed to the caller.
*/
return ret;
}
static const struct of_device_id thermal_pause_match[] = {
{ .compatible = "qcom,thermal-pause", },
{},
};
static struct platform_driver thermal_pause_driver = {
.probe = thermal_pause_probe,
.remove = thermal_pause_remove,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = thermal_pause_match,
},
};
module_platform_driver(thermal_pause_driver);
MODULE_LICENSE("GPL v2");

229
include/linux/sched/walt.h Normal file
View File

@ -0,0 +1,229 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef _LINUX_SCHED_WALT_H
#define _LINUX_SCHED_WALT_H
#include <linux/types.h>
#include <linux/spinlock_types.h>
#include <linux/cpumask.h>
enum pause_reason {
PAUSE_CORE_CTL = 0x01,
PAUSE_THERMAL = 0x02,
PAUSE_HYP = 0x04,
};
#if IS_ENABLED(CONFIG_SCHED_WALT)
#define MAX_CPUS_PER_CLUSTER 6
#define MAX_CLUSTERS 3
struct core_ctl_notif_data {
unsigned int nr_big;
unsigned int coloc_load_pct;
unsigned int ta_util_pct[MAX_CLUSTERS];
unsigned int cur_cap_pct[MAX_CLUSTERS];
};
enum task_boost_type {
TASK_BOOST_NONE = 0,
TASK_BOOST_ON_MID,
TASK_BOOST_ON_MAX,
TASK_BOOST_STRICT_MAX,
TASK_BOOST_END,
};
#define WALT_NR_CPUS 8
#define RAVG_HIST_SIZE 8
/* wts->bucket_bitmask needs to be updated if NUM_BUSY_BUCKETS > 16 */
#define NUM_BUSY_BUCKETS 16
#define NUM_BUSY_BUCKETS_SHIFT 4
struct walt_related_thread_group {
int id;
raw_spinlock_t lock;
struct list_head tasks;
struct list_head list;
bool skip_min;
struct rcu_head rcu;
u64 last_update;
u64 downmigrate_ts;
u64 start_ktime_ts;
};
struct walt_task_struct {
/*
* 'mark_start' marks the beginning of an event (task waking up, task
* starting to execute, task being preempted) within a window
*
* 'sum' represents how runnable a task has been within current
* window. It incorporates both running time and wait time and is
* frequency scaled.
*
* 'sum_history' keeps track of history of 'sum' seen over previous
* RAVG_HIST_SIZE windows. Windows where task was entirely sleeping are
* ignored.
*
* 'demand' represents maximum sum seen over previous
* sysctl_sched_ravg_hist_size windows. 'demand' could drive frequency
* demand for tasks.
*
* 'curr_window_cpu' represents task's contribution to cpu busy time on
* various CPUs in the current window
*
* 'prev_window_cpu' represents task's contribution to cpu busy time on
* various CPUs in the previous window
*
* 'curr_window' represents the sum of all entries in curr_window_cpu
*
* 'prev_window' represents the sum of all entries in prev_window_cpu
*
* 'pred_demand_scaled' represents task's current predicted cpu busy time
* in terms of 1024 units
*
* 'busy_buckets' groups historical busy time into different buckets
* used for prediction
*
* 'demand_scaled' represents task's demand scaled to 1024
*
* 'prev_on_rq' tracks enqueue/dequeue of a task for error conditions
* 0 = nothing, 1 = enqueued, 2 = dequeued
*/
u64 mark_start;
u64 window_start;
u32 sum, demand;
u32 coloc_demand;
u32 sum_history[RAVG_HIST_SIZE];
u16 sum_history_util[RAVG_HIST_SIZE];
u32 curr_window_cpu[WALT_NR_CPUS];
u32 prev_window_cpu[WALT_NR_CPUS];
u32 curr_window, prev_window;
u8 busy_buckets[NUM_BUSY_BUCKETS];
u16 bucket_bitmask;
u16 demand_scaled;
u16 pred_demand_scaled;
u64 active_time;
u64 last_win_size;
int boost;
bool wake_up_idle;
bool misfit;
bool rtg_high_prio;
u8 low_latency;
u64 boost_period;
u64 boost_expires;
u64 last_sleep_ts;
u32 init_load_pct;
u32 unfilter;
u64 last_wake_ts;
u64 last_enqueued_ts;
struct walt_related_thread_group __rcu *grp;
struct list_head grp_list;
u64 cpu_cycles;
cpumask_t cpus_requested;
bool iowaited;
int prev_on_rq;
int prev_on_rq_cpu;
struct list_head mvp_list;
u64 sum_exec_snapshot_for_slice;
u64 sum_exec_snapshot_for_total;
u64 total_exec;
int mvp_prio;
int cidx;
int load_boost;
int64_t boosted_task_load;
int prev_cpu;
int new_cpu;
u8 enqueue_after_migration;
};
#define wts_to_ts(wts) ({ \
void *__mptr = (void *)(wts); \
((struct task_struct *)(__mptr - \
offsetof(struct task_struct, android_vendor_data1))); })
static inline bool sched_get_wake_up_idle(struct task_struct *p)
{
struct walt_task_struct *wts = (struct walt_task_struct *) p->android_vendor_data1;
return wts->wake_up_idle;
}
static inline int sched_set_wake_up_idle(struct task_struct *p, bool wake_up_idle)
{
struct walt_task_struct *wts = (struct walt_task_struct *) p->android_vendor_data1;
wts->wake_up_idle = wake_up_idle;
return 0;
}
static inline void set_wake_up_idle(bool wake_up_idle)
{
struct walt_task_struct *wts = (struct walt_task_struct *) current->android_vendor_data1;
wts->wake_up_idle = wake_up_idle;
}
extern int sched_lpm_disallowed_time(int cpu, u64 *timeout);
extern int set_task_boost(int boost, u64 period);
struct notifier_block;
extern void core_ctl_notifier_register(struct notifier_block *n);
extern void core_ctl_notifier_unregister(struct notifier_block *n);
extern int core_ctl_set_boost(bool boost);
extern int walt_pause_cpus(struct cpumask *cpus, enum pause_reason reason);
extern int walt_resume_cpus(struct cpumask *cpus, enum pause_reason reason);
extern int walt_halt_cpus(struct cpumask *cpus, enum pause_reason reason);
extern int walt_start_cpus(struct cpumask *cpus, enum pause_reason reason);
#else
static inline int sched_lpm_disallowed_time(int cpu, u64 *timeout)
{
return INT_MAX;
}
static inline int set_task_boost(int boost, u64 period)
{
return 0;
}
static inline bool sched_get_wake_up_idle(struct task_struct *p)
{
return false;
}
static inline int sched_set_wake_up_idle(struct task_struct *p, bool wake_up_idle)
{
return 0;
}
static inline void set_wake_up_idle(bool wake_up_idle)
{
}
static inline int core_ctl_set_boost(bool boost)
{
return 0;
}
static inline void core_ctl_notifier_register(struct notifier_block *n)
{
}
static inline void core_ctl_notifier_unregister(struct notifier_block *n)
{
}
inline int walt_pause_cpus(struct cpumask *cpus, enum pause_reason reason)
{
return 0;
}
inline int walt_resume_cpus(struct cpumask *cpus, enum pause_reason reason)
{
return 0;
}
#endif
#endif /* _LINUX_SCHED_WALT_H */

View File

@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#ifndef _LINUX_THERMAL_PAUSE_H
#define _LINUX_THERMAL_PAUSE_H
#include <linux/notifier.h>
#include <linux/cpumask.h>
#if IS_ENABLED(CONFIG_QTI_CPU_PAUSE_COOLING_DEVICE)
extern void thermal_pause_notifier_register(struct notifier_block *n);
extern void thermal_pause_notifier_unregister(struct notifier_block *n);
extern const struct cpumask *thermal_paused_cpumask(void);
#else
static inline
void thermal_pause_notifier_register(struct notifier_block *n)
{
}
static inline
void thermal_pause_notifier_unregister(struct notifier_block *n)
{
}
static inline const struct cpumask *thermal_paused_cpumask(void)
{
return cpu_none_mask;
}
#endif /* CONFIG_QTI_CPU_PAUSE_COOLING_DEVICE */
#endif /* _LINUX_THERMAL_PAUSE_H */

View File

@ -32,3 +32,5 @@ obj-y += core.o
obj-y += fair.o
obj-y += build_policy.o
obj-y += build_utility.o
obj-$(CONFIG_SCHED_WALT) += walt/

41
kernel/sched/walt/Kconfig Normal file
View File

@ -0,0 +1,41 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# QTI WALT based scheduler
#
menu "QTI WALT based scheduler features"
config SCHED_WALT
tristate "Support window based load tracking"
depends on SMP
help
This feature will allow the scheduler to maintain a tunable window
based set of metrics for tasks and runqueues. These metrics can be
used to guide task placement as well as task frequency requirements
for cpufreq governors.
config SCHED_WALT_DEBUG
tristate "WALT debug module"
depends on SCHED_WALT
select TRACE_PREEMPT_TOGGLE
select TRACE_IRQFLAGS
help
This module provides the means of debugging long preempt and
irq disable code. This helps in identifying the scheduling
latencies. The module rely on preemptirq trace hooks and
print the stacktrace to the ftrace upon long preempt and irq
events. Sysctl knobs are available for the user to configure
the thresholds.
This module also used to crash the system to catch issues
in scenarios like RT throttling and sleeping while in atomic
context etc.
config SCHED_CONSERVATIVE_BOOST_LPM_BIAS
bool "Enable LPM bias if conservative boost is enabled"
default n
help
This feature will allow the scheduler to disable low power
modes on a cpu if conservative boost is active. The cpu
will not enter low power mode for a hysteresis time period,
which can be configured from userspace.
endmenu

View File

@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
KCOV_INSTRUMENT := n
KCSAN_SANITIZE := n
obj-$(CONFIG_SCHED_WALT) += sched-walt.o
sched-walt-$(CONFIG_SCHED_WALT) := walt.o boost.o sched_avg.o walt_halt.o core_ctl.o trace.o input-boost.o sysctl.o cpufreq_walt.o fixup.o walt_lb.o walt_rt.o walt_cfs.o walt_tp.o
obj-$(CONFIG_SCHED_WALT_DEBUG) += sched-walt-debug.o
sched-walt-debug-$(CONFIG_SCHED_WALT_DEBUG) := walt_debug.o preemptirq_long.o

300
kernel/sched/walt/boost.c Normal file
View File

@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
*/
#include <linux/of.h>
#include "walt.h"
#include "trace.h"
/*
* Scheduler boost is a mechanism to temporarily place tasks on CPUs
* with higher capacity than those where a task would have normally
* ended up with their load characteristics. Any entity enabling
* boost is responsible for disabling it as well.
*/
unsigned int sched_boost_type;
enum sched_boost_policy boost_policy;
static DEFINE_MUTEX(boost_mutex);
void walt_init_tg(struct task_group *tg)
{
struct walt_task_group *wtg;
wtg = (struct walt_task_group *) tg->android_vendor_data1;
wtg->colocate = false;
wtg->sched_boost_enable[NO_BOOST] = false;
wtg->sched_boost_enable[FULL_THROTTLE_BOOST] = true;
wtg->sched_boost_enable[CONSERVATIVE_BOOST] = false;
wtg->sched_boost_enable[RESTRAINED_BOOST] = false;
}
void walt_init_topapp_tg(struct task_group *tg)
{
struct walt_task_group *wtg;
wtg = (struct walt_task_group *) tg->android_vendor_data1;
wtg->colocate = true;
wtg->sched_boost_enable[NO_BOOST] = false;
wtg->sched_boost_enable[FULL_THROTTLE_BOOST] = true;
wtg->sched_boost_enable[CONSERVATIVE_BOOST] = true;
wtg->sched_boost_enable[RESTRAINED_BOOST] = false;
}
void walt_init_foreground_tg(struct task_group *tg)
{
struct walt_task_group *wtg;
wtg = (struct walt_task_group *) tg->android_vendor_data1;
wtg->colocate = false;
wtg->sched_boost_enable[NO_BOOST] = false;
wtg->sched_boost_enable[FULL_THROTTLE_BOOST] = true;
wtg->sched_boost_enable[CONSERVATIVE_BOOST] = true;
wtg->sched_boost_enable[RESTRAINED_BOOST] = false;
}
/*
* Scheduler boost type and boost policy might at first seem unrelated,
* however, there exists a connection between them that will allow us
* to use them interchangeably during placement decisions. We'll explain
* the connection here in one possible way so that the implications are
* clear when looking at placement policies.
*
* When policy = SCHED_BOOST_NONE, type is either none or RESTRAINED
* When policy = SCHED_BOOST_ON_ALL or SCHED_BOOST_ON_BIG, type can
* neither be none nor RESTRAINED.
*/
static void set_boost_policy(int type)
{
if (type == NO_BOOST || type == RESTRAINED_BOOST) {
boost_policy = SCHED_BOOST_NONE;
return;
}
if (hmp_capable()) {
boost_policy = SCHED_BOOST_ON_BIG;
return;
}
boost_policy = SCHED_BOOST_ON_ALL;
}
static bool verify_boost_params(int type)
{
return type >= RESTRAINED_BOOST_DISABLE && type <= RESTRAINED_BOOST;
}
static void sched_no_boost_nop(void)
{
}
static void sched_full_throttle_boost_enter(void)
{
core_ctl_set_boost(true);
walt_enable_frequency_aggregation(true);
}
static void sched_full_throttle_boost_exit(void)
{
core_ctl_set_boost(false);
walt_enable_frequency_aggregation(false);
}
static void sched_conservative_boost_enter(void)
{
}
static void sched_conservative_boost_exit(void)
{
}
static void sched_restrained_boost_enter(void)
{
walt_enable_frequency_aggregation(true);
}
static void sched_restrained_boost_exit(void)
{
walt_enable_frequency_aggregation(false);
}
struct sched_boost_data {
int refcount;
void (*enter)(void);
void (*exit)(void);
};
static struct sched_boost_data sched_boosts[] = {
[NO_BOOST] = {
.refcount = 0,
.enter = sched_no_boost_nop,
.exit = sched_no_boost_nop,
},
[FULL_THROTTLE_BOOST] = {
.refcount = 0,
.enter = sched_full_throttle_boost_enter,
.exit = sched_full_throttle_boost_exit,
},
[CONSERVATIVE_BOOST] = {
.refcount = 0,
.enter = sched_conservative_boost_enter,
.exit = sched_conservative_boost_exit,
},
[RESTRAINED_BOOST] = {
.refcount = 0,
.enter = sched_restrained_boost_enter,
.exit = sched_restrained_boost_exit,
},
};
#define SCHED_BOOST_START FULL_THROTTLE_BOOST
#define SCHED_BOOST_END (RESTRAINED_BOOST + 1)
static int sched_effective_boost(void)
{
int i;
/*
* The boosts are sorted in descending order by
* priority.
*/
for (i = SCHED_BOOST_START; i < SCHED_BOOST_END; i++) {
if (sched_boosts[i].refcount >= 1)
return i;
}
return NO_BOOST;
}
static void sched_boost_disable(int type)
{
struct sched_boost_data *sb = &sched_boosts[type];
int next_boost, prev_boost = sched_boost_type;
if (sb->refcount <= 0)
return;
sb->refcount--;
if (sb->refcount)
return;
next_boost = sched_effective_boost();
if (next_boost == prev_boost)
return;
/*
* This boost's refcount becomes zero, so it must
* be disabled. Disable it first and then apply
* the next boost.
*/
sched_boosts[prev_boost].exit();
sched_boosts[next_boost].enter();
}
static void sched_boost_enable(int type)
{
struct sched_boost_data *sb = &sched_boosts[type];
int next_boost, prev_boost = sched_boost_type;
sb->refcount++;
if (sb->refcount != 1)
return;
/*
* This boost enable request did not come before.
* Take this new request and find the next boost
* by aggregating all the enabled boosts. If there
* is a change, disable the previous boost and enable
* the next boost.
*/
next_boost = sched_effective_boost();
if (next_boost == prev_boost)
return;
sched_boosts[prev_boost].exit();
sched_boosts[next_boost].enter();
}
static void sched_boost_disable_all(void)
{
int i;
int prev_boost = sched_boost_type;
if (prev_boost != NO_BOOST) {
sched_boosts[prev_boost].exit();
for (i = SCHED_BOOST_START; i < SCHED_BOOST_END; i++)
sched_boosts[i].refcount = 0;
}
}
static void _sched_set_boost(int type)
{
if (type == 0)
sched_boost_disable_all();
else if (type > 0)
sched_boost_enable(type);
else
sched_boost_disable(-type);
/*
* sysctl_sched_boost holds the boost request from
* user space which could be different from the
* effectively enabled boost. Update the effective
* boost here.
*/
sched_boost_type = sched_effective_boost();
sysctl_sched_boost = sched_boost_type;
set_boost_policy(sysctl_sched_boost);
trace_sched_set_boost(sysctl_sched_boost);
}
int sched_set_boost(int type)
{
int ret = 0;
mutex_lock(&boost_mutex);
if (verify_boost_params(type))
_sched_set_boost(type);
else
ret = -EINVAL;
mutex_unlock(&boost_mutex);
return ret;
}
int sched_boost_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int ret;
unsigned int *data = (unsigned int *)table->data;
mutex_lock(&boost_mutex);
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
if (ret || !write)
goto done;
if (verify_boost_params(*data))
_sched_set_boost(*data);
else
ret = -EINVAL;
done:
mutex_unlock(&boost_mutex);
return ret;
}
void walt_boost_init(void)
{
/* force call the callbacks for default boost */
sched_set_boost(FULL_THROTTLE_BOOST);
}

1360
kernel/sched/walt/core_ctl.c Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

92
kernel/sched/walt/fixup.c Normal file
View File

@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <trace/hooks/cpufreq.h>
#include "walt.h"
unsigned int cpuinfo_max_freq_cached;
char sched_lib_name[LIB_PATH_LENGTH];
unsigned int sched_lib_mask_force;
static bool is_sched_lib_based_app(pid_t pid)
{
const char *name = NULL;
char *libname, *lib_list;
struct vm_area_struct *vma;
char path_buf[LIB_PATH_LENGTH];
char *tmp_lib_name;
bool found = false;
struct task_struct *p;
struct mm_struct *mm;
if (strnlen(sched_lib_name, LIB_PATH_LENGTH) == 0)
return false;
tmp_lib_name = kmalloc(LIB_PATH_LENGTH, GFP_KERNEL);
if (!tmp_lib_name)
return false;
rcu_read_lock();
p = pid ? get_pid_task(find_vpid(pid), PIDTYPE_PID) : get_task_struct(current);
rcu_read_unlock();
if (!p) {
kfree(tmp_lib_name);
return false;
}
mm = get_task_mm(p);
if (!mm)
goto put_task_struct;
down_read(&mm->mmap_lock);
for (vma = mm->mmap; vma ; vma = vma->vm_next) {
if (vma->vm_file && vma->vm_flags & VM_EXEC) {
name = d_path(&vma->vm_file->f_path,
path_buf, LIB_PATH_LENGTH);
if (IS_ERR(name))
goto release_sem;
strlcpy(tmp_lib_name, sched_lib_name, LIB_PATH_LENGTH);
lib_list = tmp_lib_name;
while ((libname = strsep(&lib_list, ","))) {
libname = skip_spaces(libname);
if (strnstr(name, libname,
strnlen(name, LIB_PATH_LENGTH))) {
found = true;
goto release_sem;
}
}
}
}
release_sem:
up_read(&mm->mmap_lock);
mmput(mm);
put_task_struct:
put_task_struct(p);
kfree(tmp_lib_name);
return found;
}
static void android_rvh_show_max_freq(void *unused, struct cpufreq_policy *policy,
unsigned int *max_freq)
{
if (!cpuinfo_max_freq_cached)
return;
if (!(BIT(policy->cpu) & sched_lib_mask_force))
return;
if (is_sched_lib_based_app(current->pid))
*max_freq = cpuinfo_max_freq_cached << 1;
}
void walt_fixup_init(void)
{
register_trace_android_rvh_show_max_freq(android_rvh_show_max_freq, NULL);
}

View File

@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2015,2017,2019-2021, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "input-boost: " fmt
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/cpu.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/time.h>
#include <linux/sysfs.h>
#include <linux/pm_qos.h>
#include "walt.h"
#define input_boost_attr_rw(_name) \
static struct kobj_attribute _name##_attr = \
__ATTR(_name, 0644, show_##_name, store_##_name)
#define show_one(file_name) \
static ssize_t show_##file_name \
(struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
{ \
return scnprintf(buf, PAGE_SIZE, "%u\n", file_name); \
}
#define store_one(file_name) \
static ssize_t store_##file_name \
(struct kobject *kobj, struct kobj_attribute *attr, \
const char *buf, size_t count) \
{ \
\
sscanf(buf, "%u", &file_name); \
return count; \
}
struct cpu_sync {
int cpu;
unsigned int input_boost_min;
unsigned int input_boost_freq;
};
static DEFINE_PER_CPU(struct cpu_sync, sync_info);
static struct workqueue_struct *input_boost_wq;
static struct work_struct input_boost_work;
static bool sched_boost_active;
static struct delayed_work input_boost_rem;
static u64 last_input_time;
#define MIN_INPUT_INTERVAL (150 * USEC_PER_MSEC)
static DEFINE_PER_CPU(struct freq_qos_request, qos_req);
static void boost_adjust_notify(struct cpufreq_policy *policy)
{
unsigned int cpu = policy->cpu;
struct cpu_sync *s = &per_cpu(sync_info, cpu);
unsigned int ib_min = s->input_boost_min;
struct freq_qos_request *req = &per_cpu(qos_req, cpu);
int ret;
pr_debug("CPU%u policy min before boost: %u kHz\n",
cpu, policy->min);
pr_debug("CPU%u boost min: %u kHz\n", cpu, ib_min);
ret = freq_qos_update_request(req, ib_min);
if (ret < 0)
pr_err("Failed to update freq constraint in boost_adjust: %d\n",
ib_min);
pr_debug("CPU%u policy min after boost: %u kHz\n", cpu, policy->min);
}
static void update_policy_online(void)
{
unsigned int i;
struct cpufreq_policy *policy;
struct cpumask online_cpus;
/* Re-evaluate policy to trigger adjust notifier for online CPUs */
cpus_read_lock();
online_cpus = *cpu_online_mask;
for_each_cpu(i, &online_cpus) {
policy = cpufreq_cpu_get(i);
if (!policy) {
pr_err("%s: cpufreq policy not found for cpu%d\n",
__func__, i);
return;
}
cpumask_andnot(&online_cpus, &online_cpus,
policy->related_cpus);
boost_adjust_notify(policy);
}
cpus_read_unlock();
}
static void do_input_boost_rem(struct work_struct *work)
{
unsigned int i, ret;
struct cpu_sync *i_sync_info;
/* Reset the input_boost_min for all CPUs in the system */
pr_debug("Resetting input boost min for all CPUs\n");
for_each_possible_cpu(i) {
i_sync_info = &per_cpu(sync_info, i);
i_sync_info->input_boost_min = 0;
}
/* Update policies for all online CPUs */
update_policy_online();
if (sched_boost_active) {
ret = sched_set_boost(0);
if (!ret)
pr_err("input-boost: sched boost disable failed\n");
sched_boost_active = false;
}
}
static void do_input_boost(struct work_struct *work)
{
unsigned int i, ret;
struct cpu_sync *i_sync_info;
cancel_delayed_work_sync(&input_boost_rem);
if (sched_boost_active) {
sched_set_boost(0);
sched_boost_active = false;
}
/* Set the input_boost_min for all CPUs in the system */
pr_debug("Setting input boost min for all CPUs\n");
for (i = 0; i < 8; i++) {
i_sync_info = &per_cpu(sync_info, i);
i_sync_info->input_boost_min = sysctl_input_boost_freq[i];
}
/* Update policies for all online CPUs */
update_policy_online();
/* Enable scheduler boost to migrate tasks to big cluster */
if (sysctl_sched_boost_on_input > 0) {
ret = sched_set_boost(sysctl_sched_boost_on_input);
if (ret)
pr_err("input-boost: sched boost enable failed\n");
else
sched_boost_active = true;
}
queue_delayed_work(input_boost_wq, &input_boost_rem,
msecs_to_jiffies(sysctl_input_boost_ms));
}
static void inputboost_input_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
u64 now;
int cpu;
int enabled = 0;
for_each_possible_cpu(cpu) {
if (sysctl_input_boost_freq[cpu] > 0) {
enabled = 1;
break;
}
}
if (!enabled)
return;
now = ktime_to_us(ktime_get());
if (now - last_input_time < MIN_INPUT_INTERVAL)
return;
if (work_pending(&input_boost_work))
return;
queue_work(input_boost_wq, &input_boost_work);
last_input_time = ktime_to_us(ktime_get());
}
static int inputboost_input_connect(struct input_handler *handler,
struct input_dev *dev, const struct input_device_id *id)
{
struct input_handle *handle;
int error;
handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;
handle->dev = dev;
handle->handler = handler;
handle->name = "cpufreq";
error = input_register_handle(handle);
if (error)
goto err2;
error = input_open_device(handle);
if (error)
goto err1;
return 0;
err1:
input_unregister_handle(handle);
err2:
kfree(handle);
return error;
}
static void inputboost_input_disconnect(struct input_handle *handle)
{
input_close_device(handle);
input_unregister_handle(handle);
kfree(handle);
}
static const struct input_device_id inputboost_ids[] = {
/* multi-touch touchscreen */
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit = { BIT_MASK(EV_ABS) },
.absbit = { [BIT_WORD(ABS_MT_POSITION_X)] =
BIT_MASK(ABS_MT_POSITION_X) |
BIT_MASK(ABS_MT_POSITION_Y)
},
},
/* touchpad */
{
.flags = INPUT_DEVICE_ID_MATCH_KEYBIT |
INPUT_DEVICE_ID_MATCH_ABSBIT,
.keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
.absbit = { [BIT_WORD(ABS_X)] =
BIT_MASK(ABS_X) | BIT_MASK(ABS_Y)
},
},
/* Keypad */
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT_MASK(EV_KEY) },
},
{ },
};
static struct input_handler inputboost_input_handler = {
.event = inputboost_input_event,
.connect = inputboost_input_connect,
.disconnect = inputboost_input_disconnect,
.name = "input-boost",
.id_table = inputboost_ids,
};
struct kobject *input_boost_kobj;
int input_boost_init(void)
{
int cpu, ret;
struct cpu_sync *s;
struct cpufreq_policy *policy;
struct freq_qos_request *req;
input_boost_wq = alloc_workqueue("inputboost_wq", WQ_HIGHPRI, 0);
if (!input_boost_wq)
return -EFAULT;
INIT_WORK(&input_boost_work, do_input_boost);
INIT_DELAYED_WORK(&input_boost_rem, do_input_boost_rem);
for_each_possible_cpu(cpu) {
s = &per_cpu(sync_info, cpu);
s->cpu = cpu;
req = &per_cpu(qos_req, cpu);
policy = cpufreq_cpu_get(cpu);
if (!policy) {
pr_err("%s: cpufreq policy not found for cpu%d\n",
__func__, cpu);
return -ESRCH;
}
ret = freq_qos_add_request(&policy->constraints, req,
FREQ_QOS_MIN, policy->min);
if (ret < 0) {
pr_err("%s: Failed to add freq constraint (%d)\n",
__func__, ret);
return ret;
}
}
ret = input_register_handler(&inputboost_input_handler);
return 0;
}

View File

@ -0,0 +1,178 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2013-2014, 2017, 2021, The Linux Foundation. All rights reserved.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM perf_trace_counters
#if !defined(_PERF_TRACE_COUNTERS_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _PERF_TRACE_COUNTERS_H_
/* Ctr index for PMCNTENSET/CLR */
#define CC 0x80000000
#define C0 0x1
#define C1 0x2
#define C2 0x4
#define C3 0x8
#define C4 0x10
#define C5 0x20
#define C_ALL (CC | C0 | C1 | C2 | C3 | C4 | C5)
#define TYPE_MASK 0xFFFF
#define NUM_L1_CTRS 6
#define NUM_AMU_CTRS 2
#include <linux/sched.h>
#include <linux/cpumask.h>
#include <linux/tracepoint.h>
DECLARE_PER_CPU(u32, cntenset_val);
DECLARE_PER_CPU(unsigned long, previous_ccnt);
DECLARE_PER_CPU(unsigned long[NUM_L1_CTRS], previous_l1_cnts);
DECLARE_PER_CPU(unsigned long[NUM_AMU_CTRS], previous_amu_cnts);
TRACE_EVENT(sched_switch_with_ctrs,
TP_PROTO(pid_t prev, pid_t next),
TP_ARGS(prev, next),
TP_STRUCT__entry(
__field(pid_t, old_pid)
__field(pid_t, new_pid)
__field(unsigned long, cctr)
__field(unsigned long, ctr0)
__field(unsigned long, ctr1)
__field(unsigned long, ctr2)
__field(unsigned long, ctr3)
__field(unsigned long, ctr4)
__field(unsigned long, ctr5)
__field(unsigned long, amu0)
__field(unsigned long, amu1)
),
TP_fast_assign(
u32 cpu = smp_processor_id();
u32 i;
u32 cnten_val;
unsigned long total_ccnt = 0;
unsigned long total_cnt = 0;
unsigned long amu_cnt = 0;
unsigned long delta_l1_cnts[NUM_L1_CTRS] = {0};
unsigned long delta_amu_cnts[NUM_AMU_CTRS] = {0};
__entry->old_pid = prev;
__entry->new_pid = next;
cnten_val = per_cpu(cntenset_val, cpu);
if (cnten_val & CC) {
/* Read value */
total_ccnt = read_sysreg(pmccntr_el0);
__entry->cctr = total_ccnt -
per_cpu(previous_ccnt, cpu);
per_cpu(previous_ccnt, cpu) = total_ccnt;
}
for (i = 0; i < NUM_L1_CTRS; i++) {
if (cnten_val & (1 << i)) {
/* Select */
write_sysreg(i, pmselr_el0);
isb();
/* Read value */
total_cnt = read_sysreg(pmxevcntr_el0);
delta_l1_cnts[i] = total_cnt -
per_cpu(previous_l1_cnts[i], cpu);
per_cpu(previous_l1_cnts[i], cpu) =
total_cnt;
} else
delta_l1_cnts[i] = 0;
}
if (IS_ENABLED(CONFIG_ARM64_AMU_EXTN)) {
amu_cnt = read_sysreg_s(SYS_AMEVCNTR0_CORE_EL0);
delta_amu_cnts[0] = amu_cnt -
per_cpu(previous_amu_cnts[0], cpu);
per_cpu(previous_amu_cnts[0], cpu) = amu_cnt;
amu_cnt = read_sysreg_s(SYS_AMEVCNTR0_INST_RET_EL0);
delta_amu_cnts[1] = amu_cnt -
per_cpu(previous_amu_cnts[1], cpu);
per_cpu(previous_amu_cnts[1], cpu) = amu_cnt;
}
__entry->ctr0 = delta_l1_cnts[0];
__entry->ctr1 = delta_l1_cnts[1];
__entry->ctr2 = delta_l1_cnts[2];
__entry->ctr3 = delta_l1_cnts[3];
__entry->ctr4 = delta_l1_cnts[4];
__entry->ctr5 = delta_l1_cnts[5];
__entry->amu0 = delta_amu_cnts[0];
__entry->amu1 = delta_amu_cnts[1];
),
TP_printk("prev_pid=%d, next_pid=%d, CCNTR: %lu, CTR0: %lu, CTR1: %lu, CTR2: %lu, CTR3: %lu, CTR4: %lu, CTR5: %lu, CYC: %lu, INST: %lu",
__entry->old_pid, __entry->new_pid,
__entry->cctr,
__entry->ctr0, __entry->ctr1,
__entry->ctr2, __entry->ctr3,
__entry->ctr4, __entry->ctr5,
__entry->amu0, __entry->amu1)
);
TRACE_EVENT(sched_switch_ctrs_cfg,
TP_PROTO(int cpu),
TP_ARGS(cpu),
TP_STRUCT__entry(
__field(int, cpu)
__field(unsigned long, ctr0)
__field(unsigned long, ctr1)
__field(unsigned long, ctr2)
__field(unsigned long, ctr3)
__field(unsigned long, ctr4)
__field(unsigned long, ctr5)
),
TP_fast_assign(
u32 i;
u32 cnten_val;
u32 ctr_type[NUM_L1_CTRS] = {0};
cnten_val = per_cpu(cntenset_val, cpu);
for (i = 0; i < NUM_L1_CTRS; i++) {
if (cnten_val & (1 << i)) {
/* Select */
write_sysreg(i, pmselr_el0);
isb();
/* Read type */
ctr_type[i] = read_sysreg(pmxevtyper_el0)
& TYPE_MASK;
} else
ctr_type[i] = 0;
}
__entry->cpu = cpu;
__entry->ctr0 = ctr_type[0];
__entry->ctr1 = ctr_type[1];
__entry->ctr2 = ctr_type[2];
__entry->ctr3 = ctr_type[3];
__entry->ctr4 = ctr_type[4];
__entry->ctr5 = ctr_type[5];
),
TP_printk("cpu=%d CTR0=%lu CTR1=%lu CTR2=%lu CTR3=%lu CTR4=%lu CTR5=%lu",
__entry->cpu,
__entry->ctr0, __entry->ctr1,
__entry->ctr2, __entry->ctr3,
__entry->ctr4, __entry->ctr5)
);
#endif
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../kernel/sched/walt
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE perf_trace_counters
#include <trace/define_trace.h>

View File

@ -0,0 +1,177 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
*/
#include <linux/ftrace.h>
#include <linux/sched.h>
#include <linux/sysctl.h>
#include <linux/printk.h>
#include <linux/sched.h>
#include <linux/sched/clock.h>
#include <trace/hooks/preemptirq.h>
#define CREATE_TRACE_POINTS
#include "preemptirq_long.h"
#define IRQSOFF_SENTINEL 0x0fffDEAD
static unsigned int sysctl_preemptoff_tracing_threshold_ns = 1000000;
static unsigned int sysctl_irqsoff_tracing_threshold_ns = 5000000;
static unsigned int sysctl_irqsoff_dmesg_output_enabled;
static unsigned int sysctl_irqsoff_crash_sentinel_value;
static unsigned int sysctl_irqsoff_crash_threshold_ns = 10000000;
static unsigned int half_million = 500000;
static unsigned int one_hundred_million = 100000000;
static unsigned int one_million = 1000000;
static DEFINE_PER_CPU(u64, irq_disabled_ts);
/*
* preemption disable tracking require additional context
* to rule out false positives. see the comment in
* test_preempt_disable_long() for more details.
*/
struct preempt_store {
u64 ts;
int pid;
unsigned long ncsw;
};
static DEFINE_PER_CPU(struct preempt_store, the_ps);
static void note_irq_disable(void *u1, unsigned long u2, unsigned long u3)
{
if (is_idle_task(current))
return;
/*
* We just have to note down the time stamp here. We
* use stacktrace trigger feature to print the stacktrace.
*/
this_cpu_write(irq_disabled_ts, sched_clock());
}
static void test_irq_disable_long(void *u1, unsigned long ip, unsigned long parent_ip)
{
u64 ts = this_cpu_read(irq_disabled_ts);
if (!ts)
return;
this_cpu_write(irq_disabled_ts, 0);
ts = sched_clock() - ts;
if (ts > sysctl_irqsoff_tracing_threshold_ns) {
trace_irq_disable_long(ts, ip, parent_ip, CALLER_ADDR4, CALLER_ADDR5);
if (sysctl_irqsoff_dmesg_output_enabled == IRQSOFF_SENTINEL)
printk_deferred("irqs off exceeds thresh delta=%llu C:(%ps<-%ps<-%ps<-%ps)\n",
ts, (void *)CALLER_ADDR2,
(void *)CALLER_ADDR3,
(void *)CALLER_ADDR4,
(void *)CALLER_ADDR5);
}
if (sysctl_irqsoff_crash_sentinel_value == IRQSOFF_SENTINEL &&
ts > sysctl_irqsoff_crash_threshold_ns) {
printk_deferred("delta=%llu(ns) > crash_threshold=%u(ns) Task=%s\n",
ts, sysctl_irqsoff_crash_threshold_ns,
current->comm);
BUG_ON(1);
}
}
static void note_preempt_disable(void *u1, unsigned long u2, unsigned long u3)
{
struct preempt_store *ps = &per_cpu(the_ps, raw_smp_processor_id());
ps->ts = sched_clock();
ps->pid = current->pid;
ps->ncsw = current->nvcsw + current->nivcsw;
}
static void test_preempt_disable_long(void *u1, unsigned long ip,
unsigned long parent_ip)
{
struct preempt_store *ps = &per_cpu(the_ps, raw_smp_processor_id());
u64 delta = 0;
if (!ps->ts)
return;
/*
* schedule() calls __schedule() with preemption disabled.
* if we had entered idle and exiting idle now, we think
* preemption is disabled the whole time. Detect this by
* checking if the preemption is disabled across the same
* task. There is a possiblity that the same task is scheduled
* after idle. To rule out this possibility, compare the
* context switch count also.
*/
if (ps->pid == current->pid && (ps->ncsw == current->nvcsw +
current->nivcsw))
delta = sched_clock() - ps->ts;
ps->ts = 0;
if (delta > sysctl_preemptoff_tracing_threshold_ns)
trace_preempt_disable_long(delta, ip, parent_ip, CALLER_ADDR4, CALLER_ADDR5);
}
static struct ctl_table preemptirq_long_table[] = {
{
.procname = "preemptoff_tracing_threshold_ns",
.data = &sysctl_preemptoff_tracing_threshold_ns,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "irqsoff_tracing_threshold_ns",
.data = &sysctl_irqsoff_tracing_threshold_ns,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_douintvec_minmax,
.extra1 = &half_million,
.extra2 = &one_hundred_million,
},
{
.procname = "irqsoff_dmesg_output_enabled",
.data = &sysctl_irqsoff_dmesg_output_enabled,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "irqsoff_crash_sentinel_value",
.data = &sysctl_irqsoff_crash_sentinel_value,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "irqsoff_crash_threshold_ns",
.data = &sysctl_irqsoff_crash_threshold_ns,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_douintvec_minmax,
.extra1 = &one_million,
.extra2 = &one_hundred_million,
},
{ }
};
int preemptirq_long_init(void)
{
if (!register_sysctl("preemptirq", preemptirq_long_table)) {
pr_err("Fail to register sysctl table\n");
return -EPERM;
}
register_trace_android_rvh_irqs_disable(note_irq_disable, NULL);
register_trace_android_rvh_irqs_enable(test_irq_disable_long, NULL);
register_trace_android_rvh_preempt_disable(note_preempt_disable, NULL);
register_trace_android_rvh_preempt_enable(test_preempt_disable_long,
NULL);
return 0;
}

View File

@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021 The Linux Foundation. All rights reserved.
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM preemptirq_long
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#if !defined(_TRACE_PREEMPTIRQ_LONG_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_PREEMPTIRQ_LONG_H
#include <linux/tracepoint.h>
/* reference preemptirq_template */
DECLARE_EVENT_CLASS(preemptirq_long_template,
TP_PROTO(u64 delta, unsigned long ip, unsigned long parent_ip,
unsigned long pparent_ip, unsigned long ppparent_ip),
TP_ARGS(delta, ip, parent_ip, pparent_ip, ppparent_ip),
TP_STRUCT__entry(
__field(u64, delta)
__field(unsigned long, caller_offs)
__field(unsigned long, parent_offs)
__field(unsigned long, pparent_offs)
__field(unsigned long, ppparent_offs)
),
TP_fast_assign(
__entry->delta = delta;
__entry->caller_offs = ip;
__entry->parent_offs = parent_ip;
__entry->pparent_offs = pparent_ip;
__entry->ppparent_offs = ppparent_ip;
),
TP_printk("delta=%llu(ns) caller=%ps <- %ps <- %ps <- %ps",
__entry->delta, __entry->caller_offs,
__entry->parent_offs, __entry->pparent_offs, __entry->ppparent_offs)
);
DEFINE_EVENT(preemptirq_long_template, irq_disable_long,
TP_PROTO(u64 delta, unsigned long ip, unsigned long parent_ip,
unsigned long pparent_ip, unsigned long ppparent_ip),
TP_ARGS(delta, ip, parent_ip, pparent_ip, ppparent_ip));
DEFINE_EVENT(preemptirq_long_template, preempt_disable_long,
TP_PROTO(u64 delta, unsigned long ip, unsigned long parent_ip,
unsigned long pparent_ip, unsigned long ppparent_ip),
TP_ARGS(delta, ip, parent_ip, pparent_ip, ppparent_ip));
#endif /* _TRACE_PREEMPTIRQ_LONG_H */
/* This part must be outside protection */
#include <trace/define_trace.h>

View File

@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, 2015-2021, The Linux Foundation. All rights reserved.
*/
/*
* Scheduler hook for average runqueue determination
*/
#include <linux/module.h>
#include <linux/percpu.h>
#include <linux/hrtimer.h>
#include <linux/sched.h>
#include <linux/math64.h>
#include "walt.h"
#include "trace.h"
static DEFINE_PER_CPU(u64, nr_prod_sum);
static DEFINE_PER_CPU(u64, last_time);
static DEFINE_PER_CPU(u64, nr_big_prod_sum);
static DEFINE_PER_CPU(u64, nr);
static DEFINE_PER_CPU(u64, nr_max);
static DEFINE_PER_CPU(spinlock_t, nr_lock) = __SPIN_LOCK_UNLOCKED(nr_lock);
static s64 last_get_time;
static DEFINE_PER_CPU(atomic64_t, busy_hyst_end_time) = ATOMIC64_INIT(0);
static DEFINE_PER_CPU(u64, hyst_time);
static DEFINE_PER_CPU(u64, coloc_hyst_busy);
static DEFINE_PER_CPU(u64, coloc_hyst_time);
static DEFINE_PER_CPU(u64, util_hyst_time);
#define NR_THRESHOLD_PCT 40
#define MAX_RTGB_TIME (sysctl_sched_coloc_busy_hyst_max_ms * NSEC_PER_MSEC)
struct sched_avg_stats stats[WALT_NR_CPUS];
unsigned int cstats_util_pct[MAX_CLUSTERS];
/**
* sched_get_cluster_util_pct
* @return: provide the percentage of this cluter that was used in the
* previous window.
*
* This routine may be called any number of times as needed during
* a window, but will always return the same result until window
* rollover.
*/
unsigned int sched_get_cluster_util_pct(struct walt_sched_cluster *cluster)
{
unsigned int cluster_util_pct = 0;
if (cluster->id < MAX_CLUSTERS)
cluster_util_pct = cstats_util_pct[cluster->id];
return cluster_util_pct;
}
/**
* sched_get_nr_running_avg
* @return: Average nr_running, iowait and nr_big_tasks value since last poll.
* Returns the avg * 100 to return up to two decimal points
* of accuracy.
*
* Obtains the average nr_running value since the last poll.
* This function may not be called concurrently with itself.
*
* It is assumed that this function is called at most once per window
* rollover.
*/
struct sched_avg_stats *sched_get_nr_running_avg(void)
{
int cpu;
u64 curr_time = sched_clock();
u64 period = curr_time - last_get_time;
u64 tmp_nr, tmp_misfit;
bool any_hyst_time = false;
struct walt_sched_cluster *cluster;
if (!period)
goto done;
/* read and reset nr_running counts */
for_each_possible_cpu(cpu) {
unsigned long flags;
u64 diff;
spin_lock_irqsave(&per_cpu(nr_lock, cpu), flags);
curr_time = sched_clock();
diff = curr_time - per_cpu(last_time, cpu);
BUG_ON((s64)diff < 0);
tmp_nr = per_cpu(nr_prod_sum, cpu);
tmp_nr += per_cpu(nr, cpu) * diff;
tmp_nr = div64_u64((tmp_nr * 100), period);
tmp_misfit = per_cpu(nr_big_prod_sum, cpu);
tmp_misfit += walt_big_tasks(cpu) * diff;
tmp_misfit = div64_u64((tmp_misfit * 100), period);
/*
* NR_THRESHOLD_PCT is to make sure that the task ran
* at least 85% in the last window to compensate any
* over estimating being done.
*/
stats[cpu].nr = (int)div64_u64((tmp_nr + NR_THRESHOLD_PCT),
100);
stats[cpu].nr_misfit = (int)div64_u64((tmp_misfit +
NR_THRESHOLD_PCT), 100);
stats[cpu].nr_max = per_cpu(nr_max, cpu);
stats[cpu].nr_scaled = tmp_nr;
trace_sched_get_nr_running_avg(cpu, stats[cpu].nr,
stats[cpu].nr_misfit, stats[cpu].nr_max,
stats[cpu].nr_scaled);
per_cpu(last_time, cpu) = curr_time;
per_cpu(nr_prod_sum, cpu) = 0;
per_cpu(nr_big_prod_sum, cpu) = 0;
per_cpu(nr_max, cpu) = per_cpu(nr, cpu);
spin_unlock_irqrestore(&per_cpu(nr_lock, cpu), flags);
}
/* collect cluster load stats */
for_each_sched_cluster(cluster) {
unsigned int num_cpus = cpumask_weight(&cluster->cpus);
unsigned int sum_util_pct = 0;
/* load is already scaled, see freq_policy_load/prev_runnable_sum */
for_each_cpu(cpu, &cluster->cpus) {
struct rq *rq = cpu_rq(cpu);
struct walt_rq *wrq = (struct walt_rq *) rq->android_vendor_data1;
/* compute the % this cpu's utilization of the cpu capacity,
* and sum it across all cpus
*/
sum_util_pct +=
(wrq->util * 100) / arch_scale_cpu_capacity(cpu);
}
/* calculate the averge per-cpu utilization */
cstats_util_pct[cluster->id] = sum_util_pct / num_cpus;
}
for_each_possible_cpu(cpu) {
if (per_cpu(coloc_hyst_time, cpu)) {
any_hyst_time = true;
break;
}
}
if (any_hyst_time && get_rtgb_active_time() >= MAX_RTGB_TIME)
sched_update_hyst_times();
last_get_time = curr_time;
done:
return &stats[0];
}
EXPORT_SYMBOL(sched_get_nr_running_avg);
void sched_update_hyst_times(void)
{
bool rtgb_active;
int cpu;
unsigned long cpu_cap, coloc_busy_pct;
rtgb_active = is_rtgb_active() && (sched_boost_type != CONSERVATIVE_BOOST)
&& (get_rtgb_active_time() < MAX_RTGB_TIME);
for_each_possible_cpu(cpu) {
cpu_cap = arch_scale_cpu_capacity(cpu);
coloc_busy_pct = sysctl_sched_coloc_busy_hyst_cpu_busy_pct[cpu];
per_cpu(hyst_time, cpu) = (BIT(cpu)
& sysctl_sched_busy_hyst_enable_cpus) ?
sysctl_sched_busy_hyst : 0;
per_cpu(coloc_hyst_time, cpu) = ((BIT(cpu)
& sysctl_sched_coloc_busy_hyst_enable_cpus)
&& rtgb_active) ?
sysctl_sched_coloc_busy_hyst_cpu[cpu] : 0;
per_cpu(coloc_hyst_busy, cpu) = mult_frac(cpu_cap,
coloc_busy_pct, 100);
per_cpu(util_hyst_time, cpu) = (BIT(cpu)
& sysctl_sched_util_busy_hyst_enable_cpus) ?
sysctl_sched_util_busy_hyst_cpu[cpu] : 0;
}
}
#define BUSY_NR_RUN 3
#define BUSY_LOAD_FACTOR 10
static inline void update_busy_hyst_end_time(int cpu, int enq,
unsigned long prev_nr_run, u64 curr_time)
{
bool nr_run_trigger = false;
bool load_trigger = false, coloc_load_trigger = false;
u64 agg_hyst_time, total_util = 0;
bool util_load_trigger = false;
int i;
bool hyst_trigger, coloc_trigger;
bool dequeue = (enq < 0);
if (!per_cpu(hyst_time, cpu) && !per_cpu(coloc_hyst_time, cpu) &&
!per_cpu(util_hyst_time, cpu))
return;
if (prev_nr_run >= BUSY_NR_RUN && per_cpu(nr, cpu) < BUSY_NR_RUN)
nr_run_trigger = true;
if (dequeue && (cpu_util(cpu) * BUSY_LOAD_FACTOR) >
capacity_orig_of(cpu))
load_trigger = true;
if (dequeue && cpu_util(cpu) > per_cpu(coloc_hyst_busy, cpu))
coloc_load_trigger = true;
if (dequeue) {
for_each_possible_cpu(i) {
total_util += cpu_util(i);
if (total_util >= sysctl_sched_util_busy_hyst_cpu_util[cpu]) {
util_load_trigger = true;
break;
}
}
}
coloc_trigger = nr_run_trigger || coloc_load_trigger;
#if IS_ENABLED(CONFIG_SCHED_CONSERVATIVE_BOOST_LPM_BIAS)
hyst_trigger = nr_run_trigger || load_trigger || (sched_boost_type == CONSERVATIVE_BOOST);
#else
hyst_trigger = nr_run_trigger || load_trigger;
#endif
agg_hyst_time = max(max(hyst_trigger ? per_cpu(hyst_time, cpu) : 0,
coloc_trigger ? per_cpu(coloc_hyst_time, cpu) : 0),
util_load_trigger ? per_cpu(util_hyst_time, cpu) : 0);
if (agg_hyst_time) {
atomic64_set(&per_cpu(busy_hyst_end_time, cpu),
curr_time + agg_hyst_time);
trace_sched_busy_hyst_time(cpu, agg_hyst_time, prev_nr_run,
cpu_util(cpu), per_cpu(hyst_time, cpu),
per_cpu(coloc_hyst_time, cpu),
per_cpu(util_hyst_time, cpu));
}
}
int sched_busy_hyst_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int ret;
if (table->maxlen > (sizeof(unsigned int) * num_possible_cpus()))
table->maxlen = sizeof(unsigned int) * num_possible_cpus();
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
if (!ret && write)
sched_update_hyst_times();
return ret;
}
/**
* sched_update_nr_prod
* @cpu: The core id of the nr running driver.
* @enq: enqueue/dequeue/misfit happening on this CPU.
* @return: N/A
*
* Update average with latest nr_running value for CPU
*/
void sched_update_nr_prod(int cpu, int enq)
{
u64 diff;
u64 curr_time;
unsigned long flags, nr_running;
spin_lock_irqsave(&per_cpu(nr_lock, cpu), flags);
nr_running = per_cpu(nr, cpu);
curr_time = sched_clock();
diff = curr_time - per_cpu(last_time, cpu);
BUG_ON((s64)diff < 0);
per_cpu(last_time, cpu) = curr_time;
per_cpu(nr, cpu) = cpu_rq(cpu)->nr_running;
if (per_cpu(nr, cpu) > per_cpu(nr_max, cpu))
per_cpu(nr_max, cpu) = per_cpu(nr, cpu);
/* Don't update hyst time for misfit tasks */
if (enq)
update_busy_hyst_end_time(cpu, enq, nr_running, curr_time);
per_cpu(nr_prod_sum, cpu) += nr_running * diff;
per_cpu(nr_big_prod_sum, cpu) += walt_big_tasks(cpu) * diff;
spin_unlock_irqrestore(&per_cpu(nr_lock, cpu), flags);
}
/*
* Returns the CPU utilization % in the last window.
*/
unsigned int sched_get_cpu_util_pct(int cpu)
{
struct rq *rq = cpu_rq(cpu);
u64 util;
unsigned long capacity, flags;
unsigned int busy;
struct walt_rq *wrq = (struct walt_rq *) cpu_rq(cpu)->android_vendor_data1;
raw_spin_lock_irqsave(&rq->__lock, flags);
capacity = capacity_orig_of(cpu);
util = wrq->prev_runnable_sum + wrq->grp_time.prev_runnable_sum;
util = scale_time_to_util(util);
raw_spin_unlock_irqrestore(&rq->__lock, flags);
util = (util >= capacity) ? capacity : util;
busy = div64_ul((util * 100), capacity);
return busy;
}
int sched_lpm_disallowed_time(int cpu, u64 *timeout)
{
u64 now = sched_clock();
u64 bias_end_time = atomic64_read(&per_cpu(busy_hyst_end_time, cpu));
if (unlikely(is_reserved(cpu))) {
*timeout = 10 * NSEC_PER_MSEC;
return 0; /* shallowest c-state */
}
if (now < bias_end_time) {
*timeout = bias_end_time - now;
return 0; /* shallowest c-state */
}
return INT_MAX; /* don't care */
}
EXPORT_SYMBOL(sched_lpm_disallowed_time);

979
kernel/sched/walt/sysctl.c Normal file
View File

@ -0,0 +1,979 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <trace/hooks/sched.h>
#include "walt.h"
#include "trace.h"
static int neg_three = -3;
static int three = 3;
static int two_hundred_fifty_five = 255;
static unsigned int ns_per_sec = NSEC_PER_SEC;
static unsigned int one_hundred_thousand = 100000;
static unsigned int two_hundred_million = 200000000;
static int __maybe_unused two = 2;
static int __maybe_unused four = 4;
static int one_hundred = 100;
static int one_thousand = 1000;
static int two_thousand = 2000;
/*
* CFS task prio range is [100 ... 139]
* 120 is the default prio.
* RTG boost range is [100 ... 119] because giving
* boost for [120 .. 139] does not make sense.
* 99 means disabled and it is the default value.
*/
static unsigned int min_cfs_boost_prio = 99;
static unsigned int max_cfs_boost_prio = 119;
unsigned int sysctl_sched_capacity_margin_up_pct[MAX_MARGIN_LEVELS];
unsigned int sysctl_sched_capacity_margin_dn_pct[MAX_MARGIN_LEVELS];
unsigned int sysctl_sched_busy_hyst_enable_cpus;
unsigned int sysctl_sched_busy_hyst;
unsigned int sysctl_sched_coloc_busy_hyst_enable_cpus;
unsigned int sysctl_sched_coloc_busy_hyst_cpu[WALT_NR_CPUS];
unsigned int sysctl_sched_coloc_busy_hyst_max_ms;
unsigned int sysctl_sched_coloc_busy_hyst_cpu_busy_pct[WALT_NR_CPUS];
unsigned int sysctl_sched_util_busy_hyst_enable_cpus;
unsigned int sysctl_sched_util_busy_hyst_cpu[WALT_NR_CPUS];
unsigned int sysctl_sched_util_busy_hyst_cpu_util[WALT_NR_CPUS];
unsigned int sysctl_sched_boost;
unsigned int sysctl_sched_wake_up_idle[2];
unsigned int sysctl_input_boost_ms;
unsigned int sysctl_input_boost_freq[8];
unsigned int sysctl_sched_boost_on_input;
/* sysctl nodes accesed by other files */
unsigned int __read_mostly sysctl_sched_coloc_downmigrate_ns;
unsigned int __read_mostly sysctl_sched_group_downmigrate_pct;
unsigned int __read_mostly sysctl_sched_group_upmigrate_pct;
unsigned int __read_mostly sysctl_sched_window_stats_policy;
unsigned int sysctl_sched_ravg_window_nr_ticks;
unsigned int sysctl_sched_walt_rotate_big_tasks;
unsigned int sysctl_sched_task_unfilter_period;
unsigned int __read_mostly sysctl_sched_asym_cap_sibling_freq_match_pct;
unsigned int sysctl_walt_low_latency_task_threshold; /* disabled by default */
unsigned int sysctl_sched_conservative_pl;
unsigned int sysctl_sched_min_task_util_for_boost = 51;
unsigned int sysctl_sched_min_task_util_for_uclamp = 51;
unsigned int sysctl_sched_min_task_util_for_colocation = 35;
unsigned int sysctl_sched_many_wakeup_threshold = WALT_MANY_WAKEUP_DEFAULT;
const int sched_user_hint_max = 1000;
unsigned int sysctl_walt_rtg_cfs_boost_prio = 99; /* disabled by default */
unsigned int sysctl_sched_sync_hint_enable = 1;
unsigned int sysctl_panic_on_walt_bug = walt_debug_initial_values();
unsigned int sysctl_sched_suppress_region2;
unsigned int sysctl_sched_skip_sp_newly_idle_lb = 1;
unsigned int sysctl_sched_hyst_min_coloc_ns = 80000000;
unsigned int sysctl_sched_asymcap_boost;
unsigned int sysctl_sched_long_running_rt_task_ms;
unsigned int sysctl_sched_idle_enough;
unsigned int sysctl_sched_cluster_util_thres_pct;
/* range is [1 .. INT_MAX] */
static int sysctl_task_read_pid = 1;
static int walt_proc_group_thresholds_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int ret;
static DEFINE_MUTEX(mutex);
struct rq *rq = cpu_rq(cpumask_first(cpu_possible_mask));
unsigned long flags;
if (unlikely(num_sched_clusters <= 0))
return -EPERM;
mutex_lock(&mutex);
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
if (ret || !write) {
mutex_unlock(&mutex);
return ret;
}
/*
* The load scale factor update happens with all
* rqs locked. so acquiring 1 CPU rq lock and
* updating the thresholds is sufficient for
* an atomic update.
*/
raw_spin_lock_irqsave(&rq->__lock, flags);
walt_update_group_thresholds();
raw_spin_unlock_irqrestore(&rq->__lock, flags);
mutex_unlock(&mutex);
return ret;
}
static int walt_proc_user_hint_handler(struct ctl_table *table,
int write, void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int ret;
unsigned int old_value;
static DEFINE_MUTEX(mutex);
mutex_lock(&mutex);
old_value = sysctl_sched_user_hint;
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
if (ret || !write || (old_value == sysctl_sched_user_hint))
goto unlock;
sched_user_hint_reset_time = jiffies + HZ;
walt_irq_work_queue(&walt_migration_irq_work);
unlock:
mutex_unlock(&mutex);
return ret;
}
static int sched_ravg_window_handler(struct ctl_table *table,
int write, void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int ret = -EPERM;
static DEFINE_MUTEX(mutex);
int val;
struct ctl_table tmp = {
.data = &val,
.maxlen = sizeof(val),
.mode = table->mode,
};
mutex_lock(&mutex);
if (write && HZ != 250)
goto unlock;
val = sysctl_sched_ravg_window_nr_ticks;
ret = proc_dointvec(&tmp, write, buffer, lenp, ppos);
if (ret || !write || (val == sysctl_sched_ravg_window_nr_ticks))
goto unlock;
if (val != 2 && val != 3 && val != 4 && val != 5 && val != 8) {
ret = -EINVAL;
goto unlock;
}
sysctl_sched_ravg_window_nr_ticks = val;
sched_window_nr_ticks_change();
unlock:
mutex_unlock(&mutex);
return ret;
}
static DEFINE_MUTEX(sysctl_pid_mutex);
static int sched_task_read_pid_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int ret;
mutex_lock(&sysctl_pid_mutex);
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
mutex_unlock(&sysctl_pid_mutex);
return ret;
}
enum {
TASK_BEGIN = 0,
WAKE_UP_IDLE,
INIT_TASK_LOAD,
GROUP_ID,
PER_TASK_BOOST,
PER_TASK_BOOST_PERIOD_MS,
LOW_LATENCY,
PIPELINE,
LOAD_BOOST,
};
static int sched_task_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int ret, param;
struct task_struct *task;
int pid_and_val[2] = {-1, -1};
int val;
struct walt_task_struct *wts;
struct ctl_table tmp = {
.data = &pid_and_val,
.maxlen = sizeof(pid_and_val),
.mode = table->mode,
};
mutex_lock(&sysctl_pid_mutex);
if (!write) {
task = get_pid_task(find_vpid(sysctl_task_read_pid),
PIDTYPE_PID);
if (!task) {
ret = -ENOENT;
goto unlock_mutex;
}
wts = (struct walt_task_struct *) task->android_vendor_data1;
pid_and_val[0] = sysctl_task_read_pid;
param = (unsigned long)table->data;
switch (param) {
case WAKE_UP_IDLE:
pid_and_val[1] = wts->wake_up_idle;
break;
case INIT_TASK_LOAD:
pid_and_val[1] = wts->init_load_pct;
break;
case GROUP_ID:
pid_and_val[1] = sched_get_group_id(task);
break;
case PER_TASK_BOOST:
pid_and_val[1] = wts->boost;
break;
case PER_TASK_BOOST_PERIOD_MS:
pid_and_val[1] =
div64_ul(wts->boost_period,
1000000UL);
break;
case LOW_LATENCY:
pid_and_val[1] = wts->low_latency &
WALT_LOW_LATENCY_PROCFS;
break;
case PIPELINE:
pid_and_val[1] = wts->low_latency &
WALT_LOW_LATENCY_PIPELINE;
break;
case LOAD_BOOST:
pid_and_val[1] = wts->load_boost;
break;
default:
ret = -EINVAL;
goto put_task;
}
ret = proc_dointvec(&tmp, write, buffer, lenp, ppos);
goto put_task;
}
ret = proc_dointvec(&tmp, write, buffer, lenp, ppos);
if (ret)
goto unlock_mutex;
if (pid_and_val[0] <= 0) {
ret = -ENOENT;
goto unlock_mutex;
}
/* parsed the values successfully in pid_and_val[] array */
task = get_pid_task(find_vpid(pid_and_val[0]), PIDTYPE_PID);
if (!task) {
ret = -ENOENT;
goto unlock_mutex;
}
wts = (struct walt_task_struct *) task->android_vendor_data1;
param = (unsigned long)table->data;
val = pid_and_val[1];
if (param != LOAD_BOOST && val < 0) {
ret = -EINVAL;
goto put_task;
}
switch (param) {
case WAKE_UP_IDLE:
wts->wake_up_idle = val;
break;
case INIT_TASK_LOAD:
if (pid_and_val[1] < 0 || pid_and_val[1] > 100) {
ret = -EINVAL;
goto put_task;
}
wts->init_load_pct = val;
break;
case GROUP_ID:
ret = sched_set_group_id(task, val);
break;
case PER_TASK_BOOST:
if (val < TASK_BOOST_NONE || val >= TASK_BOOST_END) {
ret = -EINVAL;
goto put_task;
}
wts->boost = val;
if (val == 0)
wts->boost_period = 0;
break;
case PER_TASK_BOOST_PERIOD_MS:
if (wts->boost == 0 && val) {
/* setting boost period w/o boost is invalid */
ret = -EINVAL;
goto put_task;
}
wts->boost_period = (u64)val * 1000 * 1000;
wts->boost_expires = sched_clock() + wts->boost_period;
break;
case LOW_LATENCY:
if (val)
wts->low_latency |= WALT_LOW_LATENCY_PROCFS;
else
wts->low_latency &= ~WALT_LOW_LATENCY_PROCFS;
break;
case PIPELINE:
if (val)
wts->low_latency |= WALT_LOW_LATENCY_PIPELINE;
else
wts->low_latency &= ~WALT_LOW_LATENCY_PIPELINE;
break;
case LOAD_BOOST:
if (pid_and_val[1] < -90 || pid_and_val[1] > 90) {
ret = -EINVAL;
goto put_task;
}
wts->load_boost = val;
if (val)
wts->boosted_task_load = mult_frac((int64_t)1024, (int64_t)val, 100);
else
wts->boosted_task_load = 0;
break;
default:
ret = -EINVAL;
}
trace_sched_task_handler(task, param, val, CALLER_ADDR0, CALLER_ADDR1,
CALLER_ADDR2, CALLER_ADDR3, CALLER_ADDR4, CALLER_ADDR5);
put_task:
put_task_struct(task);
unlock_mutex:
mutex_unlock(&sysctl_pid_mutex);
return ret;
}
#ifdef CONFIG_PROC_SYSCTL
static void sched_update_updown_migrate_values(bool up)
{
int i = 0, cpu;
struct walt_sched_cluster *cluster;
int cap_margin_levels = num_sched_clusters - 1;
if (cap_margin_levels > 1) {
/*
* No need to worry about CPUs in last cluster
* if there are more than 2 clusters in the system
*/
for_each_sched_cluster(cluster) {
for_each_cpu(cpu, &cluster->cpus) {
if (up)
sched_capacity_margin_up[cpu] =
SCHED_FIXEDPOINT_SCALE * 100 /
sysctl_sched_capacity_margin_up_pct[i];
else
sched_capacity_margin_down[cpu] =
SCHED_FIXEDPOINT_SCALE * 100 /
sysctl_sched_capacity_margin_dn_pct[i];
}
if (++i >= cap_margin_levels)
break;
}
} else {
for_each_possible_cpu(cpu) {
if (up)
sched_capacity_margin_up[cpu] =
SCHED_FIXEDPOINT_SCALE * 100 /
sysctl_sched_capacity_margin_up_pct[0];
else
sched_capacity_margin_down[cpu] =
sysctl_sched_capacity_margin_dn_pct[0];
}
}
}
int sched_updown_migrate_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int ret, i;
unsigned int *data = (unsigned int *)table->data;
static DEFINE_MUTEX(mutex);
int cap_margin_levels = num_sched_clusters ? num_sched_clusters - 1 : 0;
int val[MAX_MARGIN_LEVELS];
struct ctl_table tmp = {
.data = &val,
.maxlen = sizeof(int) * cap_margin_levels,
.mode = table->mode,
};
if (cap_margin_levels <= 0)
return -EINVAL;
mutex_lock(&mutex);
if (!write) {
ret = proc_dointvec(table, write, buffer, lenp, ppos);
goto unlock_mutex;
}
ret = proc_dointvec(&tmp, write, buffer, lenp, ppos);
if (ret)
goto unlock_mutex;
/* check if valid pct values are passed in */
for (i = 0; i < cap_margin_levels; i++) {
if (val[i] <= 0 || val[i] > 100) {
ret = -EINVAL;
goto unlock_mutex;
}
}
/* check up pct is greater than dn pct */
if (data == &sysctl_sched_capacity_margin_up_pct[0]) {
for (i = 0; i < cap_margin_levels; i++) {
if (val[i] < sysctl_sched_capacity_margin_dn_pct[i]) {
ret = -EINVAL;
goto unlock_mutex;
}
}
} else {
for (i = 0; i < cap_margin_levels; i++) {
if (sysctl_sched_capacity_margin_up_pct[i] < val[i]) {
ret = -EINVAL;
goto unlock_mutex;
}
}
}
/* all things checkout update the value */
for (i = 0; i < cap_margin_levels; i++)
data[i] = val[i];
/* update individual cpu thresholds */
sched_update_updown_migrate_values(data == &sysctl_sched_capacity_margin_up_pct[0]);
unlock_mutex:
mutex_unlock(&mutex);
return ret;
}
#endif /* CONFIG_PROC_SYSCTL */
struct ctl_table input_boost_sysctls[] = {
{
.procname = "input_boost_ms",
.data = &sysctl_input_boost_ms,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &one_hundred_thousand,
},
{
.procname = "input_boost_freq",
.data = &sysctl_input_boost_freq,
.maxlen = sizeof(unsigned int) * 8,
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
},
{
.procname = "sched_boost_on_input",
.data = &sysctl_sched_boost_on_input,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
},
{ }
};
struct ctl_table walt_table[] = {
{
.procname = "sched_user_hint",
.data = &sysctl_sched_user_hint,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = walt_proc_user_hint_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = (void *)&sched_user_hint_max,
},
{
.procname = "sched_window_stats_policy",
.data = &sysctl_sched_window_stats_policy,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &four,
},
{
.procname = "sched_group_upmigrate",
.data = &sysctl_sched_group_upmigrate_pct,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = walt_proc_group_thresholds_handler,
.extra1 = &sysctl_sched_group_downmigrate_pct,
},
{
.procname = "sched_group_downmigrate",
.data = &sysctl_sched_group_downmigrate_pct,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = walt_proc_group_thresholds_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &sysctl_sched_group_upmigrate_pct,
},
{
.procname = "sched_boost",
.data = &sysctl_sched_boost,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = sched_boost_handler,
.extra1 = &neg_three,
.extra2 = &three,
},
{
.procname = "sched_conservative_pl",
.data = &sysctl_sched_conservative_pl,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "sched_many_wakeup_threshold",
.data = &sysctl_sched_many_wakeup_threshold,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &two,
.extra2 = &one_thousand,
},
{
.procname = "sched_walt_rotate_big_tasks",
.data = &sysctl_sched_walt_rotate_big_tasks,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "sched_min_task_util_for_boost",
.data = &sysctl_sched_min_task_util_for_boost,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &one_thousand,
},
{
.procname = "sched_min_task_util_for_uclamp",
.data = &sysctl_sched_min_task_util_for_uclamp,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &one_thousand,
},
{
.procname = "sched_min_task_util_for_colocation",
.data = &sysctl_sched_min_task_util_for_colocation,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &one_thousand,
},
{
.procname = "sched_asym_cap_sibling_freq_match_pct",
.data = &sysctl_sched_asym_cap_sibling_freq_match_pct,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ONE,
.extra2 = &one_hundred,
},
{
.procname = "sched_coloc_downmigrate_ns",
.data = &sysctl_sched_coloc_downmigrate_ns,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_douintvec_minmax,
},
{
.procname = "sched_task_unfilter_period",
.data = &sysctl_sched_task_unfilter_period,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ONE,
.extra2 = &two_hundred_million,
},
{
.procname = "sched_busy_hysteresis_enable_cpus",
.data = &sysctl_sched_busy_hyst_enable_cpus,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = sched_busy_hyst_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &two_hundred_fifty_five,
},
{
.procname = "sched_busy_hyst_ns",
.data = &sysctl_sched_busy_hyst,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = sched_busy_hyst_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &ns_per_sec,
},
{
.procname = "sched_coloc_busy_hysteresis_enable_cpus",
.data = &sysctl_sched_coloc_busy_hyst_enable_cpus,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = sched_busy_hyst_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &two_hundred_fifty_five,
},
{
.procname = "sched_coloc_busy_hyst_cpu_ns",
.data = &sysctl_sched_coloc_busy_hyst_cpu,
.maxlen = sizeof(unsigned int) * WALT_NR_CPUS,
.mode = 0644,
.proc_handler = sched_busy_hyst_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &ns_per_sec,
},
{
.procname = "sched_coloc_busy_hyst_max_ms",
.data = &sysctl_sched_coloc_busy_hyst_max_ms,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = sched_busy_hyst_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &one_hundred_thousand,
},
{
.procname = "sched_coloc_busy_hyst_cpu_busy_pct",
.data = &sysctl_sched_coloc_busy_hyst_cpu_busy_pct,
.maxlen = sizeof(unsigned int) * WALT_NR_CPUS,
.mode = 0644,
.proc_handler = sched_busy_hyst_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &one_hundred,
},
{
.procname = "sched_util_busy_hysteresis_enable_cpus",
.data = &sysctl_sched_util_busy_hyst_enable_cpus,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = sched_busy_hyst_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &two_hundred_fifty_five,
},
{
.procname = "sched_util_busy_hyst_cpu_ns",
.data = &sysctl_sched_util_busy_hyst_cpu,
.maxlen = sizeof(unsigned int) * WALT_NR_CPUS,
.mode = 0644,
.proc_handler = sched_busy_hyst_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &ns_per_sec,
},
{
.procname = "sched_util_busy_hyst_cpu_util",
.data = &sysctl_sched_util_busy_hyst_cpu_util,
.maxlen = sizeof(unsigned int) * WALT_NR_CPUS,
.mode = 0644,
.proc_handler = sched_busy_hyst_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &one_thousand,
},
{
.procname = "sched_ravg_window_nr_ticks",
.data = &sysctl_sched_ravg_window_nr_ticks,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = sched_ravg_window_handler,
},
{
.procname = "sched_upmigrate",
.data = &sysctl_sched_capacity_margin_up_pct,
.maxlen = sizeof(unsigned int) * MAX_MARGIN_LEVELS,
.mode = 0644,
.proc_handler = sched_updown_migrate_handler,
},
{
.procname = "sched_downmigrate",
.data = &sysctl_sched_capacity_margin_dn_pct,
.maxlen = sizeof(unsigned int) * MAX_MARGIN_LEVELS,
.mode = 0644,
.proc_handler = sched_updown_migrate_handler,
},
{
.procname = "walt_rtg_cfs_boost_prio",
.data = &sysctl_walt_rtg_cfs_boost_prio,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &min_cfs_boost_prio,
.extra2 = &max_cfs_boost_prio,
},
{
.procname = "walt_low_latency_task_threshold",
.data = &sysctl_walt_low_latency_task_threshold,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &one_thousand,
},
{
.procname = "sched_force_lb_enable",
.data = &sysctl_sched_force_lb_enable,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "sched_sync_hint_enable",
.data = &sysctl_sched_sync_hint_enable,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "sched_suppress_region2",
.data = &sysctl_sched_suppress_region2,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "sched_skip_sp_newly_idle_lb",
.data = &sysctl_sched_skip_sp_newly_idle_lb,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "sched_hyst_min_coloc_ns",
.data = &sysctl_sched_hyst_min_coloc_ns,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
},
{
.procname = "panic_on_walt_bug",
.data = &sysctl_panic_on_walt_bug,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
},
{
.procname = "sched_lib_name",
.data = sched_lib_name,
.maxlen = LIB_PATH_LENGTH,
.mode = 0644,
.proc_handler = proc_dostring,
},
{
.procname = "sched_lib_mask_force",
.data = &sched_lib_mask_force,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_douintvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &two_hundred_fifty_five,
},
{
.procname = "input_boost",
.mode = 0555,
.child = input_boost_sysctls,
},
{
.procname = "sched_wake_up_idle",
.data = (int *) WAKE_UP_IDLE,
.maxlen = sizeof(unsigned int) * 2,
.mode = 0644,
.proc_handler = sched_task_handler,
},
{
.procname = "sched_init_task_load",
.data = (int *) INIT_TASK_LOAD,
.maxlen = sizeof(unsigned int) * 2,
.mode = 0644,
.proc_handler = sched_task_handler,
},
{
.procname = "sched_group_id",
.data = (int *) GROUP_ID,
.maxlen = sizeof(unsigned int) * 2,
.mode = 0644,
.proc_handler = sched_task_handler,
},
{
.procname = "sched_per_task_boost",
.data = (int *) PER_TASK_BOOST,
.maxlen = sizeof(unsigned int) * 2,
.mode = 0644,
.proc_handler = sched_task_handler,
},
{
.procname = "sched_per_task_boost_period_ms",
.data = (int *) PER_TASK_BOOST_PERIOD_MS,
.maxlen = sizeof(unsigned int) * 2,
.mode = 0644,
.proc_handler = sched_task_handler,
},
{
.procname = "sched_low_latency",
.data = (int *) LOW_LATENCY,
.maxlen = sizeof(unsigned int) * 2,
.mode = 0644,
.proc_handler = sched_task_handler,
},
{
.procname = "sched_pipeline",
.data = (int *) PIPELINE,
.maxlen = sizeof(unsigned int) * 2,
.mode = 0644,
.proc_handler = sched_task_handler,
},
{
.procname = "task_load_boost",
.data = (int *) LOAD_BOOST,
.maxlen = sizeof(unsigned int) * 2,
.mode = 0644,
.proc_handler = sched_task_handler,
},
{
.procname = "sched_task_read_pid",
.data = &sysctl_task_read_pid,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = sched_task_read_pid_handler,
.extra1 = SYSCTL_ONE,
.extra2 = SYSCTL_INT_MAX,
},
{
.procname = "sched_enable_tp",
.data = &sysctl_sched_dynamic_tp_enable,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = sched_dynamic_tp_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "sched_asymcap_boost",
.data = &sysctl_sched_asymcap_boost,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_douintvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "sched_cluster_util_thres_pct",
.data = &sysctl_sched_cluster_util_thres_pct,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_douintvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
},
{
.procname = "sched_idle_enough",
.data = &sysctl_sched_idle_enough,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_douintvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
},
{
.procname = "sched_long_running_rt_task_ms",
.data = &sysctl_sched_long_running_rt_task_ms,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = sched_long_running_rt_task_ms_handler,
.extra1 = SYSCTL_ZERO,
.extra2 = &two_thousand,
},
{ }
};
struct ctl_table walt_base_table[] = {
{
.procname = "walt",
.mode = 0555,
.child = walt_table,
},
{ },
};
void walt_tunables(void)
{
int i;
for (i = 0; i < MAX_MARGIN_LEVELS; i++) {
sysctl_sched_capacity_margin_up_pct[i] = 95; /* ~5% margin */
sysctl_sched_capacity_margin_dn_pct[i] = 85; /* ~15% margin */
}
sysctl_sched_group_upmigrate_pct = 100;
sysctl_sched_group_downmigrate_pct = 95;
sysctl_sched_asym_cap_sibling_freq_match_pct = 100;
sysctl_sched_task_unfilter_period = 100000000;
sysctl_sched_window_stats_policy = WINDOW_STATS_MAX_RECENT_AVG;
sysctl_sched_ravg_window_nr_ticks = (HZ / NR_WINDOWS_PER_SEC);
sched_load_granule = DEFAULT_SCHED_RAVG_WINDOW / NUM_LOAD_INDICES;
for (i = 0; i < WALT_NR_CPUS; i++) {
sysctl_sched_coloc_busy_hyst_cpu[i] = 39000000;
sysctl_sched_coloc_busy_hyst_cpu_busy_pct[i] = 10;
sysctl_sched_util_busy_hyst_cpu[i] = 5000000;
sysctl_sched_util_busy_hyst_cpu_util[i] = 15;
}
sysctl_sched_coloc_busy_hyst_enable_cpus = 112;
sysctl_sched_util_busy_hyst_enable_cpus = 255;
sysctl_sched_coloc_busy_hyst_max_ms = 5000;
sched_ravg_window = DEFAULT_SCHED_RAVG_WINDOW;
sysctl_input_boost_ms = 40;
for (i = 0; i < 8; i++)
sysctl_input_boost_freq[i] = 0;
}

84
kernel/sched/walt/trace.c Normal file
View File

@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved.
*/
#include "walt.h"
static inline void __window_data(u32 *dst, u32 *src)
{
if (src)
memcpy(dst, src, nr_cpu_ids * sizeof(u32));
else
memset(dst, 0, nr_cpu_ids * sizeof(u32));
}
struct trace_seq;
const char *__window_print(struct trace_seq *p, const u32 *buf, int buf_len)
{
int i;
const char *ret = p->buffer + seq_buf_used(&p->seq);
for (i = 0; i < buf_len; i++)
trace_seq_printf(p, "%u ", buf[i]);
trace_seq_putc(p, 0);
return ret;
}
static inline s64 __rq_update_sum(struct rq *rq, bool curr, bool new)
{
struct walt_rq *wrq = (struct walt_rq *) rq->android_vendor_data1;
if (curr)
if (new)
return wrq->nt_curr_runnable_sum;
else
return wrq->curr_runnable_sum;
else
if (new)
return wrq->nt_prev_runnable_sum;
else
return wrq->prev_runnable_sum;
}
static inline s64 __grp_update_sum(struct rq *rq, bool curr, bool new)
{
struct walt_rq *wrq = (struct walt_rq *) rq->android_vendor_data1;
if (curr)
if (new)
return wrq->grp_time.nt_curr_runnable_sum;
else
return wrq->grp_time.curr_runnable_sum;
else
if (new)
return wrq->grp_time.nt_prev_runnable_sum;
else
return wrq->grp_time.prev_runnable_sum;
}
static inline s64
__get_update_sum(struct rq *rq, enum migrate_types migrate_type,
bool src, bool new, bool curr)
{
switch (migrate_type) {
case RQ_TO_GROUP:
if (src)
return __rq_update_sum(rq, curr, new);
else
return __grp_update_sum(rq, curr, new);
case GROUP_TO_RQ:
if (src)
return __grp_update_sum(rq, curr, new);
else
return __rq_update_sum(rq, curr, new);
default:
WARN_ON_ONCE(1);
return -EINVAL;
}
}
#define CREATE_TRACE_POINTS
#include "trace.h"

1484
kernel/sched/walt/trace.h Normal file

File diff suppressed because it is too large Load Diff

4707
kernel/sched/walt/walt.c Normal file

File diff suppressed because it is too large Load Diff

1021
kernel/sched/walt/walt.h Normal file

File diff suppressed because it is too large Load Diff

1370
kernel/sched/walt/walt_cfs.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <trace/hooks/sched.h>
#include "walt.h"
#include "walt_debug.h"
static void android_rvh_schedule_bug(void *unused, void *unused2)
{
BUG();
}
static int __init walt_debug_init(void)
{
int ret;
ret = preemptirq_long_init();
if (ret)
return ret;
register_trace_android_rvh_schedule_bug(android_rvh_schedule_bug, NULL);
return 0;
}
module_init(walt_debug_init);
MODULE_DESCRIPTION("QTI WALT Debug Module");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
int preemptirq_long_init(void);

View File

@ -0,0 +1,569 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021-2021 Qualcomm Innovation Center, Inc. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/sched/isolation.h>
#include <trace/hooks/sched.h>
#include <walt.h>
#include "trace.h"
#ifdef CONFIG_HOTPLUG_CPU
/* if a cpu is halting */
struct cpumask __cpu_halt_mask;
/* spin lock to allow calling from non-preemptible context */
static DEFINE_RAW_SPINLOCK(halt_lock);
struct halt_cpu_state {
u64 last_halt;
u8 reason;
};
static DEFINE_PER_CPU(struct halt_cpu_state, halt_state);
static DEFINE_RAW_SPINLOCK(walt_drain_pending_lock);
/* the amount of time allowed for enqueue operations that happen
* just after a halt operation.
*/
#define WALT_HALT_CHECK_THRESHOLD_NS 400000
/*
* Remove a task from the runqueue and pretend that it's migrating. This
* should prevent migrations for the detached task and disallow further
* changes to tsk_cpus_allowed.
*/
void
detach_one_task_core(struct task_struct *p, struct rq *rq,
struct list_head *tasks)
{
lockdep_assert_held(&rq->__lock);
p->on_rq = TASK_ON_RQ_MIGRATING;
deactivate_task(rq, p, 0);
list_add(&p->se.group_node, tasks);
}
void attach_tasks_core(struct list_head *tasks, struct rq *rq)
{
struct task_struct *p;
lockdep_assert_held(&rq->__lock);
while (!list_empty(tasks)) {
p = list_first_entry(tasks, struct task_struct, se.group_node);
list_del_init(&p->se.group_node);
BUG_ON(task_rq(p) != rq);
activate_task(rq, p, 0);
p->on_rq = TASK_ON_RQ_QUEUED;
}
}
/*
* Migrate all tasks from the rq, sleeping tasks will be migrated by
* try_to_wake_up()->select_task_rq().
*
* Called with rq->__lock held even though we'er in stop_machine() and
* there's no concurrency possible, we hold the required locks anyway
* because of lock validation efforts.
*
* The function will skip CPU pinned kthreads.
*/
static void migrate_tasks(struct rq *dead_rq, struct rq_flags *rf)
{
struct rq *rq = dead_rq;
struct task_struct *next, *stop = rq->stop;
LIST_HEAD(percpu_kthreads);
unsigned int num_pinned_kthreads = 1;
struct rq_flags orf = *rf;
int dest_cpu;
/*
* Fudge the rq selection such that the below task selection loop
* doesn't get stuck on the currently eligible stop task.
*
* We're currently inside stop_machine() and the rq is either stuck
* in the stop_machine_cpu_stop() loop, or we're executing this code,
* either way we should never end up calling schedule() until we're
* done here.
*/
rq->stop = NULL;
/*
* put_prev_task() and pick_next_task() sched
* class method both need to have an up-to-date
* value of rq->clock[_task]
*/
update_rq_clock(rq);
#ifdef CONFIG_SCHED_DEBUG
/* note the clock update in orf */
orf.clock_update_flags |= RQCF_UPDATED;
#endif
for (;;) {
/*
* There's this thread running, bail when that's the only
* remaining thread:
*/
if (rq->nr_running == 1)
break;
next = pick_migrate_task(rq);
/*
* Argh ... no iterator for tasks, we need to remove the
* kthread from the run-queue to continue.
*/
if (is_per_cpu_kthread(next)) {
detach_one_task_core(next, rq, &percpu_kthreads);
num_pinned_kthreads += 1;
continue;
}
/*
* Rules for changing task_struct::cpus_mask are holding
* both pi_lock and rq->__lock, such that holding either
* stabilizes the mask.
*
* Drop rq->__lock is not quite as disastrous as it usually is
* because !cpu_active at this point, which means load-balance
* will not interfere. Also, stop-machine.
*/
rq_unlock(rq, rf);
raw_spin_lock(&next->pi_lock);
rq_relock(rq, rf);
/*
* Since we're inside stop-machine, _nothing_ should have
* changed the task, WARN if weird stuff happened, because in
* that case the above rq->__lock drop is a fail too.
*/
if (task_rq(next) != rq || !task_on_rq_queued(next)) {
raw_spin_unlock(&next->pi_lock);
continue;
}
/* Find suitable destination for @next */
dest_cpu = select_fallback_rq(dead_rq->cpu, next);
if (cpu_of(rq) != dest_cpu && !is_migration_disabled(next)) {
/* only perform a required migration */
rq = __migrate_task(rq, rf, next, dest_cpu);
if (rq != dead_rq) {
rq_unlock(rq, rf);
rq = dead_rq;
*rf = orf;
rq_relock(rq, rf);
}
} else {
detach_one_task_core(next, rq, &percpu_kthreads);
num_pinned_kthreads += 1;
}
raw_spin_unlock(&next->pi_lock);
}
if (num_pinned_kthreads > 1)
attach_tasks_core(&percpu_kthreads, rq);
rq->stop = stop;
}
static int drain_rq_cpu_stop(void *data)
{
struct rq *rq = this_rq();
struct rq_flags rf;
rq_lock_irqsave(rq, &rf);
migrate_tasks(rq, &rf);
rq_unlock_irqrestore(rq, &rf);
return 0;
}
static int cpu_drain_rq(unsigned int cpu)
{
if (!cpu_online(cpu))
return 0;
if (available_idle_cpu(cpu))
return 0;
/* this will schedule, must not be in atomic context */
return stop_one_cpu(cpu, drain_rq_cpu_stop, NULL);
}
/*
* returns true if last halt is within threshold
* note: do not take halt_lock, called from atomic context
*/
bool walt_halt_check_last(int cpu)
{
u64 last_halt = per_cpu_ptr(&halt_state, cpu)->last_halt;
/* last_halt is valid, check it against sched_clock */
if (last_halt != 0 && sched_clock() - last_halt > WALT_HALT_CHECK_THRESHOLD_NS)
return false;
return true;
}
struct drain_thread_data {
cpumask_t cpus_to_drain;
};
static struct drain_thread_data drain_data = {
.cpus_to_drain = { CPU_BITS_NONE }
};
static int __ref try_drain_rqs(void *data)
{
cpumask_t *cpus_ptr = &((struct drain_thread_data *)data)->cpus_to_drain;
int cpu;
unsigned long flags;
while (!kthread_should_stop()) {
raw_spin_lock_irqsave(&walt_drain_pending_lock, flags);
if (cpumask_weight(cpus_ptr)) {
cpumask_t local_cpus;
cpumask_copy(&local_cpus, cpus_ptr);
raw_spin_unlock_irqrestore(&walt_drain_pending_lock, flags);
for_each_cpu(cpu, &local_cpus)
cpu_drain_rq(cpu);
raw_spin_lock_irqsave(&walt_drain_pending_lock, flags);
cpumask_andnot(cpus_ptr, cpus_ptr, &local_cpus);
}
raw_spin_unlock_irqrestore(&walt_drain_pending_lock, flags);
set_current_state(TASK_INTERRUPTIBLE);
schedule();
set_current_state(TASK_RUNNING);
}
return 0;
}
struct task_struct *walt_drain_thread;
/*
* 1) add the cpus to the halt mask
* 2) migrate tasks off the cpu
*/
static int halt_cpus(struct cpumask *cpus)
{
int cpu;
int ret = 0;
u64 start_time = sched_clock();
struct halt_cpu_state *halt_cpu_state;
unsigned long flags;
trace_halt_cpus_start(cpus, 1);
for_each_cpu(cpu, cpus) {
if (cpu == cpumask_first(system_32bit_el0_cpumask())) {
ret = -EINVAL;
goto out;
}
halt_cpu_state = per_cpu_ptr(&halt_state, cpu);
/* set the cpu as halted */
cpumask_set_cpu(cpu, cpu_halt_mask);
/* guarantee mask written before updating last_halt */
wmb();
halt_cpu_state->last_halt = start_time;
}
/* signal and wakeup the drain kthread */
raw_spin_lock_irqsave(&walt_drain_pending_lock, flags);
cpumask_or(&drain_data.cpus_to_drain, &drain_data.cpus_to_drain, cpus);
raw_spin_unlock_irqrestore(&walt_drain_pending_lock, flags);
if (!IS_ERR(walt_drain_thread))
wake_up_process(walt_drain_thread);
out:
trace_halt_cpus(cpus, start_time, 1, ret);
return ret;
}
/*
* 1) remove the cpus from the halt mask
*
*/
static int start_cpus(struct cpumask *cpus)
{
u64 start_time = sched_clock();
struct halt_cpu_state *halt_cpu_state;
int cpu;
trace_halt_cpus_start(cpus, 0);
for_each_cpu(cpu, cpus) {
halt_cpu_state = per_cpu_ptr(&halt_state, cpu);
halt_cpu_state->last_halt = 0;
wmb();
/* wmb to guarantee zero'd last_halt before clearing from the mask */
cpumask_clear_cpu(cpu, cpu_halt_mask);
/* kick the cpu so it can pull tasks
* after the mask has been cleared.
*/
walt_smp_call_newidle_balance(cpu);
}
trace_halt_cpus(cpus, start_time, 0, 0);
return 0;
}
/* update reason for cpus in yield/halt mask */
static void update_reasons(struct cpumask *cpus, bool halt, enum pause_reason reason)
{
int cpu;
struct halt_cpu_state *halt_cpu_state;
for_each_cpu(cpu, cpus) {
halt_cpu_state = per_cpu_ptr(&halt_state, cpu);
if (halt)
halt_cpu_state->reason |= reason;
else
halt_cpu_state->reason &= ~reason;
}
}
/* remove cpus that are already halted */
static void update_halt_cpus(struct cpumask *cpus)
{
int cpu;
struct halt_cpu_state *halt_cpu_state;
for_each_cpu(cpu, cpus) {
halt_cpu_state = per_cpu_ptr(&halt_state, cpu);
if (halt_cpu_state->reason)
cpumask_clear_cpu(cpu, cpus);
}
}
/* cpus will be modified */
int walt_halt_cpus(struct cpumask *cpus, enum pause_reason reason)
{
int ret = 0;
cpumask_t requested_cpus;
unsigned long flags;
raw_spin_lock_irqsave(&halt_lock, flags);
cpumask_copy(&requested_cpus, cpus);
/* remove cpus that are already halted */
update_halt_cpus(cpus);
if (cpumask_empty(cpus)) {
update_reasons(&requested_cpus, true, reason);
goto unlock;
}
ret = halt_cpus(cpus);
if (ret < 0)
pr_debug("halt_cpus failure ret=%d cpus=%*pbl\n", ret,
cpumask_pr_args(&requested_cpus));
else
update_reasons(&requested_cpus, true, reason);
unlock:
raw_spin_unlock_irqrestore(&halt_lock, flags);
return ret;
}
int walt_pause_cpus(struct cpumask *cpus, enum pause_reason reason)
{
return walt_halt_cpus(cpus, reason);
}
EXPORT_SYMBOL(walt_pause_cpus);
/* cpus will be modified */
int walt_start_cpus(struct cpumask *cpus, enum pause_reason reason)
{
int ret = 0;
cpumask_t requested_cpus;
unsigned long flags;
raw_spin_lock_irqsave(&halt_lock, flags);
cpumask_copy(&requested_cpus, cpus);
update_reasons(&requested_cpus, false, reason);
/* remove cpus that should still be halted */
update_halt_cpus(cpus);
ret = start_cpus(cpus);
if (ret < 0) {
pr_debug("halt_cpus failure ret=%d cpus=%*pbl\n", ret,
cpumask_pr_args(&requested_cpus));
/* restore/increment ref counts in case of error */
update_reasons(&requested_cpus, true, reason);
}
raw_spin_unlock_irqrestore(&halt_lock, flags);
return ret;
}
int walt_resume_cpus(struct cpumask *cpus, enum pause_reason reason)
{
return walt_start_cpus(cpus, reason);
}
EXPORT_SYMBOL(walt_resume_cpus);
static void android_rvh_get_nohz_timer_target(void *unused, int *cpu, bool *done)
{
int i, default_cpu = -1;
struct sched_domain *sd;
cpumask_t unhalted;
*done = true;
if (housekeeping_cpu(*cpu, HK_FLAG_TIMER) && !cpu_halted(*cpu)) {
if (!available_idle_cpu(*cpu))
return;
default_cpu = *cpu;
}
rcu_read_lock();
for_each_domain(*cpu, sd) {
for_each_cpu_and(i, sched_domain_span(sd),
housekeeping_cpumask(HK_FLAG_TIMER)) {
if (*cpu == i)
continue;
if (!available_idle_cpu(i) && !cpu_halted(i)) {
*cpu = i;
goto unlock;
}
}
}
if (default_cpu == -1) {
cpumask_complement(&unhalted, cpu_halt_mask);
for_each_cpu_and(i, &unhalted,
housekeeping_cpumask(HK_FLAG_TIMER)) {
if (*cpu == i)
continue;
if (!available_idle_cpu(i)) {
*cpu = i;
goto unlock;
}
}
/* no active, non-halted, not-idle, choose any */
default_cpu = cpumask_any(&unhalted);
if (unlikely(default_cpu >= nr_cpu_ids))
goto unlock;
}
*cpu = default_cpu;
unlock:
rcu_read_unlock();
}
static void android_rvh_set_cpus_allowed_ptr_locked(void *unused,
const struct cpumask *cpu_valid_mask,
const struct cpumask *new_mask,
unsigned int *dest_cpu)
{
cpumask_t allowed_cpus;
if (unlikely(walt_disabled))
return;
if (cpu_halted(*dest_cpu)) {
/* remove halted cpus from the valid mask, and store locally */
cpumask_andnot(&allowed_cpus, cpu_valid_mask, cpu_halt_mask);
*dest_cpu = cpumask_any_and_distribute(&allowed_cpus, new_mask);
}
}
static void android_rvh_rto_next_cpu(void *unused, int rto_cpu, struct cpumask *rto_mask, int *cpu)
{
cpumask_t allowed_cpus;
if (unlikely(walt_disabled))
return;
if (cpu_halted(*cpu)) {
/* remove halted cpus from the valid mask, and store locally */
cpumask_andnot(&allowed_cpus, rto_mask, cpu_halt_mask);
*cpu = cpumask_next(rto_cpu, &allowed_cpus);
}
}
/**
* android_rvh_is_cpu_allowed: disallow cpus that are halted
*
* Caveat: For 32 bit tasks that are being directed to a halted cpu, allow the halted cpu
* in a particular case (32 bit task, in execve, moving to a 32 bit cpu)
* This is to handle the call to is_cpu_allowed() from __migrate_task, in the
* event that a 32bit task is being execve'd.
*/
static void android_rvh_is_cpu_allowed(void *unused, struct task_struct *p, int cpu, bool *allowed)
{
if (unlikely(walt_disabled))
return;
if (cpumask_test_cpu(cpu, cpu_halt_mask)) {
/* default reject for any halted cpu */
*allowed = false;
if (is_compat_thread(task_thread_info(p)) &&
p->in_execve) {
/*
* the task is 32 bit capable and
* the context is execve. allow this cpu.
*/
*allowed = true;
}
}
}
void walt_halt_init(void)
{
struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 };
walt_drain_thread = kthread_run(try_drain_rqs, &drain_data, "halt_drain_rqs");
if (IS_ERR(walt_drain_thread)) {
pr_err("Error creating walt drain thread\n");
return;
}
sched_setscheduler_nocheck(walt_drain_thread, SCHED_FIFO, &param);
register_trace_android_rvh_get_nohz_timer_target(android_rvh_get_nohz_timer_target, NULL);
register_trace_android_rvh_set_cpus_allowed_ptr_locked(
android_rvh_set_cpus_allowed_ptr_locked, NULL);
register_trace_android_rvh_rto_next_cpu(android_rvh_rto_next_cpu, NULL);
register_trace_android_rvh_is_cpu_allowed(android_rvh_is_cpu_allowed, NULL);
}
#endif /* CONFIG_HOTPLUG_CPU */

1080
kernel/sched/walt/walt_lb.c Normal file

File diff suppressed because it is too large Load Diff

367
kernel/sched/walt/walt_rt.c Normal file
View File

@ -0,0 +1,367 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <trace/hooks/sched.h>
#include "walt.h"
#include "trace.h"
#define MSEC_TO_NSEC (1000 * 1000)
static DEFINE_PER_CPU(cpumask_var_t, walt_local_cpu_mask);
DEFINE_PER_CPU(u64, rt_task_arrival_time) = 0;
static bool long_running_rt_task_trace_rgstrd;
static void rt_task_arrival_marker(void *unused, bool preempt,
struct task_struct *prev, struct task_struct *next)
{
unsigned int cpu = raw_smp_processor_id();
if (next->policy == SCHED_FIFO && next != cpu_rq(cpu)->stop)
per_cpu(rt_task_arrival_time, cpu) = rq_clock_task(this_rq());
else
per_cpu(rt_task_arrival_time, cpu) = 0;
}
static void long_running_rt_task_notifier(void *unused, struct rq *rq)
{
struct task_struct *curr = rq->curr;
unsigned int cpu = raw_smp_processor_id();
if (!sysctl_sched_long_running_rt_task_ms)
return;
if (!per_cpu(rt_task_arrival_time, cpu))
return;
if (per_cpu(rt_task_arrival_time, cpu) && curr->policy != SCHED_FIFO) {
/*
* It is possible that the scheduling policy for the current
* task might get changed after task arrival time stamp is
* noted during sched_switch of RT task. To avoid such false
* positives, reset arrival time stamp.
*/
per_cpu(rt_task_arrival_time, cpu) = 0;
return;
}
/*
* Since we are called from the main tick, rq clock task must have
* been updated very recently. Use it directly, instead of
* update_rq_clock_task() to avoid warnings.
*/
if (rq->clock_task -
per_cpu(rt_task_arrival_time, cpu)
> sysctl_sched_long_running_rt_task_ms * MSEC_TO_NSEC) {
printk_deferred("RT task %s (%d) runtime > %u now=%llu task arrival time=%llu runtime=%llu\n",
curr->comm, curr->pid,
sysctl_sched_long_running_rt_task_ms * MSEC_TO_NSEC,
rq->clock_task,
per_cpu(rt_task_arrival_time, cpu),
rq->clock_task -
per_cpu(rt_task_arrival_time, cpu));
BUG();
}
}
int sched_long_running_rt_task_ms_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
{
int ret;
static DEFINE_MUTEX(mutex);
mutex_lock(&mutex);
ret = proc_douintvec_minmax(table, write, buffer, lenp, ppos);
if (sysctl_sched_long_running_rt_task_ms > 0 &&
sysctl_sched_long_running_rt_task_ms < 800)
sysctl_sched_long_running_rt_task_ms = 800;
if (write && !long_running_rt_task_trace_rgstrd) {
register_trace_sched_switch(rt_task_arrival_marker, NULL);
register_trace_android_vh_scheduler_tick(long_running_rt_task_notifier, NULL);
long_running_rt_task_trace_rgstrd = true;
}
mutex_unlock(&mutex);
return ret;
}
static void walt_rt_energy_aware_wake_cpu(struct task_struct *task, struct cpumask *lowest_mask,
int ret, int *best_cpu)
{
int cpu;
unsigned long util, best_cpu_util = ULONG_MAX;
unsigned long best_cpu_util_cum = ULONG_MAX;
unsigned long util_cum;
unsigned long tutil = task_util(task);
unsigned int best_idle_exit_latency = UINT_MAX;
unsigned int cpu_idle_exit_latency = UINT_MAX;
bool boost_on_big = rt_boost_on_big();
int cluster;
int order_index = (boost_on_big && num_sched_clusters > 1) ? 1 : 0;
bool best_cpu_lt = true;
if (unlikely(walt_disabled))
return;
if (!ret)
return; /* No targets found */
rcu_read_lock();
for (cluster = 0; cluster < num_sched_clusters; cluster++) {
for_each_cpu_and(cpu, lowest_mask, &cpu_array[order_index][cluster]) {
bool lt;
trace_sched_cpu_util(cpu, lowest_mask);
if (!cpu_active(cpu))
continue;
if (cpu_halted(cpu))
continue;
if (sched_cpu_high_irqload(cpu))
continue;
if (__cpu_overutilized(cpu, tutil))
continue;
util = cpu_util(cpu);
lt = (walt_low_latency_task(cpu_rq(cpu)->curr) ||
walt_nr_rtg_high_prio(cpu));
/*
* When the best is suitable and the current is not,
* skip it
*/
if (lt && !best_cpu_lt)
continue;
/*
* Either both are sutilable or unsuitable, load takes
* precedence.
*/
if (!(best_cpu_lt ^ lt) && (util > best_cpu_util))
continue;
/*
* If the previous CPU has same load, keep it as
* best_cpu.
*/
if (best_cpu_util == util && *best_cpu == task_cpu(task))
continue;
/*
* If candidate CPU is the previous CPU, select it.
* Otherwise, if its load is same with best_cpu and in
* a shallower C-state, select it. If all above
* conditions are same, select the least cumulative
* window demand CPU.
*/
cpu_idle_exit_latency = walt_get_idle_exit_latency(cpu_rq(cpu));
util_cum = cpu_util_cum(cpu);
if (cpu != task_cpu(task) && best_cpu_util == util) {
if (best_idle_exit_latency < cpu_idle_exit_latency)
continue;
if (best_idle_exit_latency == cpu_idle_exit_latency &&
best_cpu_util_cum < util_cum)
continue;
}
best_idle_exit_latency = cpu_idle_exit_latency;
best_cpu_util_cum = util_cum;
best_cpu_util = util;
*best_cpu = cpu;
best_cpu_lt = lt;
}
if (*best_cpu != -1)
break;
}
rcu_read_unlock();
}
#ifdef CONFIG_UCLAMP_TASK
static inline bool walt_rt_task_fits_capacity(struct task_struct *p, int cpu)
{
unsigned int min_cap;
unsigned int max_cap;
unsigned int cpu_cap;
min_cap = uclamp_eff_value(p, UCLAMP_MIN);
max_cap = uclamp_eff_value(p, UCLAMP_MAX);
cpu_cap = capacity_orig_of(cpu);
return cpu_cap >= min(min_cap, max_cap);
}
#else
static inline bool walt_rt_task_fits_capacity(struct task_struct *p, int cpu)
{
return true;
}
#endif
/*
* walt specific should_honor_rt_sync (see rt.c). this will honor
* the sync flag regardless of whether the current waker is cfs or rt
*/
static inline bool walt_should_honor_rt_sync(struct rq *rq, struct task_struct *p,
bool sync)
{
return sync &&
p->prio <= rq->rt.highest_prio.next &&
rq->rt.rt_nr_running <= 2;
}
static void walt_select_task_rq_rt(void *unused, struct task_struct *task, int cpu,
int sd_flag, int wake_flags, int *new_cpu)
{
struct task_struct *curr;
struct rq *rq, *this_cpu_rq;
bool may_not_preempt;
bool sync = !!(wake_flags & WF_SYNC);
int ret, target = -1, this_cpu;
struct cpumask *lowest_mask;
int packing_cpu;
if (unlikely(walt_disabled))
return;
/* For anything but wake ups, just return the task_cpu */
if (sd_flag != SD_BALANCE_WAKE && sd_flag != SD_BALANCE_FORK)
return;
this_cpu = raw_smp_processor_id();
this_cpu_rq = cpu_rq(this_cpu);
/*
* Respect the sync flag as long as the task can run on this CPU.
*/
if (sysctl_sched_sync_hint_enable && cpu_active(this_cpu) && !cpu_halted(this_cpu) &&
cpumask_test_cpu(this_cpu, task->cpus_ptr) &&
walt_should_honor_rt_sync(this_cpu_rq, task, sync)) {
*new_cpu = this_cpu;
return;
}
*new_cpu = cpu; /* previous CPU as back up */
rq = cpu_rq(cpu);
rcu_read_lock();
curr = READ_ONCE(rq->curr); /* unlocked access */
/*
* If the current task on @p's runqueue is a softirq task,
* it may run without preemption for a time that is
* ill-suited for a waiting RT task. Therefore, try to
* wake this RT task on another runqueue.
*
* Otherwise, just let it ride on the affined RQ and the
* post-schedule router will push the preempted task away
*
* This test is optimistic, if we get it wrong the load-balancer
* will have to sort it out.
*
* We take into account the capacity of the CPU to ensure it fits the
* requirement of the task - which is only important on heterogeneous
* systems like big.LITTLE.
*/
may_not_preempt = task_may_not_preempt(curr, cpu);
lowest_mask = this_cpu_cpumask_var_ptr(walt_local_cpu_mask);
/*
* If we're on asym system ensure we consider the different capacities
* of the CPUs when searching for the lowest_mask.
*/
ret = cpupri_find_fitness(&task_rq(task)->rd->cpupri, task,
lowest_mask, walt_rt_task_fits_capacity);
/* create a fastpath for finding a packing cpu */
packing_cpu = walt_find_cluster_packing_cpu(task_cpu(task));
if (walt_choose_packing_cpu(packing_cpu, task)) {
*new_cpu = packing_cpu;
goto unlock;
}
walt_rt_energy_aware_wake_cpu(task, lowest_mask, ret, &target);
/*
* If cpu is non-preemptible, prefer remote cpu
* even if it's running a higher-prio task.
* Otherwise: Don't bother moving it if the destination CPU is
* not running a lower priority task.
*/
if (target != -1 &&
(may_not_preempt || task->prio < cpu_rq(target)->rt.highest_prio.curr))
*new_cpu = target;
/* if backup or chosen cpu is halted, pick something else */
if (cpu_halted(*new_cpu)) {
cpumask_t non_halted;
/* choose the lowest-order, unhalted, allowed CPU */
cpumask_andnot(&non_halted, task->cpus_ptr, cpu_halt_mask);
target = cpumask_first(&non_halted);
if (target < nr_cpu_ids)
*new_cpu = target;
}
unlock:
rcu_read_unlock();
}
static void walt_rt_find_lowest_rq(void *unused, struct task_struct *task,
struct cpumask *lowest_mask, int ret, int *best_cpu)
{
int packing_cpu;
if (unlikely(walt_disabled))
return;
/* create a fastpath for finding a packing cpu */
packing_cpu = walt_find_cluster_packing_cpu(task_cpu(task));
if (walt_choose_packing_cpu(packing_cpu, task)) {
*best_cpu = packing_cpu;
return;
}
walt_rt_energy_aware_wake_cpu(task, lowest_mask, ret, best_cpu);
/*
* Walt was not able to find a non-halted best cpu. Ensure that
* find_lowest_rq doesn't use a halted cpu going forward, but
* does a best effort itself to find a good CPU.
*/
if (*best_cpu == -1)
cpumask_andnot(lowest_mask, lowest_mask, cpu_halt_mask);
}
void walt_rt_init(void)
{
unsigned int i;
for_each_possible_cpu(i) {
if(!(zalloc_cpumask_var_node(&per_cpu(walt_local_cpu_mask, i),
GFP_KERNEL, cpu_to_node(i)))) {
pr_err("walt_local_cpu_mask alloc failed for cpu%d\n", i);
return;
}
}
register_trace_android_rvh_select_task_rq_rt(walt_select_task_rq_rt, NULL);
register_trace_android_rvh_find_lowest_rq(walt_rt_find_lowest_rq, NULL);
}

152
kernel/sched/walt/walt_tp.c Normal file
View File

@ -0,0 +1,152 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021, The Linux Foundation. All rights reserved.
*/
#include <linux/cpu.h>
#include <linux/tracepoint.h>
#include <trace/hooks/sched.h>
#include "trace.h"
#define CREATE_TRACE_POINTS
#include "perf_trace_counters.h"
unsigned int sysctl_sched_dynamic_tp_enable;
#define USE_CPUHP_STATE CPUHP_AP_ONLINE_DYN
DEFINE_PER_CPU(u32, cntenset_val);
DEFINE_PER_CPU(unsigned long, previous_ccnt);
DEFINE_PER_CPU(unsigned long[NUM_L1_CTRS], previous_l1_cnts);
DEFINE_PER_CPU(unsigned long[NUM_AMU_CTRS], previous_amu_cnts);
DEFINE_PER_CPU(u32, old_pid);
DEFINE_PER_CPU(u32, hotplug_flag);
DEFINE_PER_CPU(u64, prev_time);
static int tracectr_cpu_hotplug_coming_up(unsigned int cpu)
{
per_cpu(hotplug_flag, cpu) = 1;
return 0;
}
static void setup_prev_cnts(u32 cpu, u32 cnten_val)
{
int i;
if (cnten_val & CC)
per_cpu(previous_ccnt, cpu) =
read_sysreg(pmccntr_el0);
for (i = 0; i < NUM_L1_CTRS; i++) {
if (cnten_val & (1 << i)) {
/* Select */
write_sysreg(i, pmselr_el0);
isb();
/* Read value */
per_cpu(previous_l1_cnts[i], cpu) =
read_sysreg(pmxevcntr_el0);
}
}
}
void tracectr_notifier(void *ignore, bool preempt,
struct task_struct *prev, struct task_struct *next)
{
u32 cnten_val;
int current_pid;
u32 cpu = task_cpu(next);
u64 now;
if (!trace_sched_switch_with_ctrs_enabled())
return;
current_pid = next->pid;
if (per_cpu(old_pid, cpu) != -1) {
cnten_val = read_sysreg(pmcntenset_el0);
per_cpu(cntenset_val, cpu) = cnten_val;
/* Disable all the counters that were enabled */
write_sysreg(cnten_val, pmcntenclr_el0);
if (per_cpu(hotplug_flag, cpu) == 1) {
per_cpu(hotplug_flag, cpu) = 0;
setup_prev_cnts(cpu, cnten_val);
} else {
trace_sched_switch_with_ctrs(per_cpu(old_pid, cpu),
current_pid);
now = sched_clock();
if ((now - per_cpu(prev_time, cpu)) > NSEC_PER_SEC) {
trace_sched_switch_ctrs_cfg(cpu);
per_cpu(prev_time, cpu) = now;
}
}
/* Enable all the counters that were disabled */
write_sysreg(cnten_val, pmcntenset_el0);
}
per_cpu(old_pid, cpu) = current_pid;
}
static void register_sched_switch_ctrs(void)
{
int cpu, rc;
for_each_possible_cpu(cpu)
per_cpu(old_pid, cpu) = -1;
rc = cpuhp_setup_state_nocalls(USE_CPUHP_STATE, "tracectr_cpu_hotplug",
tracectr_cpu_hotplug_coming_up, NULL);
if (rc >= 0)
register_trace_sched_switch(tracectr_notifier, NULL);
}
static void unregister_sched_switch_ctrs(void)
{
unregister_trace_sched_switch(tracectr_notifier, NULL);
cpuhp_remove_state_nocalls(USE_CPUHP_STATE);
}
static void sched_overutilized(void *data, struct root_domain *rd,
bool overutilized)
{
if (trace_sched_overutilized_enabled()) {
char span[SPAN_SIZE];
cpumap_print_to_pagebuf(false, span, sched_trace_rd_span(rd));
trace_sched_overutilized(overutilized, span);
}
}
static void walt_register_dynamic_tp_events(void)
{
register_trace_sched_overutilized_tp(sched_overutilized, NULL);
register_sched_switch_ctrs();
}
static void walt_unregister_dynamic_tp_events(void)
{
unregister_trace_sched_overutilized_tp(sched_overutilized, NULL);
unregister_sched_switch_ctrs();
}
int sched_dynamic_tp_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
static DEFINE_MUTEX(mutex);
int ret = 0, *val = (unsigned int *)table->data;
unsigned int old_val;
mutex_lock(&mutex);
old_val = sysctl_sched_dynamic_tp_enable;
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
if (ret || !write || (old_val == sysctl_sched_dynamic_tp_enable))
goto done;
if (*val)
walt_register_dynamic_tp_events();
else
walt_unregister_dynamic_tp_events();
done:
mutex_unlock(&mutex);
return ret;
}