| /* Copyright (c) 2018, 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. |
| * |
| */ |
| |
| #define pr_fmt(fmt) "big_min_freq_adjust: " fmt |
| |
| #include <linux/init.h> |
| #include <linux/cpufreq.h> |
| #include <linux/cpumask.h> |
| #include <linux/cpu_pm.h> |
| #include <linux/types.h> |
| #include <linux/smp.h> |
| #include <linux/moduleparam.h> |
| |
| enum min_freq_adjust { |
| ADJUST_MIN_FLOOR, /* Set min floor to user supplied value */ |
| RESET_MIN_FLOOR, /* Reset min floor cpuinfo value */ |
| }; |
| |
| struct big_min_freq_adjust_data { |
| struct cpumask cluster_cpumask; |
| unsigned int min_freq_floor; |
| struct delayed_work min_freq_work; |
| unsigned long min_down_delay_jiffies; |
| enum min_freq_adjust min_freq_state; |
| enum min_freq_adjust min_freq_request; |
| spinlock_t lock; |
| bool big_min_freq_on; |
| bool is_init; |
| }; |
| static struct big_min_freq_adjust_data big_min_freq_adjust_data; |
| |
| static void cpufreq_min_freq_work(struct work_struct *work) |
| { |
| struct big_min_freq_adjust_data *p = &big_min_freq_adjust_data; |
| |
| spin_lock(&p->lock); |
| if (p->min_freq_state == p->min_freq_request) { |
| spin_unlock(&p->lock); |
| return; |
| } |
| spin_unlock(&p->lock); |
| cpufreq_update_policy(cpumask_first(&p->cluster_cpumask)); |
| } |
| |
| static int cpufreq_callback(struct notifier_block *nb, unsigned long val, |
| void *data) |
| { |
| struct big_min_freq_adjust_data *p = &big_min_freq_adjust_data; |
| struct cpufreq_policy *policy = data; |
| unsigned int min_freq_floor; |
| |
| if (p->big_min_freq_on == false) |
| return NOTIFY_DONE; |
| |
| if (val != CPUFREQ_ADJUST) |
| return NOTIFY_DONE; |
| |
| if (!cpumask_test_cpu(cpumask_first(&p->cluster_cpumask), |
| policy->related_cpus)) |
| return NOTIFY_DONE; |
| |
| spin_lock(&p->lock); |
| if (p->min_freq_request == ADJUST_MIN_FLOOR) |
| min_freq_floor = p->min_freq_floor; |
| else |
| min_freq_floor = policy->cpuinfo.min_freq; |
| cpufreq_verify_within_limits(policy, min_freq_floor, |
| policy->cpuinfo.max_freq); |
| p->min_freq_state = p->min_freq_request; |
| spin_unlock(&p->lock); |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block cpufreq_nb = { |
| .notifier_call = cpufreq_callback |
| }; |
| |
| #define AFFINITY_LEVEL_L2 1 |
| static int cpu_pm_callback(struct notifier_block *self, |
| unsigned long cmd, void *v) |
| { |
| struct big_min_freq_adjust_data *p = &big_min_freq_adjust_data; |
| unsigned long aff_level = (unsigned long) v; |
| unsigned long delay; |
| int cpu; |
| |
| if (p->big_min_freq_on == false) |
| return NOTIFY_DONE; |
| |
| if (aff_level != AFFINITY_LEVEL_L2) |
| return NOTIFY_DONE; |
| |
| cpu = smp_processor_id(); |
| |
| if (!cpumask_test_cpu(cpu, &p->cluster_cpumask)) |
| return NOTIFY_DONE; |
| |
| spin_lock(&p->lock); |
| switch (cmd) { |
| case CPU_CLUSTER_PM_ENTER: |
| p->min_freq_request = RESET_MIN_FLOOR; |
| delay = p->min_down_delay_jiffies; |
| break; |
| case CPU_CLUSTER_PM_ENTER_FAILED: |
| case CPU_CLUSTER_PM_EXIT: |
| p->min_freq_request = ADJUST_MIN_FLOOR; |
| /* To avoid unnecessary oscillations between exit and idle */ |
| delay = 1; |
| break; |
| default: |
| spin_unlock(&p->lock); |
| return NOTIFY_DONE; |
| } |
| |
| cancel_delayed_work(&p->min_freq_work); |
| |
| if (p->min_freq_state != p->min_freq_request) { |
| if (p->min_freq_request == ADJUST_MIN_FLOOR) { |
| if (p->min_freq_floor > cpufreq_quick_get(cpu)) |
| delay = 0; |
| } |
| queue_delayed_work(system_unbound_wq, &p->min_freq_work, delay); |
| } |
| spin_unlock(&p->lock); |
| |
| return NOTIFY_OK; |
| } |
| |
| static struct notifier_block cpu_pm_nb = { |
| .notifier_call = cpu_pm_callback |
| }; |
| |
| static unsigned long __read_mostly big_min_down_delay_ms; |
| #define MIN_DOWN_DELAY_MSEC 80 /* Default big_min_down_delay in msec */ |
| #define POLICY_MIN 1094400 /* Default min_freq_floor in KHz */ |
| |
| static void trigger_state_machine(void *d) |
| { |
| struct big_min_freq_adjust_data *p = &big_min_freq_adjust_data; |
| bool *update_policy = d; |
| |
| if (p->min_freq_request != ADJUST_MIN_FLOOR) { |
| p->min_freq_request = ADJUST_MIN_FLOOR; |
| *update_policy = true; |
| } |
| } |
| |
| static int enable_big_min_freq_adjust(void) |
| { |
| struct big_min_freq_adjust_data *p = &big_min_freq_adjust_data; |
| int ret; |
| bool update_policy = false; |
| |
| if (p->big_min_freq_on == true) |
| return 0; |
| |
| if (!cpumask_weight(&p->cluster_cpumask)) { |
| pr_err("Cluster CPU IDs not set\n"); |
| return -EPERM; |
| } |
| |
| INIT_DEFERRABLE_WORK(&p->min_freq_work, cpufreq_min_freq_work); |
| |
| if (!big_min_down_delay_ms) { |
| big_min_down_delay_ms = MIN_DOWN_DELAY_MSEC; |
| p->min_down_delay_jiffies = msecs_to_jiffies( |
| big_min_down_delay_ms); |
| } |
| if (!p->min_freq_floor) |
| p->min_freq_floor = POLICY_MIN; |
| |
| p->min_freq_state = RESET_MIN_FLOOR; |
| p->min_freq_request = RESET_MIN_FLOOR; |
| spin_lock_init(&p->lock); |
| p->big_min_freq_on = true; |
| |
| ret = cpu_pm_register_notifier(&cpu_pm_nb); |
| if (ret) { |
| pr_err("Failed to register for PM notification\n"); |
| return ret; |
| } |
| |
| ret = cpufreq_register_notifier(&cpufreq_nb, CPUFREQ_POLICY_NOTIFIER); |
| if (ret) { |
| pr_err("Failed to register for CPUFREQ POLICY notification\n"); |
| cpu_pm_unregister_notifier(&cpu_pm_nb); |
| return ret; |
| } |
| |
| /* If BIG cluster is active at this time and continue to be active |
| * forever, in that case min frequency of the cluster will never be |
| * set to floor value. This is to trigger the state machine and set |
| * the min freq and min_freq_state to appropriate values. |
| * |
| * Two possibilities here. |
| * 1) If cluster is idle before this, the wakeup is unnecessary but |
| * the state machine is set to proper state. |
| * 2) If cluster is active before this, the wakeup is necessary and |
| * the state machine is set to proper state. |
| */ |
| smp_call_function_any(&p->cluster_cpumask, |
| trigger_state_machine, &update_policy, true); |
| if (update_policy) |
| cpufreq_update_policy(cpumask_first(&p->cluster_cpumask)); |
| |
| pr_info("big min freq ajustment enabled\n"); |
| |
| return 0; |
| } |
| |
| static bool __read_mostly big_min_freq_adjust_enabled; |
| |
| static int set_big_min_freq_adjust(const char *buf, |
| const struct kernel_param *kp) |
| { |
| int ret; |
| |
| ret = param_set_bool_enable_only(buf, kp); |
| if (ret) { |
| pr_err("Unable to set big_min_freq_adjust_enabled: %d\n", ret); |
| return ret; |
| } |
| |
| if (!big_min_freq_adjust_data.is_init) |
| return ret; |
| |
| return enable_big_min_freq_adjust(); |
| } |
| |
| static const struct kernel_param_ops param_ops_big_min_freq_adjust = { |
| .set = set_big_min_freq_adjust, |
| .get = param_get_bool, |
| }; |
| module_param_cb(min_freq_adjust, ¶m_ops_big_min_freq_adjust, |
| &big_min_freq_adjust_enabled, 0644); |
| |
| module_param_named(min_freq_floor, big_min_freq_adjust_data.min_freq_floor, |
| uint, 0644); |
| |
| static int set_min_down_delay_ms(const char *buf, const struct kernel_param *kp) |
| { |
| int ret; |
| |
| ret = param_set_ulong(buf, kp); |
| if (ret) { |
| pr_err("Unable to set big_min_down_delay_ms: %d\n", ret); |
| return ret; |
| } |
| |
| big_min_freq_adjust_data.min_down_delay_jiffies = msecs_to_jiffies( |
| big_min_down_delay_ms); |
| |
| return 0; |
| } |
| |
| static const struct kernel_param_ops param_ops_big_min_down_delay_ms = { |
| .set = set_min_down_delay_ms, |
| .get = param_get_ulong, |
| }; |
| module_param_cb(min_down_delay_ms, ¶m_ops_big_min_down_delay_ms, |
| &big_min_down_delay_ms, 0644); |
| |
| #define MAX_STR_LEN 16 |
| static char big_min_freq_cluster[MAX_STR_LEN]; |
| static struct kparam_string big_min_freq_cluster_kps = { |
| .string = big_min_freq_cluster, |
| .maxlen = MAX_STR_LEN, |
| }; |
| |
| static int set_big_min_freq_cluster(const char *buf, |
| const struct kernel_param *kp) |
| { |
| struct big_min_freq_adjust_data *p = &big_min_freq_adjust_data; |
| int ret; |
| |
| if (p->big_min_freq_on == true) { |
| ret = -EPERM; |
| goto err; |
| } |
| |
| ret = param_set_copystring(buf, kp); |
| if (ret) |
| goto err; |
| |
| ret = cpulist_parse(big_min_freq_cluster_kps.string, |
| &p->cluster_cpumask); |
| if (ret) { |
| cpumask_clear(&p->cluster_cpumask); |
| goto err; |
| } |
| |
| return 0; |
| err: |
| pr_err("Unable to set big_min_freq_cluster: %d\n", ret); |
| return ret; |
| } |
| |
| static const struct kernel_param_ops param_ops_big_min_freq_cluster = { |
| .set = set_big_min_freq_cluster, |
| .get = param_get_string, |
| }; |
| module_param_cb(min_freq_cluster, ¶m_ops_big_min_freq_cluster, |
| &big_min_freq_cluster_kps, 0644); |
| |
| static int __init big_min_freq_adjust_init(void) |
| { |
| big_min_freq_adjust_data.is_init = true; |
| if (!big_min_freq_adjust_enabled) |
| return 0; |
| |
| return enable_big_min_freq_adjust(); |
| } |
| late_initcall(big_min_freq_adjust_init); |