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#ifdef 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 "util/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; 65static mtx_t gcpufreq_mutex = _MTX_INITIALIZER_NP; 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, struct pipe_context *pipe) 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 free(gr); 155 return; 156 } 157 158 gr->query_data = cfi; 159 gr->query_new_value = query_cfi_load; 160 161 hud_pane_add_graph(pane, gr); 162 hud_pane_set_max_value(pane, 3000000 /* 3 GHz */); 163} 164 165static void 166add_object(const char *name, const char *fn, int objmode, int cpu_index) 167{ 168 struct cpufreq_info *cfi = CALLOC_STRUCT(cpufreq_info); 169 170 strcpy(cfi->name, name); 171 strcpy(cfi->sysfs_filename, fn); 172 cfi->mode = objmode; 173 cfi->cpu_index = cpu_index; 174 list_addtail(&cfi->list, &gcpufreq_list); 175 gcpufreq_count++; 176} 177 178/** 179 * Initialize internal object arrays and display cpu freq HUD help. 180 * \param displayhelp true if the list of detected cpus should be 181 displayed on the console. 182 * \return number of detected CPU metrics (CPU count * 3) 183 */ 184int 185hud_get_num_cpufreq(bool displayhelp) 186{ 187 struct dirent *dp; 188 struct stat stat_buf; 189 char fn[128]; 190 int cpu_index; 191 192 /* Return the number of CPU metrics we support. */ 193 mtx_lock(&gcpufreq_mutex); 194 if (gcpufreq_count) { 195 mtx_unlock(&gcpufreq_mutex); 196 return gcpufreq_count; 197 } 198 199 /* Scan /sys/devices.../cpu, for every object type we support, create 200 * and persist an object to represent its different metrics. 201 */ 202 list_inithead(&gcpufreq_list); 203 DIR *dir = opendir("/sys/devices/system/cpu"); 204 if (!dir) { 205 mtx_unlock(&gcpufreq_mutex); 206 return 0; 207 } 208 209 while ((dp = readdir(dir)) != NULL) { 210 211 size_t d_name_len = strlen(dp->d_name); 212 213 /* Avoid 'lo' and '..' and '.', and avoid overlong names that 214 * would result in a buffer overflow in add_object. 215 */ 216 if (d_name_len <= 2 || d_name_len > 15) 217 continue; 218 219 if (sscanf(dp->d_name, "cpu%d\n", &cpu_index) != 1) 220 continue; 221 222 char basename[256]; 223 snprintf(basename, sizeof(basename), "/sys/devices/system/cpu/%s", dp->d_name); 224 225 snprintf(fn, sizeof(fn), "%s/cpufreq/scaling_cur_freq", basename); 226 if (stat(fn, &stat_buf) < 0) 227 continue; 228 229 if (!S_ISREG(stat_buf.st_mode)) 230 continue; /* Not a regular file */ 231 232 snprintf(fn, sizeof(fn), "%s/cpufreq/scaling_min_freq", basename); 233 add_object(dp->d_name, fn, CPUFREQ_MINIMUM, cpu_index); 234 235 snprintf(fn, sizeof(fn), "%s/cpufreq/scaling_cur_freq", basename); 236 add_object(dp->d_name, fn, CPUFREQ_CURRENT, cpu_index); 237 238 snprintf(fn, sizeof(fn), "%s/cpufreq/scaling_max_freq", basename); 239 add_object(dp->d_name, fn, CPUFREQ_MAXIMUM, cpu_index); 240 } 241 closedir(dir); 242 243 if (displayhelp) { 244 list_for_each_entry(struct cpufreq_info, cfi, &gcpufreq_list, list) { 245 char line[128]; 246 snprintf(line, sizeof(line), " cpufreq-%s-%s", 247 cfi->mode == CPUFREQ_MINIMUM ? "min" : 248 cfi->mode == CPUFREQ_CURRENT ? "cur" : 249 cfi->mode == CPUFREQ_MAXIMUM ? "max" : "undefined", cfi->name); 250 251 puts(line); 252 } 253 } 254 255 mtx_unlock(&gcpufreq_mutex); 256 return gcpufreq_count; 257} 258 259#endif /* HAVE_GALLIUM_EXTRA_HUD */ 260