/* 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 . */ #include #include #include #include #include #include #include #include #include #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); }