RetroArch/misc/cpufreq/cpufreq.c

443 lines
13 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2021 - David Guillen Fandos
*
* RetroArch 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 Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <file/file_path.h>
#include <lists/string_list.h>
#include <lists/dir_list.h>
#include <streams/file_stream.h>
#include <string/stdstring.h>
#include <retro_miscellaneous.h>
#include "cpufreq.h"
#include "../../configuration.h"
#define REFRESH_TIMEOUT 2
#define CPU_POLICIES_DIR "/sys/devices/system/cpu/cpufreq/"
static time_t last_update = 0;
static cpu_scaling_driver_t **scaling_drivers = NULL;
/* Mode state and its options */
static enum cpu_scaling_mode cur_smode = CPUSCALING_MANAGED_PERFORMANCE;
static cpu_scaling_opts_t cur_smode_opts = { 1, ~0U, "performance", "ondemand" };
/* Precalculate and store the absolute max and min frequencies */
static uint32_t abs_min_freq = 1, abs_max_freq = ~0U;
static bool readparse_uint32(const char *path, uint32_t *value)
{
char *tmpbuf;
if (!filestream_read_file(path, (void**)&tmpbuf, NULL))
return false;
string_remove_all_chars(tmpbuf, '\n');
if (sscanf(tmpbuf, "%" PRIu32, value) != 1)
{
free(tmpbuf);
return false;
}
free(tmpbuf);
return true;
}
static struct string_list* readparse_list(const char *path)
{
char *tmpbuf;
struct string_list* ret;
if (!filestream_read_file(path, (void**)&tmpbuf, NULL))
return NULL;
string_remove_all_chars(tmpbuf, '\n');
ret = string_split(tmpbuf, " ");
free(tmpbuf);
return ret;
}
static void free_drivers(cpu_scaling_driver_t **d)
{
if (d)
{
cpu_scaling_driver_t **it = d;
while (*it)
{
cpu_scaling_driver_t *drv = *it++;
if (drv->affected_cpus)
free(drv->affected_cpus);
if (drv->scaling_governor)
free(drv->scaling_governor);
if (drv->available_freqs)
free(drv->available_freqs);
string_list_free(drv->available_governors);
free(drv);
}
free(d);
}
}
cpu_scaling_driver_t **get_cpu_scaling_drivers(bool can_update)
{
if (can_update && (time(NULL) > last_update + REFRESH_TIMEOUT ||
!scaling_drivers))
{
/* Parse /sys/devices/system/cpu/cpufreq/ policies */
int i, j, pc;
struct string_list *policy_dir = dir_list_new(CPU_POLICIES_DIR, NULL,
true, false, false, false);
if (!policy_dir)
return NULL;
dir_list_sort(policy_dir, false);
/* Delete the previous list of drivers */
free_drivers(scaling_drivers);
scaling_drivers = (cpu_scaling_driver_t**)calloc(
(policy_dir->size + 1), sizeof(cpu_scaling_driver_t*));
for (i = 0, pc = 0; i < policy_dir->size; i++)
{
uint32_t polid;
cpu_scaling_driver_t *drv;
struct string_list *tmplst;
char fpath[PATH_MAX_LENGTH];
const char *fname = strrchr(policy_dir->elems[i].data, '/');
if (!fname)
continue;
/* Ensure this is a policy and get its ID */
if (sscanf(fname, "/policy%" PRIu32, &polid) != 1)
continue;
drv = calloc(1, sizeof(cpu_scaling_driver_t));
drv->policy_id = polid;
/* Read all nodes with freq info */
fill_pathname_join(fpath, policy_dir->elems[i].data,
"scaling_cur_freq", sizeof(fpath));
readparse_uint32(fpath, &drv->current_frequency);
fill_pathname_join(fpath, policy_dir->elems[i].data,
"cpuinfo_min_freq", sizeof(fpath));
readparse_uint32(fpath, &drv->min_cpu_freq);
fill_pathname_join(fpath, policy_dir->elems[i].data,
"cpuinfo_max_freq", sizeof(fpath));
readparse_uint32(fpath, &drv->max_cpu_freq);
fill_pathname_join(fpath, policy_dir->elems[i].data,
"scaling_min_freq", sizeof(fpath));
readparse_uint32(fpath, &drv->min_policy_freq);
/* Check current freq limits and update them */
if (abs_min_freq > drv->min_cpu_freq || abs_min_freq == 1)
abs_min_freq = drv->min_cpu_freq;
if (abs_max_freq < drv->max_cpu_freq || abs_max_freq == ~0U)
abs_max_freq = drv->max_cpu_freq;
fill_pathname_join(fpath, policy_dir->elems[i].data,
"scaling_max_freq", sizeof(fpath));
readparse_uint32(fpath, &drv->max_policy_freq);
fill_pathname_join(fpath, policy_dir->elems[i].data,
"scaling_available_governors", sizeof(fpath));
drv->available_governors = readparse_list(fpath);
fill_pathname_join(fpath, policy_dir->elems[i].data,
"affected_cpus", sizeof(fpath));
filestream_read_file(fpath, (void**)&drv->affected_cpus, NULL);
string_remove_all_chars(drv->affected_cpus, '\n');
fill_pathname_join(fpath, policy_dir->elems[i].data,
"scaling_governor", sizeof(fpath));
filestream_read_file(fpath, (void**)&drv->scaling_governor, NULL);
string_remove_all_chars(drv->scaling_governor, '\n');
/* This is not available in many platforms! */
fill_pathname_join(fpath, policy_dir->elems[i].data,
"scaling_available_frequencies", sizeof(fpath));
tmplst = readparse_list(fpath);
if (tmplst)
{
drv->available_freqs = calloc(tmplst->size, sizeof(uint32_t));
for (j = 0; j < tmplst->size; j++)
{
uint32_t freq = (uint32_t)atol(tmplst->elems[j].data);
drv->available_freqs[j] = freq;
if (abs_min_freq > freq || abs_min_freq == 1)
abs_min_freq = freq;
if (abs_max_freq < freq || abs_max_freq == ~0U)
abs_max_freq = freq;
}
string_list_free(tmplst);
}
/* Move to the list */
scaling_drivers[pc++] = drv;
}
dir_list_free(policy_dir);
last_update = time(NULL);
}
return scaling_drivers;
}
bool set_cpu_scaling_min_frequency(
cpu_scaling_driver_t *driver,
uint32_t min_freq)
{
char fpath[PATH_MAX_LENGTH];
char value[16];
snprintf(fpath, sizeof(fpath), CPU_POLICIES_DIR "policy%u/scaling_min_freq",
driver->policy_id);
snprintf(value, sizeof(value), "%" PRIu32 "\n", min_freq);
if (filestream_write_file(fpath, value, strlen(value)))
{
driver->min_policy_freq = min_freq;
last_update = 0; /* Force reload */
return true;
}
return false;
}
bool set_cpu_scaling_max_frequency(
cpu_scaling_driver_t *driver,
uint32_t max_freq)
{
char fpath[PATH_MAX_LENGTH];
char value[16];
snprintf(fpath, sizeof(fpath), CPU_POLICIES_DIR "policy%u/scaling_max_freq",
driver->policy_id);
snprintf(value, sizeof(value), "%" PRIu32 "\n", max_freq);
if (filestream_write_file(fpath, value, strlen(value)))
{
driver->max_policy_freq = max_freq;
last_update = 0; /* Force reload */
return true;
}
return false;
}
uint32_t get_cpu_scaling_next_frequency(
cpu_scaling_driver_t *driver,
uint32_t freq,
int step)
{
/* If the driver does not have a list of available frequencies */
if (driver->available_freqs)
{
uint32_t *fr = driver->available_freqs;
while (*fr)
{
if (fr[0] <= freq && fr[1] > freq && step > 0)
{
freq = fr[1];
break;
}
else if (fr[0] < freq && fr[1] >= freq && step < 0)
{
freq = fr[0];
break;
}
fr++;
}
if (!(*fr))
{
if (step > 0)
freq = driver->max_cpu_freq;
else
freq = driver->min_cpu_freq;
}
}
else {
/* Just do small steps towards the max/min, arbitrary 100MHz */
freq = freq + step * 100000;
}
freq = MIN(freq, driver->max_cpu_freq);
freq = MAX(freq, driver->min_cpu_freq);
return freq;
}
uint32_t get_cpu_scaling_next_frequency_limit(uint32_t freq, int step)
{
/* Tune step, if it's smaller than 100MHz */
unsigned fstep = 100000;
if ((abs_max_freq - abs_min_freq) / 20 < fstep)
fstep = 50000;
if (freq <= abs_min_freq && step < 0)
return 1; /* Means "minimum frequency" */
if (freq >= abs_max_freq && step > 0)
return ~0U; /* Means "maximum frequency" */
/* Just do small steps towards the max/min */
freq = freq + step * fstep;
freq = MIN(freq, abs_max_freq);
freq = MAX(freq, abs_min_freq);
return freq;
}
bool set_cpu_scaling_governor(cpu_scaling_driver_t *driver, const char* governor)
{
char fpath[PATH_MAX_LENGTH];
snprintf(fpath, sizeof(fpath), CPU_POLICIES_DIR "policy%u/scaling_governor",
driver->policy_id);
if (filestream_write_file(fpath, governor, strlen(governor)))
{
if (driver->scaling_governor)
free(driver->scaling_governor);
driver->scaling_governor = strdup(governor);
last_update = 0; /* Force reload */
return true;
}
return false;
}
static void steer_all_drivers(
const char *governor,
uint32_t minfreq,
uint32_t maxfreq)
{
cpu_scaling_driver_t **drivers = get_cpu_scaling_drivers(false);
if (!drivers)
return;
while (*drivers)
{
cpu_scaling_driver_t *d = *drivers++;
if (minfreq)
set_cpu_scaling_min_frequency(d, MAX(minfreq, d->min_cpu_freq));
if (maxfreq)
set_cpu_scaling_max_frequency(d, MIN(maxfreq, d->max_cpu_freq));
set_cpu_scaling_governor(d, governor);
}
}
void set_cpu_scaling_signal(enum cpu_scaling_event event)
{
switch (cur_smode) {
case CPUSCALING_MANAGED_PERFORMANCE:
/* Bump to perf or fall back to ondemand depending on the RA state */
if (event == CPUSCALING_EVENT_FOCUS_CORE)
steer_all_drivers("performance", cur_smode_opts.min_freq,
cur_smode_opts.max_freq);
else
steer_all_drivers("ondemand", 1, ~0U);
break;
case CPUSCALING_MANAGED_PER_CONTEXT:
/* Apply the right settings the user specified */
if (event == CPUSCALING_EVENT_FOCUS_CORE)
steer_all_drivers(cur_smode_opts.main_policy, cur_smode_opts.min_freq,
cur_smode_opts.max_freq);
else
steer_all_drivers(cur_smode_opts.menu_policy, 1, ~0U);
break;
default:
break;
};
}
enum cpu_scaling_mode get_cpu_scaling_mode(cpu_scaling_opts_t *opts)
{
if (opts)
*opts = cur_smode_opts;
return cur_smode;
}
void set_cpu_scaling_mode(
enum cpu_scaling_mode mode,
const cpu_scaling_opts_t *opts)
{
settings_t *settings = config_get_ptr();
/* Store current state */
cur_smode = mode;
if (opts)
cur_smode_opts = *opts;
switch (mode)
{
case CPUSCALING_MANUAL:
/* Do nothing, the UI allows for tweaking directly */
break;
case CPUSCALING_MANAGED_PERFORMANCE:
case CPUSCALING_MANAGED_PER_CONTEXT:
/* Simulate a state change to enforce the policy */
set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_MENU);
break;
case CPUSCALING_MAX_PERFORMANCE:
// Set performance and bump frequencies to min/max
steer_all_drivers("performance", 1, ~0U);
break;
case CPUSCALING_MIN_POWER:
// Set powersave and bump frequencies to min/max
steer_all_drivers("powersave", 1, ~0U);
break;
case CPUSCALING_BALANCED:
// Set ondemand and bump frequencies to min/max
steer_all_drivers("ondemand", 1, ~0U);
break;
};
if (settings)
{
/* Store current settings */
settings->uints.cpu_scaling_mode = (int)cur_smode;
settings->uints.cpu_min_freq = cur_smode_opts.min_freq;
settings->uints.cpu_max_freq = cur_smode_opts.max_freq;
strlcpy(settings->arrays.cpu_main_gov, cur_smode_opts.main_policy,
sizeof(settings->arrays.cpu_main_gov));
strlcpy(settings->arrays.cpu_menu_gov, cur_smode_opts.menu_policy,
sizeof(settings->arrays.cpu_menu_gov));
}
};
void cpu_scaling_driver_free()
{
if (scaling_drivers)
free_drivers(scaling_drivers);
scaling_drivers = NULL;
last_update = 0;
}
void cpu_scaling_driver_init(void)
{
/* Read the default settings */
settings_t *settings = config_get_ptr();
unsigned mode = settings->uints.cpu_scaling_mode;
cur_smode_opts.min_freq = settings->uints.cpu_min_freq;
cur_smode_opts.max_freq = settings->uints.cpu_max_freq;
if (mode <= (int)CPUSCALING_MANUAL)
cur_smode = (enum cpu_scaling_mode)mode;
if (settings->arrays.cpu_main_gov[0])
strlcpy(cur_smode_opts.main_policy, settings->arrays.cpu_main_gov,
sizeof(cur_smode_opts.main_policy));
if (settings->arrays.cpu_menu_gov[0])
strlcpy(cur_smode_opts.menu_policy, settings->arrays.cpu_menu_gov,
sizeof(cur_smode_opts.menu_policy));
/* Force update the policy tree */
get_cpu_scaling_drivers(true);
/* Force enforce these settings */
set_cpu_scaling_mode(cur_smode, NULL);
}