/* * Simple kernel gaming control * * Copyright (C) 2019 * Diep Quynh Nguyen * Mustafa Gökmen * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../soc/samsung/cal-if/exynos9810/cmucal-vclk.h" #include "../soc/samsung/cal-if/exynos9810/cmucal-node.h" #define DEFAULT_CPU_LIMIT 1794000 #define TEMP_EMULATION 20000 #define STEP_UV (6250) static struct delayed_work customs_delayed_work; static struct regulator *little_regulator, *big_regulator, *g3d_regulator; extern unsigned long arg_cpu_max_c1; extern unsigned long arg_cpu_min_c1; extern unsigned long arg_cpu_max_c2; extern unsigned long arg_cpu_min_c2; extern unsigned long arg_gpu_min; extern unsigned long arg_gpu_max; extern int exynos_cpufreq_update_volt_table(void); extern void exynos_cpufreq_set_gaming_mode(void); /* PM QoS implementation */ static struct pm_qos_request gaming_control_min_int_qos; static struct pm_qos_request gaming_control_min_mif_qos; static struct pm_qos_request gaming_control_min_big_qos; static struct pm_qos_request gaming_control_max_big_qos; static struct pm_qos_request gaming_control_min_little_qos; static struct pm_qos_request gaming_control_max_little_qos; static unsigned int min_int_freq = 534000; static unsigned int min_mif_freq = 1794000; static unsigned int min_little_freq = 1456000; static unsigned int max_little_freq = 2002000; static unsigned int min_big_freq = 1469000; static unsigned int max_big_freq = 2886000; static unsigned int min_gpu_freq = 598000; static unsigned int max_gpu_freq = 598000; static unsigned int custom_little_freq, custom_little_voltage, custom_big_freq, custom_big_voltage, custom_gpu_freq, custom_gpu_voltage = 0; static unsigned int back_little_freq, back_little_voltage, back_big_freq, back_big_voltage, back_gpu_freq, back_gpu_voltage = 0; static int nr_running_games = 0; static bool always_on = 0; static bool battery_idle = 0; static int thermal_bypass = 0; static bool gaming_mode_initialized = 0; bool gaming_mode; char games_list[GAME_LIST_LENGTH] = {0}; pid_t games_pid[NUM_SUPPORTED_RUNNING_GAMES] = { [0 ... (NUM_SUPPORTED_RUNNING_GAMES - 1)] = -1 }; static inline void set_custom_gaming_mode(void) { unsigned int little_freq = max_little_freq; unsigned int big_freq = max_big_freq; unsigned int gpu_freq = max_gpu_freq; gaming_mode_initialized = gaming_mode; if (custom_little_freq) { if (custom_little_freq <= arg_cpu_min_c1) little_freq = arg_cpu_min_c1; else if (custom_little_freq >= arg_cpu_max_c1) little_freq = arg_cpu_max_c1; __cal_dfs_set_rate(ACPM_DVFS_CPUCL0, gaming_mode ? custom_little_freq : little_freq); __cal_dfs_set_rate(VCLK_CLUSTER0, (gaming_mode ? custom_little_freq : little_freq) * 1000); __cal_dfs_set_rate(PLL_CPUCL0, (gaming_mode ? custom_little_freq : little_freq) * 1000); } if (custom_big_freq) { if (custom_big_freq <= arg_cpu_min_c2) { big_freq = arg_cpu_min_c2; } else if (custom_big_freq >= arg_cpu_max_c2) { if (gaming_mode || DEFAULT_CPU_LIMIT > arg_cpu_max_c2) big_freq = arg_cpu_max_c2; else big_freq = DEFAULT_CPU_LIMIT; } __cal_dfs_set_rate(ACPM_DVFS_CPUCL1, gaming_mode ? custom_big_freq : big_freq); __cal_dfs_set_rate(VCLK_CLUSTER1, (gaming_mode ? custom_big_freq : big_freq) * 1000); __cal_dfs_set_rate(PLL_CPUCL1, (gaming_mode ? custom_big_freq : big_freq) * 1000); } if (custom_gpu_freq) { if (custom_gpu_freq <= arg_gpu_min) gpu_freq = arg_gpu_min; else if (custom_gpu_freq >= arg_gpu_max) gpu_freq = arg_gpu_max; __cal_dfs_set_rate(ACPM_DVFS_G3D, gaming_mode ? custom_gpu_freq : gpu_freq); __cal_dfs_set_rate(VCLK_GPU, (gaming_mode ? custom_gpu_freq : gpu_freq) * 1000); __cal_dfs_set_rate(PLL_G3D, (gaming_mode ? custom_gpu_freq : gpu_freq) * 1000); } } static void set_custom_gaming_mode_handler(struct work_struct *work) { if (gaming_mode && little_regulator && big_regulator && g3d_regulator) { if ((!custom_little_voltage || abs(custom_little_voltage - regulator_get_voltage(little_regulator)) < STEP_UV) && (!custom_big_voltage || abs(custom_big_voltage - regulator_get_voltage(big_regulator)) < STEP_UV) && (!custom_gpu_voltage || abs(custom_gpu_voltage - regulator_get_voltage(g3d_regulator)) < STEP_UV)) set_custom_gaming_mode(); else queue_delayed_work(system_power_efficient_wq, &customs_delayed_work, msecs_to_jiffies(1000)); } else if (gaming_mode_initialized || little_regulator == NULL || big_regulator == NULL || g3d_regulator == NULL) { set_custom_gaming_mode(); } } static inline void set_gaming_mode(bool mode, bool force) { unsigned int little_max, little_min, big_max, big_min, gpu_max, gpu_min; if (always_on) mode = 1; if (mode == gaming_mode && !force) return; else gaming_mode = mode; if (!mode) { cancel_delayed_work_sync(&customs_delayed_work); set_custom_gaming_mode(); } little_max = max_little_freq; little_min = min_little_freq; big_max = max_big_freq; big_min = min_big_freq; gpu_max = max_gpu_freq; gpu_min = min_gpu_freq; if (custom_little_freq) { if (custom_little_freq <= arg_cpu_min_c1) little_max = little_min = arg_cpu_min_c1; else if (custom_little_freq >= arg_cpu_max_c1) little_max = little_min = arg_cpu_max_c1; } if (!back_little_freq) back_little_freq = little_max; if (custom_little_voltage && mode && !back_little_voltage) { back_little_voltage = fvmap_read(DVFS_CPUCL0, READ_VOLT, back_little_freq); fvmap_patch(DVFS_CPUCL0, back_little_freq, custom_little_voltage); } else if (!mode && back_little_voltage) { fvmap_patch(DVFS_CPUCL0, back_little_freq, back_little_voltage); back_little_freq = back_little_voltage = 0; } if (custom_big_freq) { if (custom_big_freq <= arg_cpu_min_c2) { big_max = big_min = arg_cpu_min_c2; } else if (custom_big_freq >= arg_cpu_max_c2) { if (mode || DEFAULT_CPU_LIMIT > arg_cpu_max_c2) big_max = big_min = arg_cpu_max_c2; else big_max = big_min = DEFAULT_CPU_LIMIT; } } if (!back_big_freq) back_big_freq = big_max; if (custom_big_voltage && mode && !back_big_voltage) { back_big_voltage = fvmap_read(DVFS_CPUCL1, READ_VOLT, back_big_freq); fvmap_patch(DVFS_CPUCL1, back_big_freq, custom_big_voltage); } else if (!mode && back_big_voltage) { fvmap_patch(DVFS_CPUCL1, back_big_freq, back_big_voltage); back_big_freq = back_big_voltage = 0; } if (custom_gpu_freq) { if (custom_gpu_freq <= arg_gpu_min) gpu_max = gpu_min = arg_gpu_min; else if (custom_gpu_freq >= arg_gpu_max) gpu_max = gpu_min = arg_gpu_max; } if (!back_gpu_freq) back_gpu_freq = gpu_max; if (custom_gpu_voltage && mode && !back_gpu_voltage) { back_gpu_voltage = gpex_clock_get_voltage(back_gpu_freq); fvmap_patch(DVFS_G3D, back_gpu_freq, custom_gpu_voltage); } else if (!mode && back_gpu_voltage) { fvmap_patch(DVFS_G3D, back_gpu_freq, back_gpu_voltage); back_gpu_freq = back_gpu_voltage = 0; } gpex_clock_update_config_data_from_dt(); exynos_cpufreq_update_volt_table(); exynos_cpufreq_set_gaming_mode(); pm_qos_update_request(&gaming_control_min_int_qos, mode && min_int_freq ? min_int_freq : PM_QOS_DEVICE_THROUGHPUT_DEFAULT_VALUE); pm_qos_update_request(&gaming_control_min_mif_qos, mode && min_mif_freq ? min_mif_freq : PM_QOS_BUS_THROUGHPUT_DEFAULT_VALUE); pm_qos_update_request(&gaming_control_min_little_qos, mode && little_min ? little_min : PM_QOS_CLUSTER0_FREQ_MIN_DEFAULT_VALUE); pm_qos_update_request(&gaming_control_max_little_qos, mode && little_max ? little_max : PM_QOS_CLUSTER0_FREQ_MAX_DEFAULT_VALUE); pm_qos_update_request(&gaming_control_min_big_qos, mode && big_min ? big_min : PM_QOS_CLUSTER1_FREQ_MIN_DEFAULT_VALUE); pm_qos_update_request(&gaming_control_max_big_qos, mode && big_max ? big_max : PM_QOS_CLUSTER1_FREQ_MAX_DEFAULT_VALUE); gpu_custom_max_clock(mode ? gpu_max : 0); gpu_custom_min_clock(mode ? gpu_min : 0); if (mode) set_custom_gaming_mode_handler(NULL); } unsigned long cal_dfs_check_gaming_mode(unsigned int id) { unsigned long ret = 0; if (!gaming_mode_initialized) return ret; switch (id) { /* LITTLE */ case ACPM_DVFS_CPUCL0: ret = custom_little_freq; break; /* BIG */ case ACPM_DVFS_CPUCL1: ret = custom_big_freq; break; /* GPU */ case ACPM_DVFS_G3D: ret = custom_gpu_freq; break; default: break; } return ret; } int fake_freq_gaming(int id) { int ret = 0; if (!gaming_mode) return ret; switch (id) { /* LITTLE */ case ACPM_DVFS_CPUCL0: case DVFS_CPUCL0: ret = back_little_freq; break; /* BIG */ case ACPM_DVFS_CPUCL1: case DVFS_CPUCL1: ret = back_big_freq; break; /* GPU */ case ACPM_DVFS_G3D: case DVFS_G3D: ret = back_gpu_freq; break; default: break; } return ret; } bool battery_idle_gaming(void) { if (gaming_mode && battery_idle) return 1; return 0; } int thermal_bypass_gaming(void) { if (gaming_mode && thermal_bypass) { if (thermal_bypass == 1) return TEMP_EMULATION; else return thermal_bypass; } return 0; } static inline void store_game_pid(pid_t pid) { int i; for (i = 0; i < NUM_SUPPORTED_RUNNING_GAMES; i++) { if (games_pid[i] == -1) { games_pid[i] = pid; nr_running_games++; break; } } } static inline int check_game_pid(pid_t pid) { int i; for (i = 0; i < NUM_SUPPORTED_RUNNING_GAMES; i++) { if (games_pid[i] != -1) { if (games_pid[i] == pid) return 1; } } return 0; } static inline void clear_dead_pids(void) { int i; for (i = 0; i < NUM_SUPPORTED_RUNNING_GAMES; i++) { if (games_pid[i] != -1) { rcu_read_lock(); if (!find_task_by_vpid(games_pid[i])) { games_pid[i] = -1; nr_running_games--; } rcu_read_unlock(); } } /* If there's no running games, turn off game mode */ if (nr_running_games == 0) set_gaming_mode(false, false); } static inline void removeSubstringAfterChar(char *str, char ch) { char *ptr = str; while (*ptr != '\0') { if (*ptr == ch) { *(ptr) = '\0'; return; } ptr++; } } static int check_for_games(struct task_struct *tsk) { char *cmdline; int ret; cmdline = kmalloc(GAME_LIST_LENGTH, GFP_KERNEL); if (!cmdline) return 0; ret = get_cmdline(tsk, cmdline, GAME_LIST_LENGTH); if (ret == 0) { kfree(cmdline); return 0; } /* clean extra process identifier */ removeSubstringAfterChar(cmdline, ':'); /* Invalid Android process name. Bail out */ if (!strcmp(cmdline, "android") || strlen(cmdline) < 7) { kfree(cmdline); return 0; } /* tsk isn't a game. Bail out */ if (strstr(games_list, cmdline) == NULL) { kfree(cmdline); return 0; } kfree(cmdline); return 1; } void game_option(struct task_struct *tsk, enum game_opts opts) { pid_t pid; int ret; /* Remove all zombie tasks PIDs */ clear_dead_pids(); if(always_on) { set_gaming_mode(true, false); return; } ret = check_for_games(tsk); if (!ret) return; switch (opts) { case GAME_START: case GAME_RUNNING: set_gaming_mode(true, false); pid = task_pid_vnr(tsk); if (tsk->app_state == TASK_STARTED || check_game_pid(pid)) break; store_game_pid(pid); tsk->app_state = TASK_STARTED; break; case GAME_PAUSE: set_gaming_mode(false, false); break; default: break; } } /* Show added games list */ static ssize_t game_packages_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%s\n", games_list); } /* Store added games list */ static ssize_t game_packages_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { char games[GAME_LIST_LENGTH]; sscanf(buf, "%s", games); sprintf(games_list, "%s", buf); return count; } /* Show/Store value */ #define attr_value(type) \ static ssize_t type##_store(struct kobject *kobj, \ struct kobj_attribute *attr, const char *buf, size_t count) \ { \ unsigned int value; \ \ sscanf(buf, "%u\n", &value); \ type = value; \ \ set_gaming_mode(gaming_mode, gaming_mode); \ \ return count; \ } \ \ static ssize_t type##_show(struct kobject *kobj, \ struct kobj_attribute *attr, char *buf) \ { \ return sprintf(buf, "%u\n", type); \ } \ \ static struct kobj_attribute type##_attribute = \ __ATTR(type, 0644, type##_show, type##_store); attr_value(always_on); attr_value(battery_idle); attr_value(thermal_bypass); attr_value(min_int_freq); attr_value(min_mif_freq); attr_value(min_little_freq); attr_value(max_little_freq); attr_value(min_big_freq); attr_value(max_big_freq); attr_value(min_gpu_freq); attr_value(max_gpu_freq); attr_value(custom_little_freq); attr_value(custom_little_voltage); attr_value(custom_big_freq); attr_value(custom_big_voltage); attr_value(custom_gpu_freq); attr_value(custom_gpu_voltage); static ssize_t status_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { ssize_t len = 0; int i; len += sprintf(buf + len, "%d\n\n", nr_running_games); for (i = 0; i < NUM_SUPPORTED_RUNNING_GAMES; i++) { if (games_pid[i] != -1) len += sprintf(buf + len, "%d\n", games_pid[i]); } buf[len] = '\0'; return len; } static ssize_t version_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%s\n", GAMING_CONTROL_VERSION); } static struct kobj_attribute status_attribute = __ATTR(status, 0444, status_show, NULL); static struct kobj_attribute version_attribute = __ATTR(version, 0444, version_show, NULL); static struct kobj_attribute game_packages_attribute = __ATTR(game_packages, 0644, game_packages_show, game_packages_store); static struct attribute *gaming_control_attributes[] = { &game_packages_attribute.attr, &status_attribute.attr, &version_attribute.attr, &always_on_attribute.attr, &battery_idle_attribute.attr, &thermal_bypass_attribute.attr, &min_int_freq_attribute.attr, &min_mif_freq_attribute.attr, &min_little_freq_attribute.attr, &max_little_freq_attribute.attr, &min_big_freq_attribute.attr, &max_big_freq_attribute.attr, &min_gpu_freq_attribute.attr, &max_gpu_freq_attribute.attr, &custom_little_freq_attribute.attr, &custom_little_voltage_attribute.attr, &custom_big_freq_attribute.attr, &custom_big_voltage_attribute.attr, &custom_gpu_freq_attribute.attr, &custom_gpu_voltage_attribute.attr, NULL }; static struct attribute_group gaming_control_control_group = { .attrs = gaming_control_attributes, }; static struct kobject *gaming_control_kobj; static int __init gaming_control_init(void) { int sysfs_result; little_regulator = regulator_get(NULL, "vdd_cpucl0"); if (IS_ERR(little_regulator)) little_regulator = NULL; big_regulator = regulator_get(NULL, "vdd_cpucl1"); if (IS_ERR(big_regulator)) big_regulator = NULL; g3d_regulator = regulator_get(NULL, "vdd_g3d"); if (IS_ERR(g3d_regulator)) g3d_regulator = NULL; INIT_DELAYED_WORK(&customs_delayed_work, set_custom_gaming_mode_handler); pm_qos_add_request(&gaming_control_min_int_qos, PM_QOS_DEVICE_THROUGHPUT, PM_QOS_DEVICE_THROUGHPUT_DEFAULT_VALUE); pm_qos_add_request(&gaming_control_min_mif_qos, PM_QOS_BUS_THROUGHPUT, PM_QOS_BUS_THROUGHPUT_DEFAULT_VALUE); pm_qos_add_request(&gaming_control_min_little_qos, PM_QOS_CLUSTER0_FREQ_MIN, PM_QOS_CLUSTER0_FREQ_MIN_DEFAULT_VALUE); pm_qos_add_request(&gaming_control_max_little_qos, PM_QOS_CLUSTER0_FREQ_MAX, PM_QOS_CLUSTER0_FREQ_MAX_DEFAULT_VALUE); pm_qos_add_request(&gaming_control_min_big_qos, PM_QOS_CLUSTER1_FREQ_MIN, PM_QOS_CLUSTER1_FREQ_MIN_DEFAULT_VALUE); pm_qos_add_request(&gaming_control_max_big_qos, PM_QOS_CLUSTER1_FREQ_MAX, PM_QOS_CLUSTER1_FREQ_MAX_DEFAULT_VALUE); gaming_control_kobj = kobject_create_and_add("gaming_control", kernel_kobj); if (!gaming_control_kobj) { pr_err("%s gaming_control kobject create failed!\n", __FUNCTION__); return -ENOMEM; } sysfs_result = sysfs_create_group(gaming_control_kobj, &gaming_control_control_group); if (sysfs_result) { pr_info("%s gaming_control sysfs create failed!\n", __FUNCTION__); kobject_put(gaming_control_kobj); } return sysfs_result; } static void __exit gaming_control_exit(void) { cancel_delayed_work_sync(&customs_delayed_work); pm_qos_remove_request(&gaming_control_min_int_qos); pm_qos_remove_request(&gaming_control_min_mif_qos); pm_qos_remove_request(&gaming_control_min_little_qos); pm_qos_remove_request(&gaming_control_max_little_qos); pm_qos_remove_request(&gaming_control_min_big_qos); pm_qos_remove_request(&gaming_control_max_big_qos); if (gaming_control_kobj != NULL) kobject_put(gaming_control_kobj); } module_init(gaming_control_init); module_exit(gaming_control_exit);