/* * HAL backend for hal-api-power which is used for PASS daemon * * Copyright (c) 2021 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include "sysfs.h" #define BUFF_MAX 255 /************************* * H/W Resource Defintions */ #define CPUFREQ_PATH_PREFIX "/sys/devices/system/cpu/" #define CPUFREQ_CURR_GOVERNOR_PATH_SUFFIX "/cpufreq/scaling_governor" #define CPUFREQ_AVAIL_GOVERNOR_PATH_SUFFIX "/cpufreq/scaling_available_governors" /* * The cpuinfo_cur_freq indicates the actual operating CPU freqeuncy * and scaling_cur_freq is the CPU frequency set by the CPUFREQ policy. */ #define CPUFREQ_CURR_FREQ_PATH_SUFFIX "/cpufreq/cpuinfo_cur_freq" #define CPUFREQ_AVAILABLE_MIN_FREQ_PATH_SUFFIX "/cpufreq/cpuinfo_min_freq" #define CPUFREQ_AVAILABLE_MAX_FREQ_PATH_SUFFIX "/cpufreq/cpuinfo_max_freq" #define CPUFREQ_MIN_FREQ_PATH_SUFFIX "/cpufreq/scaling_min_freq" #define CPUFREQ_MAX_FREQ_PATH_SUFFIX "/cpufreq/scaling_max_freq" #define CPUFREQ_UP_THRESHOLD_PATH_SUFFIX "/cpufreq/ondemand/up_threshold" #define CPU_ONLINE_PATH_PREFIX "/sys/devices/system/cpu/cpu" #define CPU_ONLINE_PATH_SUFFIX "/online" #define CPU_ONLINE_STATE_ON 1 #define CPU_ONLINE_STATE_OFF 0 #define TMU_PATH_PREFIX "/sys/class/thermal/" #define TMU_TEMP_PATH_SUFFIX "/temp" #define TMU_POLICY_PATH_SUFFIX "/policy" #define TMU_MAX_TEMP_MASK 0xFFFFF #define DEVFREQ_BUS_PATH_PREFIX "/sys/class/devfreq/" #define DEVFREQ_BUS_CURR_GOVERNOR_PATH_SUFFIX "/governor" #define DEVFREQ_BUS_CURR_FREQ_PATH_SUFFIX "/cur_freq" #define DEVFREQ_BUS_MIN_FREQ_PATH_SUFFIX "/min_freq" #define DEVFREQ_BUS_MAX_FREQ_PATH_SUFFIX "/max_freq" #define DEVFREQ_BUS_AVAILABLE_FREQ_PATH_SUFFIX "/available_frequencies" #define FAULT_AROUND_BYTES_PATH "/sys/kernel/debug/fault_around_bytes" static int get_path(char *path, char *prefix, char *res_name, char *suffix) { int ret; if (!path || !prefix || !res_name || !suffix) return -EINVAL; ret = snprintf(path, PATH_MAX, "%s%s%s", prefix, res_name, suffix); if (ret <= 0) return -EINVAL; path[PATH_MAX - 1] = '\0'; return 0; } static int __handle_value_string(int is_write, char *prefix, char *res_name, char *suffix, char *buf) { char path[PATH_MAX]; int ret; if (!buf) return -EINVAL; ret = get_path(path, prefix, res_name, suffix); if (ret < 0) return ret; if (!!is_write) ret = sysfs_write_str(path, buf); else ret = sysfs_read_str(path, buf, BUFF_MAX); return (ret < 0) ? ret : 0; } static int get_value_string(char *prefix, char *res_name, char *suffix, char *buf) { return __handle_value_string(0, prefix, res_name, suffix, buf); } static int set_value_string(char *prefix, char *res_name, char *suffix, char *buf) { return __handle_value_string(1, prefix, res_name, suffix, buf); } static int get_value_integer(char *prefix, char *res_name, char *suffix, int *value) { char path[PATH_MAX]; int ret; if (!value) return -EINVAL; ret = get_path(path, prefix, res_name, suffix); if (ret < 0) return ret; ret = sysfs_read_int(path, value); if (ret < 0) return ret; return 0; } static int set_value_integer(char *prefix, char *res_name, char *suffix, int value) { char path[PATH_MAX]; int ret; ret = get_path(path, prefix, res_name, suffix); if (ret < 0) return ret; ret = sysfs_write_int(path, value); if (ret < 0) return ret; return 0; } /************************************************* * HAL backend implementation for CPU H/W Resource */ static int cpufreq_dvfs_get_curr_governor(char *res_name, char *governor) { int ret; ret = get_value_string(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_CURR_GOVERNOR_PATH_SUFFIX, governor); return (ret < 0) ? ret : 0; } static int cpufreq_dvfs_set_curr_governor(char *res_name, char *governor) { int ret; ret = set_value_string(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_CURR_GOVERNOR_PATH_SUFFIX, governor); return (ret < 0) ? ret : 0; } static int cpufreq_dvfs_get_curr_freq(char *res_name) { int freq, ret; ret = get_value_integer(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_CURR_FREQ_PATH_SUFFIX, &freq); return (ret < 0) ? ret : freq; } static int cpufreq_dvfs_get_min_freq(char *res_name) { int freq, ret; ret = get_value_integer(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_MIN_FREQ_PATH_SUFFIX, &freq); return (ret < 0) ? ret : freq; } static int cpufreq_dvfs_set_min_freq(char *res_name, int freq) { int ret; if (freq < 0) return -EINVAL; ret = set_value_integer(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_MIN_FREQ_PATH_SUFFIX, freq); return (ret < 0) ? ret : 0; } static int cpufreq_dvfs_get_max_freq(char *res_name) { int freq, ret; ret = get_value_integer(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_MAX_FREQ_PATH_SUFFIX, &freq); return (ret < 0) ? ret : freq; } static int cpufreq_dvfs_set_max_freq(char *res_name, int freq) { int ret; if (freq < 0) return -EINVAL; ret = set_value_integer(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_MAX_FREQ_PATH_SUFFIX, freq); return (ret < 0) ? ret : 0; } static int cpufreq_dvfs_get_available_min_freq(char *res_name) { int val, ret; ret = get_value_integer(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_AVAILABLE_MIN_FREQ_PATH_SUFFIX, &val); return (ret < 0) ? ret : val; } static int cpufreq_dvfs_get_available_max_freq(char *res_name) { int val, ret; ret = get_value_integer(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_AVAILABLE_MAX_FREQ_PATH_SUFFIX, &val); return (ret < 0) ? ret : val; } static int cpufreq_dvfs_get_up_threshold(char *res_name) { int val, ret; ret = get_value_integer(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_UP_THRESHOLD_PATH_SUFFIX, &val); return (ret < 0) ? ret : val; } static int cpufreq_dvfs_set_up_threshold(char *res_name, int up_threshold) { int ret; if (up_threshold < 0) return -EINVAL; ret = set_value_integer(CPUFREQ_PATH_PREFIX, res_name, CPUFREQ_UP_THRESHOLD_PATH_SUFFIX, up_threshold); return (ret < 0) ? ret : 0; } static struct pass_resource_dvfs_ops cpufreq_dvfs_ops = { .get_curr_governor = cpufreq_dvfs_get_curr_governor, .set_curr_governor = cpufreq_dvfs_set_curr_governor, .get_curr_freq = cpufreq_dvfs_get_curr_freq, .get_min_freq = cpufreq_dvfs_get_min_freq, .set_min_freq = cpufreq_dvfs_set_min_freq, .get_max_freq = cpufreq_dvfs_get_max_freq, .set_max_freq = cpufreq_dvfs_set_max_freq, .get_available_min_freq = cpufreq_dvfs_get_available_min_freq, .get_available_max_freq = cpufreq_dvfs_get_available_max_freq, .get_up_threshold = cpufreq_dvfs_get_up_threshold, .set_up_threshold = cpufreq_dvfs_set_up_threshold, }; static int cpu_hotplug_get_online_state(char *res_name, int cpu) { char path[PATH_MAX]; int ret, online; if ((!res_name)) return -EINVAL; snprintf(path, PATH_MAX, "%s%d%s", CPU_ONLINE_PATH_PREFIX, cpu, CPU_ONLINE_PATH_SUFFIX); ret = sysfs_read_int(path, &online); if (ret < 0) return ret; return online; } static int cpu_hotplug_set_online_state(char *res_name, int cpu, int on) { char path[PATH_MAX]; int ret; if ((!res_name)) return -EINVAL; if ((on != CPU_ONLINE_STATE_ON) && (on != CPU_ONLINE_STATE_OFF)) return -EINVAL; /* * NOTE: Exynos SoC series cannot turn off the CPU0 * because of h/w design. To prevent the critical problem, * if someone try to turn off the CPU0, just return without any * opertaion. */ if (on == 0 && cpu == 0) { return 0; } snprintf(path, PATH_MAX, "%s%d%s", CPU_ONLINE_PATH_PREFIX, cpu, CPU_ONLINE_PATH_SUFFIX); ret = sysfs_write_int(path, on); if (ret < 0) return ret; return 0; } static struct pass_resource_hotplug_ops cpu_hotplus_ops = { .get_online_state = cpu_hotplug_get_online_state, .set_online_state = cpu_hotplug_set_online_state, .get_online_min_num = NULL, .set_online_min_num = NULL, .get_online_max_num = NULL, .set_online_max_num = NULL, }; static int tmu_get_temp(char *res_thermal_name) { int temp, ret; ret = get_value_integer(TMU_PATH_PREFIX, res_thermal_name, TMU_TEMP_PATH_SUFFIX, &temp); if (ret < 0) return ret; /* * Thermal framework provides the current temperature * as five digits interger like 54430 when temperature is 54.430 * degrees centigrade. But, Thermal Monitor in Tizen usually * use two digits interger without decimal point. * So that round temperature value. It constraints the maximume * temperature as 1048 degrees centigrade for preventing integer * overflow. Usually, the embedded device never over 1000 degrees * centigrade. */ return (((temp & TMU_MAX_TEMP_MASK) + 500) / 1000); } static int tmu_get_policy(char *res_thermal_name, char *policy) { int ret; if (!policy) return -EINVAL; ret = get_value_string(TMU_PATH_PREFIX, res_thermal_name, TMU_POLICY_PATH_SUFFIX, policy); return (ret < 0) ? ret : 0; } static struct pass_resource_tmu_ops tmu_ops = { .get_temp = tmu_get_temp, .get_policy = tmu_get_policy, }; /***************************************************** * HAL backend implementation for BUS/GPU H/W Resource */ static int devfreq_dvfs_get_curr_governor(char *res_name, char *governor) { int ret; if (!governor) return -EINVAL; ret = get_value_string(DEVFREQ_BUS_PATH_PREFIX, res_name, DEVFREQ_BUS_CURR_GOVERNOR_PATH_SUFFIX, governor); return (ret < 0) ? ret : 0; } static int devfreq_dvfs_set_curr_governor(char *res_name, char *governor) { int ret; if (!governor) return -EINVAL; ret = set_value_string(DEVFREQ_BUS_PATH_PREFIX, res_name, DEVFREQ_BUS_CURR_GOVERNOR_PATH_SUFFIX, governor); return (ret < 0) ? ret : 0; } static int devfreq_dvfs_get_curr_freq(char *res_name) { int freq, ret; ret = get_value_integer(DEVFREQ_BUS_PATH_PREFIX, res_name, DEVFREQ_BUS_CURR_FREQ_PATH_SUFFIX, &freq); return (ret < 0) ? ret : freq; } static int devfreq_dvfs_get_min_freq(char *res_name) { int freq, ret; ret = get_value_integer(DEVFREQ_BUS_PATH_PREFIX, res_name, DEVFREQ_BUS_MIN_FREQ_PATH_SUFFIX, &freq); return (ret < 0) ? ret : freq; } static int devfreq_dvfs_set_min_freq(char *res_name, int freq) { int ret; if (freq < 0) return -EINVAL; ret = set_value_integer(DEVFREQ_BUS_PATH_PREFIX, res_name, DEVFREQ_BUS_MIN_FREQ_PATH_SUFFIX, freq); return (ret < 0) ? ret : 0; } static int devfreq_dvfs_get_max_freq(char *res_name) { int freq, ret; ret = get_value_integer(DEVFREQ_BUS_PATH_PREFIX, res_name, DEVFREQ_BUS_MAX_FREQ_PATH_SUFFIX, &freq); return (ret < 0) ? ret : freq; } static int devfreq_dvfs_set_max_freq(char *res_name, int freq) { int ret; if (freq < 0) return -EINVAL; ret = set_value_integer(DEVFREQ_BUS_PATH_PREFIX, res_name, DEVFREQ_BUS_MAX_FREQ_PATH_SUFFIX, freq); return (ret < 0) ? ret : 0; } static int devfreq_dvfs_get_available_min_freq(char *res_name) { char buf[MAX_BUF_SIZE + 1]; char *p; int ret; ret = get_value_string(DEVFREQ_BUS_PATH_PREFIX, res_name, DEVFREQ_BUS_AVAILABLE_FREQ_PATH_SUFFIX, buf); if (ret < 0) return ret; p = strchr(buf, ' '); if (p) *p = '\0'; ret = strtoul(buf, NULL, 10); if (!ret) return -EINVAL; return ret; } static int devfreq_dvfs_get_available_max_freq(char *res_name) { char buf[MAX_BUF_SIZE + 1]; char *p; int ret; ret = get_value_string(DEVFREQ_BUS_PATH_PREFIX, res_name, DEVFREQ_BUS_AVAILABLE_FREQ_PATH_SUFFIX, buf); if (ret < 0) return ret; p = strrchr(buf, ' '); p = p ? p + 1 : buf; ret = strtoul(p, NULL, 10); if (!ret) return -EINVAL; return ret; } static struct pass_resource_dvfs_ops devfreq_dvfs_ops = { .get_curr_governor = devfreq_dvfs_get_curr_governor, .set_curr_governor = devfreq_dvfs_set_curr_governor, .get_curr_freq = devfreq_dvfs_get_curr_freq, .get_min_freq = devfreq_dvfs_get_min_freq, .set_min_freq = devfreq_dvfs_set_min_freq, .get_max_freq = devfreq_dvfs_get_max_freq, .set_max_freq = devfreq_dvfs_set_max_freq, .get_available_min_freq = devfreq_dvfs_get_available_min_freq, .get_available_max_freq = devfreq_dvfs_get_available_max_freq, }; /**************************************************** * HAL backend implementation for Memory H/W Resource */ static int memory_get_fault_around_bytes(char *res_name) { int ret, fault_around_bytes; if (!res_name) return -EINVAL; ret = sysfs_read_int(FAULT_AROUND_BYTES_PATH, &fault_around_bytes); if (ret < 0) return ret; return fault_around_bytes; } static int memory_set_fault_around_bytes(char *res_name, int fault_around_bytes) { int ret; if ((!res_name) || (fault_around_bytes <= 0)) return -EINVAL; ret = sysfs_write_int(FAULT_AROUND_BYTES_PATH, fault_around_bytes); if (ret < 0) return ret; return 0; } /************************ * HAL backend power data */ static int power_init(void **data) { hal_backend_power_funcs *power_funcs = NULL; struct pass_resource_cpu *cpu = NULL; struct pass_resource_bus *bus = NULL; struct pass_resource_gpu *gpu = NULL; struct pass_resource_memory *memory = NULL; int ret; /* Allocate memory */ power_funcs = calloc(1, sizeof(hal_backend_power_funcs)); if (!power_funcs) return -ENOMEM; cpu = calloc(1, sizeof(struct pass_resource_cpu)); if (!cpu) { ret = -ENOMEM; goto err_funcs; } bus = calloc(1, sizeof(struct pass_resource_bus)); if (!bus) { ret = -ENOMEM; goto err_cpu; } gpu = calloc(1, sizeof(struct pass_resource_gpu)); if (!gpu) { ret = -ENOMEM; goto err_bus; } memory = calloc(1, sizeof(struct pass_resource_memory)); if (!memory) { ret = -ENOMEM; goto err_gpu; } /* Initialize each h/w resource */ cpu->dvfs = cpufreq_dvfs_ops; cpu->hotplug = cpu_hotplus_ops; cpu->tmu = tmu_ops; bus->dvfs = devfreq_dvfs_ops; bus->tmu = tmu_ops; gpu->dvfs = devfreq_dvfs_ops; gpu->tmu = tmu_ops; memory->get_fault_around_bytes = memory_get_fault_around_bytes; memory->set_fault_around_bytes = memory_set_fault_around_bytes; /* Initialize hal_backend_power_funcs */ power_funcs->cpu = cpu; power_funcs->bus = bus; power_funcs->gpu = gpu; power_funcs->memory = memory; *data = (void *)power_funcs; return 0; err_gpu: if (gpu) free(gpu); err_bus: if (bus) free(bus); err_cpu: if (cpu) free(cpu); err_funcs: free(power_funcs); return ret; } static int power_exit(void *data) { hal_backend_power_funcs *funcs; if (!data) return -EINVAL; funcs = (hal_backend_power_funcs *)data; if (funcs->cpu) free(funcs->cpu); if (funcs->bus) free(funcs->bus); if (funcs->gpu) free(funcs->gpu); if (funcs->memory) free(funcs->memory); free(funcs); return 0; } hal_backend hal_backend_power_data = { .name = "power-cpu", .vendor = "", .abi_version = HAL_ABI_VERSION_TIZEN_6_5, .init = power_init, .exit = power_exit, };