1/************************************************************************** 2 * 3 * Copyright (C) 2016 Steven Toth <stoth@kernellabs.com> 4 * Copyright (C) 2016 Zodiac Inflight Innovations 5 * All Rights Reserved. 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a 8 * copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sub license, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice (including the 16 * next paragraph) shall be included in all copies or substantial portions 17 * of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 22 * IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR 23 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 * 27 **************************************************************************/ 28 29#if HAVE_GALLIUM_EXTRA_HUD 30 31/* Purpose: 32 * Reading /sys/devices/system/cpu/cpu?/cpufreq/scaling_???_freq 33 * cpu frequency (KHz), displaying on the HUD in Hz. 34 */ 35 36#include "hud/hud_private.h" 37#include "util/list.h" 38#include "os/os_time.h" 39#include "os/os_thread.h" 40#include "util/u_memory.h" 41#include <stdio.h> 42#include <unistd.h> 43#include <dirent.h> 44#include <stdlib.h> 45#include <errno.h> 46#include <inttypes.h> 47#include <sys/types.h> 48#include <sys/stat.h> 49 50struct cpufreq_info 51{ 52 struct list_head list; 53 int mode; /* CPUFREQ_MINIMUM, CPUFREQ_CURRENT, CPUFREQ_MAXIMUM */ 54 char name[16]; /* EG. cpu0 */ 55 int cpu_index; 56 57 /* EG. /sys/devices/system/cpu/cpu?/cpufreq/scaling_cur_freq */ 58 char sysfs_filename[128]; 59 uint64_t KHz; 60 uint64_t last_time; 61}; 62 63static int gcpufreq_count = 0; 64static struct list_head gcpufreq_list; 65pipe_static_mutex(gcpufreq_mutex); 66 67static struct cpufreq_info * 68find_cfi_by_index(int cpu_index, int mode) 69{ 70 list_for_each_entry(struct cpufreq_info, cfi, &gcpufreq_list, list) { 71 if (cfi->mode != mode) 72 continue; 73 if (cfi->cpu_index == cpu_index) 74 return cfi; 75 } 76 return 0; 77} 78 79static int 80get_file_value(const char *fn, uint64_t *KHz) 81{ 82 FILE *fh = fopen(fn, "r"); 83 if (!fh) { 84 fprintf(stderr, "%s error: %s\n", fn, strerror(errno)); 85 return -1; 86 } 87 int ret = fscanf(fh, "%" PRIu64 "", KHz); 88 fclose(fh); 89 90 return ret; 91} 92 93static void 94query_cfi_load(struct hud_graph *gr) 95{ 96 struct cpufreq_info *cfi = gr->query_data; 97 98 uint64_t now = os_time_get(); 99 if (cfi->last_time) { 100 if (cfi->last_time + gr->pane->period <= now) { 101 switch (cfi->mode) { 102 case CPUFREQ_MINIMUM: 103 case CPUFREQ_CURRENT: 104 case CPUFREQ_MAXIMUM: 105 get_file_value(cfi->sysfs_filename, &cfi->KHz); 106 hud_graph_add_value(gr, (uint64_t)cfi->KHz * 1000); 107 } 108 cfi->last_time = now; 109 } 110 } else { 111 /* initialize */ 112 get_file_value(cfi->sysfs_filename, &cfi->KHz); 113 cfi->last_time = now; 114 } 115} 116 117/** 118 * Create and initialize a new object for a specific CPU. 119 * \param pane parent context. 120 * \param cpu_index CPU identifier Eg. 0 (CPU0) 121 * \param mode query CPUFREQ_MINIMUM | CURRENT | MAXIMUM statistic. 122 */ 123void 124hud_cpufreq_graph_install(struct hud_pane *pane, int cpu_index, 125 unsigned int mode) 126{ 127 struct hud_graph *gr; 128 struct cpufreq_info *cfi; 129 130 int num_cpus = hud_get_num_cpufreq(0); 131 if (num_cpus <= 0) 132 return; 133 134 cfi = find_cfi_by_index(cpu_index, mode); 135 if (!cfi) 136 return; 137 138 gr = CALLOC_STRUCT(hud_graph); 139 if (!gr) 140 return; 141 142 cfi->mode = mode; 143 switch(cfi->mode) { 144 case CPUFREQ_MINIMUM: 145 snprintf(gr->name, sizeof(gr->name), "%s-Min", cfi->name); 146 break; 147 case CPUFREQ_CURRENT: 148 snprintf(gr->name, sizeof(gr->name), "%s-Cur", cfi->name); 149 break; 150 case CPUFREQ_MAXIMUM: 151 snprintf(gr->name, sizeof(gr->name), "%s-Max", cfi->name); 152 break; 153 default: 154 return; 155 } 156 157 gr->query_data = cfi; 158 gr->query_new_value = query_cfi_load; 159 160 hud_pane_add_graph(pane, gr); 161 hud_pane_set_max_value(pane, 3000000 /* 3 GHz */); 162} 163 164static void 165add_object(const char *name, const char *fn, int objmode, int cpu_index) 166{ 167 struct cpufreq_info *cfi = CALLOC_STRUCT(cpufreq_info); 168 169 strcpy(cfi->name, name); 170 strcpy(cfi->sysfs_filename, fn); 171 cfi->mode = objmode; 172 cfi->cpu_index = cpu_index; 173 list_addtail(&cfi->list, &gcpufreq_list); 174 gcpufreq_count++; 175} 176 177/** 178 * Initialize internal object arrays and display cpu freq HUD help. 179 * \param displayhelp true if the list of detected cpus should be 180 displayed on the console. 181 * \return number of detected CPU metrics (CPU count * 3) 182 */ 183int 184hud_get_num_cpufreq(bool displayhelp) 185{ 186 struct dirent *dp; 187 struct stat stat_buf; 188 char fn[128]; 189 int cpu_index; 190 191 /* Return the number of CPU metrics we support. */ 192 pipe_mutex_lock(gcpufreq_mutex); 193 if (gcpufreq_count) { 194 pipe_mutex_unlock(gcpufreq_mutex); 195 return gcpufreq_count; 196 } 197 198 /* Scan /sys/devices.../cpu, for every object type we support, create 199 * and persist an object to represent its different metrics. 200 */ 201 list_inithead(&gcpufreq_list); 202 DIR *dir = opendir("/sys/devices/system/cpu"); 203 if (!dir) { 204 pipe_mutex_unlock(gcpufreq_mutex); 205 return 0; 206 } 207 208 while ((dp = readdir(dir)) != NULL) { 209 210 /* Avoid 'lo' and '..' and '.' */ 211 if (strlen(dp->d_name) <= 2) 212 continue; 213 214 if (sscanf(dp->d_name, "cpu%d\n", &cpu_index) != 1) 215 continue; 216 217 char basename[256]; 218 snprintf(basename, sizeof(basename), "/sys/devices/system/cpu/%s", dp->d_name); 219 220 snprintf(fn, sizeof(fn), "%s/cpufreq/scaling_cur_freq", basename); 221 if (stat(fn, &stat_buf) < 0) 222 continue; 223 224 if (!S_ISREG(stat_buf.st_mode)) 225 continue; /* Not a regular file */ 226 227 snprintf(fn, sizeof(fn), "%s/cpufreq/scaling_min_freq", basename); 228 add_object(dp->d_name, fn, CPUFREQ_MINIMUM, cpu_index); 229 230 snprintf(fn, sizeof(fn), "%s/cpufreq/scaling_cur_freq", basename); 231 add_object(dp->d_name, fn, CPUFREQ_CURRENT, cpu_index); 232 233 snprintf(fn, sizeof(fn), "%s/cpufreq/scaling_max_freq", basename); 234 add_object(dp->d_name, fn, CPUFREQ_MAXIMUM, cpu_index); 235 } 236 closedir(dir); 237 238 if (displayhelp) { 239 list_for_each_entry(struct cpufreq_info, cfi, &gcpufreq_list, list) { 240 char line[128]; 241 snprintf(line, sizeof(line), " cpufreq-%s-%s", 242 cfi->mode == CPUFREQ_MINIMUM ? "min" : 243 cfi->mode == CPUFREQ_CURRENT ? "cur" : 244 cfi->mode == CPUFREQ_MAXIMUM ? "max" : "undefined", cfi->name); 245 246 puts(line); 247 } 248 } 249 250 pipe_mutex_unlock(gcpufreq_mutex); 251 return gcpufreq_count; 252} 253 254#endif /* HAVE_GALLIUM_EXTRA_HUD */ 255