/* * Copyright (c) 2013-2015,2017, 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) "cpu-boost: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include struct cpu_sync { int cpu; unsigned int input_boost_min; unsigned int input_boost_freq; unsigned int powerkey_input_boost_freq; }; enum input_boost_type { default_input_boost, powerkey_input_boost }; static DEFINE_PER_CPU(struct cpu_sync, sync_info); static struct kthread_work input_boost_work; static struct kthread_work powerkey_input_boost_work; static bool input_boost_enabled; static unsigned int input_boost_ms = 40; module_param(input_boost_ms, uint, 0644); static unsigned int powerkey_input_boost_ms = 400; module_param(powerkey_input_boost_ms, uint, 0644); static unsigned int sched_boost_on_input; module_param(sched_boost_on_input, uint, 0644); static bool sched_boost_on_powerkey_input = true; module_param(sched_boost_on_powerkey_input, bool, 0644); static bool sched_boost_active; static struct delayed_work input_boost_rem; static u64 last_input_time; static struct kthread_worker cpu_boost_worker; static struct task_struct *cpu_boost_worker_thread; static struct kthread_worker powerkey_cpu_boost_worker; static struct task_struct *powerkey_cpu_boost_worker_thread; #define MIN_INPUT_INTERVAL (100 * USEC_PER_MSEC) #define MAX_NAME_LENGTH 64 static int set_input_boost_freq(const char *buf, const struct kernel_param *kp) { int i, ntokens = 0; unsigned int val, cpu; const char *cp = buf; bool enabled = false; enum input_boost_type type; if (strnstr(kp->name, "input_boost_freq", MAX_NAME_LENGTH)) type = default_input_boost; if (strnstr(kp->name, "powerkey_input_boost_freq", MAX_NAME_LENGTH)) type = powerkey_input_boost; while ((cp = strpbrk(cp + 1, " :"))) ntokens++; /* single number: apply to all CPUs */ if (!ntokens) { if (sscanf(buf, "%u\n", &val) != 1) return -EINVAL; for_each_possible_cpu(i) { if (type == default_input_boost) per_cpu(sync_info, i).input_boost_freq = val; else if (type == powerkey_input_boost) per_cpu(sync_info, i).powerkey_input_boost_freq = val; } goto check_enable; } /* CPU:value pair */ if (!(ntokens % 2)) return -EINVAL; cp = buf; for (i = 0; i < ntokens; i += 2) { if (sscanf(cp, "%u:%u", &cpu, &val) != 2) return -EINVAL; if (cpu >= num_possible_cpus()) return -EINVAL; if (type == default_input_boost) per_cpu(sync_info, cpu).input_boost_freq = val; else if (type == powerkey_input_boost) per_cpu(sync_info, cpu).powerkey_input_boost_freq = val; cp = strnchr(cp, PAGE_SIZE - (cp - buf), ' '); cp++; } check_enable: for_each_possible_cpu(i) { if (per_cpu(sync_info, i).input_boost_freq || per_cpu(sync_info, i).powerkey_input_boost_freq) { enabled = true; break; } } input_boost_enabled = enabled; return 0; } static int get_input_boost_freq(char *buf, const struct kernel_param *kp) { int cnt = 0, cpu; struct cpu_sync *s; unsigned int boost_freq = 0; enum input_boost_type type; if (strnstr(kp->name, "input_boost_freq", MAX_NAME_LENGTH)) type = default_input_boost; if (strnstr(kp->name, "powerkey_input_boost_freq", MAX_NAME_LENGTH)) type = powerkey_input_boost; for_each_possible_cpu(cpu) { s = &per_cpu(sync_info, cpu); if (type == default_input_boost) boost_freq = s->input_boost_freq; else if(type == powerkey_input_boost) boost_freq = s->powerkey_input_boost_freq; cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, "%d:%u ", cpu, boost_freq); } cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, "\n"); return cnt; } static const struct kernel_param_ops param_ops_input_boost_freq = { .set = set_input_boost_freq, .get = get_input_boost_freq, }; module_param_cb(input_boost_freq, ¶m_ops_input_boost_freq, NULL, 0644); module_param_cb(powerkey_input_boost_freq, ¶m_ops_input_boost_freq, NULL, 0644); /* * The CPUFREQ_ADJUST notifier is used to override the current policy min to * make sure policy min >= boost_min. The cpufreq framework then does the job * of enforcing the new policy. */ static int boost_adjust_notify(struct notifier_block *nb, unsigned long val, void *data) { struct cpufreq_policy *policy = data; unsigned int cpu = policy->cpu; struct cpu_sync *s = &per_cpu(sync_info, cpu); unsigned int ib_min = s->input_boost_min; switch (val) { case CPUFREQ_ADJUST: if (!ib_min) break; ib_min = min(ib_min, policy->max); 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); cpufreq_verify_within_limits(policy, ib_min, UINT_MAX); pr_debug("CPU%u policy min after boost: %u kHz\n", cpu, policy->min); break; } return NOTIFY_OK; } static struct notifier_block boost_adjust_nb = { .notifier_call = boost_adjust_notify, }; static void update_policy_online(void) { unsigned int i; /* Re-evaluate policy to trigger adjust notifier for online CPUs */ get_online_cpus(); for_each_online_cpu(i) { /* * both clusters have synchronous cpus * no need to upldate the policy for each core * individually, saving at least one [down|up] write * and a [lock|unlock] irqrestore per pass */ if ((i & 1) == 0) { pr_debug("Updating policy for CPU%d\n", i); cpufreq_update_policy(i); } } put_online_cpus(); } 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("cpu-boost: sched boost disable failed\n"); sched_boost_active = false; } } static void do_input_boost(struct kthread_work *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_each_possible_cpu(i) { i_sync_info = &per_cpu(sync_info, i); i_sync_info->input_boost_min = i_sync_info->input_boost_freq; } /* Update policies for all online CPUs */ update_policy_online(); /* Enable scheduler boost to migrate tasks to big cluster */ if (sched_boost_on_input > 0) { ret = sched_set_boost(sched_boost_on_input); if (ret) pr_err("cpu-boost: sched boost enable failed\n"); else sched_boost_active = true; } schedule_delayed_work(&input_boost_rem, msecs_to_jiffies(input_boost_ms)); } static void do_powerkey_input_boost(struct kthread_work *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 powerkey_input_boost_min for all CPUs in the system */ pr_debug("Setting powerkey 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 = i_sync_info->powerkey_input_boost_freq; } /* Update policies for all online CPUs */ update_policy_online(); /* Enable scheduler boost to migrate tasks to big cluster */ if (sched_boost_on_powerkey_input) { ret = sched_set_boost(1); if (ret) pr_err("cpu-boost: HMP boost enable failed\n"); else sched_boost_active = true; } schedule_delayed_work(&input_boost_rem, msecs_to_jiffies(powerkey_input_boost_ms)); } static void cpuboost_input_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { u64 now; if (!input_boost_enabled || is_battery_saver_on()) return; now = ktime_to_us(ktime_get()); if (now - last_input_time < MIN_INPUT_INTERVAL) return; if (queuing_blocked(&cpu_boost_worker, &input_boost_work)) return; if ((type == EV_KEY && code == KEY_POWER) || (type == EV_KEY && code == KEY_WAKEUP)) { kthread_queue_work(&cpu_boost_worker, &powerkey_input_boost_work); } else kthread_queue_work(&cpu_boost_worker, &input_boost_work); last_input_time = ktime_to_us(ktime_get()); } static int cpuboost_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 cpuboost_input_disconnect(struct input_handle *handle) { input_close_device(handle); input_unregister_handle(handle); kfree(handle); } static const struct input_device_id cpuboost_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 cpuboost_input_handler = { .event = cpuboost_input_event, .connect = cpuboost_input_connect, .disconnect = cpuboost_input_disconnect, .name = "cpu-boost", .id_table = cpuboost_ids, }; static int cpu_boost_init(void) { int cpu, ret, i; struct cpu_sync *s; struct sched_param param = { .sched_priority = 2 }; cpumask_t sys_bg_mask; /* Hardcode the cpumask to bind the kthread to it */ cpumask_clear(&sys_bg_mask); for (i = 0; i <= 5; i++) { cpumask_set_cpu(i, &sys_bg_mask); } kthread_init_worker(&cpu_boost_worker); cpu_boost_worker_thread = kthread_create(kthread_worker_fn, &cpu_boost_worker, "cpu_boost_worker_thread"); if (IS_ERR(cpu_boost_worker_thread)) { pr_err("cpu-boost: Failed to init kworker!\n"); return -EFAULT; } ret = sched_setscheduler(cpu_boost_worker_thread, SCHED_FIFO, ¶m); if (ret) pr_err("cpu-boost: Failed to set SCHED_FIFO!\n"); kthread_init_worker(&powerkey_cpu_boost_worker); powerkey_cpu_boost_worker_thread = kthread_create(kthread_worker_fn, &powerkey_cpu_boost_worker, "powerkey_cpu_boost_worker_thread"); if (IS_ERR(powerkey_cpu_boost_worker_thread)) { pr_err("powerkey_cpu-boost: Failed to init kworker!\n"); return -EFAULT; } ret = sched_setscheduler(powerkey_cpu_boost_worker_thread, SCHED_FIFO, ¶m); if (ret) pr_err("powerkey_cpu-boost: Failed to set SCHED_FIFO!\n"); /* Now bind it to the cpumask */ kthread_bind_mask(cpu_boost_worker_thread, &sys_bg_mask); kthread_bind_mask(powerkey_cpu_boost_worker_thread, &sys_bg_mask); /* Wake it up! */ wake_up_process(cpu_boost_worker_thread); wake_up_process(powerkey_cpu_boost_worker_thread); kthread_init_work(&input_boost_work, do_input_boost); kthread_init_work(&powerkey_input_boost_work, do_powerkey_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; } cpufreq_register_notifier(&boost_adjust_nb, CPUFREQ_POLICY_NOTIFIER); ret = input_register_handler(&cpuboost_input_handler); return 0; } late_initcall(cpu_boost_init);