You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

755 lines
17 KiB

/* Copyright (c) 2010-2013, 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
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
/*
* Support asynchronus hint for external modules to get loading change
* in time from scheduler's help.
*
* 1. call-back function for notification of status change
* - int register_sched_hint_notifier( void(*fp)(int status) )
*
* 2. control interface for user
* - /sys/devices/system/cpu/sched/...
*
*/
#include <linux/irq_work.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/string.h>
#include <trace/events/sched.h>
#include <linux/cpu.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include "rq_stats.h"
#include "sched_ctl.h"
#include <mt-plat/met_drv.h>
#include <mt-plat/mtk_sched.h>
#define SCHED_HINT_THROTTLE_NSEC 10000000 /* 10ms for throttle */
struct sched_hint_data {
struct task_struct *task;
struct irq_work irq_work;
int sys_cap;
int sys_util;
ktime_t throttle;
struct attribute_group *attr_group;
struct kobject *kobj;
};
#if 0
/* debugging */
static char met_cpu_load[16][32] = {
"sched_load_cpu0",
"sched_load_cpu1",
"sched_load_cpu2",
"sched_load_cpu3",
"sched_load_cpu4",
"sched_load_cpu5",
"sched_load_cpu6",
"sched_load_cpu7",
"sched_load_cpu8",
"sched_load_cpu9",
"NULL"
};
#endif
/* global */
static u64 sched_hint_check_timestamp;
static u64 sched_hint_check_interval;
static struct sched_hint_data g_shd;
static int sched_hint_inited;
#ifdef CONFIG_MTK_SCHED_SYSHINT
static int sched_hint_on = 1; /* default on */
#else
static int sched_hint_on; /* default off */
#endif
static enum sched_status_t kthread_status = SCHED_STATUS_INIT;
static enum sched_status_t sched_status = SCHED_STATUS_INIT;
static int sched_hint_loading_thresh = 5; /* 5% (max 100%) */
static BLOCKING_NOTIFIER_HEAD(sched_hint_notifier_list);
static DEFINE_SPINLOCK(status_lock);
static struct kobj_attribute sched_iso_attr;
static struct kobj_attribute set_sched_iso_attr;
static struct kobj_attribute set_sched_deiso_attr;
#ifdef CONFIG_MTK_SCHED_BOOST
static struct kobj_attribute sched_boost_attr;
#endif
static int sched_hint_status(int util, int cap)
{
enum sched_status_t status;
if (((util * 100) / cap) > sched_hint_loading_thresh)
status = SCHED_STATUS_OVERUTIL;
else
status = SCHED_STATUS_UNDERUTIL;
return status;
}
/* kernel thread */
static int sched_hint_thread(void *data)
{
do {
set_current_state(TASK_INTERRUPTIBLE);
schedule();
if (kthread_should_stop())
break;
/* status change from scheduler hint */
if (kthread_status != sched_status) {
int ret;
kthread_status = sched_status;
met_tag_oneshot(0, "sched_hint", kthread_status);
ret = blocking_notifier_call_chain(&sched_hint_notifier_list,
kthread_status, NULL);
/* reset throttle time */
g_shd.throttle = ktime_add_ns(ktime_get(), SCHED_HINT_THROTTLE_NSEC);
}
#if 0
if (true) { /* debugging code */
int iter_cpu;
for (iter_cpu = 0; iter_cpu < nr_cpu_ids; iter_cpu++)
#ifdef CONFIG_MTK_SCHED_CPULOAD
met_tag_oneshot(0, met_cpu_load[iter_cpu], sched_get_cpu_load(iter_cpu));
#endif
}
#endif
} while (!kthread_should_stop());
return 0;
}
static void sched_irq_work(struct irq_work *irq_work)
{
struct sched_hint_data *shd;
if (!irq_work)
return;
shd = container_of(irq_work, struct sched_hint_data, irq_work);
wake_up_process(shd->task);
}
static bool hint_need(void)
{
return (kthread_status == sched_status) ? false : true;
}
static bool do_check(u64 wallclock)
{
bool do_check = false;
unsigned long flags;
/* check interval */
spin_lock_irqsave(&status_lock, flags);
if ((wallclock - sched_hint_check_timestamp) >= sched_hint_check_interval) {
sched_hint_check_timestamp = wallclock;
do_check = true;
}
spin_unlock_irqrestore(&status_lock, flags);
/* is throttled ? */
if (ktime_before(ktime_get(), g_shd.throttle))
do_check = false;
return do_check;
}
void sched_hint_check(u64 wallclock)
{
if (!sched_hint_inited || !sched_hint_on)
return;
if (do_check(wallclock)) {
if (hint_need())
/* wake up ksched_hint */
wake_up_process(g_shd.task);
}
}
/* scheduler update hint in fair.c */
void update_sched_hint(int sys_util, int sys_cap)
{
ktime_t throttle = g_shd.throttle;
ktime_t now = ktime_get();
if (!sched_hint_inited || !sched_hint_on)
return;
if (ktime_before(now, throttle)) {
/* met_tag_oneshot(0, "sched_hint_throttle", 1); */
return;
}
if (!sys_cap)
return;
g_shd.sys_util = sys_util;
g_shd.sys_cap = sys_cap;
sched_status = sched_hint_status(sys_util, sys_cap);
if (kthread_status != sched_status)
irq_work_queue_on(&g_shd.irq_work, smp_processor_id());
}
/* A user-space interfaces: */
static ssize_t store_sched_enable(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
unsigned int val = 0;
if (sscanf(buf, "%iu", &val) != 0)
sched_hint_on = (val) ? 1 : 0;
return count;
}
static ssize_t store_sched_load_thresh(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
unsigned int val = 0;
if (sscanf(buf, "%iu", &val) != 0) {
if (val >= 0 && val < 100)
sched_hint_loading_thresh = val;
}
return count;
}
static ssize_t show_sched_info(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
unsigned int len = 0;
unsigned int max_len = 4096;
len += snprintf(buf, max_len, "capacity total=%d\n", g_shd.sys_cap);
len += snprintf(buf+len, max_len - len, "capacity used=%d\n", g_shd.sys_util);
if (sched_hint_on) {
if (kthread_status != SCHED_STATUS_INIT)
len += snprintf(buf+len, max_len - len, "status=(%s)\n",
(kthread_status != SCHED_STATUS_OVERUTIL) ?
"under" : "over");
else
len += snprintf(buf+len, max_len - len, "status=(init)\n");
} else
len += snprintf(buf+len, max_len - len, "status=(off)\n");
len += snprintf(buf+len, max_len - len, "load thresh=%d%c\n", sched_hint_loading_thresh, '%');
return len;
}
static DEFINE_MUTEX(ip_mutex);
static ssize_t store_idle_prefer(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
unsigned int val = 0;
static unsigned int backup_mc;
static unsigned int backup_dvfs_margin;
static int is_dirty;
int en;
mutex_lock(&ip_mutex);
if (sscanf(buf, "%iu", &val) != 0)
idle_prefer_mode = val;
en = (idle_prefer_mode > 0) ? 1 : 0;
/* backup system settings */
if (!is_dirty) {
backup_mc = sysctl_sched_migration_cost;
backup_dvfs_margin = capacity_margin_dvfs;
}
#ifdef CONFIG_SCHED_TUNE
/*
* set top-app prefer idle cpu via stune
* 1: fg
* 2: bg
* 3: top-app
*/
prefer_idle_for_perf_idx(3, en);
prefer_idle_for_perf_idx(1, en);
#endif
/* migration cost to 33us */
sysctl_sched_migration_cost = en ? 33000UL : backup_mc; /*500000UL;*/
#if defined(CONFIG_MACH_MT6771)
/* marginless DVFS control for high TLP scene */
capacity_margin_dvfs = en ? 1024 : backup_dvfs_margin;
/* display idle timeout */
display_set_wait_idle_time(en ? 200 : 50);
#endif
is_dirty = en;
mutex_unlock(&ip_mutex);
return count;
}
static ssize_t show_idle_prefer(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
unsigned int len = 0;
unsigned int max_len = 4096;
len += snprintf(buf, max_len, "idle prefer = %d\n", idle_prefer_mode);
len += snprintf(buf+len, max_len - len, "idle needed = %d\n", idle_prefer_need());
return len;
}
static ssize_t show_walt_info(struct kobject *kobj,
struct kobj_attribute *attr, char *buf);
static ssize_t store_walt_info(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count);
static struct kobj_attribute sched_enable_attr =
__ATTR(hint_enable, S_IWUSR, NULL, store_sched_enable);
static struct kobj_attribute sched_load_thresh_attr =
__ATTR(hint_load_thresh, S_IWUSR, NULL, store_sched_load_thresh);
static struct kobj_attribute sched_info_attr =
__ATTR(hint_info, S_IRUSR, show_sched_info, NULL);
static struct kobj_attribute sched_idle_prefer_attr =
__ATTR(idle_prefer, S_IWUSR | S_IRUSR, show_idle_prefer, store_idle_prefer);
static struct kobj_attribute sched_walt_info_attr =
__ATTR(walt_debug, S_IWUSR | S_IRUSR, show_walt_info, store_walt_info);
static struct attribute *sched_attrs[] = {
&sched_info_attr.attr,
&sched_load_thresh_attr.attr,
&sched_enable_attr.attr,
&sched_iso_attr.attr,
&set_sched_iso_attr.attr,
&set_sched_deiso_attr.attr,
#ifdef CONFIG_MTK_SCHED_BOOST
&sched_boost_attr.attr,
#endif
&sched_idle_prefer_attr.attr,
&sched_walt_info_attr.attr,
NULL,
};
static struct attribute_group sched_attr_group = {
.attrs = sched_attrs,
};
int register_sched_hint_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&sched_hint_notifier_list, nb);
}
EXPORT_SYMBOL(register_sched_hint_notifier);
int unregister_sched_hint_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&sched_hint_notifier_list, nb);
}
EXPORT_SYMBOL(unregister_sched_hint_notifier);
/* init function */
static int __init sched_hint_init(void)
{
struct sched_param param;
int ret;
/* create thread */
g_shd.task = kthread_create(sched_hint_thread, NULL, "ksched_hint");
if (IS_ERR_OR_NULL(g_shd.task)) {
pr_info("%s: failed to create ksched_hint thread.\n", __func__);
goto err;
}
/* priority setting */
param.sched_priority = 120;
ret = sched_setscheduler_nocheck(g_shd.task, SCHED_NORMAL, &param);
if (ret)
pr_info("%s: failed to set sched_fifo\n", __func__);
/* keep thread alive */
get_task_struct(g_shd.task);
/* init throttle time */
g_shd.throttle = ktime_get();
wake_up_process(g_shd.task);
/* init irq_work */
init_irq_work(&g_shd.irq_work, sched_irq_work);
/* check interval 20ms */
sched_hint_check_interval = CPU_LOAD_AVG_DEFAULT_MS * NSEC_PER_MSEC;
/* enable sched hint */
sched_hint_inited = 1;
/*
* create a sched in cpu_subsys:
* /sys/devices/system/cpu/sched/...
*/
g_shd.attr_group = &sched_attr_group;
g_shd.kobj = kobject_create_and_add("sched", &cpu_subsys.dev_root->kobj);
if (g_shd.kobj) {
ret = sysfs_create_group(g_shd.kobj, g_shd.attr_group);
if (ret)
kobject_put(g_shd.kobj);
else
kobject_uevent(g_shd.kobj, KOBJ_ADD);
}
return 0;
err:
return -ENOMEM;
}
late_initcall(sched_hint_init);
/*
* sched_ktime_clock()
* - to get wall time but not to update in suspended.
*/
#include <linux/syscore_ops.h>
static ktime_t ktime_last;
static bool sched_ktime_suspended;
u64 sched_ktime_clock(void)
{
if (unlikely(sched_ktime_suspended))
return ktime_to_ns(ktime_last);
return ktime_get_ns();
}
static void sched_resume(void)
{
sched_ktime_suspended = false;
}
static int sched_suspend(void)
{
ktime_last = ktime_get();
sched_ktime_suspended = true;
return 0;
}
static struct syscore_ops sched_syscore_ops = {
.resume = sched_resume,
.suspend = sched_suspend
};
static int __init sched_init_ops(void)
{
register_syscore_ops(&sched_syscore_ops);
return 0;
}
late_initcall(sched_init_ops);
/* turn on/off sched boost scheduling */
static ssize_t show_sched_iso(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
unsigned int len = 0;
unsigned int max_len = 4096;
len += snprintf(buf+len, max_len-len, "cpu_isolated_mask=0x%lx\n", cpu_isolated_mask->bits[0]);
len += snprintf(buf+len, max_len-len, "iso_prio=%d\n", iso_prio);
return len;
}
static ssize_t set_sched_iso(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
unsigned int val = 0;
if (sscanf(buf, "%iu", &val) < nr_cpu_ids)
sched_isolate_cpu(val);
return count;
}
static ssize_t set_sched_deiso(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
unsigned int val = 0;
if (sscanf(buf, "%iu", &val) < nr_cpu_ids)
sched_deisolate_cpu(val);
return count;
}
static struct kobj_attribute sched_iso_attr =
__ATTR(sched_isolation, S_IRUSR, show_sched_iso, NULL);
static struct kobj_attribute set_sched_iso_attr =
__ATTR(set_sched_isolation, S_IWUSR, NULL, set_sched_iso);
static struct kobj_attribute set_sched_deiso_attr =
__ATTR(set_sched_deisolation, S_IWUSR, NULL, set_sched_deiso);
#ifdef CONFIG_MTK_SCHED_BOOST
static int sched_boost_type = SCHED_NO_BOOST;
bool sched_boost(void)
{
return (sched_boost_type == SCHED_ALL_BOOST);
}
EXPORT_SYMBOL(sched_boost);
void sched_set_boost_fg(void)
{
struct cpumask cpus;
int nr;
/* 1: Root
* 2: foreground
* 3: Foregrond/boost
* 4: background
* 5: system-background
* 6: top-app
*/
nr = arch_get_nr_clusters();
arch_get_cluster_cpus(&cpus, nr-1);
set_user_space_global_cpuset(&cpus, 3);
set_user_space_global_cpuset(&cpus, 2);
set_user_space_global_cpuset(&cpus, 6);
}
void sched_unset_boost_fg(void)
{
unset_user_space_global_cpuset(2);
unset_user_space_global_cpuset(3);
unset_user_space_global_cpuset(6);
}
/* A mutex for scheduling boost switcher */
static DEFINE_MUTEX(sched_boost_mutex);
int set_sched_boost(unsigned int val)
{
static unsigned int sysctl_sched_isolation_hint_enable_backup = -1;
if ((val < SCHED_NO_BOOST) || (val >= SCHED_UNKNOWN_BOOST))
return -1;
if (sched_boost_type == val)
return 0;
mutex_lock(&sched_boost_mutex);
/* bail out if forcing mode */
if (val != SCHED_FORCE_STOP && sched_boost_type == SCHED_FORCE_BOOST) {
mutex_unlock(&sched_boost_mutex);
return 0;
}
/* back to original setting*/
if (sched_boost_type == SCHED_ALL_BOOST)
sched_scheduler_switch(SCHED_HYBRID_LB);
else if (sched_boost_type == SCHED_FORCE_BOOST)
sched_scheduler_switch(SCHED_HYBRID_LB);
else if (sched_boost_type == SCHED_FG_BOOST)
sched_unset_boost_fg();
sched_boost_type = val;
if (val == SCHED_NO_BOOST || val == SCHED_FORCE_STOP) {
if (sysctl_sched_isolation_hint_enable_backup > 0)
sysctl_sched_isolation_hint_enable = sysctl_sched_isolation_hint_enable_backup;
} else if ((val > SCHED_NO_BOOST) && (val < SCHED_UNKNOWN_BOOST)) {
sysctl_sched_isolation_hint_enable_backup = sysctl_sched_isolation_hint_enable;
sysctl_sched_isolation_hint_enable = 0;
if (val == SCHED_ALL_BOOST || val == SCHED_FORCE_BOOST)
sched_scheduler_switch(SCHED_HMP_LB);
else if (val == SCHED_FG_BOOST)
sched_set_boost_fg();
}
printk_deferred("[name:sched_boost&] sched boost: set %d\n",
sched_boost_type);
mutex_unlock(&sched_boost_mutex);
return 0;
}
EXPORT_SYMBOL(set_sched_boost);
/* turn on/off sched boost scheduling */
static ssize_t show_sched_boost(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
unsigned int len = 0;
unsigned int max_len = 4096;
switch (sched_boost_type) {
case SCHED_ALL_BOOST:
len += snprintf(buf, max_len, "sched boost= all boost\n\n");
break;
case SCHED_FG_BOOST:
len += snprintf(buf, max_len, "sched boost= foreground boost\n\n");
break;
case SCHED_FORCE_BOOST:
len += snprintf(buf, max_len, "sched boost= force boost\n\n");
break;
default:
len += snprintf(buf, max_len, "sched boost= no boost\n\n");
break;
}
return len;
}
static ssize_t store_sched_boost(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
unsigned int val = 0;
/*
* 0: NO sched boost
* 1: boost ALL task
* 2: boost foreground task
*/
if (sscanf(buf, "%iu", &val) != 0)
set_sched_boost(val);
return count;
}
static struct kobj_attribute sched_boost_attr =
__ATTR(sched_boost, S_IWUSR | S_IRUSR, show_sched_boost,
store_sched_boost);
#endif
/* schedule loading trackign change
* 0: default
* 1: walt
*/
static int lt_walt_table[LT_UNKNOWN_USER];
static int walt_dbg;
static char walt_maker_name[LT_UNKNOWN_USER+1][32] = {
"powerHAL",
"fpsGO",
"sched",
"debug",
"unknown"
};
int sched_walt_enable(int user, int en)
{
int walted = 0;
int i;
unsigned int user_mask = 0;
if ((user < 0) || (user >= LT_UNKNOWN_USER))
return -1;
lt_walt_table[user] = en;
for (i = 0; i < LT_UNKNOWN_USER; i++) {
walted |= lt_walt_table[i];
user_mask |= (lt_walt_table[i] << i);
}
if (walt_dbg) {
walted = lt_walt_table[LT_WALT_DEBUG];
user_mask = 0xFFFF;
}
#ifdef CONFIG_SCHED_WALT
sysctl_sched_use_walt_cpu_util = walted;
sysctl_sched_use_walt_task_util = walted;
trace_sched_ctl_walt(user_mask, walted);
#endif
return 0;
}
EXPORT_SYMBOL(sched_walt_enable);
static ssize_t show_walt_info(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
unsigned int len = 0;
unsigned int max_len = 4096;
int i;
int supported = 0;
for (i = 0; i < LT_UNKNOWN_USER; i++)
len += snprintf(buf+len, max_len - len, "walt[%d] = %d (%s)\n",
i, lt_walt_table[i], walt_maker_name[i]);
#ifdef CONFIG_SCHED_WALT
supported = 1;
#endif
len += snprintf(buf+len, max_len - len, "\nWALT support:%d\n", supported);
len += snprintf(buf+len, max_len - len, "debug mode:%d\n", walt_dbg);
len += snprintf(buf+len, max_len - len, "format: echo (debug:walt)\n");
return len;
}
/*
* argu1: enable debug mode
* argu2: walted or not.
*
* echo 1 0 : enable debug mode, foring walted off.
*/
static ssize_t store_walt_info(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
int walted = -1;
if (sscanf(buf, "%d:%d", &walt_dbg, &walted) <= 0)
return count;
if (walted < 0 || walted > 1 || walt_dbg < 0 || walt_dbg > 1)
return count;
if (walt_dbg)
sched_walt_enable(LT_WALT_DEBUG, walted);
else
sched_walt_enable(LT_WALT_DEBUG, 0);
return count;
}