![Peter Zijlstra](/assets/img/avatar_default.png)
It's been broken forever (i.e. it's not scheduling in a power aware fashion), as reported by Suresh and others sending patches, and nobody cares enough to fix it properly ... so remove it to make space free for something better. There's various problems with the code as it stands today, first and foremost the user interface which is bound to topology levels and has multiple values per level. This results in a state explosion which the administrator or distro needs to master and almost nobody does. Furthermore large configuration state spaces aren't good, it means the thing doesn't just work right because it's either under so many impossibe to meet constraints, or even if there's an achievable state workloads have to be aware of it precisely and can never meet it for dynamic workloads. So pushing this kind of decision to user-space was a bad idea even with a single knob - it's exponentially worse with knobs on every node of the topology. There is a proposal to replace the user interface with a single 3 state knob: sched_balance_policy := { performance, power, auto } where 'auto' would be the preferred default which looks at things like Battery/AC mode and possible cpufreq state or whatever the hw exposes to show us power use expectations - but there's been no progress on it in the past many months. Aside from that, the actual implementation of the various knobs is known to be broken. There have been sporadic attempts at fixing things but these always stop short of reaching a mergable state. Therefore this wholesale removal with the hopes of spurring people who care to come forward once again and work on a coherent replacement. Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Suresh Siddha <suresh.b.siddha@intel.com> Cc: Arjan van de Ven <arjan@linux.intel.com> Cc: Vincent Guittot <vincent.guittot@linaro.org> Cc: Vaidyanathan Srinivasan <svaidy@linux.vnet.ibm.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Andrew Morton <akpm@linux-foundation.org> Link: http://lkml.kernel.org/r/1326104915.2442.53.camel@twins Signed-off-by: Ingo Molnar <mingo@kernel.org>
378 lines
8 KiB
C
378 lines
8 KiB
C
/*
|
|
* (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de>
|
|
* (C) 2011 Thomas Renninger <trenn@novell.com> Novell Inc.
|
|
*
|
|
* Licensed under the terms of the GNU GPL License version 2.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "helpers/sysfs.h"
|
|
|
|
unsigned int sysfs_read_file(const char *path, char *buf, size_t buflen)
|
|
{
|
|
int fd;
|
|
ssize_t numread;
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd == -1)
|
|
return 0;
|
|
|
|
numread = read(fd, buf, buflen - 1);
|
|
if (numread < 1) {
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
buf[numread] = '\0';
|
|
close(fd);
|
|
|
|
return (unsigned int) numread;
|
|
}
|
|
|
|
static unsigned int sysfs_write_file(const char *path,
|
|
const char *value, size_t len)
|
|
{
|
|
int fd;
|
|
ssize_t numwrite;
|
|
|
|
fd = open(path, O_WRONLY);
|
|
if (fd == -1)
|
|
return 0;
|
|
|
|
numwrite = write(fd, value, len);
|
|
if (numwrite < 1) {
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
close(fd);
|
|
return (unsigned int) numwrite;
|
|
}
|
|
|
|
/*
|
|
* Detect whether a CPU is online
|
|
*
|
|
* Returns:
|
|
* 1 -> if CPU is online
|
|
* 0 -> if CPU is offline
|
|
* negative errno values in error case
|
|
*/
|
|
int sysfs_is_cpu_online(unsigned int cpu)
|
|
{
|
|
char path[SYSFS_PATH_MAX];
|
|
int fd;
|
|
ssize_t numread;
|
|
unsigned long long value;
|
|
char linebuf[MAX_LINE_LEN];
|
|
char *endp;
|
|
struct stat statbuf;
|
|
|
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u", cpu);
|
|
|
|
if (stat(path, &statbuf) != 0)
|
|
return 0;
|
|
|
|
/*
|
|
* kernel without CONFIG_HOTPLUG_CPU
|
|
* -> cpuX directory exists, but not cpuX/online file
|
|
*/
|
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/online", cpu);
|
|
if (stat(path, &statbuf) != 0)
|
|
return 1;
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd == -1)
|
|
return -errno;
|
|
|
|
numread = read(fd, linebuf, MAX_LINE_LEN - 1);
|
|
if (numread < 1) {
|
|
close(fd);
|
|
return -EIO;
|
|
}
|
|
linebuf[numread] = '\0';
|
|
close(fd);
|
|
|
|
value = strtoull(linebuf, &endp, 0);
|
|
if (value > 1 || value < 0)
|
|
return -EINVAL;
|
|
|
|
return value;
|
|
}
|
|
|
|
/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */
|
|
|
|
/*
|
|
* helper function to read file from /sys into given buffer
|
|
* fname is a relative path under "cpuX/cpuidle/stateX/" dir
|
|
* cstates starting with 0, C0 is not counted as cstate.
|
|
* This means if you want C1 info, pass 0 as idlestate param
|
|
*/
|
|
unsigned int sysfs_idlestate_read_file(unsigned int cpu, unsigned int idlestate,
|
|
const char *fname, char *buf, size_t buflen)
|
|
{
|
|
char path[SYSFS_PATH_MAX];
|
|
int fd;
|
|
ssize_t numread;
|
|
|
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
|
|
cpu, idlestate, fname);
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd == -1)
|
|
return 0;
|
|
|
|
numread = read(fd, buf, buflen - 1);
|
|
if (numread < 1) {
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
buf[numread] = '\0';
|
|
close(fd);
|
|
|
|
return (unsigned int) numread;
|
|
}
|
|
|
|
/* read access to files which contain one numeric value */
|
|
|
|
enum idlestate_value {
|
|
IDLESTATE_USAGE,
|
|
IDLESTATE_POWER,
|
|
IDLESTATE_LATENCY,
|
|
IDLESTATE_TIME,
|
|
MAX_IDLESTATE_VALUE_FILES
|
|
};
|
|
|
|
static const char *idlestate_value_files[MAX_IDLESTATE_VALUE_FILES] = {
|
|
[IDLESTATE_USAGE] = "usage",
|
|
[IDLESTATE_POWER] = "power",
|
|
[IDLESTATE_LATENCY] = "latency",
|
|
[IDLESTATE_TIME] = "time",
|
|
};
|
|
|
|
static unsigned long long sysfs_idlestate_get_one_value(unsigned int cpu,
|
|
unsigned int idlestate,
|
|
enum idlestate_value which)
|
|
{
|
|
unsigned long long value;
|
|
unsigned int len;
|
|
char linebuf[MAX_LINE_LEN];
|
|
char *endp;
|
|
|
|
if (which >= MAX_IDLESTATE_VALUE_FILES)
|
|
return 0;
|
|
|
|
len = sysfs_idlestate_read_file(cpu, idlestate,
|
|
idlestate_value_files[which],
|
|
linebuf, sizeof(linebuf));
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
value = strtoull(linebuf, &endp, 0);
|
|
|
|
if (endp == linebuf || errno == ERANGE)
|
|
return 0;
|
|
|
|
return value;
|
|
}
|
|
|
|
/* read access to files which contain one string */
|
|
|
|
enum idlestate_string {
|
|
IDLESTATE_DESC,
|
|
IDLESTATE_NAME,
|
|
MAX_IDLESTATE_STRING_FILES
|
|
};
|
|
|
|
static const char *idlestate_string_files[MAX_IDLESTATE_STRING_FILES] = {
|
|
[IDLESTATE_DESC] = "desc",
|
|
[IDLESTATE_NAME] = "name",
|
|
};
|
|
|
|
|
|
static char *sysfs_idlestate_get_one_string(unsigned int cpu,
|
|
unsigned int idlestate,
|
|
enum idlestate_string which)
|
|
{
|
|
char linebuf[MAX_LINE_LEN];
|
|
char *result;
|
|
unsigned int len;
|
|
|
|
if (which >= MAX_IDLESTATE_STRING_FILES)
|
|
return NULL;
|
|
|
|
len = sysfs_idlestate_read_file(cpu, idlestate,
|
|
idlestate_string_files[which],
|
|
linebuf, sizeof(linebuf));
|
|
if (len == 0)
|
|
return NULL;
|
|
|
|
result = strdup(linebuf);
|
|
if (result == NULL)
|
|
return NULL;
|
|
|
|
if (result[strlen(result) - 1] == '\n')
|
|
result[strlen(result) - 1] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
unsigned long sysfs_get_idlestate_latency(unsigned int cpu,
|
|
unsigned int idlestate)
|
|
{
|
|
return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_LATENCY);
|
|
}
|
|
|
|
unsigned long sysfs_get_idlestate_usage(unsigned int cpu,
|
|
unsigned int idlestate)
|
|
{
|
|
return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_USAGE);
|
|
}
|
|
|
|
unsigned long long sysfs_get_idlestate_time(unsigned int cpu,
|
|
unsigned int idlestate)
|
|
{
|
|
return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_TIME);
|
|
}
|
|
|
|
char *sysfs_get_idlestate_name(unsigned int cpu, unsigned int idlestate)
|
|
{
|
|
return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_NAME);
|
|
}
|
|
|
|
char *sysfs_get_idlestate_desc(unsigned int cpu, unsigned int idlestate)
|
|
{
|
|
return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_DESC);
|
|
}
|
|
|
|
/*
|
|
* Returns number of supported C-states of CPU core cpu
|
|
* Negativ in error case
|
|
* Zero if cpuidle does not export any C-states
|
|
*/
|
|
int sysfs_get_idlestate_count(unsigned int cpu)
|
|
{
|
|
char file[SYSFS_PATH_MAX];
|
|
struct stat statbuf;
|
|
int idlestates = 1;
|
|
|
|
|
|
snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpuidle");
|
|
if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
|
|
return -ENODEV;
|
|
|
|
snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpu%u/cpuidle/state0", cpu);
|
|
if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
|
|
return 0;
|
|
|
|
while (stat(file, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
|
|
snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU
|
|
"cpu%u/cpuidle/state%d", cpu, idlestates);
|
|
idlestates++;
|
|
}
|
|
idlestates--;
|
|
return idlestates;
|
|
}
|
|
|
|
/* CPUidle general /sys/devices/system/cpu/cpuidle/ sysfs access ********/
|
|
|
|
/*
|
|
* helper function to read file from /sys into given buffer
|
|
* fname is a relative path under "cpu/cpuidle/" dir
|
|
*/
|
|
static unsigned int sysfs_cpuidle_read_file(const char *fname, char *buf,
|
|
size_t buflen)
|
|
{
|
|
char path[SYSFS_PATH_MAX];
|
|
|
|
snprintf(path, sizeof(path), PATH_TO_CPU "cpuidle/%s", fname);
|
|
|
|
return sysfs_read_file(path, buf, buflen);
|
|
}
|
|
|
|
|
|
|
|
/* read access to files which contain one string */
|
|
|
|
enum cpuidle_string {
|
|
CPUIDLE_GOVERNOR,
|
|
CPUIDLE_GOVERNOR_RO,
|
|
CPUIDLE_DRIVER,
|
|
MAX_CPUIDLE_STRING_FILES
|
|
};
|
|
|
|
static const char *cpuidle_string_files[MAX_CPUIDLE_STRING_FILES] = {
|
|
[CPUIDLE_GOVERNOR] = "current_governor",
|
|
[CPUIDLE_GOVERNOR_RO] = "current_governor_ro",
|
|
[CPUIDLE_DRIVER] = "current_driver",
|
|
};
|
|
|
|
|
|
static char *sysfs_cpuidle_get_one_string(enum cpuidle_string which)
|
|
{
|
|
char linebuf[MAX_LINE_LEN];
|
|
char *result;
|
|
unsigned int len;
|
|
|
|
if (which >= MAX_CPUIDLE_STRING_FILES)
|
|
return NULL;
|
|
|
|
len = sysfs_cpuidle_read_file(cpuidle_string_files[which],
|
|
linebuf, sizeof(linebuf));
|
|
if (len == 0)
|
|
return NULL;
|
|
|
|
result = strdup(linebuf);
|
|
if (result == NULL)
|
|
return NULL;
|
|
|
|
if (result[strlen(result) - 1] == '\n')
|
|
result[strlen(result) - 1] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
char *sysfs_get_cpuidle_governor(void)
|
|
{
|
|
char *tmp = sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR_RO);
|
|
if (!tmp)
|
|
return sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR);
|
|
else
|
|
return tmp;
|
|
}
|
|
|
|
char *sysfs_get_cpuidle_driver(void)
|
|
{
|
|
return sysfs_cpuidle_get_one_string(CPUIDLE_DRIVER);
|
|
}
|
|
/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */
|
|
|
|
/*
|
|
* Get sched_mc or sched_smt settings
|
|
* Pass "mc" or "smt" as argument
|
|
*
|
|
* Returns negative value on failure
|
|
*/
|
|
int sysfs_get_sched(const char *smt_mc)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
|
|
/*
|
|
* Get sched_mc or sched_smt settings
|
|
* Pass "mc" or "smt" as argument
|
|
*
|
|
* Returns negative value on failure
|
|
*/
|
|
int sysfs_set_sched(const char *smt_mc, int val)
|
|
{
|
|
return -ENODEV;
|
|
}
|