[PATCH 1/3] CPU frequency scaling addon
Holger Macht
hmacht at suse.de
Fri Aug 11 10:45:15 PDT 2006
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 at suse.de>
---
diff --git a/fdi/policy/10osvendor/10-power-mgmt-policy.fdi b/fdi/policy/10osvendor/10-power-mgmt-policy.fdi
index 3c292fd..27f837f 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/linux2/addons/Makefile.am b/hald/linux2/addons/Makefile.am
index 33cdd9c..1923093 100644
--- a/hald/linux2/addons/Makefile.am
+++ b/hald/linux2/addons/Makefile.am
@@ -15,7 +15,8 @@ libexec_PROGRAMS = \
hald-addon-acpi-buttons-toshiba \
hald-addon-storage \
hald-addon-keyboard \
- hald-addon-pmu
+ hald-addon-pmu \
+ hald-addon-cpufreq
if HAVE_LIBUSB
libexec_PROGRAMS += hald-addon-usb-csr
@@ -25,6 +26,10 @@ libexec_PROGRAMS += hald-addon-macbookpr
endif
endif
+hald_addon_cpufreq_SOURCES = addon-cpufreq.c addon-cpufreq.h addon-cpufreq-userspace.h \
+ addon-cpufreq-userspace.c
+hald_addon_cpufreq_LDADD = $(top_builddir)/libhal/libhal.la @GLIB_LIBS@ @POLKIT_LIBS@
+
hald_addon_hid_ups_SOURCES = addon-hid-ups.c
hald_addon_hid_ups_LDADD = $(top_builddir)/libhal/libhal.la
diff --git a/hald/linux2/addons/addon-acpi-buttons-toshiba.c b/hald/linux2/addons/addon-acpi-buttons-toshiba.c
diff --git a/hald/linux2/addons/addon-acpi.c b/hald/linux2/addons/addon-acpi.c
diff --git a/hald/linux2/addons/addon-cpufreq-userspace.c b/hald/linux2/addons/addon-cpufreq-userspace.c
new file mode 100644
index 0000000..db458bf
--- /dev/null
+++ b/hald/linux2/addons/addon-cpufreq-userspace.c
@@ -0,0 +1,528 @@
+/***************************************************************************
+ * *
+ * addon-cpufreq-userspace.c *
+ * *
+ * Copyright (C) 2006 SUSE Linux Products GmbH *
+ * *
+ * Author(s): Holger Macht <hmacht at suse.de> *
+ * Speed adjustments based on code by *
+ * Thomas Renninger <trenn at 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 "addon-cpufreq.h"
+#include "addon-cpufreq-userspace.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()
+{
+ 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) {
+ dbg("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) {
+ ERROR("%s too short (%s)", PROC_STAT_FILE, cpu_string);
+ fclose(fp);
+ return -1;
+ }
+ if (memcmp(line, cpu_string, strlen(cpu_string))) {
+ ERROR("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) {
+ ERROR("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)
+ dbg("%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) {
+ ERROR("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)
+{
+ GString *speed_file = g_string_new("");
+ gboolean ret = TRUE;
+
+ if (!cpu_online(cpu_id)) {
+ ret = FALSE;
+ goto Out;
+ }
+
+ g_string_printf(speed_file, SYSFS_SCALING_SETSPEED_FILE, cpu_id);
+
+ if(!write_line(speed_file->str, "%u", kHz)){
+ ERROR("Could not set speed to: %u kHz; %s", kHz, strerror(errno));
+ ret = FALSE;
+ goto Out;
+ }
+
+ dbg("Speed set to: %uKHz for CPU %d", kHz, cpu_id);
+Out:
+ g_string_free(speed_file, TRUE);
+ return ret;
+}
+
+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);
+ dbg("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) {
+ dbg("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) {
+ dbg("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)) {
+ dbg("checking cpu %d: cpu_core: %d", (int)it->data, (int)it->data);
+ if (get_cpu_load((int)it->data) > cpu_load)
+ cpu_load = get_cpu_load((int)it->data);
+ }
+
+ dbg("cpu_max: %d cpu_high_limit: %d consider_nice: %d",
+ config.up_threshold, config.cpu_high_limit,
+ config.consider_nice);
+ dbg("Current: %u; current speed: %u MHz",
+ iface->current_speed, g_a_i(iface->speeds_kHz, iface->current_speed));
+ dbg("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;
+ dbg("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);
+ dbg("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);
+ dbg("decreased to %d kHz", g_a_i(iface->speeds_kHz, iface->current_speed));
+ ret = -1;
+ } else {
+ ret = 0;
+ dbg("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);
+ dbg("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;
+ GString *available_frequencies_file = g_string_new("");
+
+ if (!cpu_online(iface->base_cpu))
+ return FALSE;
+
+ g_string_printf(available_frequencies_file,
+ SYSFS_SCALING_AVAILABLE_FREQS_FILE,
+ iface->base_cpu);
+ if (!read_line_int_split(available_frequencies_file, " ", &available_freqs))
+ return FALSE;
+
+ g_string_free(available_frequencies_file, TRUE);
+
+ 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 = (unsigned)it->data;
+ g_array_append_val(iface->speeds_kHz, index);
+ }
+ g_slist_free(available_freqs);
+
+ iface->last_step = num_speeds - 1;
+ dbg("Number of speeds: %d, last_step: %d", num_speeds, iface->last_step);
+
+ reinit_speed(iface, 0);
+
+ dbg("Available speeds:");
+ for (num_speeds = 0; g_a_i(iface->speeds_kHz, num_speeds); num_speeds++) {
+ dbg(" %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;
+
+ dbg("Adjusting speeds...");
+
+ if ((calc_cpu_load(DEFAULT_CONSIDER_NICE) < 0)) {
+ dbg("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 = (int)cpus->data;
+
+ if ((getenv("HALD_VERBOSE")) != NULL)
+ is_verbose = TRUE;
+
+ if (!write_governor(USERSPACE_STRING, (int)cpus->data)) {
+ ERROR("Could not set userspace governor.");
+ return FALSE;
+ }
+
+ if (!read_frequencies(iface)) {
+ ERROR("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;
+
+ dbg("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)
+{
+ dbg("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/linux2/addons/addon-cpufreq-userspace.h b/hald/linux2/addons/addon-cpufreq-userspace.h
new file mode 100644
index 0000000..e5674d5
--- /dev/null
+++ b/hald/linux2/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 at 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/linux2/addons/addon-cpufreq.c b/hald/linux2/addons/addon-cpufreq.c
new file mode 100644
index 0000000..c5b13d5
--- /dev/null
+++ b/hald/linux2/addons/addon-cpufreq.c
@@ -0,0 +1,1185 @@
+/***************************************************************************
+ * *
+ * addon-cpufreq.c *
+ * *
+ * Copyright (C) 2006 SUSE Linux Products GmbH *
+ * *
+ * Author(s): Holger Macht <hmacht at 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 "addon-cpufreq.h"
+#include "addon-cpufreq-userspace.h"
+#include "libhal/libhal.h"
+
+#ifdef HAVE_POLKIT
+#include <libpolkit/libpolkit.h>
+#endif
+
+#define MAX_LINE_SIZE 255
+#define PROC_CPUINFO_FILE "/proc/cpuinfo"
+#define CPUFREQ_POLKIT_PRIVILEGE "hal-power-cpufreq"
+#define DBUS_INTERFACE "org.freedesktop.Hal.Device.SystemPowerManagement"
+
+#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)) {
+ ERROR("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) {
+ ERROR("Could not open '%s': %s", filename, strerror(errno));
+ return FALSE;
+ }
+ if ((!fgets(line, len, fp))) {
+ ERROR("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) {
+ ERROR("Could not open file for writing: %s; %s", filename,
+ strerror(errno));
+ return FALSE;
+ }
+
+ va_start(ap, fmt);
+
+ if (vfprintf(fp, fmt, ap) < 0) {
+ ERROR("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(GString *filename, gchar *delim)
+{
+ gchar line[MAX_LINE_SIZE];
+ int i;
+ gchar **l;
+
+ if(!read_line(filename->str, 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(GString *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)) {
+
+ dbg("comparing %d with %d", (int)current_it->data, (int)list_it->data);
+ if ((int)current_it->data != (int)list_it->data)
+ equal = FALSE;
+ }
+
+ dbg("equal? %s, %d", equal ? "yes" : "no", equal);
+ if (equal) {
+ dbg("remove: %d", g_slist_length(*list));
+ *list = g_slist_remove(*list, current);
+ dbg("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;
+ GString *affected_cpus_file = g_string_new("");
+
+ g_string_printf(affected_cpus_file, SYSFS_AFFECTED_CPUS_FILE, i);
+
+ if (!read_line_int_split(affected_cpus_file, " ", &affected_cpus))
+ return FALSE;
+ g_string_free(affected_cpus_file, TRUE);
+
+ 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)) {
+ ERROR("failed to get affected_cpus for cpu %d", i);
+ continue;
+ }
+
+ *cpu_list = g_slist_append(*cpu_list, int_cpus);
+ }
+
+ dbg("Number of CPUs before uniquing cpu_list: %d", g_slist_length(*cpu_list));
+ g_slist_foreach(*cpu_list, (GFunc)cpu_list_unique, cpu_list);
+ dbg("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];
+
+ GString *online_file = g_string_new("");
+ g_string_printf(online_file, SYSFS_CPU_ONLINE_FILE, cpu_id);
+
+ if (access(online_file->str, F_OK) < 0) {
+ online = TRUE;
+ goto Out;
+ }
+
+ if (!read_line(online_file->str, online_str, 2)) {
+ ERROR("Unable to open file: %s", online_file->str);
+ online = FALSE;
+ goto Out;
+ }
+
+ online = atoi(online_str);
+
+ if (!online)
+ online = FALSE;
+Out:
+ g_string_free(online_file, TRUE);
+ return online;
+}
+
+/** writes the new_governor string into the sysfs interface */
+gboolean write_governor(char *new_governor, int cpu_id)
+{
+ gboolean ret = TRUE;
+ GString *governor_file = g_string_new("");
+ char governor[MAX_LINE_SIZE + 1];
+
+ if (!cpu_online(cpu_id))
+ goto Out;
+
+ g_string_printf(governor_file, SYSFS_GOVERNOR_FILE, cpu_id);
+ dbg("Trying ot write governor %s", new_governor);
+
+ if (!write_line(governor_file->str, "%s", new_governor)) {
+ ret = FALSE;
+ goto Out;
+ }
+
+ /* check if governor has been set */
+ usleep(1000);
+ read_line(governor_file->str, governor, MAX_LINE_SIZE);
+ if (strstr(governor, new_governor))
+ ret = TRUE;
+ else
+ ret = FALSE;
+Out:
+ g_string_free(governor_file, TRUE);
+ 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;
+ GString *up_threshold_file = g_string_new("");
+
+ g_string_printf(up_threshold_file, ONDEMAND_UP_THRESHOLD_FILE, iface->base_cpu);
+
+ if(!write_line(up_threshold_file->str, "%u", performance)){
+ ERROR("Could not set up_threshold to %u kHz; %s", performance,
+ strerror(errno));
+ return FALSE;
+ }
+ dbg("Up threshold set to %d for ondemand", performance);
+ g_string_free(up_threshold_file, TRUE);
+
+ return TRUE;
+}
+
+static int ondemand_get_performance(void)
+{
+ GString *governor_file = g_string_new("");
+ int performance = -1;
+
+ g_string_printf(governor_file, ONDEMAND_UP_THRESHOLD_FILE, 0);
+
+ if (!read_line_int(governor_file->str, &performance)) {
+ ERROR("Could not read up_threshold");
+ return -1;
+ }
+ g_string_free(governor_file, TRUE);
+
+ return performance;
+}
+
+static gboolean ondemand_set_consider_nice(void *data, gboolean consider)
+{
+ struct ondemand_interface *iface = data;
+ GString *consider_file = g_string_new("");
+
+ g_string_printf(consider_file, ONDEMAND_IGNORE_NICE_LOAD_FILE, iface->base_cpu);
+
+ if(!write_line(consider_file->str, "%u", consider)){
+ ERROR("Could not set ignore_nice_load to: %u kHz; %s", consider,
+ strerror(errno));
+ return FALSE;
+ }
+ dbg("Set consider nice to %d for ondemand", consider);
+ g_string_free(consider_file, TRUE);
+
+ return TRUE;
+}
+
+static gboolean ondemand_get_consider_nice(void)
+{
+ GString *governor_file = g_string_new("");
+ gboolean consider = -1;
+
+ /* only read the setting of cpu0 */
+ g_string_printf(governor_file, ONDEMAND_IGNORE_NICE_LOAD_FILE, 0);
+
+ if (!read_line_int(governor_file->str, &consider)) {
+ ERROR("Could not read ignore_nice_load file");
+ return -1;
+ }
+ g_string_free(governor_file, TRUE);
+
+ return consider;
+}
+
+static gboolean ondemand_init(struct ondemand_interface *iface, GSList *cores)
+{
+ if (iface == NULL)
+ return FALSE;
+
+ if (!write_governor(ONDEMAND_STRING, (int)cores->data)) {
+ ERROR("Could not set ondemand governor.");
+ return FALSE;
+ }
+
+ iface->base_cpu = (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)
+{
+ GString *agovs_file = g_string_new("");
+
+ g_string_printf(agovs_file, SYSFS_AVAILABLE_GOVERNORS_FILE, 0);
+ *governors = read_line_str_split(agovs_file, " ");
+ g_string_free(agovs_file, TRUE);
+
+ 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");
+ ERROR("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");
+ ERROR("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);
+ dbg("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);
+ dbg("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,
+ (int)((GSList*)it->data)->data)) {
+ dbus_raise_governor_init_failed(connection, message,
+ (char*)governor);
+ ERROR("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)
+{
+ GString *governor_file = g_string_new("");
+ int cpu_id = 0;
+
+ g_string_printf(governor_file, SYSFS_GOVERNOR_FILE, cpu_id);
+
+ if (!read_line(governor_file->str, governor, MAX_LINE_SIZE)) {
+ dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL,
+ "Could not read current governor");
+ return FALSE;
+ }
+ g_string_free(governor_file, TRUE);
+
+ /* 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;
+ GString *error = g_string_new("");
+
+ if (connection == NULL || message == NULL)
+ return FALSE;
+
+ va_start(args, format);
+ vsnprintf(buf, sizeof buf, format, args);
+ va_end(args);
+
+ g_string_printf(error, "%s.%s", DBUS_INTERFACE, error_name);
+ reply = dbus_message_new_error(message, error->str, buf);
+ g_string_free(error, TRUE);
+ if (reply == NULL) {
+ ERROR("No memory");
+ return FALSE;
+ }
+
+ if (!dbus_connection_send(connection, reply, NULL)) {
+ ERROR("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;
+ GString *caller_unix_user_str = g_string_new("");
+ 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);
+ dbg("Connection name of caller: %s", caller_dbus_name);
+ dbg("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;
+ }
+
+ g_string_printf(caller_unix_user_str, "%ld", caller_unix_user);
+ res = libpolkit_is_uid_allowed_for_privilege(polctx,
+ caller_dbus_name,
+ caller_unix_user_str->str,
+ CPUFREQ_POLKIT_PRIVILEGE,
+ getenv("UDI"),
+ &out_is_allowed,
+ &out_is_temporary,
+ NULL);
+ g_string_free(caller_unix_user_str, TRUE);
+
+ if (res != LIBPOLKIT_RESULT_OK) {
+ dbus_raise_error(connection, message, CPUFREQ_ERROR_GENERAL,
+ "Cannot lookup privilege: %d", res);
+ goto Error;
+ }
+
+ if (!out_is_allowed) {
+ dbg("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;
+ }
+
+ dbg("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) {
+ ERROR("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)) {
+ ERROR("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;
+ int i;
+
+ if ((reply = dbus_message_new_method_return(message)) == NULL) {
+ ERROR("Could not allocate memory for the DBus reply");
+ return FALSE;
+ }
+
+ for (i = 0; list[i] != NULL; i++)
+ dbus_message_append_args(reply, DBUS_TYPE_STRING, &list[i], DBUS_TYPE_INVALID);
+
+ if (!dbus_connection_send(connection, reply, NULL)) {
+ ERROR("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)) {
+ ERROR("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);
+
+ dbg("Received DBus message with member %s", member);
+ dbg("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;
+ }
+ dbg("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;
+ }
+ dbg("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;
+ }
+ dbg("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")) {
+ dbg("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.SystemPowerManagement.method_names",
+ method,
+ NULL);
+ libhal_device_property_strlist_append(halctx,
+ "/org/freedesktop/Hal/devices/computer",
+ "org.freedesktop.Hal.Device.SystemPowerManagement.method_signatures",
+ signature,
+ NULL);
+}
+
+/** 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;
+ GString *governor_file = g_string_new("");
+
+ dbus_error_init(&dbus_error);
+
+ if ((halctx = libhal_ctx_init_direct(&dbus_error)) == NULL) {
+ ERROR("Cannot connect to hald");
+ goto Error;
+ }
+
+ if ((dbus_connection = libhal_ctx_get_dbus_connection(halctx)) == NULL) {
+ ERROR("Cannot get DBus connection");
+ goto Error;
+ }
+
+ if (!libhal_device_claim_interface(halctx, udi,
+ "org.freedesktop.Hal.Device.SystemPowerManagement",
+ " <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=\"strlist\"/>"
+ " </method>",
+ &dbus_error)) {
+
+ ERROR("Cannot claim interface: %s", dbus_error.message);
+ fprintf(stderr, "direct Cannot claim interface: %s", dbus_error.message);
+ goto Error;
+ }
+
+ g_string_printf(governor_file, SYSFS_GOVERNOR_FILE, 0);
+ if (access(governor_file->str, F_OK) == 0) {
+ 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", "");
+ }
+ g_string_free(governor_file, TRUE);
+
+ 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);
+
+ dbg("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 ((getenv("HALD_VERBOSE")) != NULL)
+ is_verbose = TRUE;
+
+ if (dbus_init())
+ ERROR("Could not establish DBus connection");
+
+ gmain = g_main_loop_new(NULL, FALSE);
+ g_main_loop_run(gmain);
+
+ _set_debug ();
+ drop_privileges(0);
+ char *ar[] = { };
+ hal_set_proc_title_init (0, ar);
+ hal_set_proc_title ("");
+
+ return 0;
+}
diff --git a/hald/linux2/addons/addon-cpufreq.h b/hald/linux2/addons/addon-cpufreq.h
new file mode 100644
index 0000000..43a6f86
--- /dev/null
+++ b/hald/linux2/addons/addon-cpufreq.h
@@ -0,0 +1,75 @@
+/***************************************************************************
+ * *
+ * addon-cpufreq.h *
+ * *
+ * Copyright (C) 2006 SUSE Linux Products GmbH *
+ * *
+ * Author(s): Holger Macht <hmacht at 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>
+#include "../probing/shared.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
+
+#define ERROR(string, args...) dbg(string, ## args)
+
+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 (GString *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/hald/linux2/addons/addon-hid-ups.c b/hald/linux2/addons/addon-hid-ups.c
diff --git a/hald/linux2/addons/addon-keyboard.c b/hald/linux2/addons/addon-keyboard.c
diff --git a/hald/linux2/addons/addon-macbookpro-backlight.c b/hald/linux2/addons/addon-macbookpro-backlight.c
diff --git a/hald/linux2/addons/addon-pmu.c b/hald/linux2/addons/addon-pmu.c
diff --git a/hald/linux2/addons/addon-storage.c b/hald/linux2/addons/addon-storage.c
diff --git a/hald/linux2/addons/addon-usb-csr.c b/hald/linux2/addons/addon-usb-csr.c
More information about the hal
mailing list