From 69c029d50474dba758193a0ea1f427fc549da22c Mon Sep 17 00:00:00 2001 From: wloot Date: Sun, 31 Mar 2019 16:37:30 +0800 Subject: [PATCH 1/7] Partial revert "treewide: Revert all sultan's boost drives" This reverts commit 989d23515c5f4b7641c5d781d7d38adc5b73e202. --- arch/arm64/configs/chiron_defconfig | 8 +- arch/arm64/configs/sagit_defconfig | 8 +- drivers/cpufreq/Kconfig | 38 +++ drivers/cpufreq/Makefile | 3 + drivers/cpufreq/cpu_input_boost.c | 371 +++++++++++++++++++++++++ drivers/devfreq/Kconfig | 33 +++ drivers/devfreq/Makefile | 3 + drivers/devfreq/devfreq.c | 17 +- drivers/devfreq/devfreq_boost.c | 411 ++++++++++++++++++++++++++++ drivers/devfreq/devfreq_devbw.c | 4 + drivers/devfreq/governor.h | 3 - include/linux/cpu_input_boost.h | 20 ++ include/linux/devfreq.h | 10 + include/linux/devfreq_boost.h | 34 +++ 14 files changed, 954 insertions(+), 9 deletions(-) create mode 100644 drivers/cpufreq/cpu_input_boost.c create mode 100644 drivers/devfreq/devfreq_boost.c create mode 100644 include/linux/cpu_input_boost.h create mode 100644 include/linux/devfreq_boost.h diff --git a/arch/arm64/configs/chiron_defconfig b/arch/arm64/configs/chiron_defconfig index 561e2292bfba..d5c16d9d7538 100644 --- a/arch/arm64/configs/chiron_defconfig +++ b/arch/arm64/configs/chiron_defconfig @@ -88,7 +88,10 @@ CONFIG_CPU_FREQ=y CONFIG_CPU_FREQ_GOV_POWERSAVE=y CONFIG_CPU_FREQ_GOV_USERSPACE=y CONFIG_CPU_FREQ_GOV_INTERACTIVE=y -CONFIG_CPU_BOOST=y +CONFIG_CPU_INPUT_BOOST=y +CONFIG_INPUT_BOOST_DURATION_MS=64 +CONFIG_INPUT_BOOST_FREQ_LP=825600 +CONFIG_INPUT_BOOST_FREQ_PERF=422400 CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y @@ -626,6 +629,9 @@ CONFIG_DEVFREQ_GOV_QCOM_BW_HWMON=y CONFIG_QCOM_DEVFREQ_DEVBW=y CONFIG_SPDM_SCM=y CONFIG_DEVFREQ_SPDM=y +CONFIG_DEVFREQ_BOOST=y +CONFIG_DEVFREQ_INPUT_BOOST_DURATION_MS=64 +CONFIG_DEVFREQ_MSM_CPUBW_BOOST_FREQ=4173 CONFIG_EXTCON=y CONFIG_IIO=y CONFIG_QCOM_RRADC=y diff --git a/arch/arm64/configs/sagit_defconfig b/arch/arm64/configs/sagit_defconfig index dc64f9a9ae72..402f5419b804 100644 --- a/arch/arm64/configs/sagit_defconfig +++ b/arch/arm64/configs/sagit_defconfig @@ -88,7 +88,10 @@ CONFIG_CPU_FREQ=y CONFIG_CPU_FREQ_GOV_POWERSAVE=y CONFIG_CPU_FREQ_GOV_USERSPACE=y CONFIG_CPU_FREQ_GOV_INTERACTIVE=y -CONFIG_CPU_BOOST=y +CONFIG_CPU_INPUT_BOOST=y +CONFIG_INPUT_BOOST_DURATION_MS=64 +CONFIG_INPUT_BOOST_FREQ_LP=825600 +CONFIG_INPUT_BOOST_FREQ_PERF=422400 CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y @@ -626,6 +629,9 @@ CONFIG_DEVFREQ_GOV_QCOM_BW_HWMON=y CONFIG_QCOM_DEVFREQ_DEVBW=y CONFIG_SPDM_SCM=y CONFIG_DEVFREQ_SPDM=y +CONFIG_DEVFREQ_BOOST=y +CONFIG_DEVFREQ_INPUT_BOOST_DURATION_MS=64 +CONFIG_DEVFREQ_MSM_CPUBW_BOOST_FREQ=4173 CONFIG_EXTCON=y CONFIG_IIO=y CONFIG_QCOM_RRADC=y diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index d345ce59629c..b755a8e7b567 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -253,6 +253,44 @@ config CPU_FREQ_GOV_SCHEDUTIL If in doubt, say N. +config CPU_INPUT_BOOST + bool "CPU Input Boost" + help + Boosts the CPU on touchscreen and touchpad input, and allows for + boosting on other custom events, mainly which is intended to be for + boosting when there is a new frame ready to be rendered to the + display. The boost frequencies for this driver should be set so that + frame drops are near-zero at the boosted frequencies and power + consumption is minimized at said frequency combination. + +if CPU_INPUT_BOOST + +config INPUT_BOOST_DURATION_MS + int "Input boost duration" + default "100" + help + Input boost duration in milliseconds. + +config WAKE_BOOST_DURATION_MS + int "Wake boost duration" + default "1000" + help + Wake boost duration in milliseconds. + +config INPUT_BOOST_FREQ_LP + int "Low-power cluster boost freq" + default "0" + help + Input boost frequency for the low-power CPU cluster. + +config INPUT_BOOST_FREQ_PERF + int "Performance cluster boost freq" + default "0" + help + Input boost frequency for the performance CPU cluster. + +endif + comment "CPU frequency scaling drivers" config CPUFREQ_DT diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 4f9ba8eb0130..063ba32a9422 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -17,6 +17,9 @@ obj-$(CONFIG_CPU_FREQ_GOV_INTERACTIVE) += cpufreq_interactive.o obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o obj-$(CONFIG_CPU_BOOST) += cpu-boost.o +# CPU Input Boost +obj-$(CONFIG_CPU_INPUT_BOOST) += cpu_input_boost.o + obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt.o ################################################################################## diff --git a/drivers/cpufreq/cpu_input_boost.c b/drivers/cpufreq/cpu_input_boost.c new file mode 100644 index 000000000000..ff8ffdd03e54 --- /dev/null +++ b/drivers/cpufreq/cpu_input_boost.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Sultan Alsawaf . + */ + +#define pr_fmt(fmt) "cpu_input_boost: " fmt + +#include +#include +#include +#include +#include + +/* Available bits for boost_drv state */ +#define SCREEN_AWAKE BIT(0) +#define INPUT_BOOST BIT(1) +#define WAKE_BOOST BIT(2) +#define MAX_BOOST BIT(3) + +struct boost_drv { + struct workqueue_struct *wq; + struct work_struct input_boost; + struct delayed_work input_unboost; + struct work_struct max_boost; + struct delayed_work max_unboost; + struct notifier_block cpu_notif; + struct notifier_block fb_notif; + atomic64_t max_boost_expires; + atomic_t max_boost_dur; + atomic_t state; +}; + +static struct boost_drv *boost_drv_g __read_mostly; + +static u32 get_boost_freq(struct boost_drv *b, u32 cpu) +{ + if (cpumask_test_cpu(cpu, cpu_lp_mask)) + return CONFIG_INPUT_BOOST_FREQ_LP; + + return CONFIG_INPUT_BOOST_FREQ_PERF; +} + +static u32 get_boost_state(struct boost_drv *b) +{ + return atomic_read(&b->state); +} + +static void set_boost_bit(struct boost_drv *b, u32 state) +{ + atomic_or(state, &b->state); +} + +static void clear_boost_bit(struct boost_drv *b, u32 state) +{ + atomic_andnot(state, &b->state); +} + +static void update_online_cpu_policy(void) +{ + u32 cpu; + + /* Only one CPU from each cluster needs to be updated */ + get_online_cpus(); + cpu = cpumask_first_and(cpu_lp_mask, cpu_online_mask); + cpufreq_update_policy(cpu); + cpu = cpumask_first_and(cpu_perf_mask, cpu_online_mask); + cpufreq_update_policy(cpu); + put_online_cpus(); +} + +static void unboost_all_cpus(struct boost_drv *b) +{ + if (!cancel_delayed_work_sync(&b->input_unboost) && + !cancel_delayed_work_sync(&b->max_unboost)) + return; + + clear_boost_bit(b, INPUT_BOOST | WAKE_BOOST | MAX_BOOST); + update_online_cpu_policy(); +} + +void cpu_input_boost_kick(void) +{ + struct boost_drv *b = boost_drv_g; + + if (!b) + return; + + if (!(get_boost_state(b) & SCREEN_AWAKE)) + return; + + queue_work(b->wq, &b->input_boost); +} + +static void __cpu_input_boost_kick_max(struct boost_drv *b, + unsigned int duration_ms) +{ + unsigned long curr_expires, new_expires; + + do { + curr_expires = atomic64_read(&b->max_boost_expires); + new_expires = jiffies + msecs_to_jiffies(duration_ms); + + /* Skip this boost if there's a longer boost in effect */ + if (time_after(curr_expires, new_expires)) + return; + } while (atomic64_cmpxchg(&b->max_boost_expires, curr_expires, + new_expires) != curr_expires); + + atomic_set(&b->max_boost_dur, duration_ms); + queue_work(b->wq, &b->max_boost); +} + +void cpu_input_boost_kick_max(unsigned int duration_ms) +{ + struct boost_drv *b = boost_drv_g; + + if (!b) + return; + + if (!(get_boost_state(b) & SCREEN_AWAKE)) + return; + + __cpu_input_boost_kick_max(b, duration_ms); +} + +static void input_boost_worker(struct work_struct *work) +{ + struct boost_drv *b = container_of(work, typeof(*b), input_boost); + + if (!cancel_delayed_work_sync(&b->input_unboost)) { + set_boost_bit(b, INPUT_BOOST); + update_online_cpu_policy(); + } + + queue_delayed_work(b->wq, &b->input_unboost, + msecs_to_jiffies(CONFIG_INPUT_BOOST_DURATION_MS)); +} + +static void input_unboost_worker(struct work_struct *work) +{ + struct boost_drv *b = container_of(to_delayed_work(work), + typeof(*b), input_unboost); + + clear_boost_bit(b, INPUT_BOOST); + update_online_cpu_policy(); +} + +static void max_boost_worker(struct work_struct *work) +{ + struct boost_drv *b = container_of(work, typeof(*b), max_boost); + + if (!cancel_delayed_work_sync(&b->max_unboost)) { + set_boost_bit(b, MAX_BOOST); + update_online_cpu_policy(); + } + + queue_delayed_work(b->wq, &b->max_unboost, + msecs_to_jiffies(atomic_read(&b->max_boost_dur))); +} + +static void max_unboost_worker(struct work_struct *work) +{ + struct boost_drv *b = container_of(to_delayed_work(work), + typeof(*b), max_unboost); + + clear_boost_bit(b, WAKE_BOOST | MAX_BOOST); + update_online_cpu_policy(); +} + +static int cpu_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct boost_drv *b = container_of(nb, typeof(*b), cpu_notif); + struct cpufreq_policy *policy = data; + u32 boost_freq, state; + + if (action != CPUFREQ_ADJUST) + return NOTIFY_OK; + + state = get_boost_state(b); + + /* Boost CPU to max frequency for max boost */ + if (state & MAX_BOOST) { + policy->min = policy->max; + return NOTIFY_OK; + } + + /* + * Boost to policy->max if the boost frequency is higher. When + * unboosting, set policy->min to the absolute min freq for the CPU. + */ + if (state & INPUT_BOOST) { + boost_freq = get_boost_freq(b, policy->cpu); + policy->min = min(policy->max, boost_freq); + } else { + policy->min = policy->cpuinfo.min_freq; + } + + return NOTIFY_OK; +} + +static int fb_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct boost_drv *b = container_of(nb, typeof(*b), fb_notif); + struct fb_event *evdata = data; + int *blank = evdata->data; + + /* Parse framebuffer blank events as soon as they occur */ + if (action != FB_EARLY_EVENT_BLANK) + return NOTIFY_OK; + + /* Boost when the screen turns on and unboost when it turns off */ + if (*blank == FB_BLANK_UNBLANK) { + set_boost_bit(b, SCREEN_AWAKE); + __cpu_input_boost_kick_max(b, CONFIG_WAKE_BOOST_DURATION_MS); + } else { + clear_boost_bit(b, SCREEN_AWAKE); + unboost_all_cpus(b); + } + + return NOTIFY_OK; +} + +static void cpu_input_boost_input_event(struct input_handle *handle, + unsigned int type, unsigned int code, + int value) +{ + struct boost_drv *b = handle->handler->private; + u32 state; + + state = get_boost_state(b); + + if (!(state & SCREEN_AWAKE)) + return; + + queue_work(b->wq, &b->input_boost); +} + +static int cpu_input_boost_input_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int ret; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "cpu_input_boost_handle"; + + ret = input_register_handle(handle); + if (ret) + goto free_handle; + + ret = input_open_device(handle); + if (ret) + goto unregister_handle; + + return 0; + +unregister_handle: + input_unregister_handle(handle); +free_handle: + kfree(handle); + return ret; +} + +static void cpu_input_boost_input_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id cpu_input_boost_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 cpu_input_boost_input_handler = { + .event = cpu_input_boost_input_event, + .connect = cpu_input_boost_input_connect, + .disconnect = cpu_input_boost_input_disconnect, + .name = "cpu_input_boost_handler", + .id_table = cpu_input_boost_ids +}; + +static int __init cpu_input_boost_init(void) +{ + struct boost_drv *b; + int ret; + + b = kzalloc(sizeof(*b), GFP_KERNEL); + if (!b) + return -ENOMEM; + + b->wq = alloc_workqueue("cpu_input_boost_wq", WQ_HIGHPRI, 0); + if (!b->wq) { + ret = -ENOMEM; + goto free_b; + } + + atomic64_set(&b->max_boost_expires, 0); + INIT_WORK(&b->input_boost, input_boost_worker); + INIT_DELAYED_WORK(&b->input_unboost, input_unboost_worker); + INIT_WORK(&b->max_boost, max_boost_worker); + INIT_DELAYED_WORK(&b->max_unboost, max_unboost_worker); + atomic_set(&b->state, 0); + + b->cpu_notif.notifier_call = cpu_notifier_cb; + ret = cpufreq_register_notifier(&b->cpu_notif, CPUFREQ_POLICY_NOTIFIER); + if (ret) { + pr_err("Failed to register cpufreq notifier, err: %d\n", ret); + goto destroy_wq; + } + + cpu_input_boost_input_handler.private = b; + ret = input_register_handler(&cpu_input_boost_input_handler); + if (ret) { + pr_err("Failed to register input handler, err: %d\n", ret); + goto unregister_cpu_notif; + } + + b->fb_notif.notifier_call = fb_notifier_cb; + b->fb_notif.priority = INT_MAX; + ret = fb_register_client(&b->fb_notif); + if (ret) { + pr_err("Failed to register fb notifier, err: %d\n", ret); + goto unregister_handler; + } + + boost_drv_g = b; + + return 0; + +unregister_handler: + input_unregister_handler(&cpu_input_boost_input_handler); +unregister_cpu_notif: + cpufreq_unregister_notifier(&b->cpu_notif, CPUFREQ_POLICY_NOTIFIER); +destroy_wq: + destroy_workqueue(b->wq); +free_b: + kfree(b); + return ret; +} +late_initcall(cpu_input_boost_init); diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 459cbaee990f..7485d37c3016 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -246,6 +246,39 @@ config DEVFREQ_SPDM This driver allows any SPDM based client to vote for bandwidth. Used with the QCOM SPDM Hypervisor Governor. +config DEVFREQ_BOOST + bool "Devfreq Boost" + help + Boosts enumerated devfreq devices upon input, and allows for boosting + specific devfreq devices on other custom events. The boost frequencies + for this driver should be set so that frame drops are near-zero at the + boosted frequencies and power consumption is minimized at said + frequencies. The goal of this driver is to provide an interface to + achieve optimal device performance by requesting boosts on key events, + such as when a frame is ready to rendered to the display. + +if DEVFREQ_BOOST + +config DEVFREQ_INPUT_BOOST_DURATION_MS + int "Input boost duration" + default "100" + help + Input boost duration in milliseconds for all boostable devices. + +config DEVFREQ_WAKE_BOOST_DURATION_MS + int "Wake boost duration" + default "1000" + help + Wake boost duration in milliseconds for all boostable devices. + +config DEVFREQ_MSM_CPUBW_BOOST_FREQ + int "Boost freq for cpubw device" + default "0" + help + Boost frequency for the MSM DDR bus. + +endif + source "drivers/devfreq/event/Kconfig" endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 0fbb2b2c5296..6298b6f02081 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -28,3 +28,6 @@ obj-$(CONFIG_DEVFREQ_SPDM) += devfreq_spdm.o devfreq_spdm_debugfs.o # DEVFREQ Event Drivers obj-$(CONFIG_PM_DEVFREQ_EVENT) += event/ + +# DEVFREQ Boost +obj-$(CONFIG_DEVFREQ_BOOST) += devfreq_boost.o diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 928a34ebc81b..910890dba2b7 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -195,10 +195,15 @@ int update_devfreq(struct devfreq *devfreq) if (!devfreq->governor) return -EINVAL; - /* Reevaluate the proper frequency */ - err = devfreq->governor->get_target_freq(devfreq, &freq, &flags); - if (err) - return err; + if (devfreq->max_boost) { + /* Use the max freq for max boosts */ + freq = ULONG_MAX; + } else { + /* Reevaluate the proper frequency */ + err = devfreq->governor->get_target_freq(devfreq, &freq, &flags); + if (err) + return err; + } /* * Adjust the frequency with user freq and QoS. @@ -935,6 +940,10 @@ static ssize_t min_freq_store(struct device *dev, struct device_attribute *attr, int ret; unsigned long max; + /* Minfreq is managed by devfreq_boost */ + if (df->is_boost_device) + return count; + ret = sscanf(buf, "%lu", &value); if (ret != 1) return -EINVAL; diff --git a/drivers/devfreq/devfreq_boost.c b/drivers/devfreq/devfreq_boost.c new file mode 100644 index 000000000000..4fcbbe715246 --- /dev/null +++ b/drivers/devfreq/devfreq_boost.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2019 Sultan Alsawaf . + */ + +#define pr_fmt(fmt) "devfreq_boost: " fmt + +#include +#include +#include + +struct boost_dev { + struct workqueue_struct *wq; + struct devfreq *df; + struct work_struct input_boost; + struct delayed_work input_unboost; + struct work_struct max_boost; + struct delayed_work max_unboost; + unsigned long abs_min_freq; + unsigned long boost_freq; + unsigned long max_boost_expires; + unsigned long max_boost_jiffies; + spinlock_t lock; +}; + +struct df_boost_drv { + struct boost_dev devices[DEVFREQ_MAX]; + struct notifier_block fb_notif; + bool screen_awake; +}; + +static struct df_boost_drv *df_boost_drv_g __read_mostly; + +static void __devfreq_boost_kick(struct boost_dev *b) +{ + unsigned long flags; + + spin_lock_irqsave(&b->lock, flags); + if (!b->df) { + spin_unlock_irqrestore(&b->lock, flags); + return; + } + spin_unlock_irqrestore(&b->lock, flags); + + queue_work(b->wq, &b->input_boost); +} + +void devfreq_boost_kick(enum df_device device) +{ + struct df_boost_drv *d = df_boost_drv_g; + + if (!d) + return; + + if (!d->screen_awake) + return; + + __devfreq_boost_kick(d->devices + device); +} + +static void __devfreq_boost_kick_max(struct boost_dev *b, + unsigned int duration_ms) +{ + unsigned long flags, new_expires; + + spin_lock_irqsave(&b->lock, flags); + if (!b->df) { + spin_unlock_irqrestore(&b->lock, flags); + return; + } + + new_expires = jiffies + b->max_boost_jiffies; + if (time_after(b->max_boost_expires, new_expires)) { + spin_unlock_irqrestore(&b->lock, flags); + return; + } + b->max_boost_expires = new_expires; + b->max_boost_jiffies = msecs_to_jiffies(duration_ms); + spin_unlock_irqrestore(&b->lock, flags); + + queue_work(b->wq, &b->max_boost); +} + +void devfreq_boost_kick_max(enum df_device device, unsigned int duration_ms) +{ + struct df_boost_drv *d = df_boost_drv_g; + + if (!d) + return; + + if (!d->screen_awake) + return; + + __devfreq_boost_kick_max(d->devices + device, duration_ms); +} + +void devfreq_register_boost_device(enum df_device device, struct devfreq *df) +{ + struct df_boost_drv *d = df_boost_drv_g; + struct boost_dev *b; + unsigned long flags; + + if (!d) + return; + + df->is_boost_device = true; + + b = d->devices + device; + spin_lock_irqsave(&b->lock, flags); + b->df = df; + spin_unlock_irqrestore(&b->lock, flags); +} + +static unsigned long devfreq_abs_min_freq(struct boost_dev *b) +{ + struct devfreq *df = b->df; + int i; + + /* Reuse the absolute min freq found the first time this was called */ + if (b->abs_min_freq != ULONG_MAX) + return b->abs_min_freq; + + /* Find the lowest non-zero freq from the freq table */ + for (i = 0; i < df->profile->max_state; i++) { + unsigned int freq = df->profile->freq_table[i]; + + if (!freq) + continue; + + if (b->abs_min_freq > freq) + b->abs_min_freq = freq; + } + + /* Use zero for the absolute min freq if nothing was found */ + if (b->abs_min_freq == ULONG_MAX) + b->abs_min_freq = 0; + + return b->abs_min_freq; +} + +static void devfreq_unboost_all(struct df_boost_drv *d) +{ + int i; + + for (i = 0; i < DEVFREQ_MAX; i++) { + struct boost_dev *b = d->devices + i; + struct devfreq *df; + unsigned long flags; + + spin_lock_irqsave(&b->lock, flags); + df = b->df; + spin_unlock_irqrestore(&b->lock, flags); + + if (!df) + continue; + + cancel_work_sync(&b->max_boost); + cancel_delayed_work_sync(&b->max_unboost); + cancel_work_sync(&b->input_boost); + cancel_delayed_work_sync(&b->input_unboost); + + mutex_lock(&df->lock); + df->max_boost = false; + df->min_freq = devfreq_abs_min_freq(b); + update_devfreq(df); + mutex_unlock(&df->lock); + } +} + +static void devfreq_input_boost(struct work_struct *work) +{ + struct boost_dev *b = container_of(work, typeof(*b), input_boost); + + if (!cancel_delayed_work_sync(&b->input_unboost)) { + struct devfreq *df = b->df; + unsigned long boost_freq, flags; + + spin_lock_irqsave(&b->lock, flags); + boost_freq = b->boost_freq; + spin_unlock_irqrestore(&b->lock, flags); + + mutex_lock(&df->lock); + if (df->max_freq) + df->min_freq = min(boost_freq, df->max_freq); + else + df->min_freq = boost_freq; + update_devfreq(df); + mutex_unlock(&df->lock); + } + + queue_delayed_work(b->wq, &b->input_unboost, + msecs_to_jiffies(CONFIG_DEVFREQ_INPUT_BOOST_DURATION_MS)); +} + +static void devfreq_input_unboost(struct work_struct *work) +{ + struct boost_dev *b = container_of(to_delayed_work(work), + typeof(*b), input_unboost); + struct devfreq *df = b->df; + + mutex_lock(&df->lock); + df->min_freq = devfreq_abs_min_freq(b); + update_devfreq(df); + mutex_unlock(&df->lock); +} + +static void devfreq_max_boost(struct work_struct *work) +{ + struct boost_dev *b = container_of(work, typeof(*b), max_boost); + unsigned long boost_jiffies, flags; + + if (!cancel_delayed_work_sync(&b->max_unboost)) { + struct devfreq *df = b->df; + + mutex_lock(&df->lock); + df->max_boost = true; + update_devfreq(df); + mutex_unlock(&df->lock); + } + + spin_lock_irqsave(&b->lock, flags); + boost_jiffies = b->max_boost_jiffies; + spin_unlock_irqrestore(&b->lock, flags); + + queue_delayed_work(b->wq, &b->max_unboost, boost_jiffies); +} + +static void devfreq_max_unboost(struct work_struct *work) +{ + struct boost_dev *b = container_of(to_delayed_work(work), + typeof(*b), max_unboost); + struct devfreq *df = b->df; + + mutex_lock(&df->lock); + df->max_boost = false; + update_devfreq(df); + mutex_unlock(&df->lock); +} + +static int fb_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct df_boost_drv *d = container_of(nb, typeof(*d), fb_notif); + struct fb_event *evdata = data; + int *blank = evdata->data; + + /* Parse framebuffer blank events as soon as they occur */ + if (action != FB_EARLY_EVENT_BLANK) + return NOTIFY_OK; + + /* Boost when the screen turns on and unboost when it turns off */ + d->screen_awake = *blank == FB_BLANK_UNBLANK; + if (d->screen_awake) { + int i; + + for (i = 0; i < DEVFREQ_MAX; i++) + __devfreq_boost_kick_max(d->devices + i, + CONFIG_DEVFREQ_WAKE_BOOST_DURATION_MS); + } else { + devfreq_unboost_all(d); + } + + return NOTIFY_OK; +} + +static void devfreq_boost_input_event(struct input_handle *handle, + unsigned int type, unsigned int code, + int value) +{ + struct df_boost_drv *d = handle->handler->private; + int i; + + if (!d->screen_awake) + return; + + for (i = 0; i < DEVFREQ_MAX; i++) + __devfreq_boost_kick(d->devices + i); +} + +static int devfreq_boost_input_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int ret; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "devfreq_boost_handle"; + + ret = input_register_handle(handle); + if (ret) + goto free_handle; + + ret = input_open_device(handle); + if (ret) + goto unregister_handle; + + return 0; + +unregister_handle: + input_unregister_handle(handle); +free_handle: + kfree(handle); + return ret; +} + +static void devfreq_boost_input_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id devfreq_boost_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 devfreq_boost_input_handler = { + .event = devfreq_boost_input_event, + .connect = devfreq_boost_input_connect, + .disconnect = devfreq_boost_input_disconnect, + .name = "devfreq_boost_handler", + .id_table = devfreq_boost_ids +}; + +static int __init devfreq_boost_init(void) +{ + struct df_boost_drv *d; + struct workqueue_struct *wq; + int i, ret; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + wq = alloc_workqueue("devfreq_boost_wq", WQ_HIGHPRI, 0); + if (!wq) { + ret = -ENOMEM; + goto free_d; + } + + for (i = 0; i < DEVFREQ_MAX; i++) { + struct boost_dev *b = d->devices + i; + + b->wq = wq; + b->abs_min_freq = ULONG_MAX; + spin_lock_init(&b->lock); + INIT_WORK(&b->input_boost, devfreq_input_boost); + INIT_DELAYED_WORK(&b->input_unboost, devfreq_input_unboost); + INIT_WORK(&b->max_boost, devfreq_max_boost); + INIT_DELAYED_WORK(&b->max_unboost, devfreq_max_unboost); + } + + d->devices[DEVFREQ_MSM_CPUBW].boost_freq = + CONFIG_DEVFREQ_MSM_CPUBW_BOOST_FREQ; + + devfreq_boost_input_handler.private = d; + ret = input_register_handler(&devfreq_boost_input_handler); + if (ret) { + pr_err("Failed to register input handler, err: %d\n", ret); + goto destroy_wq; + } + + d->fb_notif.notifier_call = fb_notifier_cb; + d->fb_notif.priority = INT_MAX; + ret = fb_register_client(&d->fb_notif); + if (ret) { + pr_err("Failed to register fb notifier, err: %d\n", ret); + goto unregister_handler; + } + + df_boost_drv_g = d; + + return 0; + +unregister_handler: + input_unregister_handler(&devfreq_boost_input_handler); +destroy_wq: + destroy_workqueue(wq); +free_d: + kfree(d); + return ret; +} +subsys_initcall(devfreq_boost_init); diff --git a/drivers/devfreq/devfreq_devbw.c b/drivers/devfreq/devfreq_devbw.c index 8a836d92e542..e4bca3800958 100644 --- a/drivers/devfreq/devfreq_devbw.c +++ b/drivers/devfreq/devfreq_devbw.c @@ -29,6 +29,7 @@ #include #include #include +#include /* Has to be ULL to prevent overflow where this macro is used. */ #define MBYTE (1ULL << 20) @@ -231,6 +232,9 @@ int devfreq_add_devbw(struct device *dev) return PTR_ERR(d->df); } + if (!strcmp(dev_name(dev), "soc:qcom,cpubw")) + devfreq_register_boost_device(DEVFREQ_MSM_CPUBW, d->df); + return 0; } diff --git a/drivers/devfreq/governor.h b/drivers/devfreq/governor.h index ebde695e99c7..29215cd190e6 100644 --- a/drivers/devfreq/governor.h +++ b/drivers/devfreq/governor.h @@ -25,9 +25,6 @@ #define DEVFREQ_GOV_SUSPEND 0x4 #define DEVFREQ_GOV_RESUME 0x5 -/* Caution: devfreq->lock must be locked before calling update_devfreq */ -extern int update_devfreq(struct devfreq *devfreq); - extern void devfreq_monitor_start(struct devfreq *devfreq); extern void devfreq_monitor_stop(struct devfreq *devfreq); extern void devfreq_monitor_suspend(struct devfreq *devfreq); diff --git a/include/linux/cpu_input_boost.h b/include/linux/cpu_input_boost.h new file mode 100644 index 000000000000..722d4b8178c8 --- /dev/null +++ b/include/linux/cpu_input_boost.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Sultan Alsawaf . + */ +#ifndef _CPU_INPUT_BOOST_H_ +#define _CPU_INPUT_BOOST_H_ + +#ifdef CONFIG_CPU_INPUT_BOOST +void cpu_input_boost_kick(void); +void cpu_input_boost_kick_max(unsigned int duration_ms); +#else +static inline void cpu_input_boost_kick(void) +{ +} +static inline void cpu_input_boost_kick_max(unsigned int duration_ms) +{ +} +#endif + +#endif /* _CPU_INPUT_BOOST_H_ */ diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index b54b1a748d83..f72816cfff57 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -175,6 +175,8 @@ struct devfreq { unsigned long min_freq; unsigned long max_freq; + bool is_boost_device; + bool max_boost; bool stop_polling; /* information for device frequency transition */ @@ -250,6 +252,9 @@ struct devfreq_simple_ondemand_data { }; #endif +/* Caution: devfreq->lock must be locked before calling update_devfreq */ +extern int update_devfreq(struct devfreq *devfreq); + #else /* !CONFIG_PM_DEVFREQ */ static inline struct devfreq *devfreq_add_device(struct device *dev, struct devfreq_dev_profile *profile, @@ -320,6 +325,11 @@ static inline int devfreq_update_stats(struct devfreq *df) { return -EINVAL; } + +static inline int update_devfreq(struct devfreq *devfreq) +{ + return -EINVAL; +} #endif /* CONFIG_PM_DEVFREQ */ #endif /* __LINUX_DEVFREQ_H__ */ diff --git a/include/linux/devfreq_boost.h b/include/linux/devfreq_boost.h new file mode 100644 index 000000000000..7641c9a799d2 --- /dev/null +++ b/include/linux/devfreq_boost.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2019 Sultan Alsawaf . + */ +#ifndef _DEVFREQ_BOOST_H_ +#define _DEVFREQ_BOOST_H_ + +#include + +enum df_device { + DEVFREQ_MSM_CPUBW, + DEVFREQ_MAX +}; + +#ifdef CONFIG_DEVFREQ_BOOST +void devfreq_boost_kick(enum df_device device); +void devfreq_boost_kick_max(enum df_device device, unsigned int duration_ms); +void devfreq_register_boost_device(enum df_device device, struct devfreq *df); +#else +static inline +void devfreq_boost_kick(enum df_device device) +{ +} +static inline +void devfreq_boost_kick_max(enum df_device device, unsigned int duration_ms) +{ +} +static inline +void devfreq_register_boost_device(enum df_device device, struct devfreq *df) +{ +} +#endif + +#endif /* _DEVFREQ_BOOST_H_ */ From e1d33aa72b4127f1875b34801907d84becec55e0 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 2 Apr 2019 00:35:50 -0700 Subject: [PATCH 2/7] cpu_input_boost: Serialize everything with a master kthread Allowing multiple workers, executing in parallel on multiple CPUs no less, to modify the CPU policy as they please is quite racy and adds additional complexity to the code. Instead of letting each type of boost modify the CPU policy directly, serialize the CPU policy updates through a master kthread to make the whole boost process cleaner, faster, and far more efficient. Signed-off-by: Sultan Alsawaf --- drivers/cpufreq/cpu_input_boost.c | 152 +++++++++++++++--------------- include/linux/cpu_input_boost.h | 2 +- 2 files changed, 78 insertions(+), 76 deletions(-) diff --git a/drivers/cpufreq/cpu_input_boost.c b/drivers/cpufreq/cpu_input_boost.c index ff8ffdd03e54..f18e7178e0d1 100644 --- a/drivers/cpufreq/cpu_input_boost.c +++ b/drivers/cpufreq/cpu_input_boost.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (C) 2018 Sultan Alsawaf . + * Copyright (C) 2018-2019 Sultan Alsawaf . */ #define pr_fmt(fmt) "cpu_input_boost: " fmt @@ -9,24 +9,27 @@ #include #include #include +#include #include +#include -/* Available bits for boost_drv state */ -#define SCREEN_AWAKE BIT(0) +/* The sched_param struct is located elsewhere in newer kernels */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) +#include +#endif + +/* Available bits for boost state */ +#define SCREEN_OFF BIT(0) #define INPUT_BOOST BIT(1) -#define WAKE_BOOST BIT(2) -#define MAX_BOOST BIT(3) +#define MAX_BOOST BIT(2) struct boost_drv { - struct workqueue_struct *wq; - struct work_struct input_boost; struct delayed_work input_unboost; - struct work_struct max_boost; struct delayed_work max_unboost; struct notifier_block cpu_notif; struct notifier_block fb_notif; + wait_queue_head_t boost_waitq; atomic64_t max_boost_expires; - atomic_t max_boost_dur; atomic_t state; }; @@ -68,14 +71,15 @@ static void update_online_cpu_policy(void) put_online_cpus(); } -static void unboost_all_cpus(struct boost_drv *b) +static void __cpu_input_boost_kick(struct boost_drv *b) { - if (!cancel_delayed_work_sync(&b->input_unboost) && - !cancel_delayed_work_sync(&b->max_unboost)) + if (get_boost_state(b) & SCREEN_OFF) return; - clear_boost_bit(b, INPUT_BOOST | WAKE_BOOST | MAX_BOOST); - update_online_cpu_policy(); + set_boost_bit(b, INPUT_BOOST); + wake_up(&b->boost_waitq); + mod_delayed_work(system_unbound_wq, &b->input_unboost, + msecs_to_jiffies(CONFIG_INPUT_BOOST_DURATION_MS)); } void cpu_input_boost_kick(void) @@ -85,29 +89,31 @@ void cpu_input_boost_kick(void) if (!b) return; - if (!(get_boost_state(b) & SCREEN_AWAKE)) - return; - - queue_work(b->wq, &b->input_boost); + __cpu_input_boost_kick(b); } static void __cpu_input_boost_kick_max(struct boost_drv *b, unsigned int duration_ms) { + unsigned long boost_jiffies = msecs_to_jiffies(duration_ms); unsigned long curr_expires, new_expires; + if (get_boost_state(b) & SCREEN_OFF) + return; + do { curr_expires = atomic64_read(&b->max_boost_expires); - new_expires = jiffies + msecs_to_jiffies(duration_ms); + new_expires = jiffies + boost_jiffies; /* Skip this boost if there's a longer boost in effect */ if (time_after(curr_expires, new_expires)) return; } while (atomic64_cmpxchg(&b->max_boost_expires, curr_expires, - new_expires) != curr_expires); + new_expires) != curr_expires); - atomic_set(&b->max_boost_dur, duration_ms); - queue_work(b->wq, &b->max_boost); + set_boost_bit(b, MAX_BOOST); + wake_up(&b->boost_waitq); + mod_delayed_work(system_unbound_wq, &b->max_unboost, boost_jiffies); } void cpu_input_boost_kick_max(unsigned int duration_ms) @@ -117,54 +123,49 @@ void cpu_input_boost_kick_max(unsigned int duration_ms) if (!b) return; - if (!(get_boost_state(b) & SCREEN_AWAKE)) - return; - __cpu_input_boost_kick_max(b, duration_ms); } -static void input_boost_worker(struct work_struct *work) -{ - struct boost_drv *b = container_of(work, typeof(*b), input_boost); - - if (!cancel_delayed_work_sync(&b->input_unboost)) { - set_boost_bit(b, INPUT_BOOST); - update_online_cpu_policy(); - } - - queue_delayed_work(b->wq, &b->input_unboost, - msecs_to_jiffies(CONFIG_INPUT_BOOST_DURATION_MS)); -} - static void input_unboost_worker(struct work_struct *work) { struct boost_drv *b = container_of(to_delayed_work(work), typeof(*b), input_unboost); clear_boost_bit(b, INPUT_BOOST); - update_online_cpu_policy(); + wake_up(&b->boost_waitq); } -static void max_boost_worker(struct work_struct *work) +static void max_unboost_worker(struct work_struct *work) { - struct boost_drv *b = container_of(work, typeof(*b), max_boost); - - if (!cancel_delayed_work_sync(&b->max_unboost)) { - set_boost_bit(b, MAX_BOOST); - update_online_cpu_policy(); - } + struct boost_drv *b = container_of(to_delayed_work(work), + typeof(*b), max_unboost); - queue_delayed_work(b->wq, &b->max_unboost, - msecs_to_jiffies(atomic_read(&b->max_boost_dur))); + clear_boost_bit(b, MAX_BOOST); + wake_up(&b->boost_waitq); } -static void max_unboost_worker(struct work_struct *work) +static int cpu_boost_thread(void *data) { - struct boost_drv *b = container_of(to_delayed_work(work), - typeof(*b), max_unboost); + static const struct sched_param sched_max_rt_prio = { + .sched_priority = MAX_RT_PRIO - 1 + }; + struct boost_drv *b = data; + u32 old_state = 0; - clear_boost_bit(b, WAKE_BOOST | MAX_BOOST); - update_online_cpu_policy(); + sched_setscheduler_nocheck(current, SCHED_FIFO, &sched_max_rt_prio); + + while (!kthread_should_stop()) { + u32 curr_state; + + wait_event_interruptible(b->boost_waitq, + (curr_state = get_boost_state(b)) != old_state || + kthread_should_stop()); + + old_state = curr_state; + update_online_cpu_policy(); + } + + return 0; } static int cpu_notifier_cb(struct notifier_block *nb, @@ -179,6 +180,12 @@ static int cpu_notifier_cb(struct notifier_block *nb, state = get_boost_state(b); + /* Unboost when the screen is off */ + if (state & SCREEN_OFF) { + policy->min = policy->cpuinfo.min_freq; + return NOTIFY_OK; + } + /* Boost CPU to max frequency for max boost */ if (state & MAX_BOOST) { policy->min = policy->max; @@ -212,11 +219,11 @@ static int fb_notifier_cb(struct notifier_block *nb, /* Boost when the screen turns on and unboost when it turns off */ if (*blank == FB_BLANK_UNBLANK) { - set_boost_bit(b, SCREEN_AWAKE); + clear_boost_bit(b, SCREEN_OFF); __cpu_input_boost_kick_max(b, CONFIG_WAKE_BOOST_DURATION_MS); } else { - clear_boost_bit(b, SCREEN_AWAKE); - unboost_all_cpus(b); + set_boost_bit(b, SCREEN_OFF); + wake_up(&b->boost_waitq); } return NOTIFY_OK; @@ -227,14 +234,8 @@ static void cpu_input_boost_input_event(struct input_handle *handle, int value) { struct boost_drv *b = handle->handler->private; - u32 state; - state = get_boost_state(b); - - if (!(state & SCREEN_AWAKE)) - return; - - queue_work(b->wq, &b->input_boost); + __cpu_input_boost_kick(b); } static int cpu_input_boost_input_connect(struct input_handler *handler, @@ -312,6 +313,7 @@ static struct input_handler cpu_input_boost_input_handler = { static int __init cpu_input_boost_init(void) { + struct task_struct *boost_thread; struct boost_drv *b; int ret; @@ -319,24 +321,17 @@ static int __init cpu_input_boost_init(void) if (!b) return -ENOMEM; - b->wq = alloc_workqueue("cpu_input_boost_wq", WQ_HIGHPRI, 0); - if (!b->wq) { - ret = -ENOMEM; - goto free_b; - } - - atomic64_set(&b->max_boost_expires, 0); - INIT_WORK(&b->input_boost, input_boost_worker); INIT_DELAYED_WORK(&b->input_unboost, input_unboost_worker); - INIT_WORK(&b->max_boost, max_boost_worker); INIT_DELAYED_WORK(&b->max_unboost, max_unboost_worker); + init_waitqueue_head(&b->boost_waitq); + atomic64_set(&b->max_boost_expires, 0); atomic_set(&b->state, 0); b->cpu_notif.notifier_call = cpu_notifier_cb; ret = cpufreq_register_notifier(&b->cpu_notif, CPUFREQ_POLICY_NOTIFIER); if (ret) { pr_err("Failed to register cpufreq notifier, err: %d\n", ret); - goto destroy_wq; + goto free_b; } cpu_input_boost_input_handler.private = b; @@ -354,16 +349,23 @@ static int __init cpu_input_boost_init(void) goto unregister_handler; } + boost_thread = kthread_run(cpu_boost_thread, b, "cpu_boostd"); + if (IS_ERR(boost_thread)) { + pr_err("Failed to start CPU boost thread, err: %ld\n", + PTR_ERR(boost_thread)); + goto unregister_fb_notif; + } + boost_drv_g = b; return 0; +unregister_fb_notif: + fb_unregister_client(&b->fb_notif); unregister_handler: input_unregister_handler(&cpu_input_boost_input_handler); unregister_cpu_notif: cpufreq_unregister_notifier(&b->cpu_notif, CPUFREQ_POLICY_NOTIFIER); -destroy_wq: - destroy_workqueue(b->wq); free_b: kfree(b); return ret; diff --git a/include/linux/cpu_input_boost.h b/include/linux/cpu_input_boost.h index 722d4b8178c8..a6bbde72f394 100644 --- a/include/linux/cpu_input_boost.h +++ b/include/linux/cpu_input_boost.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (C) 2018 Sultan Alsawaf . + * Copyright (C) 2018-2019 Sultan Alsawaf . */ #ifndef _CPU_INPUT_BOOST_H_ #define _CPU_INPUT_BOOST_H_ From d69b964f65a647004aaf7e5fda042f1497b0556c Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Sun, 31 Mar 2019 16:39:07 -0700 Subject: [PATCH 3/7] cpu_input_boost: Mark boost kthread as performance critical The boost kthread is performance critical for obvious reasons. Signed-off-by: Sultan Alsawaf --- drivers/cpufreq/cpu_input_boost.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/cpu_input_boost.c b/drivers/cpufreq/cpu_input_boost.c index f18e7178e0d1..c2d17962f169 100644 --- a/drivers/cpufreq/cpu_input_boost.c +++ b/drivers/cpufreq/cpu_input_boost.c @@ -349,7 +349,8 @@ static int __init cpu_input_boost_init(void) goto unregister_handler; } - boost_thread = kthread_run(cpu_boost_thread, b, "cpu_boostd"); + boost_thread = kthread_run_perf_critical(cpu_boost_thread, b, + "cpu_boostd"); if (IS_ERR(boost_thread)) { pr_err("Failed to start CPU boost thread, err: %ld\n", PTR_ERR(boost_thread)); From fc91c432c9db8a20564c9b3f1c4faf66fab38085 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Sun, 31 Mar 2019 16:39:19 -0700 Subject: [PATCH 4/7] cpu_input_boost: Fix SPDX identifier format in header Signed-off-by: Sultan Alsawaf --- include/linux/cpu_input_boost.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/cpu_input_boost.h b/include/linux/cpu_input_boost.h index a6bbde72f394..a988039ffd7d 100644 --- a/include/linux/cpu_input_boost.h +++ b/include/linux/cpu_input_boost.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (C) 2018-2019 Sultan Alsawaf . */ From 9a5e0a9ea09d9a088f9fde64861afe4132802d8f Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Mon, 1 Apr 2019 11:53:52 -0700 Subject: [PATCH 5/7] cpu_input_boost: Allow configuration of each cluster's max-boost freq On certain CPUs, the highest frequency could be very inefficient, and thus frequently boosting to the frequency for max boosts can incur a high power cost. To mitigate this, allow configuration of the max-boost frequency for each CPU cluster, so that a more efficient frequency can be selected for max boosts. Signed-off-by: Sultan Alsawaf --- drivers/cpufreq/Kconfig | 12 +++++++++++ drivers/cpufreq/cpu_input_boost.c | 36 +++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index b755a8e7b567..451fe09ce6a7 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -289,6 +289,18 @@ config INPUT_BOOST_FREQ_PERF help Input boost frequency for the performance CPU cluster. +config MAX_BOOST_FREQ_LP + int "Low-power cluster max-boost freq" + default "0" + help + Max-boost frequency for the low-power CPU cluster. + +config MAX_BOOST_FREQ_PERF + int "Performance cluster max-boost freq" + default "0" + help + Max-boost frequency for the performance CPU cluster. + endif comment "CPU frequency scaling drivers" diff --git a/drivers/cpufreq/cpu_input_boost.c b/drivers/cpufreq/cpu_input_boost.c index c2d17962f169..1334aab7a44a 100644 --- a/drivers/cpufreq/cpu_input_boost.c +++ b/drivers/cpufreq/cpu_input_boost.c @@ -35,12 +35,28 @@ struct boost_drv { static struct boost_drv *boost_drv_g __read_mostly; -static u32 get_boost_freq(struct boost_drv *b, u32 cpu) +static u32 get_input_boost_freq(struct cpufreq_policy *policy) { - if (cpumask_test_cpu(cpu, cpu_lp_mask)) - return CONFIG_INPUT_BOOST_FREQ_LP; + u32 freq; - return CONFIG_INPUT_BOOST_FREQ_PERF; + if (cpumask_test_cpu(policy->cpu, cpu_lp_mask)) + freq = CONFIG_INPUT_BOOST_FREQ_LP; + else + freq = CONFIG_INPUT_BOOST_FREQ_PERF; + + return min(freq, policy->max); +} + +static u32 get_max_boost_freq(struct cpufreq_policy *policy) +{ + u32 freq; + + if (cpumask_test_cpu(policy->cpu, cpu_lp_mask)) + freq = CONFIG_MAX_BOOST_FREQ_LP; + else + freq = CONFIG_MAX_BOOST_FREQ_PERF; + + return min(freq, policy->max); } static u32 get_boost_state(struct boost_drv *b) @@ -173,7 +189,7 @@ static int cpu_notifier_cb(struct notifier_block *nb, { struct boost_drv *b = container_of(nb, typeof(*b), cpu_notif); struct cpufreq_policy *policy = data; - u32 boost_freq, state; + u32 state; if (action != CPUFREQ_ADJUST) return NOTIFY_OK; @@ -188,7 +204,7 @@ static int cpu_notifier_cb(struct notifier_block *nb, /* Boost CPU to max frequency for max boost */ if (state & MAX_BOOST) { - policy->min = policy->max; + policy->min = get_max_boost_freq(policy); return NOTIFY_OK; } @@ -196,12 +212,10 @@ static int cpu_notifier_cb(struct notifier_block *nb, * Boost to policy->max if the boost frequency is higher. When * unboosting, set policy->min to the absolute min freq for the CPU. */ - if (state & INPUT_BOOST) { - boost_freq = get_boost_freq(b, policy->cpu); - policy->min = min(policy->max, boost_freq); - } else { + if (state & INPUT_BOOST) + policy->min = get_input_boost_freq(policy); + else policy->min = policy->cpuinfo.min_freq; - } return NOTIFY_OK; } From 6b1eda482c3bba40f92bf7c949a41232b78b9fba Mon Sep 17 00:00:00 2001 From: wloot Date: Tue, 2 Apr 2019 19:49:52 +0800 Subject: [PATCH 6/7] defconfig: Configure taken from stock cpu_boost for cpu_input_boost --- arch/arm64/configs/chiron_defconfig | 5 ++--- arch/arm64/configs/sagit_defconfig | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/arch/arm64/configs/chiron_defconfig b/arch/arm64/configs/chiron_defconfig index d5c16d9d7538..83a0584c054d 100644 --- a/arch/arm64/configs/chiron_defconfig +++ b/arch/arm64/configs/chiron_defconfig @@ -89,9 +89,8 @@ CONFIG_CPU_FREQ_GOV_POWERSAVE=y CONFIG_CPU_FREQ_GOV_USERSPACE=y CONFIG_CPU_FREQ_GOV_INTERACTIVE=y CONFIG_CPU_INPUT_BOOST=y -CONFIG_INPUT_BOOST_DURATION_MS=64 -CONFIG_INPUT_BOOST_FREQ_LP=825600 -CONFIG_INPUT_BOOST_FREQ_PERF=422400 +CONFIG_INPUT_BOOST_DURATION_MS=40 +CONFIG_INPUT_BOOST_FREQ_LP=1324800 CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y diff --git a/arch/arm64/configs/sagit_defconfig b/arch/arm64/configs/sagit_defconfig index 402f5419b804..3dccb28edabb 100644 --- a/arch/arm64/configs/sagit_defconfig +++ b/arch/arm64/configs/sagit_defconfig @@ -89,9 +89,8 @@ CONFIG_CPU_FREQ_GOV_POWERSAVE=y CONFIG_CPU_FREQ_GOV_USERSPACE=y CONFIG_CPU_FREQ_GOV_INTERACTIVE=y CONFIG_CPU_INPUT_BOOST=y -CONFIG_INPUT_BOOST_DURATION_MS=64 -CONFIG_INPUT_BOOST_FREQ_LP=825600 -CONFIG_INPUT_BOOST_FREQ_PERF=422400 +CONFIG_INPUT_BOOST_DURATION_MS=40 +CONFIG_INPUT_BOOST_FREQ_LP=1324800 CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y From 51a2633890d955403fb9f35311c6e5cabb48036e Mon Sep 17 00:00:00 2001 From: wloot Date: Tue, 2 Apr 2019 20:31:51 +0800 Subject: [PATCH 7/7] cpu_input_boost: Boost lower when no task running for little cluster --- drivers/cpufreq/cpu_input_boost.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/cpu_input_boost.c b/drivers/cpufreq/cpu_input_boost.c index 1334aab7a44a..f0f3fefe8c64 100644 --- a/drivers/cpufreq/cpu_input_boost.c +++ b/drivers/cpufreq/cpu_input_boost.c @@ -12,6 +12,7 @@ #include #include #include +#include "../../kernel/sched/sched.h" /* The sched_param struct is located elsewhere in newer kernels */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) @@ -35,12 +36,17 @@ struct boost_drv { static struct boost_drv *boost_drv_g __read_mostly; +static __read_mostly unsigned int CONSERVATIVE_BOOST_FREQ_LP = 825600; + static u32 get_input_boost_freq(struct cpufreq_policy *policy) { u32 freq; if (cpumask_test_cpu(policy->cpu, cpu_lp_mask)) - freq = CONFIG_INPUT_BOOST_FREQ_LP; + if (cpu_rq(policy->cpu)->nr_running < 1) + return CONSERVATIVE_BOOST_FREQ_LP; + else + freq = CONFIG_INPUT_BOOST_FREQ_LP; else freq = CONFIG_INPUT_BOOST_FREQ_PERF;