From 3f29dccdf14473f934a089c2a6b5c0f38b8b4745 Mon Sep 17 00:00:00 2001 From: Simo Piiroinen Date: Mon, 25 Mar 2019 09:58:47 +0200 Subject: [PATCH] [display] Add automatic cpu governor policy MCE has cpu scaling governor feature that was meant to be used for activating performance governor during bootup / shutdown and otherwise select interactive governor. It was never taken in active use as is was found to interfere with more advanced already existing android side services. However - after fixing some obvious faults - having such feature available might be useful for some device ports. Make it possible to configure the following governor modes: - performance - maximum performance - interactive - high performance - inactive - medium performance - powersave - low performance Implement policy that switches active governor based on device state. Utilize a run-time setting for: - disabling the whole feature - explicitly selecting one of the modes - automatically switching between the modes depending on device state Also provide: - an annotated example configuration file - python script for generating skeleton configuration files Signed-off-by: Simo Piiroinen --- inifiles/cpu-governor.ini | 63 ++++++++ modules/display.c | 259 ++++++++++++++++++++++-------- modules/display.h | 6 +- tools/generate-governor-config.py | 146 +++++++++++++++++ tools/mcetool.c | 22 ++- 5 files changed, 422 insertions(+), 74 deletions(-) create mode 100644 inifiles/cpu-governor.ini create mode 100644 tools/generate-governor-config.py diff --git a/inifiles/cpu-governor.ini b/inifiles/cpu-governor.ini new file mode 100644 index 00000000..53eb94a9 --- /dev/null +++ b/inifiles/cpu-governor.ini @@ -0,0 +1,63 @@ +# Configuration file for MCE - CPU Governor control data +# +# Having configuration is completely optional. +# +# Also note that defining a configuration might not work +# as expected for example when: +# - there is some other daemon adjusting frequencies +# (adjustments get overridden / otherwise out of sync) +# - sysfs control files appear/disapper dynamically +# (configured files do not exist when they would be written to) +# +# The mode can be selected explicitly or automatically based +# on device state +# +# mcetool --set-cpu-scaling-governor= +# +# Where mode is one of: +# - disabled - The feature is disabled +# - performance - "Default" configuration is applied +# - interactive - "Interactive" configuration is applied +# - inactive - "Inactive" configuration is applied +# - idle - "Idle" configuration is applied +# - automatic - Configuration is selected based on device state + +# "Default" values are applied when: +# - device is booting up / shutting down +# - mce is starting up / about to exit +[CPUScalingGovernorDefault] +path1=/sys/devices/system/cpu/cpu*/cpufreq/scaling_min_freq +data1=432000 + +# Up to 32 path/data pairs can be defined. Writing is done +# in the given order. Each block is handled as-defined, i.e. +# if it is somehow benefitial they can have different sets +# of files to write to. +#path2=xxx +#data2=yyy +# : +#path32=xxx +#data32=yyy + +# When the "Default" is not enforced, other values can be +# either selected explicitly, or automatically based on +# device state. + +# In automatic mode "Interactive" values are applied when: +# - display is on and there is user activity +# - during display power up / down +[CPUScalingGovernorInteractive] +path1=/sys/devices/system/cpu/cpu*/cpufreq/scaling_min_freq +data1=432000 + +# In automatic mode "Inactive" values are applied when: +# - display is on and there is no user activity +[CPUScalingGovernorInactive] +path1=/sys/devices/system/cpu/cpu*/cpufreq/scaling_min_freq +data1=384000 + +# In automatic mode "Idle" values are applied when: +# - display is off +[CPUScalingGovernorIdle] +path1=/sys/devices/system/cpu/cpu*/cpufreq/scaling_min_freq +data1=216000 diff --git a/modules/display.c b/modules/display.c index af8e69a9..6e831be4 100644 --- a/modules/display.c +++ b/modules/display.c @@ -891,6 +891,7 @@ static void mdy_governor_free_settings(governor_setting_t *settin static bool mdy_governor_write_data(const char *path, const char *data); static void mdy_governor_apply_setting(const governor_setting_t *setting); static void mdy_governor_set_state(int state); +static int mdy_governor_select(void); static void mdy_governor_rethink(void); static void mdy_governor_setting_cb(GConfClient *const client, const guint id, GConfEntry *const entry, gpointer const data); @@ -1702,6 +1703,7 @@ static void mdy_datapipe_display_state_curr_cb(gconstpointer data) mdy_statistics_update(); mdy_blanking_pause_evaluate_allowed(); mdy_blanking_inhibit_schedule_broadcast(); + mdy_governor_rethink(); EXIT: return; @@ -1984,6 +1986,9 @@ static void mdy_datapipe_power_saving_mode_active_cb(gconstpointer data) mce_log(LL_DEBUG, "power_saving_mode_active = %d", power_saving_mode_active); + /* Re-evaluate CPU scaling governor config */ + mdy_governor_rethink(); + /* Switch active brightness and CABC mode */ mdy_datapipe_execute_brightness(); mdy_cabc_mode_set(0); @@ -2019,7 +2024,7 @@ static void mdy_datapipe_call_state_trigger_cb(gconstpointer data) } /** Cached inactivity state */ -static gboolean device_inactive = FALSE; +static bool device_inactive = FALSE; /** Handle device_inactive_pipe notifications * @@ -2029,6 +2034,7 @@ static gboolean device_inactive = FALSE; */ static void mdy_datapipe_device_inactive_cb(gconstpointer data) { + bool prev = device_inactive; device_inactive = GPOINTER_TO_INT(data); /* while inactivity can be considered a "state", @@ -2038,9 +2044,16 @@ static void mdy_datapipe_device_inactive_cb(gconstpointer data) mce_log(LL_DEBUG, "device_inactive = %d", device_inactive); + /* Handle state changes */ + if( prev != device_inactive ) { + mdy_governor_rethink(); + } + if( device_inactive ) goto EXIT; + /* Handle activity events */ + /* Activity while adaptive dimming is primed causes * the next on->dim transition to use longer timeout */ mdy_blanking_trigger_adaptive_dimming(); @@ -8931,15 +8944,101 @@ static void mdy_statistics_update(void) static gint mdy_governor_conf = MCE_DEFAULT_CPU_SCALING_GOVERNOR; static guint mdy_governor_conf_setting_id = 0; -/** GOVERNOR_DEFAULT CPU scaling governor settings */ -static governor_setting_t *mdy_governor_default = 0; - -/** GOVERNOR_INTERACTIVE CPU scaling governor settings */ -static governor_setting_t *mdy_governor_interactive = 0; - /** Limit number of files that can be modified via settings */ #define GOVERNOR_MAX_SETTINGS 32 +static governor_setting_t *mdy_governor_data_lut[GOVERNOR_NUMOF] = { }; + +static const char * const mdy_governor_name_lut[GOVERNOR_NUMOF] = +{ + [GOVERNOR_UNSET] = "Unset", + [GOVERNOR_PERFORMANCE] = "Performance", + [GOVERNOR_INTERACTIVE] = "Interactive", + [GOVERNOR_INACTIVE] = "Inactive", + [GOVERNOR_POWERSAVE] = "Powersave", + [GOVERNOR_AUTOMATIC] = "Automatic", +}; + +static bool +mdy_governor_is_valid(int type) +{ + return (unsigned)type < (unsigned)GOVERNOR_NUMOF; +} + +static bool +mdy_governor_has_data(int type) +{ + bool has_data = false; + switch( type ) { + case GOVERNOR_PERFORMANCE: + case GOVERNOR_INTERACTIVE: + case GOVERNOR_INACTIVE: + case GOVERNOR_POWERSAVE: + has_data = true; + break; + default: + break; + } + return has_data; +} + +static const char * +mdy_governor_name(int type) +{ + const char *name = "Invalid"; + if( mdy_governor_is_valid(type) ) + name = mdy_governor_name_lut[type]; + return name; +} + +static governor_setting_t * +mdy_governor_data(int type) +{ + governor_setting_t *data = 0; + if( mdy_governor_is_valid(type) ) + data = mdy_governor_data_lut[type]; + return data; +} + +static void +mdy_governor_init(void) +{ + /* Get CPU scaling governor settings from INI-files */ + for( int type = 0; type < GOVERNOR_NUMOF; ++type ) { + if( !mdy_governor_has_data(type) ) + continue; + const char *tag = mdy_governor_name(type); + mdy_governor_data_lut[type] = mdy_governor_get_settings(tag); + } + + /* Get cpu scaling governor configuration & track changes */ + mce_setting_track_int(MCE_SETTING_CPU_SCALING_GOVERNOR, + &mdy_governor_conf, + MCE_DEFAULT_CPU_SCALING_GOVERNOR, + mdy_governor_setting_cb, + &mdy_governor_conf_setting_id); + + /* Evaluate initial state */ + mdy_governor_rethink(); +} + +static void +mdy_governor_quit(void) +{ + /* Remove cpu scaling governor change notifier */ + mce_setting_notifier_remove(mdy_governor_conf_setting_id), + mdy_governor_conf_setting_id = 0; + + /* Switch back to defaults */ + mdy_governor_rethink(); + + /* Release CPU scaling governor settings from INI-files */ + for( int type = 0; type < GOVERNOR_NUMOF; ++type ) { + mdy_governor_free_settings(mdy_governor_data_lut[type]), + mdy_governor_data_lut[type] = 0; + } +} + /** Obtain arrays of settings from mce ini-files * * Use mdy_governor_free_settings() to release data returned from this @@ -9086,8 +9185,8 @@ static bool mdy_governor_write_data(const char *path, const char *data) errno = 0, done = TEMP_FAILURE_RETRY(write(fd, data, todo)); if( done != todo ) { - mce_log(LL_WARN, "%s: wrote %d of %d bytes: %m", - dest, done, todo); + mce_log(LL_WARN, "%s: '%s' - wrote %d of %d bytes: %m", + dest, data, done, todo); goto cleanup; } @@ -9141,23 +9240,11 @@ static void mdy_governor_apply_setting(const governor_setting_t *setting) /** Switch cpu scaling governor state * - * @param state GOVERNOR_DEFAULT, GOVERNOR_DEFAULT, ... + * @param state GOVERNOR_PERFORMANCE, GOVERNOR_PERFORMANCE, ... */ static void mdy_governor_set_state(int state) { - const governor_setting_t *settings = 0; - - switch( state ) - { - case GOVERNOR_DEFAULT: - settings = mdy_governor_default; - break; - case GOVERNOR_INTERACTIVE: - settings = mdy_governor_interactive; - break; - - default: break; - } + const governor_setting_t *settings = mdy_governor_data(state); if( !settings ) { mce_log(LL_WARN, "governor state=%d has no mapping", state); @@ -9169,45 +9256,106 @@ static void mdy_governor_set_state(int state) } } +/** Select cpu scaling governor config based on policy + * + * Use GOVERNOR_POWERSAVE when display is off + * + * Use GOVERNOR_PERFORMANCE during display ramp up / down + * - to maximize smoothness of high visibility transitions + * while minimizing effects of waking up from deep sleep + * + * While display is on, switch between GOVERNOR_INTERACTIVE + * and GOVERNOR_INACTIVE based on user activity. + * + * @return governor type to use + */ +static int +mdy_governor_select(void) +{ + /* Assume GOVERNOR_INTERACTIVE and then adjust based + * on device state. */ + int type = GOVERNOR_INTERACTIVE; + + switch( display_state_curr ) { + case MCE_DISPLAY_POWER_UP: + case MCE_DISPLAY_POWER_DOWN: + type = GOVERNOR_PERFORMANCE; + break; + + case MCE_DISPLAY_OFF: + case MCE_DISPLAY_LPM_OFF: + type = GOVERNOR_POWERSAVE; + break; + + case MCE_DISPLAY_LPM_ON: + type = GOVERNOR_INACTIVE; + break; + + default: + if( device_inactive ) + type = GOVERNOR_INACTIVE; + break; + } + + return type; +} + /** Evaluate and apply CPU scaling governor policy */ static void mdy_governor_rethink(void) { static int governor_have = GOVERNOR_UNSET; - /* By default we want to use "interactive" - * cpu scaling governor, except ... */ - int governor_want = GOVERNOR_INTERACTIVE; + /* Default to: leave as-is + */ + + int governor_want = GOVERNOR_UNSET; + + if( mdy_governor_conf == GOVERNOR_UNSET ) + goto APPLY; + + /* Default to: performance + */ + + governor_want = GOVERNOR_PERFORMANCE; /* Use default when in transitional states */ if( system_state != MCE_SYSTEM_STATE_USER && - system_state != MCE_SYSTEM_STATE_ACTDEAD ) { - governor_want = GOVERNOR_DEFAULT; - } + system_state != MCE_SYSTEM_STATE_ACTDEAD ) + goto APPLY; /* Use default during bootup */ - if( mdy_desktop_ready_id || mdy_init_done != TRISTATE_TRUE ) { - governor_want = GOVERNOR_DEFAULT; - } + if( mdy_desktop_ready_id || mdy_init_done != TRISTATE_TRUE ) + goto APPLY; /* Use default during shutdown */ - if( mdy_shutdown_in_progress() ) { - governor_want = GOVERNOR_DEFAULT; - } + if( mdy_shutdown_in_progress() ) + goto APPLY; /* Restore default on unload / mce exit */ - if( mdy_unloading_module ) { - governor_want = GOVERNOR_DEFAULT; - } + if( mdy_unloading_module ) + goto APPLY; - /* Config override has been set */ - if( mdy_governor_conf != GOVERNOR_UNSET ) { + /* Act based on the configuration value + */ + if( power_saving_mode_active ) { + /* Power saving mode overrides setting */ + governor_want = GOVERNOR_POWERSAVE; + } + else if( mdy_governor_conf == GOVERNOR_AUTOMATIC ) { + /* Select according to policy */ + governor_want = mdy_governor_select(); + } + else { + /* Use explicitly selected mode */ governor_want = mdy_governor_conf; } +APPLY: /* Apply new policy state */ if( governor_have != governor_want ) { - mce_log(LL_NOTICE, "state: %d -> %d", - governor_have, governor_want); + mce_log(LL_NOTICE, "state: %s -> %s", + mdy_governor_name(governor_have), + mdy_governor_name(governor_want)); mdy_governor_set_state(governor_want); governor_have = governor_want; } @@ -11519,19 +11667,7 @@ const gchar *g_module_check_init(GModule *module) mdy_display_type_get(); #ifdef ENABLE_CPU_GOVERNOR - /* Get CPU scaling governor settings from INI-files */ - mdy_governor_default = mdy_governor_get_settings("Default"); - mdy_governor_interactive = mdy_governor_get_settings("Interactive"); - - /* Get cpu scaling governor configuration & track changes */ - mce_setting_track_int(MCE_SETTING_CPU_SCALING_GOVERNOR, - &mdy_governor_conf, - MCE_DEFAULT_CPU_SCALING_GOVERNOR, - mdy_governor_setting_cb, - &mdy_governor_conf_setting_id); - - /* Evaluate initial state */ - mdy_governor_rethink(); + mdy_governor_init(); #endif #ifdef ENABLE_WAKELOCKS @@ -11637,18 +11773,7 @@ void g_module_unload(GModule *module) #endif #ifdef ENABLE_CPU_GOVERNOR - /* Remove cpu scaling governor change notifier */ - mce_setting_notifier_remove(mdy_governor_conf_setting_id), - mdy_governor_conf_setting_id = 0; - - /* Switch back to defaults */ - mdy_governor_rethink(); - - /* Release CPU scaling governor settings from INI-files */ - mdy_governor_free_settings(mdy_governor_default), - mdy_governor_default = 0; - mdy_governor_free_settings(mdy_governor_interactive), - mdy_governor_interactive = 0; + mdy_governor_quit(); #endif /* Remove triggers/filters from datapipes */ diff --git a/modules/display.h b/modules/display.h index bfb9d4da..320619d8 100644 --- a/modules/display.h +++ b/modules/display.h @@ -546,8 +546,12 @@ enum enum { GOVERNOR_UNSET = 0, - GOVERNOR_DEFAULT = 1, + GOVERNOR_PERFORMANCE = 1, GOVERNOR_INTERACTIVE = 2, + GOVERNOR_INACTIVE = 3, + GOVERNOR_POWERSAVE = 4, + GOVERNOR_AUTOMATIC = 5, + GOVERNOR_NUMOF }; /** Select cpu scaling governor to use when device is in user mode diff --git a/tools/generate-governor-config.py b/tools/generate-governor-config.py new file mode 100644 index 00000000..828fe6af --- /dev/null +++ b/tools/generate-governor-config.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +import sys,os,glob + +# Governor selection lists: Preferred ones first + +PREFER_PERFORMANCE = ("performance", + "interactive", + "ondemand", + "conservative", + "userspace", + "powersave", + ) + +PREFER_INTERACTIVE = ("interactive", + "ondemand", + "conservative", + "performance", + "userspace", + "powersave", + ) + +PREFER_POWERSAVE = ("powersave", + "conservative", + "interactive", + "ondemand", + "performance", + "userspace", + ) + +# Config blocks to create +# 1: frequency selection "percentage" +# 2: config block name +# 3: governor selection list + +CONFIG_BLOCKS = ((80, "Performance", PREFER_PERFORMANCE), + (50, "Interactive", PREFER_INTERACTIVE), + (0, "Inactive", PREFER_INTERACTIVE), + (0, "Powersave", PREFER_POWERSAVE), + ) + +def read_values(path): + data = open(path).read().split() + return data + +INI_DATA = {} + +def secname(s): + return "[CPUScalingGovernor%s]" % s + +def add_sec(s): + s = secname(s) + if not s in INI_DATA: + INI_DATA[s] = [] + return INI_DATA[s] + +def add_val(s,b,p,d): + s = add_sec(s) + n = len(s)//2 + 1 + p = os.path.join(b, p) + d = str(d) + s.append("path%d=%s" % (n,p)) + s.append("data%d=%s" % (n,d)) + +def select(available, wanted): + for s in wanted: + if s in available: + return s + return None + +def writetest(path): + # Read data from a file and then + # write the same data back to the file. + ack = True + try: + fh = open(path) + data = fh.read() + fh.close() + fh = open(path, "w") + fh.write(data) + fh.close() + except: + # Failed -> ignore the file + ack = False + return ack + +def generate_config_data(dirpath): + path = os.path.join(dirpath, "scaling_available_frequencies") + frequencies = map(int, read_values(path)) + + path = os.path.join(dirpath, "scaling_available_governors") + governors = read_values(path) + + M = len(frequencies) + for scale,name,wanted in CONFIG_BLOCKS: + governor = select(governors, wanted) + if not governor: + print>>sys.stderr, "# no governor: %s" % dirpath + else: + add_val(name, dirpath, "scaling_governor", governor) + maxval = frequencies[-1] + minval = frequencies[(M-1)*scale//100] + add_val(name, dirpath, "scaling_max_freq", maxval) + add_val(name, dirpath, "scaling_min_freq", minval) + +if __name__ == "__main__": + + # Get directories to probe from command line + paths = sys.argv[1:] + + if not paths: + # The logical paths are likely to be symlinks + # with several cpus pointing to the same controls. + # + # Glob logical paths, generate unique set of + # control directories and ignore directrories + # where we can't seem to be able to write. + hits = {} + done = {} + pat = "/sys/devices/system/cpu/cpu*/cpufreq" + for d in glob.glob(pat): + d = os.path.realpath(d) + if d in done: + continue + done[d] = None + t = os.path.join(d, "scaling_min_freq") + if writetest(t): + print>>sys.stderr, "# including: %s" % d + hits[d] = None + else: + print>>sys.stderr, "# excluding: %s" % d + paths = hits.keys() + + # Generate config data for each directory given/found + for dirpath in paths: + generate_config_data(dirpath) + + # Output results + print "# Automatically generated MCE CPU scaling config file" + print "" + for scale,name,wanted in CONFIG_BLOCKS: + s = secname(name) + print "%s" % s + for s in INI_DATA[s]: + print "%s" % s + print "" diff --git a/tools/mcetool.c b/tools/mcetool.c index e067c432..3a90fcb6 100644 --- a/tools/mcetool.c +++ b/tools/mcetool.c @@ -1873,9 +1873,12 @@ static const symbol_t suspendpol_values[] = { /** Lookup table for cpu scaling governor overrides */ static const symbol_t governor_values[] = { - { "automatic", GOVERNOR_UNSET }, - { "performance", GOVERNOR_DEFAULT }, + { "disabled", GOVERNOR_UNSET }, + { "performance", GOVERNOR_PERFORMANCE }, { "interactive", GOVERNOR_INTERACTIVE }, + { "inactive", GOVERNOR_INACTIVE }, + { "powersave", GOVERNOR_POWERSAVE }, + { "automatic", GOVERNOR_AUTOMATIC }, { NULL, -1 } }; @@ -7270,11 +7273,18 @@ static const mce_opt_t options[] = .name = "set-cpu-scaling-governor", .flag = 'S', .with_arg = xmce_set_cpu_scaling_governor, - .values = "automatic|performance|interactive", + .values = "disabled|performance|interactive|inactive|powersave|automatic", .usage = - "set the cpu scaling governor override; valid\n" - "modes are: 'automatic', 'performance',\n" - "'interactive'\n" + "set the cpu scaling governor policy; valid odes are:\n" + " disabled Do not modify anything\n" + " performance High performance, bootup/shutdown\n" + " interactive High performance, during active use\n" + " inactive Medium performance, not in active use\n" + " idle Low performance, while display is off\n" + " automatic Switch between interactive, inactive and idle\n" + "\n" + "Note that this setting has no effect unless suitable device specific\n" + "configuration files are installed in /etc/mce directory.\n" }, #ifdef ENABLE_DOUBLETAP_EMULATION {