mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-24 00:20:01 +00:00
443 lines
13 KiB
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);
|
|
}
|
|
|