On Wed, Aug 05, 2020 at 03:36:21PM +0800, Dongdong Yang wrote: > From: Dongdong Yang <yangdongd...@xiaomi.com> > > The power consumption and UI response are more cared for by the portable > equipment users. USF(User Sensitive Feedback factor) auxiliary cpufreq > governor is providing more util adjustment settings to the high level > by scenario identification. > > From the view of portable equipment, screen off status usually stands > for no request from the user, however, the kernel is still expected to > notify the user in time on modem, network or powerkey events occur. In > some scenarios, such as listening to music, low power processors, such > as DSP, take more actions and CPU load requirements cut down. It would > bring more power consumption benefit if high level have interfaces to > adjust utils according to the current scenario and load. > > In addition, the portable equipment user usually heavily interact with > devices by touch, and other peripherals. The boost preemptive counts > are marking the load requirement urgent, vice versa. If such feedback > factor could be set to high level according to the scenario, it would > contribute to the power consumption and UI response. > > If no USF sysfs inode is set, and no screen on or off event, > adjust_pred_demand shall not be invoked. Once up_l0_r down_r or non_ux_r > be set, adjust_pred_demand shall be called back to update settings > according to high level scenario identification. > > We can get about 17% mean power consumption save at listening to music > with speaker on "screen off" scenario, as below statistical data from > 7766 XiaoMi devices for two weeks with non_ux_r be set: > > day1 day2 day3 day4 > count 7766.000000 7766.000000 7766.000000 7766.000000 > mean 88.035525 85.500282 83.829305 86.054997 > std 111.049980 108.258834 107.562583 108.558240 > min 0.099000 0.037000 0.067000 0.045000 > 25% 34.765500 34.021750 34.101500 34.423000 > 50% 54.950000 55.286500 54.189500 54.248500 > 75% 95.954000 93.942000 91.738000 94.0592500 > 80% 114.675000 107.430000 106.378000 108.673000 > 85% 137.851000 129.511000 127.156500 131.750750 > 90% 179.669000 170.208500 164.027000 172.348000 > 95% 272.395000 257.845500 247.750500 263.275750 > 98% 399.034500 412.170400 391.484000 402.835600 > > day5 day6 day7 day8 > count 7766.000000 7766.00000 7766.000000 7766.000000 > mean 82.532677 79.21923 77.611380 81.075081 > std 104.870079 101.34819 103.140037 97.506221 > min 0.051000 0.02900 0.007000 0.068000 > 25% 32.873000 33.44400 31.965500 33.863500 > 50% 52.180500 51.56550 50.806500 53.080000 > 75% 90.905750 86.82625 83.859250 89.973000 > 80% 105.455000 99.64700 97.271000 104.225000 > 85% 128.300000 118.47825 116.570250 126.648250 > 90% 166.647500 149.18000 150.649500 161.087000 > 95% 247.208500 224.36050 226.380000 245.291250 > 98% 393.002000 347.92060 369.791800 378.778600 > > day9 day10 day11 day12 > count 7766.000000 7766.000000 7766.000000 7766.000000 > mean 79.989170 83.859417 78.032930 77.060542 > std 104.226122 108.893043 102.561715 99.844276 > min 0.118000 0.017000 0.028000 0.039000 > 25% 32.056250 33.454500 31.176250 30.897750 > 50% 51.506000 54.056000 48.969500 49.069000 > 75% 88.513500 92.953500 83.506750 84.096000 > 80% 102.876000 107.845000 97.717000 98.073000 > 85% 124.363000 128.288000 118.366500 116.869250 > 90% 160.557000 167.084000 154.342500 148.187500 > 95% 231.149000 242.925750 236.759000 228.131250 > 98% 367.206600 388.619100 385.269100 376.541600 > > day13 day14 > count 7766.000000 7766.000000 > mean 75.528036 73.702878 > std 90.750594 86.796016 > min 0.066000 0.054000 > 25% 31.170500 31.608500 > 50% 48.758500 49.215000 > 75% 84.522750 83.053000 > 80% 97.879000 94.875000 > 85% 116.680250 113.573750 > 90% 149.083500 144.089500 > 95% 226.177750 211.488750 > 98% 347.011100 331.317100 > > Signed-off-by: Dongdong Yang <yangdongd...@xiaomi.com> > Co-developed-by: Jun Tao <tao...@xiaomi.com> > Co-developed-by: Qiwu Huang <huangq...@xiaomi.com> > Co-developed-by: Peng Wang <rock...@linux.alibaba.com> > Signed-off-by: Dongdong Yang <yangdongd...@xiaomi.com> > --- > Documentation/ABI/testing/sysfs-devices-system-cpu | 31 ++ > drivers/cpufreq/Kconfig | 11 + > kernel/sched/Makefile | 1 + > kernel/sched/cpufreq_schedutil.c | 5 + > kernel/sched/usf.c | 314 > +++++++++++++++++++++ > 5 files changed, 362 insertions(+) > create mode 100644 kernel/sched/usf.c > > diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu > b/Documentation/ABI/testing/sysfs-devices-system-cpu > index b555df8..e9a4cfd 100644 > --- a/Documentation/ABI/testing/sysfs-devices-system-cpu > +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu > @@ -614,3 +614,34 @@ Description: SPURR ticks for cpuX when it was idle. > > This sysfs interface exposes the number of SPURR ticks > for cpuX when it was idle. > + > +What: /sys/devices/system/cpu/sched_usf > + /sys/devices/system/cpu/sched_usf/non_ux_r > + /sys/devices/system/cpu/sched_usf/up_l0_r > + /sys/devices/system/cpu/sched_usf/down_r > +Date: Aug 2020 > +Contact: Linux kernel mailing list <linux-ker...@vger.kernel.org> > +Description: User Sensitive Feedback factor auxiliary scheduling which > + is providing more util adjustment settings based on schedutil > + governor to the high level by scenario identification on > + portable equipment. > + non_ux_r: > + The default value is 0. The range is [-100 , 0]. > + If it falls into [-50, 0), the half of utils, which > + calculates cpufreq, shall be cut down on screen off. > + If it falls into [-100, -50), only a quarter of utils > + are left to continue to calculate cpufreq on screen off. > + > + up_l0_r: > + The default value is 0. The range is [0 , 100]. > + If it falls into (0, 50], a quarter of extra utils, > + which calculate cpufreq, shall be added on screen on. > + If it falls into (50, 100], the half of extra utils are > + added to continue to calculate cpufreq on screen on. > + > + down_r: > + The default value is 0. The range is [-100 , 0]. > + If it falls into [-50, 0), the half of utils, which > + calculate cpufreq, shall be cut down on screen on. > + If it falls into [-100, -50), only a quarter of utils > + are left to continue to calculate cpufreq on screen on. > diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig > index e917501..a21c6ad 100644 > --- a/drivers/cpufreq/Kconfig > +++ b/drivers/cpufreq/Kconfig > @@ -224,6 +224,17 @@ config CPUFREQ_DT_PLATDEV > > If in doubt, say N. > > +config SCHED_USF > + bool "User Sensitive Factors for Scheduler" > + depends on CPU_FREQ_GOV_SCHEDUTIL && FB > + help > + Select this option to enable the adjustment on the cpufreq with > + the user sensitive factors on schedule. It is special for mobile > + devices which more power care and quick response requirement on > + screen on. > + > + If unsure, say N. > + > if X86 > source "drivers/cpufreq/Kconfig.x86" > endif > diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile > index 5fc9c9b..58a0e7b 100644 > --- a/kernel/sched/Makefile > +++ b/kernel/sched/Makefile > @@ -36,3 +36,4 @@ obj-$(CONFIG_CPU_FREQ_GOV_SCHEDUTIL) += cpufreq_schedutil.o > obj-$(CONFIG_MEMBARRIER) += membarrier.o > obj-$(CONFIG_CPU_ISOLATION) += isolation.o > obj-$(CONFIG_PSI) += psi.o > +obj-$(CONFIG_SCHED_USF) += usf.o > diff --git a/kernel/sched/cpufreq_schedutil.c > b/kernel/sched/cpufreq_schedutil.c > index 7fbaee2..6f9cb6c 100644 > --- a/kernel/sched/cpufreq_schedutil.c > +++ b/kernel/sched/cpufreq_schedutil.c > @@ -289,12 +289,17 @@ unsigned long schedutil_cpu_util(int cpu, unsigned long > util_cfs, > return min(max, util); > } > > +void (*adjust_pred_demand_p)(int cpuid, unsigned long *util, > + struct rq *rq) = NULL;
Remove the _p. We all know this is a pointer already without the Hungarian notation. > + > static unsigned long sugov_get_util(struct sugov_cpu *sg_cpu) > { > struct rq *rq = cpu_rq(sg_cpu->cpu); > unsigned long util = cpu_util_cfs(rq); > unsigned long max = arch_scale_cpu_capacity(sg_cpu->cpu); > > + if (unlikely(adjust_pred_demand_p)) > + adjust_pred_demand_p(sg_cpu->cpu, &util, rq); > sg_cpu->max = max; > sg_cpu->bw_dl = cpu_bw_dl(rq); > > diff --git a/kernel/sched/usf.c b/kernel/sched/usf.c > new file mode 100644 > index 0000000..f3183f1 > --- /dev/null > +++ b/kernel/sched/usf.c > @@ -0,0 +1,314 @@ > +/* > + * Copyright (C) 2020 XiaoMi Inc. > + * Author: Yang Dongdong <yangdongd...@xiaomi.com> > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License 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 http://www.gnu.org/licenses/gpl-2.0.html for more details. > + */ > + > +#include <linux/module.h> > +#include <linux/init.h> > +#include <linux/platform_device.h> > +#include <linux/kthread.h> > +#include <linux/cpu.h> > +#include <linux/sysfs.h> > +#include <linux/kthread.h> > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/kallsyms.h> > +#include <linux/fb.h> > +#include <linux/notifier.h> > +#include "sched.h" > + > +#define BOOST_MIN_V -100 > +#define BOOST_MAX_V 100 > +#define LEVEL_TOP 3 > + > +extern void (*adjust_pred_demand_p)(int cpuid, > + unsigned long *util, struct rq *rq); > +DEFINE_PER_CPU(unsigned long[PID_MAX_DEFAULT], task_hist_nivcsw); > + > +static struct { > + bool is_enabled; > + bool is_screen_on; > + int sysctl_up_l0; > + int sysctl_down; > + int sysctl_non_ux; I don't understand the point of sysctl_up_l0, sysctl_down and sysctl_non_ux. They are a value from BOOST_MIN_V-BOOST_MAX_V but we only check them against zero/non-zero. Am I missing something? > + int usf_up_l0; > + int usf_down; > + int usf_non_ux; > +} usf_vdev; > + > +void adjust_pred_demand(int cpuid, > + unsigned long *util, > + struct rq *rq) > +{ > + /* > + * The initial value of bl_sw_num is the ratio of > + * sysctl_sched_latency/sysctl_sched_min_granularity. > + * It stands for the basic acceptable fluency. > + */ > + u32 bl_sw_num = 3; > + > + if (!usf_vdev.is_enabled || !rq || !rq->curr || > + (rq->curr->pid >= PID_MAX_DEFAULT)) > + return; Please indent like this: if (!usf_vdev.is_enabled || !rq || !rq->curr || (rq->curr->pid >= PID_MAX_DEFAULT)) return; > + /* > + * usf_non_ux: > + * It comes from non_ux_r, which is the ratio of utils > + * cut down on screen off. There are 3 levels. The default > + * value is 0, which no util is adjusted on calculating > + * utils to select cpufreq. If non_ux_r falls into [-50, 0), > + * usf_non_ux equals 1, and a half of utils, which calculates > + * cpufreq, shall be cut down. If non_ux_r falls into > + * [-100, -50), usf_non_ux equals to 2, only a quarter of > + * utils are left to continue to calculate cpufreq. > + * > + * usf_up_l0: > + * It comes from sysfs up_l0, which is the ratio of utils > + * boost up on screen on. There are 3 levels. The default > + * value is 0, which no util is adjusted when cpufreq be > + * calculated according it. If up_l0 falls into (0, 50], > + * usf_up_l0 equals to 2. And a quarter of extra utils, > + * which calculate cpufreq, shall be added. If up_l0 falls > + * into (50, 100], usf_up_l0 equals to 1. And the half of > + * extra utils are added to continue to calculate cpufreq. > + * > + * usf_down: > + * It comes from down_r, which is the ratio of utils cut > + * down on screen on. There are 3 levels. The default value > + * is 0, which no util is adjusted on calculating utils to > + * select cpufreq. If down_r falls into [-50, 0), usf_down > + * equals to 1, and a half of utils, which calculate cpufreq > + * shall be cut down. If down_r falls into [-100, -50) > + * usf_down equals to 2, and only a quarter of utils are > + * left to continue to calculate cpufreq. > + */ > + if (usf_vdev.is_screen_on) { > + if (rq->curr->nivcsw > > + (per_cpu(task_hist_nivcsw, cpuid)[rq->curr->pid] > + + bl_sw_num + 1)) { Put the + on the first line: if (rq->curr->nivcsw > (per_cpu(task_hist_nivcsw, cpuid)[rq->curr->pid] + bl_sw_num + 1)) { > + (*util) += (*util) >> usf_vdev.usf_up_l0; > + } else if (rq->curr->nivcsw < > + (per_cpu(task_hist_nivcsw, cpuid)[rq->curr->pid] > + + bl_sw_num - 1) && (rq->nr_running < bl_sw_num)) { Plus on the first line. > + (*util) >>= usf_vdev.usf_down; > + } > + per_cpu(task_hist_nivcsw, cpuid)[rq->curr->pid] = > + rq->curr->nivcsw; > + } else if (rq->curr->mm) { > + (*util) >>= usf_vdev.usf_non_ux; > + } > +} > + > +static int usf_lcd_notifier(struct notifier_block *nb, > + unsigned long val, void *data) > +{ > + struct fb_event *evdata = data; > + unsigned int blank; > + > + if (!evdata) > + return 0; Should this be return NOTIFY_DONE? > + > + if (val != FB_EVENT_BLANK) > + return 0; > + > + if (evdata->data && val == FB_EVENT_BLANK) { The FB_EVENT_BLANK check is duplicated from the line before. Remove it and flip the condition around. if (!evdata->data) return NOTIFY_DONE; Then we can pull everything in one indent level. > + blank = *(int *)(evdata->data); > + > + switch (blank) { > + case FB_BLANK_POWERDOWN: > + usf_vdev.is_screen_on = false; > + if (usf_vdev.sysctl_non_ux != 0) > + adjust_pred_demand_p = adjust_pred_demand; > + else > + adjust_pred_demand_p = NULL; > + > + break; > + > + case FB_BLANK_UNBLANK: > + usf_vdev.is_screen_on = true; > + if (usf_vdev.sysctl_up_l0 != 0 || > + usf_vdev.sysctl_down != 0) > + adjust_pred_demand_p = adjust_pred_demand; > + else > + adjust_pred_demand_p = NULL; > + break; > + default: > + break; > + } > + > + usf_vdev.is_enabled = true; > + pr_info("%s : usf_vdev.is_screen_on:%b\n", > + __func__, usf_vdev.is_screen_on); I don't think you want to print this every time the notifier is called. > + } > + return NOTIFY_OK; > +} > + > +static struct notifier_block usf_lcd_nb = { > + .notifier_call = usf_lcd_notifier, > + .priority = INT_MAX, > +}; > + > +static ssize_t up_l0_r_store(struct device *kobj, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + int val = 0; > + int ret = 0; Delete both of these unused initializers. > + > + ret = kstrtoint(buf, 0, &val); > + if (ret) > + return ret; > + > + if (val == 0) { > + usf_vdev.sysctl_up_l0 = val; > + usf_vdev.usf_up_l0 = 0; Set ret on this path? > + } else if ((val > 0) && (val <= BOOST_MAX_V)) { > + usf_vdev.sysctl_up_l0 = val; > + usf_vdev.usf_up_l0 = LEVEL_TOP - > + DIV_ROUND_UP(val, BOOST_MAX_V / 2); > + ret = count; > + } else { > + pr_err("USF BUG: %d should fall into [%d %d]", > + val, 0, BOOST_MAX_V); > + ret = -EINVAL; I really wish this just returned when we passed invalid data instead of setting adjust_pred_demand_p = NULL; > + } > + if ((usf_vdev.sysctl_up_l0 == 0) && > + (usf_vdev.sysctl_down == 0)) > + adjust_pred_demand_p = NULL; > + else > + adjust_pred_demand_p = adjust_pred_demand; > + > + return ret; > +} > + > +static ssize_t down_r_store(struct device *kobj, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + int val = 0; > + int ret = 0; Delete initializers. > + > + ret = kstrtoint(buf, 0, &val); > + if (ret) > + return ret; > + > + if ((val >= BOOST_MIN_V) && (val <= 0)) { > + usf_vdev.sysctl_down = val; > + usf_vdev.usf_down = DIV_ROUND_UP(-val, -BOOST_MIN_V / 2); > + ret = count; > + } else { > + pr_err("USF BUG: %d should fall into [%d %d]", > + val, BOOST_MIN_V, 0); > + ret = -EINVAL; > + } > + if ((usf_vdev.sysctl_up_l0 == 0) && > + (usf_vdev.sysctl_down == 0)) > + adjust_pred_demand_p = NULL; > + else > + adjust_pred_demand_p = adjust_pred_demand; > + > + return ret; > +} > + > +static ssize_t non_ux_r_store(struct device *kobj, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + int val = 0; > + int ret = 0; Delete initializers. > + > + ret = kstrtoint(buf, 0, &val); > + if (ret) > + return ret; > + > + if ((val >= BOOST_MIN_V) && (val <= 0)) { > + usf_vdev.sysctl_non_ux = val; > + usf_vdev.usf_non_ux = DIV_ROUND_UP(-val, -BOOST_MIN_V / 2); > + ret = count; > + } else { > + pr_err("USF BUG: %d should fall into [%d %d]", > + val, BOOST_MIN_V, 0); > + ret = -EINVAL; > + } > + if (usf_vdev.sysctl_non_ux == 0) > + adjust_pred_demand_p = NULL; > + else > + adjust_pred_demand_p = adjust_pred_demand; > + > + return ret; > +} > + > +#define usf_attr_rw(_name) \ > +static struct device_attribute _name = > \ > +__ATTR_RW(_name) > + > +#define usf_show_node(_name, _value) \ > +static ssize_t _name##_show \ > +(struct device *kobj, struct device_attribute *attr, char *buf) > \ > +{ \ > + return sprintf(buf, "%d", usf_vdev.sysctl_##_value); \ > +} > + > +usf_show_node(up_l0_r, up_l0); > +usf_show_node(down_r, down); > +usf_show_node(non_ux_r, non_ux); > + > +usf_attr_rw(up_l0_r); > +usf_attr_rw(down_r); > +usf_attr_rw(non_ux_r); > + > +static struct attribute *sched_usf_attrs[] = { > + &up_l0_r.attr, > + &down_r.attr, > + &non_ux_r.attr, > + NULL, > +}; > + > +ATTRIBUTE_GROUPS(sched_usf); > + > +static int __init intera_monitor_init(void) > +{ > + int res = -1; Delete initializer > + struct device *dev; Get rid of the dev variable and use cpu_subsys.dev_root directly. > + > + res = fb_register_client(&usf_lcd_nb); > + if (res < 0) { > + pr_err("Failed to register usf_lcd_nb!\n"); > + return res; > + } > + > + /* > + * create a sched_usf in cpu_subsys: > + * /sys/devices/system/cpu/sched_usf/... > + */ > + dev = cpu_subsys.dev_root; > + res = sysfs_create_group(&dev->kobj, &sched_usf_group); > + if (res) { > + fb_unregister_client(&usf_lcd_nb); > + return res; > + } > + > + return res; "return 0;" is more readable than "return res;" > +} > + > +module_init(intera_monitor_init); > + > +static void __exit intera_monitor_exit(void) > +{ > + struct device *dev; Get rid of the dev variable. > + > + dev = cpu_subsys.dev_root; > + sysfs_remove_group(&dev->kobj, &sched_usf_group); > + fb_unregister_client(&usf_lcd_nb); > + adjust_pred_demand_p = NULL; I'm pretty sure this is not required. Delete this line. regards, dan carpenter _______________________________________________ devel mailing list de...@linuxdriverproject.org http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel