cpuidle: lpm-levels: Track and predict next rescheduling IPI

Add changes to track and predict next rescheduling IPI
based on past history. Add a module param to control enabling
it.

Change-Id: Ie495d8906288ee410708693ee15ed51643aefb44
Signed-off-by: Maulik Shah <mkshah@codeaurora.org>
This commit is contained in:
Maulik Shah 2019-02-20 17:11:20 +05:30 committed by Raghavendra Kakarla
parent 3db6f4c3b1
commit 541abe7e4a
6 changed files with 117 additions and 51 deletions

View File

@ -52,6 +52,8 @@
#include <asm/mach/arch.h>
#include <asm/mpu.h>
#include <soc/qcom/lpm_levels.h>
#define CREATE_TRACE_POINTS
#include <trace/events/ipi.h>
@ -721,6 +723,7 @@ void handle_IPI(int ipinr, struct pt_regs *regs)
void smp_send_reschedule(int cpu)
{
update_ipi_history(cpu);
smp_cross_call_common(cpumask_of(cpu), IPI_RESCHEDULE);
}

View File

@ -61,6 +61,7 @@
#include <soc/qcom/minidump.h>
#include <soc/qcom/scm.h>
#include <soc/qcom/lpm_levels.h>
#define CREATE_TRACE_POINTS
#include <trace/events/ipi.h>
@ -932,6 +933,7 @@ void handle_IPI(int ipinr, struct pt_regs *regs)
void smp_send_reschedule(int cpu)
{
BUG_ON(cpu_is_offline(cpu));
update_ipi_history(cpu);
smp_cross_call_common(cpumask_of(cpu), IPI_RESCHEDULE);
}

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved.
/* Copyright (c) 2014-2020, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -589,8 +589,11 @@ static int parse_cpu_levels(struct device_node *node, struct lpm_cluster *c)
if (ret)
goto failed;
key = "qcom,disable-prediction";
cpu->lpm_prediction = !(of_property_read_bool(node, key));
cpu->ipi_prediction = !(of_property_read_bool(node,
"qcom,disable-ipi-prediction"));
cpu->lpm_prediction = !(of_property_read_bool(node,
"qcom,disable-prediction"));
if (cpu->lpm_prediction) {
key = "qcom,ref-stddev";

View File

@ -96,6 +96,8 @@ module_param_named(lpm_prediction, lpm_prediction, bool, 0664);
static uint32_t bias_hyst;
module_param_named(bias_hyst, bias_hyst, uint, 0664);
static bool lpm_ipi_prediction;
module_param_named(lpm_ipi_prediction, lpm_ipi_prediction, bool, 0664);
struct lpm_history {
uint32_t resi[MAXSAMPLES];
@ -107,8 +109,14 @@ struct lpm_history {
int64_t stime;
};
static DEFINE_PER_CPU(struct lpm_history, hist);
struct ipi_history {
uint32_t interval[MAXSAMPLES];
uint32_t current_ptr;
ktime_t cpu_idle_resched_ts;
};
static DEFINE_PER_CPU(struct lpm_history, hist);
static DEFINE_PER_CPU(struct ipi_history, cpu_ipi_history);
static DEFINE_PER_CPU(struct lpm_cpu*, cpu_lpm);
static bool suspend_in_progress;
static struct hrtimer lpm_hrtimer;
@ -468,14 +476,66 @@ static void biastimer_start(uint32_t time_ns)
hrtimer_start(cpu_biastimer, bias_ktime, HRTIMER_MODE_REL_PINNED);
}
static uint64_t lpm_cpuidle_predict(struct cpuidle_device *dev,
struct lpm_cpu *cpu, int *idx_restrict,
uint32_t *idx_restrict_time)
static uint64_t find_deviation(int *interval, uint32_t ref_stddev,
int64_t *stime)
{
int i, j, divisor;
int divisor, i;
uint64_t max, avg, stddev;
int64_t thresh = LLONG_MAX;
if (lpm_ipi_prediction)
ref_stddev += DEFAULT_IPI_STDDEV;
do {
max = avg = divisor = stddev = 0;
for (i = 0; i < MAXSAMPLES; i++) {
int64_t value = interval[i];
if (value <= thresh) {
avg += value;
divisor++;
if (value > max)
max = value;
}
}
do_div(avg, divisor);
for (i = 0; i < MAXSAMPLES; i++) {
int64_t value = interval[i];
if (value <= thresh) {
int64_t diff = value - avg;
stddev += diff * diff;
}
}
do_div(stddev, divisor);
stddev = int_sqrt(stddev);
/*
* If the deviation is less, return the average, else
* ignore one maximum sample and retry
*/
if (((avg > stddev * 6) && (divisor >= (MAXSAMPLES - 1)))
|| stddev <= ref_stddev) {
*stime = ktime_to_us(ktime_get()) + avg;
return avg;
}
thresh = max - 1;
} while (divisor > (MAXSAMPLES - 1));
return 0;
}
static uint64_t lpm_cpuidle_predict(struct cpuidle_device *dev,
struct lpm_cpu *cpu, int *idx_restrict,
uint32_t *idx_restrict_time, uint32_t *ipi_predicted)
{
int i, j;
uint64_t avg;
struct lpm_history *history = &per_cpu(hist, dev->cpu);
struct ipi_history *ipi_history = &per_cpu(cpu_ipi_history, dev->cpu);
if (!lpm_prediction || !cpu->lpm_prediction)
return 0;
@ -506,44 +566,9 @@ static uint64_t lpm_cpuidle_predict(struct cpuidle_device *dev,
* that mode.
*/
again:
max = avg = divisor = stddev = 0;
for (i = 0; i < MAXSAMPLES; i++) {
int64_t value = history->resi[i];
if (value <= thresh) {
avg += value;
divisor++;
if (value > max)
max = value;
}
}
do_div(avg, divisor);
for (i = 0; i < MAXSAMPLES; i++) {
int64_t value = history->resi[i];
if (value <= thresh) {
int64_t diff = value - avg;
stddev += diff * diff;
}
}
do_div(stddev, divisor);
stddev = int_sqrt(stddev);
/*
* If the deviation is less, return the average, else
* ignore one maximum sample and retry
*/
if (((avg > stddev * 6) && (divisor >= (MAXSAMPLES - 1)))
|| stddev <= cpu->ref_stddev) {
history->stime = ktime_to_us(ktime_get()) + avg;
avg = find_deviation(history->resi, cpu->ref_stddev, &(history->stime));
if (avg)
return avg;
} else if (divisor > (MAXSAMPLES - 1)) {
thresh = max - 1;
goto again;
}
/*
* Find the number of premature exits for each of the mode,
@ -586,6 +611,17 @@ again:
}
}
}
if (*idx_restrict_time || !cpu->ipi_prediction || !lpm_ipi_prediction)
return 0;
avg = find_deviation(ipi_history->interval, cpu->ref_stddev,
&(history->stime));
if (avg) {
*ipi_predicted = 1;
return avg;
}
return 0;
}
@ -659,7 +695,7 @@ static int cpu_power_select(struct cpuidle_device *dev,
int i, idx_restrict;
uint32_t lvl_latency_us = 0;
uint64_t predicted = 0;
uint32_t htime = 0, idx_restrict_time = 0;
uint32_t htime = 0, idx_restrict_time = 0, ipi_predicted = 0;
uint32_t next_wakeup_us = (uint32_t)sleep_us;
uint32_t min_residency, max_residency;
struct power_params *pwr_params;
@ -710,7 +746,8 @@ static int cpu_power_select(struct cpuidle_device *dev,
*/
if (next_wakeup_us > max_residency) {
predicted = lpm_cpuidle_predict(dev, cpu,
&idx_restrict, &idx_restrict_time);
&idx_restrict, &idx_restrict_time,
&ipi_predicted);
if (predicted && (predicted < min_residency))
predicted = min_residency;
} else
@ -747,7 +784,9 @@ static int cpu_power_select(struct cpuidle_device *dev,
if ((predicted || (idx_restrict != (cpu->nlevels + 1)))
&& (best_level < (cpu->nlevels-1))) {
htime = predicted + cpu->tmr_add;
if (htime == cpu->tmr_add)
if (lpm_ipi_prediction && cpu->ipi_prediction)
htime += DEFAULT_IPI_TIMER_ADD;
if (!predicted)
htime = idx_restrict_time;
else if (htime > max_residency)
htime = max_residency;
@ -760,8 +799,8 @@ static int cpu_power_select(struct cpuidle_device *dev,
done_select:
trace_cpu_power_select(best_level, sleep_us, latency_us, next_event_us);
trace_cpu_pred_select(idx_restrict_time ? 2 : (predicted ? 1 : 0),
predicted, htime);
trace_cpu_pred_select(idx_restrict_time ? 2 : (ipi_predicted ?
3 : (predicted ? 1 : 0)), predicted, htime);
return best_level;
}
@ -1398,6 +1437,20 @@ static int lpm_cpuidle_select(struct cpuidle_driver *drv,
return cpu_power_select(dev, cpu);
}
void update_ipi_history(int cpu)
{
struct ipi_history *history = &per_cpu(cpu_ipi_history, cpu);
ktime_t now = ktime_get();
history->interval[history->current_ptr] =
ktime_to_us(ktime_sub(now,
history->cpu_idle_resched_ts));
(history->current_ptr)++;
if (history->current_ptr >= MAXSAMPLES)
history->current_ptr = 0;
history->cpu_idle_resched_ts = now;
}
static void update_history(struct cpuidle_device *dev, int idx)
{
struct lpm_history *history = &per_cpu(hist, dev->cpu);

View File

@ -17,7 +17,9 @@
#define CLUST_SMPL_INVLD_TIME 40000
#define DEFAULT_PREMATURE_CNT 3
#define DEFAULT_STDDEV 100
#define DEFAULT_IPI_STDDEV 400
#define DEFAULT_TIMER_ADD 100
#define DEFAULT_IPI_TIMER_ADD 900
#define TIMER_ADD_LOW 100
#define TIMER_ADD_HIGH 1500
#define STDDEV_LOW 100
@ -52,6 +54,7 @@ struct lpm_cpu {
uint32_t ref_premature_cnt;
uint32_t tmr_add;
bool lpm_prediction;
bool ipi_prediction;
uint64_t bias;
struct cpuidle_driver *drv;
struct lpm_cluster *parent;

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2018, 2020 The Linux Foundation. All rights reserved.
/* Copyright (c) 2018-2020 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -22,9 +22,11 @@ struct system_pm_ops {
#if defined(CONFIG_MSM_PM) || defined(CONFIG_MSM_PM_LEGACY)
uint32_t register_system_pm_ops(struct system_pm_ops *pm_ops);
void update_ipi_history(int cpu);
#else
static inline uint32_t register_system_pm_ops(struct system_pm_ops *pm_ops)
{ return -ENODEV; }
static inline void update_ipi_history(int cpu) {}
#endif
#endif