diff options
author | Holger Macht <hmacht@suse.de> | 2006-08-23 21:37:45 -0400 |
---|---|---|
committer | David Zeuthen <davidz@redhat.com> | 2006-08-23 21:37:45 -0400 |
commit | 18f52da6185f14b8b2d806cf17034bbda7593eee (patch) | |
tree | a6289642e3dff658e9318f68c5cdd8c265735f3e | |
parent | be78af22b72006a2209fb09f077a7421b5c637ab (diff) |
add cpu frequency scaling support to hal
The following patches add CPU frequency capabilities to HAL via an addon.
This was already discussed in another thread [1].
Addon-cpufreq supports all kernel governors and also implements a
userspace controlling mechanism. Furthermore, it is supposed to abstract
all the different settings you can make for the different governors. It is
unique because it gives you a fine grained control over dynamic scaling
mechanisms via a DBus interface higher level applications like
gnome-power-manager or kpowersave can make use of.
[1] http://lists.freedesktop.org/archives/hal/2006-July/005545.html
---
Patch adding the cpufreq addon itself.
This version implements the following new things:
- specific DBus errors on failure (exceptions)
- PolicyKit integration
- add DBus method to get a list of all available governors
Signed-off-by: Holger Macht <hmacht@suse.de>
---
Patch adding the privilege descriptor for the hal-power-cpufreq privilege.
Signed-off-by: Holger Macht <hmacht@suse.de>
---
Patch adding the documentation for all CPUFreq methods on the
org.freedesktop.Hal.Device.SystemPowerManagement interface to the
Hal specification.
Signed-off-by: Holger Macht <hmacht@suse.de>
---
-rw-r--r-- | doc/api/tmpl/hal-unused.sgml | 24 | ||||
-rw-r--r-- | doc/api/tmpl/logger.sgml | 16 | ||||
-rw-r--r-- | doc/api/tmpl/util.sgml | 26 | ||||
-rw-r--r-- | doc/spec/hal-spec-properties.xml | 212 | ||||
-rw-r--r-- | fdi/policy/10osvendor/10-power-mgmt-policy.fdi | 2 | ||||
-rw-r--r-- | hald/linux/addons/Makefile.am | 7 | ||||
-rw-r--r-- | hald/linux/addons/addon-cpufreq-userspace.c | 525 | ||||
-rw-r--r-- | hald/linux/addons/addon-cpufreq-userspace.h | 61 | ||||
-rw-r--r-- | hald/linux/addons/addon-cpufreq.c | 1214 | ||||
-rw-r--r-- | hald/linux/addons/addon-cpufreq.h | 72 | ||||
-rw-r--r-- | privileges/Makefile.am | 3 | ||||
-rw-r--r-- | privileges/hal-power-cpufreq.privilege | 12 |
12 files changed, 2146 insertions, 28 deletions
diff --git a/doc/api/tmpl/hal-unused.sgml b/doc/api/tmpl/hal-unused.sgml index e4bf5ce5..0f22ab22 100644 --- a/doc/api/tmpl/hal-unused.sgml +++ b/doc/api/tmpl/hal-unused.sgml @@ -286,6 +286,30 @@ logging main +<!-- ##### SECTION ./tmpl/shared.sgml:Long_Description ##### --> +<para> + +</para> + + +<!-- ##### SECTION ./tmpl/shared.sgml:See_Also ##### --> +<para> + +</para> + + +<!-- ##### SECTION ./tmpl/shared.sgml:Short_Description ##### --> + + + +<!-- ##### SECTION ./tmpl/shared.sgml:Stability_Level ##### --> + + + +<!-- ##### SECTION ./tmpl/shared.sgml:Title ##### --> +shared + + <!-- ##### SECTION ./tmpl/sysfs.sgml:Long_Description ##### --> <para> diff --git a/doc/api/tmpl/logger.sgml b/doc/api/tmpl/logger.sgml index 2878b232..ee46b8c6 100644 --- a/doc/api/tmpl/logger.sgml +++ b/doc/api/tmpl/logger.sgml @@ -37,6 +37,15 @@ logger @Varargs: +<!-- ##### FUNCTION logger_forward_debug ##### --> +<para> + +</para> + +@format: +@Varargs: + + <!-- ##### FUNCTION logger_enable ##### --> <para> @@ -65,6 +74,13 @@ logger +<!-- ##### FUNCTION setup_logger ##### --> +<para> + +</para> + + + <!-- ##### MACRO HAL_TRACE ##### --> <para> diff --git a/doc/api/tmpl/util.sgml b/doc/api/tmpl/util.sgml index d2e23210..a61ab350 100644 --- a/doc/api/tmpl/util.sgml +++ b/doc/api/tmpl/util.sgml @@ -38,32 +38,6 @@ util -<!-- ##### FUNCTION util_compute_time_remaining ##### --> -<para> - -</para> - -@id: -@chargeRate: -@chargeLevel: -@chargeLastFull: -@isDischarging: -@isCharging: -@guessChargeRate: -@Returns: - - -<!-- ##### FUNCTION util_compute_percentage_charge ##### --> -<para> - -</para> - -@id: -@chargeLevel: -@chargeLastFull: -@Returns: - - <!-- ##### FUNCTION hal_util_remove_trailing_slash ##### --> <para> diff --git a/doc/spec/hal-spec-properties.xml b/doc/spec/hal-spec-properties.xml index 7c1eddda..b81aa9e2 100644 --- a/doc/spec/hal-spec-properties.xml +++ b/doc/spec/hal-spec-properties.xml @@ -5491,6 +5491,218 @@ </tbody> </tgroup> </informaltable> + <para> + The following methods exist on the interface + <literal>org.freedesktop.Hal.Device.CPUFreq</literal>. + </para> + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Method (parameter types)</entry> + <entry>Parameters</entry> + <entry>Mandatory</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <literal>SetCPUFreqGovernor</literal> (string) + </entry> + <entry> + The name of the governor to set. Get a list of available governors + with the GetCPUFreqAvailableGovernors method. + </entry> + <entry>No</entry> + <entry> + Sets a CPU frequency scaling governor for all CPUFreq + interfaces the kernel provides. If the userspace governor + is set, this interface also contains a proper scaling + mechanism. The default performance is set to 50. + </entry> + </row> + <row> + <entry> + <literal>SetCPUFreqPerformance</literal> (integer) + </entry> + <entry> + The performance between 1 and 100 to set in dynamic scaling modes. + </entry> + <entry>No</entry> + <entry> + Sets the performance of the dynamic scaling mechanism. This method + summarizes and abstracts all the different settings which can be taken + for dynamic frequency adjustments, like at which load to switch up + frequency or how many steps the mechanism should traverse until + reaching the maximum frequency. The higher the value, the more + performance you get. Respectively, the higher the value, the sooner + and the more often the frequency is switched up. + </entry> + </row> + <row> + <entry> + <literal>SetCPUFreqConsiderNice</literal> (boolean) + </entry> + <entry> + Whether or not niced processes should be considered on CPU + load calculation. + </entry> + <entry>No</entry> + <entry> + Whether or not niced processes should be considered on CPU + load calculation. If niced processes are considered, they can cause a + frequency increment although their absolute load percentage wouldn't + trigger the scaling mechanism to switch up the frequency. The default + setting is 'false'. + </entry> + </row> + <row> + <entry> + <literal>GetCPUFreqGovernor</literal> (void) + </entry> + <entry> + </entry> + <entry>No</entry> + <entry> + Get the current active governor for all CPU frequency interfaces (string). + </entry> + </row> + <row> + <entry> + <literal>GetCPUFreqPerformance</literal> (void) + </entry> + <entry> + </entry> + <entry>No</entry> + <entry> + Get the current active performance setting if a dynamic scaling + mechanism is in use (integer between 1 and 100). + </entry> + </row> + <row> + <entry> + <literal>GetCPUFreqConsiderNice</literal> (void) + </entry> + <entry> + </entry> + <entry>No</entry> + <entry> + Returns whether niced processed are considered during CPU load + calculation or not (returns boolean). + </entry> + </row> + <row> + <entry> + <literal>GetCPUFreqAvailableGovernors</literal> (void) + </entry> + <entry> + </entry> + <entry>No</entry> + <entry> + Returns a list of strings of all available governors which + could be set with the SetCPUFreqGovernor method. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + <para> + The following errors maybe raised on the interface + <literal>org.freedesktop.Hal.Device.CPUFreq</literal>. + </para> + <informaltable> + <tgroup cols="2"> + <thead> + <row> + <entry>Error</entry> + <entry>Description</entry> + <entry>Detail field</entry> + </row> + </thead> + <tbody> + <row> + <entry> + <literal>GeneralError</literal> + </entry> + <entry> + A general error occured. + </entry> + <entry> + The exact error. + </entry> + </row> + <row> + <entry> + <literal>UnknownMethod</literal> + </entry> + <entry> + The executed method doesn't exist. + </entry> + <entry> + The method which was tried to be executed. + </entry> + </row> + <row> + <entry> + <literal>UnknownGovernor</literal> + </entry> + <entry> + The governor which was tried to be set doesn't exist. + </entry> + <entry> + The governor which was tried be to set. + </entry> + </row> + <row> + <entry> + <literal>InvalidMessage</literal> + </entry> + <entry> + The message that was sent to the interface is invalid. + For instance, a parameter is missing. + </entry> + <entry> + A DBus error message. + </entry> + </row> + <row> + <entry> + <literal>PermissionDenied</literal> + </entry> + <entry> + The caller doesn't have the privilege to execute this + method. + </entry> + <entry> + The privilege the caller needs to execute the method. + </entry> + </row> + <row> + <entry> + <literal>NoSuitableGovernor</literal> + </entry> + <entry> + The method executed doesn't exist for the current active governor. + </entry> + <entry> + The method which was tried to be executed. + </entry> + </row> + <row> + <entry> + <literal>GovernorInitFailed</literal> + </entry> + <entry> + The initialization of the governor failed. + </entry> + <entry> + The reason for the failure. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> </sect2> <sect2 id="device-properties-tape"> <title> diff --git a/fdi/policy/10osvendor/10-power-mgmt-policy.fdi b/fdi/policy/10osvendor/10-power-mgmt-policy.fdi index 2c2705ab..c0b48451 100644 --- a/fdi/policy/10osvendor/10-power-mgmt-policy.fdi +++ b/fdi/policy/10osvendor/10-power-mgmt-policy.fdi @@ -26,6 +26,8 @@ <match key="info.udi" string="/org/freedesktop/Hal/devices/computer"> <append key="info.interfaces" type="strlist">org.freedesktop.Hal.Device.SystemPowerManagement</append> + <append key="info.addons" type="strlist">hald-addon-cpufreq</append> + <append key="org.freedesktop.Hal.Device.SystemPowerManagement.method_names" type="strlist">Suspend</append> <append key="org.freedesktop.Hal.Device.SystemPowerManagement.method_signatures" type="strlist">i</append> <append key="org.freedesktop.Hal.Device.SystemPowerManagement.method_argnames" type="strlist">num_seconds_to_sleep</append> diff --git a/hald/linux/addons/Makefile.am b/hald/linux/addons/Makefile.am index 48226afc..63e2d603 100644 --- a/hald/linux/addons/Makefile.am +++ b/hald/linux/addons/Makefile.am @@ -15,7 +15,8 @@ libexec_PROGRAMS = \ hald-addon-hid-ups \ hald-addon-keyboard \ hald-addon-pmu \ - hald-addon-storage + hald-addon-storage \ + hald-addon-cpufreq if HAVE_LIBPCI libexec_PROGRAMS += hald-addon-macbookpro-backlight @@ -25,6 +26,10 @@ libexec_PROGRAMS += hald-addon-usb-csr endif endif +hald_addon_cpufreq_SOURCES = addon-cpufreq.c addon-cpufreq.h addon-cpufreq-userspace.h \ + addon-cpufreq-userspace.c ../../logger.c +hald_addon_cpufreq_LDADD = $(top_builddir)/libhal/libhal.la @GLIB_LIBS@ @POLKIT_LIBS@ + hald_addon_acpi_SOURCES = addon-acpi.c ../../logger.c ../../util_helper.c hald_addon_acpi_LDADD = $(top_builddir)/libhal/libhal.la diff --git a/hald/linux/addons/addon-cpufreq-userspace.c b/hald/linux/addons/addon-cpufreq-userspace.c new file mode 100644 index 00000000..953d46af --- /dev/null +++ b/hald/linux/addons/addon-cpufreq-userspace.c @@ -0,0 +1,525 @@ +/*************************************************************************** + * * + * addon-cpufreq-userspace.c * + * * + * Copyright (C) 2006 SUSE Linux Products GmbH * + * * + * Author(s): Holger Macht <hmacht@suse.de> * + * Speed adjustments based on code by * + * Thomas Renninger <trenn@suse.de> * + * * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at you * + * option) any later version. * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * + * * + ***************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include "addon-cpufreq.h" +#include "addon-cpufreq-userspace.h" +#include "../../logger.h" + +/** at which load difference (in percent) we should immediately switch to + * the maximum possible frequency */ +#define JUMP_CPUFREQ_LIMIT_MIN 20 +/** the load difference at which we jump up to the maximum freq + * immediately is calculated by the UP_THRESHOLD multiplied with this + * relation value */ +#define THRESHOLD_JUMP_LIMIT_RELATION 0.625 +/** how many frequency steps we should consider */ +#define HYSTERESIS 5 +#define DEFAULT_CONSIDER_NICE FALSE +#define PROC_STAT_FILE "/proc/stat" + +const char SYSFS_SCALING_SETSPEED_FILE[] = + "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_setspeed"; + +const char SYSFS_SCALING_AVAILABLE_FREQS_FILE[] = + "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_available_frequencies"; + +/** shortcut for g_array_index */ +#define g_a_i(a,i) g_array_index(a, unsigned, i) + +struct userspace_config { + int up_threshold; + int cpu_high_limit; + int consider_nice; + int performance; +}; + +static struct userspace_config config = { UP_THRESHOLD_MAX, + JUMP_CPUFREQ_LIMIT_MIN, + DEFAULT_CONSIDER_NICE, + DEFAULT_PERFORMANCE }; + +/********************* CPU load calculation *********************/ +struct cpuload_data { + int num_cpus; + int *load; + unsigned long *last_total_time; + unsigned long *last_working_time; +}; +static struct cpuload_data cpuload = { -1, + NULL, + NULL, + NULL }; + +/** frees data needed for CPU load calculation */ +void free_cpu_load_data(void) +{ + if (cpuload.num_cpus != -1) { + free(cpuload.last_working_time); + free(cpuload.last_total_time); + free(cpuload.load); + cpuload.num_cpus = -1; + cpuload.load = NULL; + cpuload.last_total_time = NULL; + cpuload.last_working_time = NULL; + } +} + +/** calculates current cpu load and stores it in cpuload_data object */ +static int calc_cpu_load(const int consider_nice) +{ + unsigned long total_elapsed, working_elapsed; + char what[32]; + unsigned long user_time, nice_time, system_time, idle_time; + unsigned long total_time, iowait_time; + unsigned scan_ret; + char line[256]; + char cpu_string[7]; + FILE *fp; + int new_num_cpus; + + new_num_cpus = sysconf(_SC_NPROCESSORS_CONF); + if (new_num_cpus == -1 || new_num_cpus != cpuload.num_cpus) { + free_cpu_load_data(); + cpuload.num_cpus = new_num_cpus; + if (cpuload.num_cpus <= 0) { + errno = ENODEV; + return -20; + } + + cpuload.last_total_time = (unsigned long *)calloc(cpuload.num_cpus + 1, + sizeof(unsigned long)); + cpuload.last_working_time = (unsigned long *)calloc(cpuload.num_cpus + 1, + sizeof(unsigned long)); + cpuload.load = (int *)calloc(cpuload.num_cpus + 1, sizeof(int)); + } + + if ((fp = fopen(PROC_STAT_FILE, "r")) == NULL) { + HAL_DEBUG(("Could not open %s: %s", PROC_STAT_FILE, strerror(errno))); + return -1; + } + + /* start with the first line, "overall" cpu load */ + /* if cpuload.num_cpus == 1, we do not need to evaluate "overall" and "per-cpu" load */ + sprintf(cpu_string, "cpu "); + int i; + for (i = 0; i <= cpuload.num_cpus - (cpuload.num_cpus == 1); i++) { + + if (fgets(line,255,fp) == NULL) { + HAL_WARNING(("%s too short (%s)", PROC_STAT_FILE, cpu_string)); + fclose(fp); + return -1; + } + if (memcmp(line, cpu_string, strlen(cpu_string))) { + HAL_WARNING(("no '%s' string in %s line %d", + cpu_string, PROC_STAT_FILE, i)); + fclose(fp); + return -1; + } + /* initialized, since it is simply not there in 2.4 */ + iowait_time = 0; + scan_ret = sscanf(line, "%s %lu %lu %lu %lu %lu", what, &user_time, &nice_time, + &system_time, &idle_time, &iowait_time); + if (scan_ret < 5) { + HAL_WARNING(("only %d values in %s. Please report.", + scan_ret, PROC_STAT_FILE)); + fclose(fp); + return -1; + } + + unsigned long working_time; + if (consider_nice) { + working_time = user_time + system_time + nice_time; + idle_time += iowait_time; + } else { + working_time = user_time + system_time; + idle_time += (nice_time + iowait_time); + } + total_time = working_time + idle_time; + total_elapsed = total_time - cpuload.last_total_time[i]; + working_elapsed = working_time - cpuload.last_working_time[i]; + cpuload.last_working_time[i] = working_time; + cpuload.last_total_time[i] = total_time; + + if (!total_elapsed) { + /* not once per CPU, only once per check. */ + if (!i) + HAL_DEBUG(("%s not updated yet, poll slower.", PROC_STAT_FILE)); + } else + cpuload.load[i] = working_elapsed * 100 / total_elapsed; + + sprintf(cpu_string, "cpu%d ", i); + } + /* shortcut for UP systems */ + if (cpuload.num_cpus == 1) + cpuload.load[1] = cpuload.load[0]; + + fclose(fp); + + return 0; +} + +/** returns current cpuload which has been caluclated before */ +static int get_cpu_load(const int cpu_id) +{ + if (cpu_id < -1) { + errno = EINVAL; + return -10; + } + + if (cpuload.load == NULL) { + HAL_WARNING(("cpuload.load uninitialized")); + errno = EFAULT; + return -40; + } + + if (cpu_id >= cpuload.num_cpus) { + errno = ENODEV; + return -30; + } + + return cpuload.load[cpu_id + 1]; +} +/********************* CPU load end *********************/ + +/********************* userspace interface *********************/ +static gboolean write_speed(unsigned kHz, int cpu_id) +{ + char *speed_file = NULL; + + if (!cpu_online(cpu_id)) + return FALSE; + + speed_file = g_strdup_printf(SYSFS_SCALING_SETSPEED_FILE, cpu_id); + if(!write_line(speed_file, "%u", kHz)){ + HAL_WARNING(("Could not set speed to: %u kHz; %s", kHz, strerror(errno))); + g_free(speed_file); + return FALSE; + } + g_free(speed_file); + HAL_DEBUG(("Speed set to: %uKHz for CPU %d", kHz, cpu_id)); + + return TRUE; +} + +static void reinit_speed(struct userspace_interface *iface, int current_speed) +{ + if (!cpu_online(iface->base_cpu)) + return; + + write_speed(g_a_i(iface->speeds_kHz, current_speed), iface->base_cpu); + HAL_DEBUG(("forced speed to %d kHz", g_a_i(iface->speeds_kHz, current_speed))); +} + +/** @brief set a speed with traversing all intermediary speeds */ +static int set_speed(struct userspace_interface *iface, int target_speed) +{ + int delta; + int current_speed = iface->current_speed; + + if (current_speed == target_speed) + return -1; + + if (current_speed > target_speed) + delta = -1; + else + delta = 1; + + do { + current_speed += delta; + write_speed(g_a_i(iface->speeds_kHz, current_speed), iface->base_cpu); + } while (current_speed != target_speed); + + return current_speed; +} + +/** @brief set speed to the next higher supported value + * + * @return integer with result of increase speed + * @retval 0 if maximum is already reached + * @retval 1 if new speed could be set + * @retval -1 if mode is not userspace + */ +static int increase_speed(struct userspace_interface *iface) +{ + int new_speed = iface->current_speed; + int current_speed = iface->current_speed; + + if (current_speed != 0) + new_speed--; + else + return current_speed; + if (current_speed != new_speed) { + HAL_DEBUG(("current: %u new: %u", g_a_i(iface->speeds_kHz, current_speed), + g_a_i(iface->speeds_kHz, new_speed))); + set_speed(iface, new_speed); + } + return new_speed; +} + +/** @brief set speed to the next lower supported value + * + * @return integer with result of increase speed + * @retval 0 if maximum is already reached + * @retval 1 if new speed could be set + * @retval -1 if mode is not userspace + */ +static int decrease_speed(struct userspace_interface *iface) +{ + int new_speed = iface->current_speed; + int current_speed = iface->current_speed; + + + if (g_a_i(iface->speeds_kHz, new_speed + 1) != 0) + new_speed++; + else + return current_speed; + if (current_speed != new_speed) { + HAL_DEBUG(("current: %u new: %u", g_a_i(iface->speeds_kHz, current_speed), + g_a_i(iface->speeds_kHz, new_speed))); + set_speed(iface, new_speed); + } + return new_speed; +} + +/** increases and decreases speeds */ +static gboolean adjust_speed(struct userspace_interface *iface) +{ + GSList *cpus = (GSList*)iface->cpus; + GSList *it = NULL; + int ret = 0; + int cpu_load = 0; + + for (it = cpus; it != NULL; it = g_slist_next(it)) { + HAL_DEBUG(("checking cpu %d: cpu_core: %d", + GPOINTER_TO_INT(it->data), GPOINTER_TO_INT(it->data))); + if (get_cpu_load(GPOINTER_TO_INT(it->data)) > cpu_load) + cpu_load = get_cpu_load(GPOINTER_TO_INT(it->data)); + } + + HAL_DEBUG(("cpu_max: %d cpu_high_limit: %d consider_nice: %d", + config.up_threshold, config.cpu_high_limit, + config.consider_nice)); + HAL_DEBUG(("Current: %u; current speed: %u MHz", + iface->current_speed, g_a_i(iface->speeds_kHz, iface->current_speed))); + HAL_DEBUG(("CPU load: %d, Previous CPU load %d, cpu_load diff: %d, last_step: %d, demotion: %u", + cpu_load, iface->prev_cpu_load, cpu_load - iface->prev_cpu_load, iface->last_step, + g_a_i(iface->demotion, iface->current_speed))); + + /* directly increase speed to maximum if cpu load jumped */ + if (config.cpu_high_limit && + (cpu_load - iface->prev_cpu_load) > config.cpu_high_limit) { + if (iface->current_speed != 0) { + set_speed(iface, 0); + iface->current_speed = 0; + HAL_DEBUG(("jumped to max (%d kHz)", + g_a_i(iface->speeds_kHz, iface->current_speed))); + ret = 1; + } + } else if (cpu_load > config.up_threshold && iface->current_speed > 0) { + iface->current_speed = increase_speed(iface); + HAL_DEBUG(("increased to %d kHz", g_a_i(iface->speeds_kHz, iface->current_speed))); + ret = 1; + } else if (cpu_load < (int)g_a_i(iface->demotion, iface->current_speed) && + iface->current_speed < iface->last_step) { + iface->current_speed = decrease_speed(iface); + HAL_DEBUG(("decreased to %d kHz", g_a_i(iface->speeds_kHz, iface->current_speed))); + ret = -1; + } else { + ret = 0; + HAL_DEBUG(("Speed not changed")); + } + + iface->prev_cpu_load = cpu_load; + return TRUE; +} + +/** @brief create the hysteresis array */ +static void create_hysteresis_array(struct userspace_interface *iface) +{ + g_array_free(iface->demotion, TRUE); + iface->demotion = g_array_new(TRUE, TRUE, sizeof(unsigned)); + + int i; + if (iface->last_step > 0) { + for (i = 0; i < iface->last_step; i++) { + int demotion = (config.up_threshold - HYSTERESIS) * + g_a_i(iface->speeds_kHz, i + 1) / + g_a_i(iface->speeds_kHz, i); + g_array_append_val(iface->demotion, demotion); + HAL_DEBUG(("Speed: %2u, kHz: %9u, demotion: %3u %%", i, + g_a_i(iface->speeds_kHz, i), g_a_i(iface->demotion, i))); + } + } +} + +static gboolean read_frequencies(struct userspace_interface *iface) +{ + int num_speeds = 0; + GSList *it = NULL; + GSList *available_freqs = NULL; + char *available_frequencies_file = NULL; + + if (!cpu_online(iface->base_cpu)) + return FALSE; + + available_frequencies_file = g_strdup_printf(SYSFS_SCALING_AVAILABLE_FREQS_FILE, + iface->base_cpu); + if (!read_line_int_split(available_frequencies_file, " ", &available_freqs)) { + g_free(available_frequencies_file); + return FALSE; + } + g_free(available_frequencies_file); + + if (available_freqs == NULL) { + iface->last_step = 0; + return FALSE; + } + + for (num_speeds = 0, it = available_freqs; it != NULL; + num_speeds++, it = g_slist_next(it)) { + + unsigned index = GPOINTER_TO_UINT(it->data); + g_array_append_val(iface->speeds_kHz, index); + } + g_slist_free(available_freqs); + + iface->last_step = num_speeds - 1; + HAL_DEBUG(("Number of speeds: %d, last_step: %d", num_speeds, iface->last_step)); + + reinit_speed(iface, 0); + + HAL_DEBUG(("Available speeds:")); + for (num_speeds = 0; g_a_i(iface->speeds_kHz, num_speeds); num_speeds++) { + HAL_DEBUG((" %2u: %9uKHz", num_speeds, g_a_i(iface->speeds_kHz, num_speeds))); + } + + return TRUE; +} + +/** calculates current cpu load and traverses all existing interfaces */ +gboolean userspace_adjust_speeds(GSList *cpufreq_objs) +{ + GSList *it = NULL; + + HAL_DEBUG(("Adjusting speeds...")); + + if ((calc_cpu_load(DEFAULT_CONSIDER_NICE) < 0)) { + HAL_DEBUG(("calc_cpu_load failed. Cannot adjust speeds")); + return TRUE; + } + + for (it = cpufreq_objs; it != NULL; it = g_slist_next(it)) { + struct cpufreq_obj *obj = it->data; + adjust_speed(obj->iface); + } + + return TRUE; +} + +/** inits one userspace interface with the given cores list. iface has to + * be allocated before passing it to that fucntion */ +gboolean userspace_init(struct userspace_interface *iface, GSList *cpus) +{ + if (iface == NULL) + return FALSE; + + iface->demotion = g_array_new(TRUE, TRUE, sizeof(unsigned)); + iface->speeds_kHz = g_array_new(TRUE, TRUE, sizeof(unsigned)); + iface->last_step = -1; + iface->current_speed = 0; + iface->cpus = cpus; + iface->prev_cpu_load = 50; + iface->base_cpu = GPOINTER_TO_INT(cpus->data); + + if (!write_governor(USERSPACE_STRING, GPOINTER_TO_INT(cpus->data))) { + HAL_WARNING(("Could not set userspace governor.")); + return FALSE; + } + + if (!read_frequencies(iface)) { + HAL_WARNING(("Could not read available frequencies")); + return FALSE; + } + + return TRUE; +} + +/** frees the userspace data */ +void userspace_free(void *data) +{ + struct userspace_interface *iface = data; + free_cpu_load_data(); + g_array_free(iface->speeds_kHz, TRUE); + g_array_free(iface->demotion, TRUE); +} + +/** sets the performance of the userspace governor. num has to be between + * 1 and 100 */ +gboolean userspace_set_performance(void *data, int up_threshold) +{ + struct userspace_interface *iface = data; + + config.up_threshold = up_threshold; + + config.cpu_high_limit = (int)(up_threshold * THRESHOLD_JUMP_LIMIT_RELATION); + if (config.cpu_high_limit < JUMP_CPUFREQ_LIMIT_MIN) + config.cpu_high_limit = JUMP_CPUFREQ_LIMIT_MIN; + + HAL_DEBUG(("cpu_max set to %d, cpu_high_limit set to %d", + config.up_threshold, config.cpu_high_limit)); + + create_hysteresis_array(iface); + + return TRUE; +} + +/** return the current performance setting */ +int userspace_get_performance(void) +{ + return config.up_threshold; +} + +/** sets whether niced processes should be considered when calculating CPU + * load */ +gboolean userspace_set_consider_nice(void *data, gboolean consider) +{ + HAL_DEBUG(("consider nice set to %d for userspace", consider)); + config.consider_nice = consider; + return TRUE; +} + +/** return the current consider nice setting */ +gboolean userspace_get_consider_nice(void) +{ + return config.consider_nice; +} +/********************* userspace end *********************/ diff --git a/hald/linux/addons/addon-cpufreq-userspace.h b/hald/linux/addons/addon-cpufreq-userspace.h new file mode 100644 index 00000000..4424de20 --- /dev/null +++ b/hald/linux/addons/addon-cpufreq-userspace.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * * + * addon-cpufreq-userspace.h * + * * + * Copyright (C) 2006 SUSE Linux Products GmbH * + * * + * Author(s): Holger Macht <hmacht@suse.de> * + * * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at you * + * option) any later version. * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * + * * + ***************************************************************************/ + +#ifndef ADDON_CPUFREQ_USERSPACE_H +#define ADDON_CPUFREQ_USERSPACE_H + +#define USERSPACE_STRING "userspace" +#define USERSPACE_POLL_INTERVAL 333 + +struct userspace_interface { + int base_cpu; + int last_step; + int current_speed; + int g_source_id; + int prev_cpu_load; + GSList *cpus; + GArray *speeds_kHz; + GArray *demotion; +}; + +gboolean userspace_adjust_speeds (GSList *cpufreq_objs); + +gboolean userspace_init (struct userspace_interface *iface, + GSList *cpus); + +gboolean userspace_set_performance (void *data, + int performance); + +int userspace_get_performance (void); + +gboolean userspace_set_consider_nice (void *data, + gboolean consider); + +gboolean userspace_get_consider_nice (void); + +void userspace_free (void *data); + +void free_cpu_load_data (void); + +#endif /* ADDON_CPUFREQ_USERSPACE_H */ diff --git a/hald/linux/addons/addon-cpufreq.c b/hald/linux/addons/addon-cpufreq.c new file mode 100644 index 00000000..a74fecb3 --- /dev/null +++ b/hald/linux/addons/addon-cpufreq.c @@ -0,0 +1,1214 @@ +/*************************************************************************** + * * + * addon-cpufreq.c * + * * + * Copyright (C) 2006 SUSE Linux Products GmbH * + * * + * Author(s): Holger Macht <hmacht@suse.de> * + * * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at you * + * option) any later version. * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <getopt.h> +#include <glib/gprintf.h> + +#include "addon-cpufreq.h" +#include "addon-cpufreq-userspace.h" +#include "libhal/libhal.h" +#include "../../logger.h" + +#ifdef HAVE_POLKIT +#include <libpolkit/libpolkit.h> +#endif + +#define MAX_LINE_SIZE 255 +#define CPUFREQ_POLKIT_PRIVILEGE "hal-power-cpufreq" +#define DBUS_INTERFACE "org.freedesktop.Hal.Device.CPUFreq" + +#define CPUFREQ_ERROR_GENERAL "GeneralError" +#define CPUFREQ_ERROR_UNKNOWN_METHOD "UnknownMethod" +#define CPUFREQ_ERROR_UNKNOWN_GOVERNOR "UnknownGovernor" +#define CPUFREQ_ERROR_INVALID_MESSAGE "InvalidMessage" +#define CPUFREQ_ERROR_PERMISSION_DENIED "PermissionDenied" +#define CPUFREQ_ERROR_NO_SUITABLE_GOVERNOR "NoSuitableGovernor" +#define CPUFREQ_ERROR_GOVERNOR_INIT_FAILED "GovernorInitFailed" + +const char SYSFS_GOVERNOR_FILE[] = + "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_governor"; + +const char SYSFS_AVAILABLE_GOVERNORS_FILE[] = + "/sys/devices/system/cpu/cpu%u/cpufreq/scaling_available_governors"; + +const char ONDEMAND_UP_THRESHOLD_FILE[] = + "/sys/devices/system/cpu/cpu%u/cpufreq/ondemand/up_threshold"; + +const char SYSFS_AFFECTED_CPUS_FILE[] = + "/sys/devices/system/cpu/cpu%u/cpufreq/affected_cpus"; + +const char SYSFS_CPU_ONLINE_FILE[] = + "/sys/devices/system/cpu/cpu%u/online"; + +const char ONDEMAND_IGNORE_NICE_LOAD_FILE[] = + "/sys/devices/system/cpu/cpu%u/cpufreq/ondemand/ignore_nice_load"; + +static gboolean dbus_raise_error(DBusConnection *connection, DBusMessage *message, + const char *error_name, char *format, ...); + +static gboolean dbus_raise_no_suitable_governor(DBusConnection *connection, + DBusMessage *message, + char *method); + +static gboolean dbus_raise_governor_init_failed(DBusConnection *connection, + DBusMessage *message, + char *governor); + +/** list holding all cpufreq objects (userspace, ondemand, etc.) */ +static GSList *cpufreq_objs = NULL; + +/******************** helper functions **********************/ + +/** reads one integer from filename and stores it in val */ +static gboolean read_line_int(const char *filename, int *val) +{ + char line[MAX_LINE_SIZE + 1]; + + if (!read_line(filename, line, MAX_LINE_SIZE)) { + HAL_WARNING(("Could not read from %s", filename)); + return FALSE; + } + + /* strip trailing '\n' */ + line[strlen(line) - 1] = '\0'; + *val = atoi(line); + + return TRUE; +} + +/** reads one line from filename with the given length */ +gboolean read_line(const char *filename, char *line, unsigned len) +{ + FILE *fp = fopen(filename, "r"); + if (!fp) { + HAL_WARNING(("Could not open '%s': %s", filename, strerror(errno))); + return FALSE; + } + if ((!fgets(line, len, fp))) { + HAL_WARNING(("Could not read from '%s': %s", filename, strerror(errno))); + fclose(fp); + return FALSE; + } + fclose(fp); + return TRUE; +} + +/** writes one line with the given format to filename */ +gboolean write_line(const char *filename, const char *fmt, ...) +{ + va_list ap; + FILE *fp; + + fp = fopen(filename, "w+"); + if (!fp) { + HAL_WARNING(("Could not open file for writing: %s; %s", filename, + strerror(errno))); + return FALSE; + } + + va_start(ap, fmt); + + if (vfprintf(fp, fmt, ap) < 0) { + HAL_WARNING(("Could not write to file: %s", filename)); + fclose(fp); + return FALSE; + } + + va_end(ap); + fclose(fp); + return TRUE; +} + +/** reads one line from filename, splits it by delim and returns a two + * dimension array of strings or NULL on error */ +static gchar **read_line_str_split(char *filename, gchar *delim) +{ + gchar line[MAX_LINE_SIZE]; + int i; + gchar **l; + + if(!read_line(filename, line, MAX_LINE_SIZE)) { + printf("returning NULL from str split\n"); + return NULL; + } + + /* strip trailing '\n' */ + line[strlen(line)-1] = '\0'; + + l = g_strsplit(line, delim, MAX_LINE_SIZE); + + if (l[0] == NULL) + return NULL; + + for (i = 0; l[i] != NULL; i++) { + if (g_strcasecmp(l[i], "") == 0) { + free(l[i]); + l[i] = NULL; + } + } + return l; +} + +/** reads one line from filename, splits its integers by delim and stores + * all items in the given list */ +gboolean read_line_int_split(char *filename, gchar *delim, GSList **list) +{ + gchar **l; + int i; + + l = read_line_str_split(filename, delim); + + for (i = 0; l[i] != NULL; i++) { + int value = atoi(l[i]); + *list = g_slist_append(*list, GINT_TO_POINTER(value)); + } + g_strfreev(l); + return TRUE; +} + +/** gets a two dimensional list of integers and sorts out duplicates */ +static void cpu_list_unique(gpointer data, gpointer whole_list) +{ + GSList **list = (GSList**)whole_list; + GSList *current = (GSList*)data; + GSList *it = NULL; + + for (it = *list; it != NULL; it = g_slist_next(it)) { + gboolean equal = TRUE; + if (current == it->data) + continue; + + GSList *list_it = NULL; + GSList *current_it = NULL; + for (list_it = it->data, current_it = current; + list_it != NULL && current_it != NULL; + list_it = g_slist_next(list_it), current_it = g_slist_next(current_it)) { + + HAL_DEBUG(("comparing %d with %d", GPOINTER_TO_INT(current_it->data), + GPOINTER_TO_INT(list_it->data))); + if (GPOINTER_TO_INT(current_it->data) != GPOINTER_TO_INT(list_it->data)) + equal = FALSE; + } + + HAL_DEBUG(("equal? %s, %d", equal ? "yes" : "no", equal)); + if (equal) { + HAL_DEBUG(("remove: %d", g_slist_length(*list))); + *list = g_slist_remove(*list, current); + HAL_DEBUG(("remove_2: %d", g_slist_length(*list))); + return; + } + } +} + +/** @brief gets the CPUs and their dependencies */ +static gboolean get_cpu_dependencies(GSList **cpu_list, int num_cpus) +{ + int i; + + for (i = 0; i < num_cpus; i++) { + GSList *int_cpus = NULL; + GSList *affected_cpus = NULL; + GSList *it = NULL; + char *affected_cpus_file = NULL; + + affected_cpus_file = g_strdup_printf(SYSFS_AFFECTED_CPUS_FILE, i); + + if (!read_line_int_split(affected_cpus_file, " ", &affected_cpus)) { + g_free(affected_cpus_file); + return FALSE; + } + g_free(affected_cpus_file); + + if (affected_cpus == NULL) + return FALSE; + + for (it = affected_cpus; it != NULL; it = g_slist_next(it)) { + int_cpus = g_slist_append(int_cpus, + GINT_TO_POINTER(affected_cpus->data)); + } + g_slist_free(affected_cpus); + + if (!g_slist_length(int_cpus)) { + HAL_WARNING(("failed to get affected_cpus for cpu %d", i)); + continue; + } + + *cpu_list = g_slist_append(*cpu_list, int_cpus); + } + + HAL_DEBUG(("Number of CPUs before uniquing cpu_list: %d", g_slist_length(*cpu_list))); + g_slist_foreach(*cpu_list, (GFunc)cpu_list_unique, cpu_list); + HAL_DEBUG(("Number of CPUs after uniquing cpu_list: %d", g_slist_length(*cpu_list))); + + if (g_slist_length(*cpu_list) == 0) + return FALSE; + return TRUE; +} + +/** check if given CPU starting from 0 is online */ +gboolean cpu_online(int cpu_id) +{ + gboolean online; + char online_str[2]; + char *online_file; + + online_file = g_strdup_printf(SYSFS_CPU_ONLINE_FILE, cpu_id); + + if (access(online_file, F_OK) < 0) { + online = TRUE; + goto Out; + } + + if (!read_line(online_file, online_str, 2)) { + HAL_WARNING(("Unable to open file: %s", online_file)); + online = FALSE; + goto Out; + } + + online = atoi(online_str); + + if (!online) + online = FALSE; +Out: + g_free(online_file); + return online; +} + +/** writes the new_governor string into the sysfs interface */ +gboolean write_governor(char *new_governor, int cpu_id) +{ + gboolean ret = TRUE; + char *governor_file = NULL; + char governor[MAX_LINE_SIZE + 1]; + + if (!cpu_online(cpu_id)) + goto Out; + + governor_file = g_strdup_printf(SYSFS_GOVERNOR_FILE, cpu_id); + HAL_DEBUG(("Trying ot write governor %s", new_governor)); + + if (!write_line(governor_file, "%s", new_governor)) { + ret = FALSE; + goto Out; + } + + /* check if governor has been set */ + read_line(governor_file, governor, MAX_LINE_SIZE); + if (strstr(governor, new_governor)) + ret = TRUE; + else + ret = FALSE; +Out: + g_free(governor_file); + return ret; +} +/******************** helper functions end ********************/ + +/********************* ondemand interface *********************/ +#define ONDEMAND_STRING "ondemand" + +struct ondemand_interface { + int base_cpu; +}; + +static gboolean ondemand_set_performance(void *data, int performance) +{ + struct ondemand_interface *iface = data; + char *up_threshold_file = NULL; + + up_threshold_file = g_strdup_printf(ONDEMAND_UP_THRESHOLD_FILE, + iface->base_cpu); + + if(!write_line(up_threshold_file, "%u", performance)){ + HAL_WARNING(("Could not set up_threshold to %u kHz; %s", performance, + strerror(errno))); + g_free(up_threshold_file); + return FALSE; + } + g_free(up_threshold_file); + HAL_DEBUG(("Up threshold set to %d for ondemand", performance)); + + return TRUE; +} + +static int ondemand_get_performance(void) +{ + char *governor_file; + int performance = -1; + + governor_file = g_strdup_printf(ONDEMAND_UP_THRESHOLD_FILE, 0); + + if (!read_line_int(governor_file, &performance)) { + HAL_WARNING(("Could not read up_threshold")); + g_free(governor_file); + return -1; + } + g_free(governor_file); + + return performance; +} + +static gboolean ondemand_set_consider_nice(void *data, gboolean consider) +{ + struct ondemand_interface *iface = data; + char *consider_file; + + consider_file = g_strdup_printf(ONDEMAND_IGNORE_NICE_LOAD_FILE, iface->base_cpu); + + if(!write_line(consider_file, "%u", consider)){ + HAL_WARNING(("Could not set ignore_nice_load to: %u kHz; %s", consider, + strerror(errno))); + g_free(consider_file); + return FALSE; + } + g_free(consider_file); + HAL_DEBUG(("Set consider nice to %d for ondemand", consider)); + + return TRUE; +} + +static gboolean ondemand_get_consider_nice(void) +{ + char *governor_file; + gboolean consider = -1; + + /* only read the setting of cpu0 */ + governor_file = g_strdup_printf(ONDEMAND_IGNORE_NICE_LOAD_FILE, 0); + + if (!read_line_int(governor_file, &consider)) { + HAL_WARNING(("Could not read ignore_nice_load file")); + g_free(governor_file); + return -1; + } + g_free(governor_file); + + return consider; +} + +static gboolean ondemand_init(struct ondemand_interface *iface, GSList *cores) +{ + if (iface == NULL) + return FALSE; + + if (!write_governor(ONDEMAND_STRING, GPOINTER_TO_INT(cores->data))) { + HAL_WARNING(("Could not set ondemand governor.")); + return FALSE; + } + + iface->base_cpu = GPOINTER_TO_INT(cores->data); + + return TRUE; +} + +static void ondemand_free(void *data) +{ + return; +} + +/********************* ondemand end *********************/ + +/********************* main interface *********************/ + +/** sets the performance for all cpufreq objects + * + * @raises NoSuitableGoveror + */ +static gboolean set_performance(DBusConnection *connection, DBusMessage *message, + int performance) +{ + float steps; + float up_threshold; + GSList *it = NULL; + + if (cpufreq_objs == NULL) { + dbus_raise_no_suitable_governor(connection, message, + "CPUFreqSetPerformance"); + return FALSE; + } + + if (performance < 1) + performance = 1; + if (performance > 100) + performance = 100; + + if (performance >= 50) { + steps = UP_THRESHOLD_BASE - UP_THRESHOLD_MIN + 1; + up_threshold = (UP_THRESHOLD_BASE) - (((float)performance - 50.0) * + (steps / 51.0)); + performance = (int)up_threshold; + } else if (performance < 50) { + steps = UP_THRESHOLD_MAX - UP_THRESHOLD_BASE; + up_threshold = (UP_THRESHOLD_MAX + 1) - ((float)performance * + (steps / 49.0)); + performance = (int)up_threshold; + } + + for (it = cpufreq_objs; it != NULL; it = g_slist_next(it)) { + struct cpufreq_obj *obj = it->data; + obj->set_performance(obj->iface, performance); + } + return TRUE; +} + +/** sets the performance for all cpufreq objects + * + * @raises (NoSuitableGoveror|GeneralError) + */ +static gboolean get_performance(DBusConnection *connection, DBusMessage *message, + int *performance) +{ + struct cpufreq_obj *obj; + float steps; + float perf; + int up_threshold; + + if (cpufreq_objs == NULL) { + dbus_raise_no_suitable_governor(connection, message, + "CPUFreqGetPerformance"); + return FALSE; + } + + obj = cpufreq_objs->data; + + up_threshold = obj->get_performance(); + if (up_threshold < 0) { + dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL, + "Could not read up_threshold"); + return FALSE; + } + + if (up_threshold < UP_THRESHOLD_BASE) { + steps = UP_THRESHOLD_BASE - UP_THRESHOLD_MIN + 1; + perf = (((UP_THRESHOLD_BASE) - up_threshold) / + (steps / 51.0)) + 50.0; + } else if (up_threshold >= UP_THRESHOLD_BASE) { + steps = UP_THRESHOLD_MAX - UP_THRESHOLD_BASE; + perf = ((UP_THRESHOLD_MAX + 1) - up_threshold) / + (steps / 49.0); + } + + *performance = (int)perf; + + return TRUE; +} + +/** sets the performance for all cpufreq objects + * + * @raises NoSuitableGoveror + */ +static gboolean set_consider_nice(DBusConnection *connection, DBusMessage *message, + gboolean consider) +{ + GSList *it = NULL; + + if (cpufreq_objs == NULL) { + dbus_raise_no_suitable_governor(connection, message, + "CPUFreqSetConsiderNice"); + return FALSE; + } + + for (it = cpufreq_objs; it != NULL; it = g_slist_next(it)) { + struct cpufreq_obj *obj= it->data; + obj->set_consider_nice(obj->iface, consider); + } + return TRUE; +} + +/** sets the performance for all cpufreq objects + * + * @raises NoSuitableGoveror + */ +static gboolean get_consider_nice(DBusConnection *connection, DBusMessage *message, + int *consider) +{ + struct cpufreq_obj *obj; + + if (cpufreq_objs == NULL) { + dbus_raise_no_suitable_governor(connection, message, + "CPUFreqGetConsiderNice"); + return FALSE; + } + obj = cpufreq_objs->data; + + *consider = obj->get_consider_nice(); + + return TRUE; +} + +/** stores a list of all available governors in the given list. + * + * @raises GeneralError + */ +static gboolean get_available_governors(DBusConnection *connection, DBusMessage *message, + gchar ***governors) +{ + char *agovs_file; + + agovs_file = g_strdup_printf(SYSFS_AVAILABLE_GOVERNORS_FILE, 0); + *governors = read_line_str_split(agovs_file, " "); + g_free(agovs_file); + + if (*governors == NULL) { + dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL, + "No CPUFreq governors"); + return FALSE; + } + + return TRUE; +} + +/** sets a governor for all cpufreq objects + * + * @raises (GeneralError|UnknownGovernor|GovernorInitFailed) + */ +static gboolean set_governors(DBusConnection *connection, DBusMessage *message, + const char *governor) +{ + GSList *cpus = NULL; + GSList *it = NULL; + static int g_source_id = -1; + gboolean have_governor = FALSE; + int i; + int num_cpus; + gchar **available_governors; + + if (!get_available_governors(connection, message, &available_governors)) + return FALSE; + + for (i = 0; available_governors[i] != NULL; i++) { + if (strcmp(available_governors[i], governor) == 0) { + have_governor = TRUE; + break; + } + } + g_strfreev(available_governors); + + if (!have_governor) { + dbus_raise_error(connection, message, + CPUFREQ_ERROR_UNKNOWN_GOVERNOR, + "No governor '%s' available", governor); + return FALSE; + } + + /** clear all previous cpufreq_objs */ + if (g_slist_length(cpufreq_objs) > 0) { + GSList *it = NULL; + for (it = cpufreq_objs; it != NULL; it = g_slist_next(it)) { + struct cpufreq_obj *obj = it->data; + obj->free(obj->iface); + free(obj->iface); + free(obj); + } + g_slist_free(cpufreq_objs); + cpufreq_objs = NULL; + g_source_remove(g_source_id); + g_source_id = -1; + } + + num_cpus = sysconf(_SC_NPROCESSORS_CONF); + if (num_cpus < 0) { + dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL, + "No CPUs found in system"); + HAL_WARNING(("No CPUs found in system")); + return FALSE; + } + + if (!get_cpu_dependencies(&cpus, num_cpus)) { + dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL, + "Could not figure out cpu core dependencies"); + HAL_WARNING(("Could not figure out cpu core dependencies")); + return FALSE; + } + + if (!strcmp(governor, USERSPACE_STRING)) { + struct cpufreq_obj *cpufreq_obj; + struct userspace_interface *iface; + + for (it = cpus; it != NULL; it = g_slist_next(it)) { + cpufreq_obj = malloc(sizeof(struct cpufreq_obj)); + iface = malloc(sizeof(struct userspace_interface)); + + if (userspace_init(iface, it->data)) { + cpufreq_obj->iface = iface; + cpufreq_obj->set_performance = userspace_set_performance; + cpufreq_obj->get_performance = userspace_get_performance; + cpufreq_obj->set_consider_nice = userspace_set_consider_nice; + cpufreq_obj->get_consider_nice = userspace_get_consider_nice; + cpufreq_obj->free = userspace_free; + cpufreq_objs = g_slist_append(cpufreq_objs, cpufreq_obj); + HAL_DEBUG(("added userspace interface")); + } else { + dbus_raise_governor_init_failed(connection, message, + (char*)governor); + return FALSE; + } + } + g_source_id = g_timeout_add(USERSPACE_POLL_INTERVAL, + (GSourceFunc)userspace_adjust_speeds, + cpufreq_objs); + + } else if (!strcmp(governor, ONDEMAND_STRING)) { + struct cpufreq_obj *cpufreq_obj; + struct ondemand_interface *iface; + + for (it = cpus; it != NULL; it = g_slist_next(it)) { + cpufreq_obj = malloc(sizeof(struct cpufreq_obj)); + iface = malloc(sizeof(struct ondemand_interface)); + + if (ondemand_init(iface, it->data)) { + cpufreq_obj->iface = iface; + cpufreq_obj->set_performance = ondemand_set_performance; + cpufreq_obj->get_performance = ondemand_get_performance; + cpufreq_obj->set_consider_nice = ondemand_set_consider_nice; + cpufreq_obj->get_consider_nice = ondemand_get_consider_nice; + cpufreq_obj->free = ondemand_free; + cpufreq_objs = g_slist_append(cpufreq_objs, cpufreq_obj); + HAL_DEBUG(("added ondemand interface")); + } else { + dbus_raise_governor_init_failed(connection, message, + (char*)governor); + return FALSE; + } + } + } else { + for (it = cpus; it != NULL; it = g_slist_next(it)) { + if (!write_governor((char*)governor, + GPOINTER_TO_INT(((GSList*)it->data)->data))) { + dbus_raise_governor_init_failed(connection, message, + (char*)governor); + HAL_WARNING(("Could not set %s governor.", governor)); + return FALSE; + } + } + } + + set_performance(NULL, NULL, DEFAULT_PERFORMANCE); + + return TRUE; +} + +/** gets the current governor which is set for all cpufreq objects + * + * @raises GeneralError + */ +static gboolean get_governors(DBusConnection *connection, DBusMessage *message, + char *governor) +{ + char *governor_file; + int cpu_id = 0; + + governor_file = g_strdup_printf(SYSFS_GOVERNOR_FILE, cpu_id); + + if (!read_line(governor_file, governor, MAX_LINE_SIZE)) { + dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL, + "Could not read current governor"); + g_free(governor_file); + return FALSE; + } + g_free(governor_file); + + /* strip trailing '\n' */ + governor[strlen(governor)-1] = '\0'; + + return TRUE; +} +/********************* main interface end *********************/ + +/********************* DBus stuff *********************/ + +/** raises the NoSuitableGovernor error with the given method in the + * detail field */ +static gboolean dbus_raise_no_suitable_governor(DBusConnection *connection, + DBusMessage *message, + char *method) +{ + return dbus_raise_error(connection, message, + CPUFREQ_ERROR_NO_SUITABLE_GOVERNOR, + "No '%s' setting for current governor", + method); +} + +/** raises the GovernorInitFailed error with the given governor in the + * detail field */ +static gboolean dbus_raise_governor_init_failed(DBusConnection *connection, + DBusMessage *message, + char *governor) +{ + return dbus_raise_error(connection, message, + CPUFREQ_ERROR_GOVERNOR_INIT_FAILED, + "Initialization of %s interface failed", + governor); +} + +/** raises the given error_name with the format in the detail field */ +static gboolean dbus_raise_error(DBusConnection *connection, DBusMessage *message, + const char *error_name, char *format, ...) +{ + char buf[2 * MAX_LINE_SIZE]; + DBusMessage *reply; + va_list args; + char *error = NULL; + + if (connection == NULL || message == NULL) + return FALSE; + + va_start(args, format); + vsnprintf(buf, sizeof buf, format, args); + va_end(args); + + error = g_strdup_printf("%s.%s", DBUS_INTERFACE, error_name); + reply = dbus_message_new_error(message, error, buf); + g_free(error); + if (reply == NULL) { + HAL_WARNING(("No memory")); + return FALSE; + } + + if (!dbus_connection_send(connection, reply, NULL)) { + HAL_WARNING(("No memory")); + dbus_message_unref(reply); + return FALSE; + } + dbus_message_unref(reply); + + return TRUE; +} + +#ifdef HAVE_POLKIT +/** checks if caller of message possesses the CPUFREQ_POLKIT_PRIVILGE */ +static gboolean dbus_is_privileged(DBusConnection *connection, DBusMessage *message, + DBusError *error) +{ + LibPolKitContext *polctx = NULL; + char *caller_unix_user_str; + const char *caller_dbus_name; + unsigned long caller_unix_user; + DBusConnection *connection_new; + gboolean out_is_allowed; + gboolean out_is_temporary; + LibPolKitResult res; + + connection_new = dbus_bus_get(DBUS_BUS_SYSTEM, error); + if (dbus_error_is_set(error)) { + dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL, + "Cannot get connection to system bus"); + return FALSE; + } + + polctx = libpolkit_new_context(connection_new); + if (polctx == NULL) { + dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL, + "Cannot get PolicyKit context"); + return FALSE; + } + + caller_dbus_name = dbus_message_get_sender(message); + caller_unix_user = dbus_bus_get_unix_user(connection_new, caller_dbus_name, error); + HAL_DEBUG(("Connection name of caller: %s", caller_dbus_name)); + HAL_DEBUG(("Unix user id of caller: %ld", caller_unix_user)); + if (dbus_error_is_set(error)) { + dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL, + "Cannot get unix user of caller"); + dbus_error_free(error); + goto Error; + } + + caller_unix_user_str = g_strdup_printf("%ld", caller_unix_user); + res = libpolkit_is_uid_allowed_for_privilege(polctx, + caller_dbus_name, + caller_unix_user_str, + CPUFREQ_POLKIT_PRIVILEGE, + getenv("UDI"), + &out_is_allowed, + &out_is_temporary, + NULL); + g_free(caller_unix_user_str); + + if (res != LIBPOLKIT_RESULT_OK) { + dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL, + "Cannot lookup privilege: %d", res); + goto Error; + } + + if (!out_is_allowed) { + HAL_DEBUG(("caller don't possess privilege")); + dbus_raise_error(connection, message, CPUFREQ_ERROR_PERMISSION_DENIED, + "%s refused uid %d", CPUFREQ_POLKIT_PRIVILEGE, caller_unix_user); + goto Error; + } + + HAL_DEBUG(("Caller is privileged")); + return out_is_allowed; + +Error: + libpolkit_free_context(polctx); + return FALSE; +} +#endif + +/** sends a reply to message with the given data and its dbus_type */ +static gboolean dbus_send_reply(DBusConnection *connection, DBusMessage *message, + int dbus_type, void *data) +{ + DBusMessage *reply; + + if ((reply = dbus_message_new_method_return(message)) == NULL) { + HAL_WARNING(("Could not allocate memory for the DBus reply")); + return FALSE; + } + + if (data != NULL) + dbus_message_append_args(reply, dbus_type, data, DBUS_TYPE_INVALID); + + if (!dbus_connection_send(connection, reply, NULL)) { + HAL_WARNING(("Could not sent reply")); + return FALSE; + } + dbus_connection_flush(connection); + dbus_message_unref(reply); + + return TRUE; +} + +/** sends a reply to message appending a list of strings */ +static gboolean dbus_send_reply_strlist(DBusConnection *connection, DBusMessage *message, + gchar **list) +{ + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter iter_array; + int i; + + if ((reply = dbus_message_new_method_return(message)) == NULL) { + HAL_WARNING(("Could not allocate memory for the DBus reply")); + return FALSE; + } + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, + DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, + &iter_array); + + for (i = 0; list[i] != NULL; i++) + dbus_message_iter_append_basic (&iter_array, DBUS_TYPE_STRING, &list[i]); + + dbus_message_iter_close_container (&iter, &iter_array); + + if (!dbus_connection_send(connection, reply, NULL)) { + HAL_WARNING(("Could not sent reply")); + return FALSE; + } + + dbus_connection_flush(connection); + dbus_message_unref(reply); + + return TRUE; +} + +/** gets one argument from message with the given dbus_type and stores it + * in arg + * + * @raises InvalidMessage + */ +static gboolean dbus_get_argument(DBusConnection *connection, DBusMessage *message, + DBusError *dbus_error, int dbus_type, void *arg) +{ + dbus_message_get_args(message, dbus_error, dbus_type, arg, + DBUS_TYPE_INVALID); + if (dbus_error_is_set(dbus_error)) { + HAL_WARNING(("Could not get argument of DBus message: %s", + dbus_error->message)); + + dbus_raise_error(connection, message, + CPUFREQ_ERROR_INVALID_MESSAGE, + "%s", dbus_error->message); + dbus_error_free(dbus_error); + return FALSE; + } + return TRUE; +} + +/** dbus filter function + * + * @raises UnknownMethod + */ +static DBusHandlerResult dbus_filter_function(DBusConnection *connection, + DBusMessage *message, + void *user_data) +{ + DBusError dbus_error; + const char *member = dbus_message_get_member(message); + const char *path = dbus_message_get_path(message); + + HAL_DEBUG(("Received DBus message with member %s", member)); + HAL_DEBUG(("Received DBus message with path %s", path)); + + dbus_error_init(&dbus_error); + +#ifdef HAVE_POLKIT + if (!dbus_is_privileged(connection, message, &dbus_error)) + return DBUS_HANDLER_RESULT_HANDLED; +#endif + + if (dbus_message_is_method_call(message, DBUS_INTERFACE, + "SetCPUFreqGovernor")) { + char *arg; + + if (!dbus_get_argument(connection, message, &dbus_error, + DBUS_TYPE_STRING, &arg)) { + return DBUS_HANDLER_RESULT_HANDLED; + } + HAL_DEBUG(("Received argument: %s", arg)); + + if (set_governors(connection, message, arg)) + dbus_send_reply(connection, message, DBUS_TYPE_INVALID, NULL); + + } else if (dbus_message_is_method_call(message, DBUS_INTERFACE, + "SetCPUFreqPerformance")) { + int arg; + + if (!dbus_get_argument(connection, message, &dbus_error, + DBUS_TYPE_INT32, &arg)) { + return DBUS_HANDLER_RESULT_HANDLED; + } + HAL_DEBUG(("Received argument: %d", arg)); + + if (set_performance(connection, message, arg)) + dbus_send_reply(connection, message, DBUS_TYPE_INVALID, NULL); + + } else if (dbus_message_is_method_call(message, DBUS_INTERFACE, + "SetCPUFreqConsiderNice")) { + gboolean arg; + + if (!dbus_get_argument(connection, message, &dbus_error, + DBUS_TYPE_BOOLEAN, &arg)) { + return DBUS_HANDLER_RESULT_HANDLED; + } + HAL_DEBUG(("Received argument: %d", arg)); + + if (set_consider_nice(connection, message, arg)) + dbus_send_reply(connection, message, DBUS_TYPE_INVALID, NULL); + + } else if (dbus_message_is_method_call(message, DBUS_INTERFACE, + "GetCPUFreqGovernor")) { + char governor[MAX_LINE_SIZE + 1]; + char *gov = governor; + + if (get_governors(connection, message, governor)) + dbus_send_reply(connection, message, DBUS_TYPE_STRING, &gov); + + } else if (dbus_message_is_method_call(message, DBUS_INTERFACE, + "GetCPUFreqPerformance")) { + int performance = -1; + + if (get_performance(connection, message, &performance)) + dbus_send_reply(connection, message, DBUS_TYPE_INT32, &performance); + + } else if (dbus_message_is_method_call(message, DBUS_INTERFACE, + "GetCPUFreqConsiderNice")) { + int consider = -1; + + if (get_consider_nice(connection, message, &consider)) + dbus_send_reply(connection, message, DBUS_TYPE_BOOLEAN, &consider); + + } else if (dbus_message_is_method_call(message, DBUS_INTERFACE, + "GetCPUFreqAvailableGovernors")) { + gchar **governors = NULL; + + if (get_available_governors(connection, message, &governors)) + dbus_send_reply_strlist(connection, message, governors); + g_strfreev(governors); + + } else if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, + "Disconnected")) { + HAL_DEBUG(("DBus daemon disconnected. Trying to reconnect...")); + dbus_connection_close(connection); + dbus_connection_unref(connection); + g_timeout_add(5000, (GSourceFunc)dbus_init, NULL); + + } else + dbus_raise_error(connection, message, CPUFREQ_ERROR_UNKNOWN_METHOD, + "No such method '%s'", member); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void dbus_add_method(LibHalContext *halctx, const char *method, + const char *signature) +{ + libhal_device_property_strlist_append(halctx, + "/org/freedesktop/Hal/devices/computer", + "org.freedesktop.Hal.Device.CPUFreq.method_names", + method, + NULL); + libhal_device_property_strlist_append(halctx, + "/org/freedesktop/Hal/devices/computer", + "org.freedesktop.Hal.Device.CPUFreq.method_signatures", + signature, + NULL); +} + +static gboolean is_supported(void) +{ + char *governor_file = NULL; + + governor_file = g_strdup_printf(SYSFS_GOVERNOR_FILE, 0); + if (access(governor_file, F_OK) != 0) { + g_free(governor_file); + return FALSE; + } + g_free(governor_file); + return TRUE; +} + +/** returns FALSE on success because it's used as a callback */ +gboolean dbus_init(void) +{ + DBusError dbus_error; + DBusConnection *dbus_connection; + char *udi = getenv("UDI"); + LibHalContext *halctx = NULL; + + dbus_error_init(&dbus_error); + + if ((halctx = libhal_ctx_init_direct(&dbus_error)) == NULL) { + HAL_WARNING(("Cannot connect to hald")); + goto Error; + } + + if ((dbus_connection = libhal_ctx_get_dbus_connection(halctx)) == NULL) { + HAL_WARNING(("Cannot get DBus connection")); + goto Error; + } + + if (!libhal_device_claim_interface(halctx, udi, + "org.freedesktop.Hal.Device.CPUFreq", + " <method name=\"SetCPUFreqGovernor\">" + " <arg name=\"governor_string\" direction=\"in\" type=\"s\"/>" + " <arg name=\"return_code\" direction=\"out\" type=\"i\"/>" + " </method>" + " <method name=\"SetCPUFreqPerformance\">" + " <arg name=\"value\" direction=\"in\" type=\"i\"/>" + " <arg name=\"return_code\" direction=\"out\" type=\"i\"/>" + " </method>" + " <method name=\"SetCPUFreqConsiderNice\">" + " <arg name=\"value\" direction=\"in\" type=\"b\"/>" + " <arg name=\"return_code\" direction=\"out\" type=\"i\"/>" + " </method>" + " <method name=\"GetCPUFreqGovernor\">" + " <arg name=\"return_code\" direction=\"out\" type=\"s\"/>" + " </method>" + " <method name=\"GetCPUFreqPerformance\">" + " <arg name=\"return_code\" direction=\"out\" type=\"i\"/>" + " </method>" + " <method name=\"GetCPUFreqConsiderNice\">" + " <arg name=\"return_code\" direction=\"out\" type=\"b\"/>" + " </method>" + " <method name=\"GetCPUFreqAvailableGovernors\">" + " <arg name=\"return_code\" direction=\"out\" type=\"as\"/>" + " </method>", + &dbus_error)) { + + HAL_WARNING(("Cannot claim interface: %s", dbus_error.message)); + fprintf(stderr, "direct Cannot claim interface: %s", dbus_error.message); + goto Error; + } + + libhal_device_add_capability(halctx, + "/org/freedesktop/Hal/devices/computer", + "cpufreq_control", + NULL); + + dbus_add_method(halctx, "SetCPUFreqGovernor", "s"); + dbus_add_method(halctx, "SetCPUFreqPerformance", "i"); + dbus_add_method(halctx, "SetCPUFreqConsiderNice", "b"); + dbus_add_method(halctx, "GetCPUFreqGovernor", ""); + dbus_add_method(halctx, "GetCPUFreqPerformance", ""); + dbus_add_method(halctx, "GetCPUFreqConsiderNice", ""); + dbus_add_method(halctx, "GetCPUFreqAvailableGovernors", ""); + + + dbus_connection_setup_with_g_main(dbus_connection, NULL); + dbus_connection_add_filter(dbus_connection, dbus_filter_function, NULL, NULL); + dbus_connection_set_exit_on_disconnect(dbus_connection, 0); + return FALSE; + +Error: + dbus_error_free(&dbus_error); + return TRUE; +} +/********************* DBus end *********************/ + +static void exit_handler(int i) +{ + GSList *it = NULL; + + for (it = cpufreq_objs; it != NULL; it = g_slist_next(it)) { + struct cpufreq_obj *obj = it->data; + obj->free(obj->iface); + free(obj->iface); + free(obj); + } + g_slist_free(cpufreq_objs); + + HAL_DEBUG(("exit")); + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + struct sigaction signal_action; + GMainLoop *gmain; + + memset(&signal_action, 0, sizeof(signal_action)); + sigaddset(&signal_action.sa_mask, SIGTERM); + signal_action.sa_flags = SA_RESTART || SA_NOCLDSTOP; + signal_action.sa_handler = exit_handler; + sigaction(SIGINT, &signal_action, 0); + sigaction(SIGQUIT, &signal_action, 0); + sigaction(SIGTERM, &signal_action, 0); + + if (!is_supported()) { + HAL_WARNING(("CPUFreq not supported. Exiting...")); + exit(EXIT_FAILURE); + } + + if (dbus_init()) + exit(EXIT_FAILURE); + + gmain = g_main_loop_new(NULL, FALSE); + g_main_loop_run(gmain); + + return 0; +} diff --git a/hald/linux/addons/addon-cpufreq.h b/hald/linux/addons/addon-cpufreq.h new file mode 100644 index 00000000..d8122190 --- /dev/null +++ b/hald/linux/addons/addon-cpufreq.h @@ -0,0 +1,72 @@ +/*************************************************************************** + * * + * addon-cpufreq.h * + * * + * Copyright (C) 2006 SUSE Linux Products GmbH * + * * + * Author(s): Holger Macht <hmacht@suse.de> * + * * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at you * + * option) any later version. * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * + * * + ***************************************************************************/ + +#ifndef ADDON_CPUFREQ_H +#define ADDON_CPUFREQ_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> +#include <glib.h> +#include <stdio.h> +#include <stdlib.h> + +/** UP_THRESHOLD defines at which CPU load (in percent) we switch up */ +#define UP_THRESHOLD_MAX 99 +#define UP_THRESHOLD_MIN 11 +/** this is the kernel default up_threshold */ +#define UP_THRESHOLD_BASE 80 +#define DEFAULT_PERFORMANCE 50 + +struct cpufreq_obj { + void *iface; + gboolean (*set_performance) (void *data, int); + gboolean (*set_consider_nice) (void *data, gboolean); + int (*get_performance) (void); + gboolean (*get_consider_nice) (void); + void (*free) (void *data); +}; + +gboolean write_line (const char *filename, + const char *fmt, ...); + +gboolean read_line (const char *filename, + char *line, + unsigned len); + +gboolean read_line_int_split (char *filename, + gchar *delim, + GSList **list); + +gboolean cpu_online (int cpu_id); + +gboolean write_governor (char *new_governor, + int cpu_id); + +gboolean dbus_init (void); + +#endif /* ADDON_CPUFREQ_H */ diff --git a/privileges/Makefile.am b/privileges/Makefile.am index f32ab1c9..d300a603 100644 --- a/privileges/Makefile.am +++ b/privileges/Makefile.am @@ -9,7 +9,8 @@ dist_polkit_privilege_DATA = \ hal-power-suspend.privilege \ hal-power-hibernate.privilege \ hal-power-poweroff.privilege \ - hal-power-reboot.privilege + hal-power-reboot.privilege \ + hal-power-cpufreq.privilege clean-local : rm -f *~ diff --git a/privileges/hal-power-cpufreq.privilege b/privileges/hal-power-cpufreq.privilege new file mode 100644 index 00000000..cee08b46 --- /dev/null +++ b/privileges/hal-power-cpufreq.privilege @@ -0,0 +1,12 @@ + +# This privilege specifies who is allowed to control CPUFreq +# via the org.freedesktop.Hal.Device.CPUFreq interface + +[Privilege] +RequiredPrivileges=desktop-console +SufficientPrivileges= +Allow=uid:__all__ +Deny= +CanObtain=True +CanGrant=True +ObtainRequireRoot=False |