1070 lines
26 KiB
C
Executable file
1070 lines
26 KiB
C
Executable file
/*
|
|
* Multi-purpose Load tracker
|
|
*
|
|
* Copyright (C) 2018 Samsung Electronics Co., Ltd
|
|
* Park Bumgyu <bumgyu.park@samsung.com>
|
|
*/
|
|
|
|
#include "../sched.h"
|
|
#include "../sched-pelt.h"
|
|
#include "ems.h"
|
|
|
|
#include <trace/events/ems.h>
|
|
#include <trace/events/ems_debug.h>
|
|
|
|
/******************************************************************************
|
|
* MULTI LOAD for TASK *
|
|
******************************************************************************/
|
|
/*
|
|
* ml_task_util - task util
|
|
*
|
|
* Task utilization. The calculation is the same as the task util of cfs.
|
|
*/
|
|
unsigned long ml_task_util(struct task_struct *p)
|
|
{
|
|
return READ_ONCE(p->se.avg.util_avg);
|
|
}
|
|
|
|
int ml_task_hungry(struct task_struct *p)
|
|
{
|
|
return 0; /* HACK */
|
|
}
|
|
|
|
/*
|
|
* ml_task_util_est - task util with util-est
|
|
*
|
|
* Task utilization with util-est, The calculation is the same as
|
|
* task_util_est of cfs.
|
|
*/
|
|
static unsigned long _ml_task_util_est(struct task_struct *p)
|
|
{
|
|
struct util_est ue = READ_ONCE(p->se.avg.util_est);
|
|
|
|
return max(ue.ewma, (ue.enqueued & ~UTIL_AVG_UNCHANGED));
|
|
}
|
|
|
|
unsigned long ml_task_util_est(struct task_struct *p)
|
|
{
|
|
return max(ml_task_util(p), _ml_task_util_est(p));
|
|
}
|
|
|
|
/*
|
|
* ml_task_load_avg - task load_avg
|
|
*/
|
|
unsigned long ml_task_load_avg(struct task_struct *p)
|
|
{
|
|
return READ_ONCE(p->se.avg.load_avg);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* MULTI LOAD for CPU *
|
|
******************************************************************************/
|
|
/*
|
|
* ml_cpu_util - cpu utilization
|
|
*/
|
|
unsigned long ml_cpu_util(int cpu)
|
|
{
|
|
struct cfs_rq *cfs_rq;
|
|
unsigned int util;
|
|
|
|
cfs_rq = &cpu_rq(cpu)->cfs;
|
|
util = READ_ONCE(cfs_rq->avg.util_avg);
|
|
|
|
util = max(util, READ_ONCE(cfs_rq->avg.util_est.enqueued));
|
|
|
|
return min_t(unsigned long, util, capacity_cpu_orig(cpu));
|
|
}
|
|
|
|
/*
|
|
* ml_cpu_util_est - return cpu util_est
|
|
*/
|
|
unsigned long ml_cpu_util_est(int cpu)
|
|
{
|
|
struct cfs_rq *cfs_rq;
|
|
unsigned int util_est;
|
|
|
|
cfs_rq = &cpu_rq(cpu)->cfs;
|
|
util_est = READ_ONCE(cfs_rq->avg.util_est.enqueued);
|
|
|
|
return min_t(unsigned long, util_est, capacity_cpu_orig(cpu));
|
|
}
|
|
|
|
/*
|
|
* Remove and clamp on negative, from a local variable.
|
|
*
|
|
* A variant of sub_positive(), which does not use explicit load-store
|
|
* and is thus optimized for local variable updates.
|
|
*/
|
|
#define lsub_positive(_ptr, _val) do { \
|
|
typeof(_ptr) ptr = (_ptr); \
|
|
*ptr -= min_t(typeof(*ptr), *ptr, _val); \
|
|
} while (0)
|
|
|
|
/*
|
|
* ml_cpu_util_without - cpu utilization without waking task
|
|
*/
|
|
unsigned long ml_cpu_util_without(int cpu, struct task_struct *p)
|
|
{
|
|
struct cfs_rq *cfs_rq;
|
|
unsigned long util, util_est;
|
|
|
|
/* Task has no contribution or is new */
|
|
if (cpu != task_cpu(p) || !READ_ONCE(p->se.avg.last_update_time))
|
|
return ml_cpu_util(cpu);
|
|
|
|
cfs_rq = &cpu_rq(cpu)->cfs;
|
|
|
|
/* Calculate util avg */
|
|
util = READ_ONCE(cfs_rq->avg.util_avg);
|
|
lsub_positive(&util, ml_task_util(p));
|
|
|
|
/* Calcuate util est */
|
|
util_est = READ_ONCE(cfs_rq->avg.util_est.enqueued);
|
|
if (task_on_rq_queued(p) || current == p)
|
|
lsub_positive(&util_est, _ml_task_util_est(p));
|
|
|
|
util = max(util, util_est);
|
|
|
|
return util;
|
|
}
|
|
|
|
/*
|
|
* ml_cpu_util_with - cpu utilization with waking task
|
|
*
|
|
* When task is queued,
|
|
* 1) task_cpu(p) == cpu
|
|
* => The contribution is already present on target cpu
|
|
* 2) task_cpu(p) != cpu
|
|
* => The contribution is not present on target cpu
|
|
*
|
|
* When task is not queued,
|
|
* 3) task_cpu(p) == cpu
|
|
* => The contribution is already applied to util_avg,
|
|
* but is not applied to util_est
|
|
* 4) task_cpu(p) != cpu
|
|
* => The contribution is not present on target cpu
|
|
*/
|
|
unsigned long ml_cpu_util_with(struct task_struct *p, int cpu)
|
|
{
|
|
struct cfs_rq *cfs_rq = &cpu_rq(cpu)->cfs;
|
|
unsigned long util, util_est;
|
|
|
|
/* Calculate util avg */
|
|
util = READ_ONCE(cfs_rq->avg.util_avg);
|
|
if (task_cpu(p) != cpu)
|
|
util += ml_task_util(p);
|
|
|
|
/* Calcuate util est */
|
|
util_est = READ_ONCE(cfs_rq->avg.util_est.enqueued);
|
|
if (!task_on_rq_queued(p) || task_cpu(p) != cpu)
|
|
util_est += _ml_task_util_est(p);
|
|
|
|
util = max(util, util_est);
|
|
util = max(util, (unsigned long)1);
|
|
|
|
return util;
|
|
}
|
|
|
|
/*
|
|
* ml_cpu_load_avg - cpu load_avg
|
|
*/
|
|
unsigned long ml_cpu_load_avg(int cpu)
|
|
{
|
|
return cpu_rq(cpu)->cfs.avg.load_avg;
|
|
}
|
|
|
|
/*
|
|
* ml_cpu_util_est_with - cpu util_est with waking task
|
|
*/
|
|
unsigned long ml_cpu_util_est_with(struct task_struct *p, int cpu)
|
|
{
|
|
struct cfs_rq *cfs_rq = &cpu_rq(cpu)->cfs;
|
|
unsigned long util_est;
|
|
|
|
/* Calculate util est */
|
|
util_est = READ_ONCE(cfs_rq->avg.util_est.enqueued);
|
|
if (!task_on_rq_queued(p) || task_cpu(p) != cpu)
|
|
util_est += max(_ml_task_util_est(p), (unsigned long)1);
|
|
|
|
return util_est;
|
|
}
|
|
|
|
/*
|
|
* ml_cpu_util_est_without - cpu util_est without waking task
|
|
*/
|
|
unsigned long ml_cpu_util_est_without(int cpu, struct task_struct *p)
|
|
{
|
|
struct cfs_rq *cfs_rq;
|
|
unsigned long util_est;
|
|
|
|
/* Task has no contribution or is new */
|
|
if (cpu != task_cpu(p) || !READ_ONCE(p->se.avg.last_update_time))
|
|
return ml_cpu_util_est(cpu);
|
|
|
|
cfs_rq = &cpu_rq(cpu)->cfs;
|
|
|
|
/* Calculate util est */
|
|
util_est = READ_ONCE(cfs_rq->avg.util_est.enqueued);
|
|
if (task_on_rq_queued(p) || current == p)
|
|
lsub_positive(&util_est, _ml_task_util_est(p));
|
|
|
|
return util_est;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* New task utilization init *
|
|
******************************************************************************/
|
|
static int ntu_ratio[CGROUP_COUNT];
|
|
|
|
void ntu_apply(struct sched_entity *se)
|
|
{
|
|
struct sched_avg *sa = &se->avg;
|
|
int grp_idx = cpuctl_task_group_idx(task_of(se));
|
|
int ratio = ntu_ratio[grp_idx];
|
|
|
|
sa->util_avg = sa->util_avg * ratio / 100;
|
|
sa->runnable_avg = sa->runnable_avg * ratio / 100;
|
|
}
|
|
|
|
static int
|
|
ntu_emstune_notifier_call(struct notifier_block *nb,
|
|
unsigned long val, void *v)
|
|
{
|
|
struct emstune_set *cur_set = (struct emstune_set *)v;
|
|
int i;
|
|
|
|
for (i = 0; i < CGROUP_COUNT; i++)
|
|
ntu_ratio[i] = cur_set->ntu.ratio[i];
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block ntu_emstune_notifier = {
|
|
.notifier_call = ntu_emstune_notifier_call,
|
|
};
|
|
|
|
void ntu_init(struct kobject *ems_kobj)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CGROUP_COUNT; i++)
|
|
ntu_ratio[i] = 100;
|
|
|
|
emstune_register_notifier(&ntu_emstune_notifier);
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Multi load tracking *
|
|
******************************************************************************/
|
|
struct mlt __percpu *pcpu_mlt; /* active ratio tracking */
|
|
static struct mlt __percpu *pcpu_mlt_cs; /* c-state tracking */
|
|
|
|
static inline int mlt_move_period(int period, int count)
|
|
{
|
|
return (period + count + MLT_PERIOD_COUNT) % MLT_PERIOD_COUNT;
|
|
}
|
|
|
|
enum track_type {
|
|
TRACK_TIME,
|
|
TRACK_IPC,
|
|
TRACK_MSPC,
|
|
};
|
|
|
|
static void mlt_update_periods(enum track_type type, struct mlt *mlt,
|
|
int cur_period, int count, int value)
|
|
{
|
|
int **periods;
|
|
int i;
|
|
|
|
switch (type) {
|
|
case TRACK_TIME:
|
|
periods = mlt->periods;
|
|
break;
|
|
case TRACK_IPC:
|
|
periods = mlt->uarch->ipc_periods;
|
|
break;
|
|
case TRACK_MSPC:
|
|
periods = mlt->uarch->mspc_periods;
|
|
break;
|
|
}
|
|
|
|
while (count--) {
|
|
cur_period = mlt_move_period(cur_period, 1);
|
|
for (i = 0; i < mlt->state_count; i++) {
|
|
if (i == mlt->state)
|
|
periods[i][cur_period] = value;
|
|
else
|
|
periods[i][cur_period] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mlt_recent_elapsed(enum track_type type,
|
|
struct mlt *mlt, int cur_period)
|
|
{
|
|
int i;
|
|
int **periods;
|
|
int *recent;
|
|
u64 *recent_sum;
|
|
|
|
switch (type) {
|
|
case TRACK_TIME:
|
|
periods = mlt->periods;
|
|
recent = mlt->recent;
|
|
recent_sum = mlt->recent_sum;
|
|
break;
|
|
case TRACK_IPC:
|
|
periods = mlt->uarch->ipc_periods;
|
|
recent = mlt->uarch->ipc_recent;
|
|
recent_sum = mlt->uarch->inst_ret_sum;
|
|
break;
|
|
case TRACK_MSPC:
|
|
periods = mlt->uarch->mspc_periods;
|
|
recent = mlt->uarch->mspc_recent;
|
|
recent_sum = mlt->uarch->mem_stall_sum;
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < mlt->state_count; i++)
|
|
periods[i][cur_period] = recent[i];
|
|
|
|
memset(recent_sum, 0, sizeof(u64) * mlt->state_count);
|
|
memset(recent, 0, sizeof(int) * mlt->state_count);
|
|
}
|
|
|
|
static inline u64 mlt_contrib(u64 value, u64 contrib, u64 total)
|
|
{
|
|
if (unlikely(!total))
|
|
return 0;
|
|
|
|
/* multiply by 1024 to minimize dropping decimal place */
|
|
return div64_u64(value * contrib << SCHED_CAPACITY_SHIFT, total);
|
|
}
|
|
|
|
static void mlt_update_recent(enum track_type type, struct mlt *mlt,
|
|
u64 contrib, u64 period_size)
|
|
{
|
|
int state = mlt->state;
|
|
int *recent;
|
|
u64 *recent_sum;
|
|
|
|
if (state < 0)
|
|
return;
|
|
|
|
switch (type) {
|
|
case TRACK_TIME:
|
|
recent = mlt->recent;
|
|
recent_sum = mlt->recent_sum;
|
|
break;
|
|
case TRACK_IPC:
|
|
recent = mlt->uarch->ipc_recent;
|
|
recent_sum = mlt->uarch->inst_ret_sum;
|
|
break;
|
|
case TRACK_MSPC:
|
|
recent = mlt->uarch->mspc_recent;
|
|
recent_sum = mlt->uarch->mem_stall_sum;
|
|
break;
|
|
}
|
|
|
|
recent_sum[state] += contrib;
|
|
recent[state] = div64_u64(
|
|
recent_sum[state] << SCHED_CAPACITY_SHIFT, period_size);
|
|
}
|
|
|
|
static void __mlt_update(enum track_type type, struct mlt *mlt,
|
|
int period, int period_count, u64 contrib,
|
|
u64 remain, u64 period_size, u64 period_value)
|
|
{
|
|
/*
|
|
* [before]
|
|
* last_updated now
|
|
* ↓ ↓
|
|
* |<---1,2--->|<-------3------->|<---4--->|
|
|
* timeline --|----------------|-----------------|-----------------|-->
|
|
* recent period
|
|
*
|
|
* Update recent period first(1) and quit sequence if the recent
|
|
* period has not elapsed, otherwise store recent period in the period
|
|
* history(2). If more than 1 period has elapsed, fill in the elapsed
|
|
* period with an appropriate value (running = 1024, idle = 0) and
|
|
* store in the period history(3). And store remain time in new recent
|
|
* period(4).
|
|
*
|
|
* [after]
|
|
* last_updated
|
|
* ↓
|
|
* |
|
|
* timeline --|----------------|-----------------|-----------------|-->
|
|
* last period recent period
|
|
*/
|
|
|
|
/* (1) update recent period */
|
|
mlt_update_recent(type, mlt, contrib, period_size);
|
|
|
|
if (period_count) {
|
|
int count = period_count;
|
|
|
|
/* (2) store recent period to period history */
|
|
period = mlt_move_period(period, 1);
|
|
mlt_recent_elapsed(type, mlt, period);
|
|
count--;
|
|
|
|
/* (3) update fully elapsed period */
|
|
count = min(count, MLT_PERIOD_COUNT);
|
|
mlt_update_periods(type, mlt, period, count, period_value);
|
|
|
|
/* (4) store remain time to recent period */
|
|
mlt_update_recent(type, mlt, remain, period_size);
|
|
}
|
|
}
|
|
|
|
static void mlt_update_uarch(struct mlt *mlt, int period,
|
|
int period_count, u64 contrib, u64 remain)
|
|
{
|
|
struct uarch_data *ud = mlt->uarch;
|
|
u64 core_cycle, inst_ret, mem_stall;
|
|
u64 cc_delta, ir_delta, ms_delta, ipc = 0, mspc = 0;
|
|
u64 total_time;
|
|
int state = mlt->state;
|
|
|
|
if (state < 0)
|
|
return;
|
|
|
|
core_cycle = amu_core_cycles();
|
|
inst_ret = amu_inst_ret();
|
|
mem_stall = amu_mem_stall();
|
|
|
|
cc_delta = core_cycle - ud->last_cycle;
|
|
ir_delta = inst_ret - ud->last_inst_ret;
|
|
ms_delta = mem_stall - ud->last_mem_stall;
|
|
|
|
if (likely(cc_delta)) {
|
|
ipc = div64_u64(ir_delta << SCHED_CAPACITY_SHIFT, cc_delta);
|
|
mspc = div64_u64(ms_delta << SCHED_CAPACITY_SHIFT, cc_delta);
|
|
}
|
|
|
|
total_time = contrib + period_count * MLT_PERIOD_SIZE + remain;
|
|
|
|
ud->cycle_sum[state] += mlt_contrib(cc_delta,
|
|
contrib, total_time);
|
|
|
|
__mlt_update(TRACK_IPC, mlt, period, period_count,
|
|
mlt_contrib(ir_delta, contrib, total_time),
|
|
mlt_contrib(ir_delta, remain, total_time),
|
|
ud->cycle_sum[state], ipc);
|
|
|
|
__mlt_update(TRACK_MSPC, mlt, period, period_count,
|
|
mlt_contrib(ms_delta, contrib, total_time),
|
|
mlt_contrib(ms_delta, remain, total_time),
|
|
ud->cycle_sum[state], mspc);
|
|
|
|
if (period_count) {
|
|
memset(ud->cycle_sum, 0, sizeof(u64) * mlt->state_count);
|
|
ud->cycle_sum[state] = mlt_contrib(cc_delta,
|
|
remain, total_time);
|
|
}
|
|
|
|
ud->last_cycle = core_cycle;
|
|
ud->last_inst_ret = inst_ret;
|
|
ud->last_mem_stall = mem_stall;
|
|
}
|
|
|
|
static void mlt_update(struct mlt *mlt, u64 now)
|
|
{
|
|
u64 contrib = 0, remain = 0;
|
|
int period_count = 0;
|
|
|
|
if (likely(now > mlt->last_updated)) {
|
|
contrib = min(now, mlt->period_start + MLT_PERIOD_SIZE);
|
|
contrib -= mlt->last_updated;
|
|
}
|
|
|
|
if (likely(now > mlt->period_start)) {
|
|
period_count = div64_u64_rem(now - mlt->period_start,
|
|
MLT_PERIOD_SIZE, &remain);
|
|
}
|
|
|
|
__mlt_update(TRACK_TIME, mlt, mlt->cur_period, period_count,
|
|
contrib, remain, MLT_PERIOD_SIZE,
|
|
SCHED_CAPACITY_SCALE);
|
|
|
|
if (mlt->uarch)
|
|
mlt_update_uarch(mlt, mlt->cur_period,
|
|
period_count, contrib, remain);
|
|
|
|
mlt->cur_period = mlt_move_period(mlt->cur_period, period_count);
|
|
mlt->period_start += MLT_PERIOD_SIZE * period_count;
|
|
mlt->last_updated = now;
|
|
}
|
|
|
|
#define INACTIVE (-1)
|
|
static inline void mlt_set_state(struct mlt *mlt, int state)
|
|
{
|
|
if (state == MLT_STATE_NOCHANGE)
|
|
return;
|
|
|
|
mlt->state = state;
|
|
}
|
|
|
|
static void mlt_update_cstate(int cpu, int next_cstate, u64 now)
|
|
{
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt_cs, cpu);
|
|
|
|
mlt_update(mlt, now);
|
|
trace_mlt_update_cstate(mlt);
|
|
|
|
mlt_set_state(mlt, next_cstate);
|
|
}
|
|
|
|
static void mlt_update_cpu(int cpu, int next_cgroup, u64 now)
|
|
{
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt, cpu);
|
|
|
|
mlt_update(mlt, now);
|
|
|
|
trace_mlt_update_cpu(mlt);
|
|
if (mlt->uarch) {
|
|
trace_mlt_update_cpu_ipc(mlt);
|
|
trace_mlt_update_cpu_mspc(mlt);
|
|
}
|
|
|
|
mlt_set_state(mlt, next_cgroup);
|
|
}
|
|
|
|
#define task_mlt(p) (struct mlt *)(&TASK_AVD1_6(p))
|
|
|
|
void mlt_update_task(struct task_struct *p, int next_state, u64 now)
|
|
{
|
|
struct mlt *mlt = task_mlt(p);
|
|
int state = next_state ? 0 : INACTIVE;
|
|
|
|
if (unlikely(!mlt->state_count))
|
|
return;
|
|
|
|
mlt_update(mlt, now);
|
|
trace_mlt_update_task(p, mlt);
|
|
|
|
mlt_set_state(mlt, state);
|
|
}
|
|
|
|
void mlt_idle_enter(int cpu, int cstate)
|
|
{
|
|
mlt_update_cstate(cpu, cstate, sched_clock());
|
|
}
|
|
|
|
void mlt_idle_exit(int cpu)
|
|
{
|
|
mlt_update_cstate(cpu, INACTIVE, sched_clock());
|
|
}
|
|
|
|
static void mlt_update_nr_run(struct rq *rq);
|
|
void mlt_tick(struct rq *rq)
|
|
{
|
|
u64 now = sched_clock();
|
|
|
|
mlt_update_nr_run(rq);
|
|
|
|
mlt_update_cpu(cpu_of(rq), MLT_STATE_NOCHANGE, now);
|
|
|
|
if (get_sched_class(rq->curr) != EMS_SCHED_IDLE)
|
|
mlt_update_task(rq->curr, MLT_STATE_NOCHANGE, now);
|
|
}
|
|
|
|
void mlt_task_switch(int cpu, struct task_struct *prev,
|
|
struct task_struct *next)
|
|
{
|
|
u64 now = sched_clock();
|
|
int cgroup;
|
|
|
|
if (get_sched_class(next) == EMS_SCHED_IDLE)
|
|
cgroup = INACTIVE;
|
|
else
|
|
cgroup = cpuctl_task_group_idx(next);
|
|
|
|
mlt_update_cpu(cpu, cgroup, now);
|
|
|
|
mlt_update_task(prev, 0, now);
|
|
mlt_update_task(next, 1, now);
|
|
}
|
|
|
|
void mlt_task_migration(struct task_struct *p, int new_cpu)
|
|
{
|
|
struct mlt *mlt = task_mlt(p);
|
|
|
|
if (unlikely(!mlt->state_count))
|
|
return;
|
|
|
|
mlt->cpu = new_cpu;
|
|
}
|
|
|
|
int mlt_cur_period(int cpu)
|
|
{
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt, cpu);
|
|
|
|
return mlt->cur_period;
|
|
}
|
|
|
|
int mlt_prev_period(int period)
|
|
{
|
|
period = period - 1;
|
|
if (period < 0)
|
|
return MLT_PERIOD_COUNT - 1;
|
|
|
|
return period;
|
|
}
|
|
|
|
int mlt_period_with_delta(int idx, int delta)
|
|
{
|
|
if (delta > 0)
|
|
return (idx + delta) % MLT_PERIOD_COUNT;
|
|
|
|
idx = idx + delta;
|
|
if (idx < 0)
|
|
return MLT_PERIOD_COUNT + idx;
|
|
|
|
return idx;
|
|
}
|
|
|
|
/* return task's ratio of current period */
|
|
int mlt_task_cur_period(struct task_struct *p)
|
|
{
|
|
struct mlt *mlt = task_mlt(p);
|
|
return mlt->cur_period;
|
|
}
|
|
|
|
int __mlt_get_recent(struct mlt *mlt)
|
|
{
|
|
int i, value = 0;
|
|
|
|
for (i = 0; i < mlt->state_count; i++)
|
|
value += mlt->recent[i];
|
|
return value;
|
|
}
|
|
|
|
int __mlt_get_value(struct mlt *mlt, int target_period)
|
|
{
|
|
int i, value = 0;
|
|
|
|
for (i = 0; i < mlt->state_count; i++)
|
|
value += mlt->periods[i][target_period];
|
|
|
|
return value;
|
|
}
|
|
|
|
/* return task's ratio of target_period */
|
|
int mlt_task_value(struct task_struct *p, int target_period)
|
|
{
|
|
struct mlt *mlt = task_mlt(p);
|
|
return __mlt_get_value(mlt, target_period);
|
|
}
|
|
|
|
int mlt_task_recent(struct task_struct *p)
|
|
{
|
|
struct mlt *mlt = task_mlt(p);
|
|
return __mlt_get_recent(mlt);
|
|
}
|
|
|
|
/* return task's average ratio of all periods */
|
|
int mlt_task_avg(struct task_struct *p)
|
|
{
|
|
int i, value = 0;
|
|
|
|
for (i = 0; i < MLT_PERIOD_COUNT; i++)
|
|
value += mlt_task_value(p, i);
|
|
|
|
return (value * SCHED_CAPACITY_SCALE) / MLT_PERIOD_SUM;
|
|
}
|
|
|
|
int mlt_art_value(int cpu, int target_period)
|
|
{
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt, cpu);
|
|
return __mlt_get_value(mlt, target_period);
|
|
}
|
|
|
|
int mlt_art_recent(int cpu)
|
|
{
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt, cpu);
|
|
return __mlt_get_recent(mlt);
|
|
}
|
|
|
|
int mlt_art_last_value(int cpu)
|
|
{
|
|
return mlt_art_value(cpu, per_cpu_ptr(pcpu_mlt, cpu)->cur_period);
|
|
}
|
|
|
|
u64 mlt_art_last_update_time(int cpu)
|
|
{
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt, cpu);
|
|
return mlt->last_updated;
|
|
}
|
|
|
|
int mlt_art_cgroup_value(int cpu, int target_period, int cgroup)
|
|
{
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt, cpu);
|
|
|
|
return mlt->periods[cgroup][target_period];
|
|
}
|
|
|
|
int mlt_cst_value(int cpu, int target_period, int cstate)
|
|
{
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt_cs, cpu);
|
|
|
|
return mlt->periods[cstate][target_period];
|
|
}
|
|
|
|
static char *cg_name[] = {
|
|
"root",
|
|
"fg",
|
|
"bg",
|
|
"ta",
|
|
"rt",
|
|
"sy",
|
|
"syb",
|
|
"n-h",
|
|
"c-d",
|
|
};
|
|
|
|
/******************************************************************************
|
|
* MULTI LOAD for NR_RUNNING *
|
|
******************************************************************************/
|
|
#define NR_RUN_PERIOD (4 * NSEC_PER_MSEC)
|
|
|
|
static struct mlt_nr_run __percpu *mlt_nr_run; /* nr_run tracking */
|
|
|
|
int mlt_avg_nr_run(struct rq *rq)
|
|
{
|
|
int cpu = cpu_of(rq);
|
|
struct mlt_nr_run *mnr = per_cpu_ptr(mlt_nr_run, cpu);
|
|
u64 now = sched_clock();
|
|
|
|
if ((mnr->last_ctrb_updated + MLT_IDLE_THR_TIME) < now)
|
|
return 0;
|
|
|
|
return mnr->avg_nr_run;
|
|
}
|
|
|
|
static void mlt_update_nr_run_ctrb(struct rq *rq)
|
|
{
|
|
int cpu = cpu_of(rq);
|
|
struct mlt_nr_run *mnr = per_cpu_ptr(mlt_nr_run, cpu);
|
|
int nr_run = rq->nr_running * NR_RUN_UNIT;
|
|
u64 now = sched_clock();
|
|
s64 delta;
|
|
|
|
if (unlikely(now < mnr->last_ctrb_updated))
|
|
return;
|
|
|
|
delta = now - mnr->last_ctrb_updated;
|
|
|
|
mnr->nr_run_ctrb += (nr_run * delta);
|
|
mnr->accomulate_time += delta;
|
|
mnr->last_ctrb_updated = now;
|
|
}
|
|
|
|
static void mlt_update_nr_run(struct rq *rq)
|
|
{
|
|
int cpu = cpu_of(rq);
|
|
struct mlt_nr_run *mnr = per_cpu_ptr(mlt_nr_run, cpu);
|
|
|
|
mlt_update_nr_run_ctrb(rq);
|
|
|
|
mnr->avg_nr_run = mnr->nr_run_ctrb / mnr->accomulate_time;
|
|
|
|
trace_mlt_nr_run_update(cpu, mnr->accomulate_time, mnr->nr_run_ctrb,
|
|
rq->nr_running, mnr->avg_nr_run);
|
|
|
|
mnr->nr_run_ctrb = 0;
|
|
mnr->accomulate_time = 0;
|
|
}
|
|
|
|
/* It will be called before increase nr_run */
|
|
void mlt_enqueue_task(struct rq *rq)
|
|
{
|
|
mlt_update_nr_run_ctrb(rq);
|
|
}
|
|
|
|
/* It will be called before decrease nr_run */
|
|
void mlt_dequeue_task(struct rq *rq)
|
|
{
|
|
mlt_update_nr_run_ctrb(rq);
|
|
}
|
|
|
|
int init_mlt_nr_run(void)
|
|
{
|
|
int cpu;
|
|
|
|
mlt_nr_run = alloc_percpu(struct mlt_nr_run);
|
|
if (!mlt_nr_run) {
|
|
pr_err("failed to allocate mlt_nr_run\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
struct mlt_nr_run *mnr = per_cpu_ptr(mlt_nr_run, cpu);
|
|
raw_spin_lock_init(&mnr->lock);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define MSG_SIZE 9216
|
|
static ssize_t multi_load_read(struct file *file, struct kobject *kobj,
|
|
struct bin_attribute *attr, char *buf,
|
|
loff_t offset, size_t size)
|
|
{
|
|
char *msg = kcalloc(MSG_SIZE, sizeof(char), GFP_KERNEL);
|
|
ssize_t msg_size, count = 0;
|
|
int cpu;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt, cpu);
|
|
int group;
|
|
|
|
count += sprintf(msg + count, " CPU%d | Recent | Latest "
|
|
" Oldest\n", cpu);
|
|
count += sprintf(msg + count, "---------------------------------------------------"
|
|
"-----------------------------------\n");
|
|
|
|
for (group = 0; group < mlt->state_count; group++) {
|
|
int p, c;
|
|
|
|
if (group == mlt->state)
|
|
count += sprintf(msg + count, ">> ");
|
|
else
|
|
count += sprintf(msg + count, " ");
|
|
count += sprintf(msg + count, "%4s | ", cg_name[group]);
|
|
|
|
count += sprintf(msg + count, "%8d | ", mlt->recent[group]);
|
|
|
|
for (c = 0; c < MLT_PERIOD_COUNT; c++) {
|
|
p = mlt_move_period(mlt->cur_period, -c);
|
|
count += sprintf(msg + count, "%8d", mlt->periods[group][p]);
|
|
}
|
|
count += sprintf(msg + count, "\n");
|
|
}
|
|
count += sprintf(msg + count, "\n");
|
|
}
|
|
|
|
msg_size = min_t(ssize_t, count, MSG_SIZE);
|
|
msg_size = memory_read_from_buffer(buf, size, &offset, msg, msg_size);
|
|
|
|
kfree(msg);
|
|
|
|
return msg_size;
|
|
}
|
|
BIN_ATTR_RO(multi_load, 0);
|
|
|
|
static int mlt_uarch_data_free(struct uarch_data *ud, int state_count)
|
|
{
|
|
int i;
|
|
|
|
if (ud->ipc_periods)
|
|
for (i = 0; i < state_count; i++)
|
|
kfree(ud->ipc_periods[i]);
|
|
if (ud->mspc_periods)
|
|
for (i = 0; i < state_count; i++)
|
|
kfree(ud->mspc_periods[i]);
|
|
kfree(ud->ipc_periods);
|
|
kfree(ud->mspc_periods);
|
|
kfree(ud->ipc_recent);
|
|
kfree(ud->mspc_recent);
|
|
kfree(ud->cycle_sum);
|
|
kfree(ud->inst_ret_sum);
|
|
kfree(ud->mem_stall_sum);
|
|
kfree(ud);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int mlt_uarch_data_init(struct mlt *mlt)
|
|
{
|
|
struct uarch_data *ud;
|
|
int i, state_count = mlt->state_count;
|
|
|
|
ud = kzalloc(sizeof(struct uarch_data), GFP_KERNEL);
|
|
if (!ud)
|
|
return -ENOMEM;
|
|
|
|
ud->ipc_periods = kcalloc(state_count, sizeof(int *), GFP_KERNEL);
|
|
if (!ud->ipc_periods)
|
|
return mlt_uarch_data_free(ud, state_count);
|
|
|
|
ud->mspc_periods = kcalloc(state_count, sizeof(int *), GFP_KERNEL);
|
|
if (!ud->mspc_periods)
|
|
return mlt_uarch_data_free(ud, state_count);
|
|
|
|
for (i = 0; i < state_count; i++) {
|
|
ud->ipc_periods[i] = kcalloc(MLT_PERIOD_COUNT, sizeof(int), GFP_KERNEL);
|
|
if (!ud->ipc_periods[i])
|
|
return mlt_uarch_data_free(ud, state_count);
|
|
|
|
ud->mspc_periods[i] = kcalloc(MLT_PERIOD_COUNT, sizeof(int), GFP_KERNEL);
|
|
if (!ud->mspc_periods[i])
|
|
return mlt_uarch_data_free(ud, state_count);
|
|
}
|
|
|
|
ud->ipc_recent = kcalloc(state_count, sizeof(u64), GFP_KERNEL);
|
|
if (!ud->ipc_recent)
|
|
return mlt_uarch_data_free(ud, state_count);
|
|
|
|
ud->mspc_recent = kcalloc(state_count, sizeof(u64), GFP_KERNEL);
|
|
if (!ud->mspc_recent)
|
|
return mlt_uarch_data_free(ud, state_count);
|
|
|
|
ud->cycle_sum = kcalloc(state_count, sizeof(u64), GFP_KERNEL);
|
|
if (!ud->cycle_sum)
|
|
return mlt_uarch_data_free(ud, state_count);
|
|
|
|
ud->inst_ret_sum = kcalloc(state_count, sizeof(u64), GFP_KERNEL);
|
|
if (!ud->inst_ret_sum)
|
|
return mlt_uarch_data_free(ud, state_count);
|
|
|
|
ud->mem_stall_sum = kcalloc(state_count, sizeof(u64), GFP_KERNEL);
|
|
if (!ud->mem_stall_sum)
|
|
return mlt_uarch_data_free(ud, state_count);
|
|
|
|
mlt->uarch = ud;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mlt_alloc_and_init(struct mlt *mlt, int cpu,
|
|
int state_count, u64 now, bool uarch)
|
|
{
|
|
int i;
|
|
|
|
mlt->cpu = cpu;
|
|
mlt->cur_period = 0;
|
|
mlt->period_start = now;
|
|
mlt->last_updated = now;
|
|
mlt->state = -1;
|
|
mlt->state_count = state_count;
|
|
|
|
mlt->periods = kcalloc(state_count, sizeof(int *), GFP_KERNEL);
|
|
if (!mlt->periods)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < state_count; i++) {
|
|
mlt->periods[i] = kcalloc(MLT_PERIOD_COUNT,
|
|
sizeof(int), GFP_KERNEL);
|
|
if (!mlt->periods[i])
|
|
goto fail_alloc;
|
|
}
|
|
|
|
mlt->recent = kcalloc(state_count, sizeof(int), GFP_KERNEL);
|
|
if (!mlt->recent)
|
|
goto fail_alloc;
|
|
|
|
mlt->recent_sum = kcalloc(state_count, sizeof(u64), GFP_KERNEL);
|
|
if (!mlt->recent_sum)
|
|
goto fail_alloc;
|
|
|
|
if (uarch) {
|
|
if (mlt_uarch_data_init(mlt))
|
|
goto fail_alloc;
|
|
} else
|
|
mlt->uarch = NULL;
|
|
|
|
return 0;
|
|
|
|
fail_alloc:
|
|
for (i = 0; i < state_count; i++)
|
|
kfree(mlt->periods[i]);
|
|
kfree(mlt->periods);
|
|
kfree(mlt->recent);
|
|
kfree(mlt->recent_sum);
|
|
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static bool uarch_supported;
|
|
void mlt_task_init(struct task_struct *p)
|
|
{
|
|
struct mlt *mlt = task_mlt(p);
|
|
struct mlt *mlt_cpu;
|
|
int *pos = (int *)mlt;
|
|
|
|
memset(mlt, 0, MLT_TASK_SIZE);
|
|
mlt_cpu = per_cpu_ptr(pcpu_mlt, p->cpu);
|
|
|
|
mlt->cpu = mlt_cpu->cpu;
|
|
mlt->cur_period = mlt_cpu->cur_period;
|
|
mlt->period_start = mlt_cpu->period_start;
|
|
mlt->last_updated = mlt_cpu->last_updated;
|
|
mlt->state = -1;
|
|
mlt->state_count = 1;
|
|
|
|
pos += (sizeof(struct mlt) / sizeof(int));
|
|
|
|
mlt->periods = (int **)pos;
|
|
mlt->periods[0] = pos + 2;
|
|
mlt->recent = pos + 2 + MLT_PERIOD_COUNT;
|
|
mlt->recent_sum = (u64 *)(pos + 4 + MLT_PERIOD_COUNT);
|
|
|
|
if (!uarch_supported)
|
|
return;
|
|
|
|
mlt->uarch = (struct uarch_data *)(pos + 6 + MLT_PERIOD_COUNT);
|
|
|
|
pos = pos + 6 + MLT_PERIOD_COUNT;
|
|
pos += (sizeof(struct uarch_data) / sizeof(int));
|
|
|
|
mlt->uarch->ipc_periods = (int **)pos;
|
|
mlt->uarch->ipc_periods[0] = pos + 2;
|
|
mlt->uarch->mspc_periods = (int **)(pos + 2 + MLT_PERIOD_COUNT);
|
|
mlt->uarch->mspc_periods[0] = pos + 4 + MLT_PERIOD_COUNT;
|
|
mlt->uarch->ipc_recent = pos + 4 + (MLT_PERIOD_COUNT * 2);
|
|
mlt->uarch->mspc_recent = pos + 6 + (MLT_PERIOD_COUNT * 2);
|
|
mlt->uarch->cycle_sum = (u64 *)(pos + 8 + (MLT_PERIOD_COUNT * 2));
|
|
mlt->uarch->inst_ret_sum = (u64 *)(pos + 10 + (MLT_PERIOD_COUNT * 2));
|
|
mlt->uarch->mem_stall_sum = (u64 *)(pos + 12 + (MLT_PERIOD_COUNT * 2));
|
|
}
|
|
|
|
int mlt_init(struct kobject *ems_kobj, struct device_node *dn)
|
|
{
|
|
int cpu;
|
|
u64 now;
|
|
struct task_struct *p;
|
|
|
|
pcpu_mlt = alloc_percpu(struct mlt);
|
|
if (!pcpu_mlt) {
|
|
pr_err("failed to allocate mlt\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pcpu_mlt_cs = alloc_percpu(struct mlt);
|
|
if (!pcpu_mlt_cs) {
|
|
pr_err("failed to allocate mlt for c-state\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (of_property_read_bool(dn, "uarch-mlt"))
|
|
uarch_supported = true;
|
|
|
|
now = sched_clock();
|
|
for_each_possible_cpu(cpu) {
|
|
struct mlt *mlt = per_cpu_ptr(pcpu_mlt, cpu);
|
|
struct mlt *mlt_cs = per_cpu_ptr(pcpu_mlt_cs, cpu);
|
|
|
|
mlt_alloc_and_init(mlt, cpu, CGROUP_COUNT, now, uarch_supported);
|
|
mlt_alloc_and_init(mlt_cs, cpu, CSTATE_MAX, now, false);
|
|
}
|
|
|
|
rcu_read_lock();
|
|
list_for_each_entry_rcu(p, &init_task.tasks, tasks) {
|
|
get_task_struct(p);
|
|
mlt_task_init(p);
|
|
put_task_struct(p);
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
if (init_mlt_nr_run()) {
|
|
pr_err("failed to init mlt avg_nr_run\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (sysfs_create_bin_file(ems_kobj, &bin_attr_multi_load))
|
|
pr_warn("failed to create multi_load\n");
|
|
|
|
return 0;
|
|
}
|