| 1 | // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) |
| 2 | #include "counts.h" |
| 3 | #include "debug.h" |
| 4 | #include "evsel.h" |
| 5 | #include "hashmap.h" |
| 6 | #include "hwmon_pmu.h" |
| 7 | #include "pmu.h" |
| 8 | #include <internal/xyarray.h> |
| 9 | #include <internal/threadmap.h> |
| 10 | #include <perf/threadmap.h> |
| 11 | #include <sys/types.h> |
| 12 | #include <assert.h> |
| 13 | #include <ctype.h> |
| 14 | #include <fcntl.h> |
| 15 | #include <stddef.h> |
| 16 | #include <stdlib.h> |
| 17 | #include <string.h> |
| 18 | #include <api/fs/fs.h> |
| 19 | #include <api/io.h> |
| 20 | #include <api/io_dir.h> |
| 21 | #include <linux/kernel.h> |
| 22 | #include <linux/string.h> |
| 23 | #include <linux/zalloc.h> |
| 24 | |
| 25 | /** Strings that correspond to enum hwmon_type. */ |
| 26 | static const char * const hwmon_type_strs[HWMON_TYPE_MAX] = { |
| 27 | NULL, |
| 28 | "cpu" , |
| 29 | "curr" , |
| 30 | "energy" , |
| 31 | "fan" , |
| 32 | "humidity" , |
| 33 | "in" , |
| 34 | "intrusion" , |
| 35 | "power" , |
| 36 | "pwm" , |
| 37 | "temp" , |
| 38 | }; |
| 39 | #define LONGEST_HWMON_TYPE_STR "intrusion" |
| 40 | |
| 41 | /** Strings that correspond to enum hwmon_item. */ |
| 42 | static const char * const hwmon_item_strs[HWMON_ITEM__MAX] = { |
| 43 | NULL, |
| 44 | "accuracy" , |
| 45 | "alarm" , |
| 46 | "auto_channels_temp" , |
| 47 | "average" , |
| 48 | "average_highest" , |
| 49 | "average_interval" , |
| 50 | "average_interval_max" , |
| 51 | "average_interval_min" , |
| 52 | "average_lowest" , |
| 53 | "average_max" , |
| 54 | "average_min" , |
| 55 | "beep" , |
| 56 | "cap" , |
| 57 | "cap_hyst" , |
| 58 | "cap_max" , |
| 59 | "cap_min" , |
| 60 | "crit" , |
| 61 | "crit_hyst" , |
| 62 | "div" , |
| 63 | "emergency" , |
| 64 | "emergency_hist" , |
| 65 | "enable" , |
| 66 | "fault" , |
| 67 | "freq" , |
| 68 | "highest" , |
| 69 | "input" , |
| 70 | "label" , |
| 71 | "lcrit" , |
| 72 | "lcrit_hyst" , |
| 73 | "lowest" , |
| 74 | "max" , |
| 75 | "max_hyst" , |
| 76 | "min" , |
| 77 | "min_hyst" , |
| 78 | "mod" , |
| 79 | "offset" , |
| 80 | "pulses" , |
| 81 | "rated_max" , |
| 82 | "rated_min" , |
| 83 | "reset_history" , |
| 84 | "target" , |
| 85 | "type" , |
| 86 | "vid" , |
| 87 | }; |
| 88 | #define LONGEST_HWMON_ITEM_STR "average_interval_max" |
| 89 | |
| 90 | static const char *const hwmon_units[HWMON_TYPE_MAX] = { |
| 91 | NULL, |
| 92 | "V" , /* cpu */ |
| 93 | "A" , /* curr */ |
| 94 | "J" , /* energy */ |
| 95 | "rpm" , /* fan */ |
| 96 | "%" , /* humidity */ |
| 97 | "V" , /* in */ |
| 98 | "" , /* intrusion */ |
| 99 | "W" , /* power */ |
| 100 | "Hz" , /* pwm */ |
| 101 | "'C" , /* temp */ |
| 102 | }; |
| 103 | |
| 104 | struct hwmon_pmu { |
| 105 | struct perf_pmu pmu; |
| 106 | struct hashmap events; |
| 107 | char *hwmon_dir; |
| 108 | }; |
| 109 | |
| 110 | /** |
| 111 | * struct hwmon_pmu_event_value: Value in hwmon_pmu->events. |
| 112 | * |
| 113 | * Hwmon files are of the form <type><number>_<item> and may have a suffix |
| 114 | * _alarm. |
| 115 | */ |
| 116 | struct hwmon_pmu_event_value { |
| 117 | /** @items: which item files are present. */ |
| 118 | DECLARE_BITMAP(items, HWMON_ITEM__MAX); |
| 119 | /** @alarm_items: which item files are present. */ |
| 120 | DECLARE_BITMAP(alarm_items, HWMON_ITEM__MAX); |
| 121 | /** @label: contents of <type><number>_label if present. */ |
| 122 | char *label; |
| 123 | /** @name: name computed from label of the form <type>_<label>. */ |
| 124 | char *name; |
| 125 | }; |
| 126 | |
| 127 | bool perf_pmu__is_hwmon(const struct perf_pmu *pmu) |
| 128 | { |
| 129 | return pmu && pmu->type >= PERF_PMU_TYPE_HWMON_START && |
| 130 | pmu->type <= PERF_PMU_TYPE_HWMON_END; |
| 131 | } |
| 132 | |
| 133 | bool evsel__is_hwmon(const struct evsel *evsel) |
| 134 | { |
| 135 | return perf_pmu__is_hwmon(pmu: evsel->pmu); |
| 136 | } |
| 137 | |
| 138 | static size_t hwmon_pmu__event_hashmap_hash(long key, void *ctx __maybe_unused) |
| 139 | { |
| 140 | return ((union hwmon_pmu_event_key)key).type_and_num; |
| 141 | } |
| 142 | |
| 143 | static bool hwmon_pmu__event_hashmap_equal(long key1, long key2, void *ctx __maybe_unused) |
| 144 | { |
| 145 | return ((union hwmon_pmu_event_key)key1).type_and_num == |
| 146 | ((union hwmon_pmu_event_key)key2).type_and_num; |
| 147 | } |
| 148 | |
| 149 | static int hwmon_strcmp(const void *a, const void *b) |
| 150 | { |
| 151 | const char *sa = a; |
| 152 | const char * const *sb = b; |
| 153 | |
| 154 | return strcmp(sa, *sb); |
| 155 | } |
| 156 | |
| 157 | bool parse_hwmon_filename(const char *filename, |
| 158 | enum hwmon_type *type, |
| 159 | int *number, |
| 160 | enum hwmon_item *item, |
| 161 | bool *alarm) |
| 162 | { |
| 163 | char fn_type[24]; |
| 164 | const char **elem; |
| 165 | const char *fn_item = NULL; |
| 166 | size_t fn_item_len; |
| 167 | |
| 168 | assert(strlen(LONGEST_HWMON_TYPE_STR) < sizeof(fn_type)); |
| 169 | strlcpy(fn_type, filename, sizeof(fn_type)); |
| 170 | for (size_t i = 0; fn_type[i] != '\0'; i++) { |
| 171 | if (fn_type[i] >= '0' && fn_type[i] <= '9') { |
| 172 | fn_type[i] = '\0'; |
| 173 | *number = strtoul(&filename[i], (char **)&fn_item, 10); |
| 174 | if (*fn_item == '_') |
| 175 | fn_item++; |
| 176 | break; |
| 177 | } |
| 178 | if (fn_type[i] == '_') { |
| 179 | fn_type[i] = '\0'; |
| 180 | *number = -1; |
| 181 | fn_item = &filename[i + 1]; |
| 182 | break; |
| 183 | } |
| 184 | } |
| 185 | if (fn_item == NULL || fn_type[0] == '\0' || (item != NULL && fn_item[0] == '\0')) { |
| 186 | pr_debug3("hwmon_pmu: not a hwmon file '%s'\n" , filename); |
| 187 | return false; |
| 188 | } |
| 189 | elem = bsearch(key: &fn_type, base: hwmon_type_strs + 1, ARRAY_SIZE(hwmon_type_strs) - 1, |
| 190 | size: sizeof(hwmon_type_strs[0]), cmp: hwmon_strcmp); |
| 191 | if (!elem) { |
| 192 | pr_debug3("hwmon_pmu: not a hwmon type '%s' in file name '%s'\n" , |
| 193 | fn_type, filename); |
| 194 | return false; |
| 195 | } |
| 196 | |
| 197 | *type = elem - &hwmon_type_strs[0]; |
| 198 | if (!item) |
| 199 | return true; |
| 200 | |
| 201 | *alarm = false; |
| 202 | fn_item_len = strlen(fn_item); |
| 203 | if (fn_item_len > 6 && !strcmp(&fn_item[fn_item_len - 6], "_alarm" )) { |
| 204 | assert(strlen(LONGEST_HWMON_ITEM_STR) < sizeof(fn_type)); |
| 205 | strlcpy(fn_type, fn_item, fn_item_len - 5); |
| 206 | fn_item = fn_type; |
| 207 | *alarm = true; |
| 208 | } |
| 209 | elem = bsearch(key: fn_item, base: hwmon_item_strs + 1, ARRAY_SIZE(hwmon_item_strs) - 1, |
| 210 | size: sizeof(hwmon_item_strs[0]), cmp: hwmon_strcmp); |
| 211 | if (!elem) { |
| 212 | pr_debug3("hwmon_pmu: not a hwmon item '%s' in file name '%s'\n" , |
| 213 | fn_item, filename); |
| 214 | return false; |
| 215 | } |
| 216 | *item = elem - &hwmon_item_strs[0]; |
| 217 | return true; |
| 218 | } |
| 219 | |
| 220 | static void fix_name(char *p) |
| 221 | { |
| 222 | char *s = strchr(p, '\n'); |
| 223 | |
| 224 | if (s) |
| 225 | *s = '\0'; |
| 226 | |
| 227 | while (*p != '\0') { |
| 228 | if (strchr(" :,/\n\t" , *p)) |
| 229 | *p = '_'; |
| 230 | else |
| 231 | *p = tolower(*p); |
| 232 | p++; |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | static int hwmon_pmu__read_events(struct hwmon_pmu *pmu) |
| 237 | { |
| 238 | int err = 0; |
| 239 | struct hashmap_entry *cur, *tmp; |
| 240 | size_t bkt; |
| 241 | struct io_dirent64 *ent; |
| 242 | struct io_dir dir; |
| 243 | |
| 244 | if (pmu->pmu.sysfs_aliases_loaded) |
| 245 | return 0; |
| 246 | |
| 247 | /* Use openat so that the directory contents are refreshed. */ |
| 248 | io_dir__init(&dir, open(pmu->hwmon_dir, O_CLOEXEC | O_DIRECTORY | O_RDONLY)); |
| 249 | |
| 250 | if (dir.dirfd < 0) |
| 251 | return -ENOENT; |
| 252 | |
| 253 | while ((ent = io_dir__readdir(&dir)) != NULL) { |
| 254 | enum hwmon_type type; |
| 255 | int number; |
| 256 | enum hwmon_item item; |
| 257 | bool alarm; |
| 258 | union hwmon_pmu_event_key key = { .type_and_num = 0 }; |
| 259 | struct hwmon_pmu_event_value *value; |
| 260 | |
| 261 | if (ent->d_type != DT_REG) |
| 262 | continue; |
| 263 | |
| 264 | if (!parse_hwmon_filename(filename: ent->d_name, type: &type, number: &number, item: &item, alarm: &alarm)) { |
| 265 | pr_debug3("Not a hwmon file '%s'\n" , ent->d_name); |
| 266 | continue; |
| 267 | } |
| 268 | key.num = number; |
| 269 | key.type = type; |
| 270 | if (!hashmap__find(&pmu->events, key.type_and_num, &value)) { |
| 271 | value = zalloc(sizeof(*value)); |
| 272 | if (!value) { |
| 273 | err = -ENOMEM; |
| 274 | goto err_out; |
| 275 | } |
| 276 | err = hashmap__add(&pmu->events, key.type_and_num, value); |
| 277 | if (err) { |
| 278 | free(value); |
| 279 | err = -ENOMEM; |
| 280 | goto err_out; |
| 281 | } |
| 282 | } |
| 283 | __set_bit(item, alarm ? value->alarm_items : value->items); |
| 284 | if (item == HWMON_ITEM_LABEL) { |
| 285 | char buf[128]; |
| 286 | int fd = openat(dir.dirfd, ent->d_name, O_RDONLY); |
| 287 | ssize_t read_len; |
| 288 | |
| 289 | if (fd < 0) |
| 290 | continue; |
| 291 | |
| 292 | read_len = read(fd, buf, sizeof(buf)); |
| 293 | |
| 294 | while (read_len > 0 && buf[read_len - 1] == '\n') |
| 295 | read_len--; |
| 296 | |
| 297 | if (read_len > 0) |
| 298 | buf[read_len] = '\0'; |
| 299 | |
| 300 | if (buf[0] == '\0') { |
| 301 | pr_debug("hwmon_pmu: empty label file %s %s\n" , |
| 302 | pmu->pmu.name, ent->d_name); |
| 303 | close(fd); |
| 304 | continue; |
| 305 | } |
| 306 | value->label = strdup(buf); |
| 307 | if (!value->label) { |
| 308 | pr_debug("hwmon_pmu: memory allocation failure\n" ); |
| 309 | close(fd); |
| 310 | continue; |
| 311 | } |
| 312 | snprintf(buf, size: sizeof(buf), fmt: "%s_%s" , hwmon_type_strs[type], value->label); |
| 313 | fix_name(p: buf); |
| 314 | value->name = strdup(buf); |
| 315 | if (!value->name) |
| 316 | pr_debug("hwmon_pmu: memory allocation failure\n" ); |
| 317 | close(fd); |
| 318 | } |
| 319 | } |
| 320 | if (hashmap__size(map: &pmu->events) == 0) |
| 321 | pr_debug2("hwmon_pmu: %s has no events\n" , pmu->pmu.name); |
| 322 | |
| 323 | hashmap__for_each_entry_safe((&pmu->events), cur, tmp, bkt) { |
| 324 | union hwmon_pmu_event_key key = { |
| 325 | .type_and_num = cur->key, |
| 326 | }; |
| 327 | struct hwmon_pmu_event_value *value = cur->pvalue; |
| 328 | |
| 329 | if (!test_bit(HWMON_ITEM_INPUT, value->items)) { |
| 330 | pr_debug("hwmon_pmu: %s removing event '%s%d' that has no input file\n" , |
| 331 | pmu->pmu.name, hwmon_type_strs[key.type], key.num); |
| 332 | hashmap__delete(&pmu->events, key.type_and_num, &key, &value); |
| 333 | zfree(&value->label); |
| 334 | zfree(&value->name); |
| 335 | free(value); |
| 336 | } |
| 337 | } |
| 338 | pmu->pmu.sysfs_aliases_loaded = true; |
| 339 | |
| 340 | err_out: |
| 341 | close(dir.dirfd); |
| 342 | return err; |
| 343 | } |
| 344 | |
| 345 | struct perf_pmu *hwmon_pmu__new(struct list_head *pmus, const char *hwmon_dir, |
| 346 | const char *sysfs_name, const char *name) |
| 347 | { |
| 348 | char buf[64]; |
| 349 | struct hwmon_pmu *hwm; |
| 350 | __u32 type = PERF_PMU_TYPE_HWMON_START + strtoul(sysfs_name + 5, NULL, 10); |
| 351 | |
| 352 | if (type > PERF_PMU_TYPE_HWMON_END) { |
| 353 | pr_err("Unable to encode hwmon type from %s in valid PMU type\n" , sysfs_name); |
| 354 | return NULL; |
| 355 | } |
| 356 | |
| 357 | snprintf(buf, size: sizeof(buf), fmt: "hwmon_%s" , name); |
| 358 | fix_name(p: buf + 6); |
| 359 | |
| 360 | hwm = zalloc(sizeof(*hwm)); |
| 361 | if (!hwm) |
| 362 | return NULL; |
| 363 | |
| 364 | if (perf_pmu__init(pmu: &hwm->pmu, type, name: buf) != 0) { |
| 365 | perf_pmu__delete(pmu: &hwm->pmu); |
| 366 | return NULL; |
| 367 | } |
| 368 | |
| 369 | hwm->hwmon_dir = strdup(hwmon_dir); |
| 370 | if (!hwm->hwmon_dir) { |
| 371 | perf_pmu__delete(pmu: &hwm->pmu); |
| 372 | return NULL; |
| 373 | } |
| 374 | hwm->pmu.alias_name = strdup(sysfs_name); |
| 375 | if (!hwm->pmu.alias_name) { |
| 376 | perf_pmu__delete(pmu: &hwm->pmu); |
| 377 | return NULL; |
| 378 | } |
| 379 | hwm->pmu.cpus = perf_cpu_map__new_int(0); |
| 380 | if (!hwm->pmu.cpus) { |
| 381 | perf_pmu__delete(pmu: &hwm->pmu); |
| 382 | return NULL; |
| 383 | } |
| 384 | INIT_LIST_HEAD(list: &hwm->pmu.format); |
| 385 | INIT_LIST_HEAD(list: &hwm->pmu.caps); |
| 386 | hashmap__init(map: &hwm->events, hash_fn: hwmon_pmu__event_hashmap_hash, |
| 387 | equal_fn: hwmon_pmu__event_hashmap_equal, /*ctx=*/NULL); |
| 388 | |
| 389 | list_add_tail(new: &hwm->pmu.list, head: pmus); |
| 390 | return &hwm->pmu; |
| 391 | } |
| 392 | |
| 393 | void hwmon_pmu__exit(struct perf_pmu *pmu) |
| 394 | { |
| 395 | struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); |
| 396 | struct hashmap_entry *cur, *tmp; |
| 397 | size_t bkt; |
| 398 | |
| 399 | hashmap__for_each_entry_safe((&hwm->events), cur, tmp, bkt) { |
| 400 | struct hwmon_pmu_event_value *value = cur->pvalue; |
| 401 | |
| 402 | zfree(&value->label); |
| 403 | zfree(&value->name); |
| 404 | free(value); |
| 405 | } |
| 406 | hashmap__clear(map: &hwm->events); |
| 407 | zfree(&hwm->hwmon_dir); |
| 408 | } |
| 409 | |
| 410 | static size_t hwmon_pmu__describe_items(struct hwmon_pmu *hwm, char *out_buf, size_t out_buf_len, |
| 411 | union hwmon_pmu_event_key key, |
| 412 | const unsigned long *items, bool is_alarm) |
| 413 | { |
| 414 | size_t bit; |
| 415 | char buf[64]; |
| 416 | size_t len = 0; |
| 417 | int dir = open(hwm->hwmon_dir, O_CLOEXEC | O_DIRECTORY | O_RDONLY); |
| 418 | |
| 419 | if (dir < 0) |
| 420 | return 0; |
| 421 | |
| 422 | for_each_set_bit(bit, items, HWMON_ITEM__MAX) { |
| 423 | int fd; |
| 424 | |
| 425 | if (bit == HWMON_ITEM_LABEL || bit == HWMON_ITEM_INPUT) |
| 426 | continue; |
| 427 | |
| 428 | snprintf(buf, size: sizeof(buf), fmt: "%s%d_%s%s" , |
| 429 | hwmon_type_strs[key.type], |
| 430 | key.num, |
| 431 | hwmon_item_strs[bit], |
| 432 | is_alarm ? "_alarm" : "" ); |
| 433 | fd = openat(dir, buf, O_RDONLY); |
| 434 | if (fd > 0) { |
| 435 | ssize_t read_len = read(fd, buf, sizeof(buf)); |
| 436 | |
| 437 | while (read_len > 0 && buf[read_len - 1] == '\n') |
| 438 | read_len--; |
| 439 | |
| 440 | if (read_len > 0) { |
| 441 | long long val; |
| 442 | |
| 443 | buf[read_len] = '\0'; |
| 444 | val = strtoll(buf, /*endptr=*/NULL, 10); |
| 445 | len += snprintf(buf: out_buf + len, size: out_buf_len - len, fmt: "%s%s%s=%g%s" , |
| 446 | len == 0 ? " " : ", " , |
| 447 | hwmon_item_strs[bit], |
| 448 | is_alarm ? "_alarm" : "" , |
| 449 | (double)val / 1000.0, |
| 450 | hwmon_units[key.type]); |
| 451 | } |
| 452 | close(fd); |
| 453 | } |
| 454 | } |
| 455 | close(dir); |
| 456 | return len; |
| 457 | } |
| 458 | |
| 459 | int hwmon_pmu__for_each_event(struct perf_pmu *pmu, void *state, pmu_event_callback cb) |
| 460 | { |
| 461 | struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); |
| 462 | struct hashmap_entry *cur; |
| 463 | size_t bkt; |
| 464 | |
| 465 | if (hwmon_pmu__read_events(pmu: hwm)) |
| 466 | return false; |
| 467 | |
| 468 | hashmap__for_each_entry((&hwm->events), cur, bkt) { |
| 469 | static const char *const hwmon_scale_units[HWMON_TYPE_MAX] = { |
| 470 | NULL, |
| 471 | "0.001V" , /* cpu */ |
| 472 | "0.001A" , /* curr */ |
| 473 | "0.001J" , /* energy */ |
| 474 | "1rpm" , /* fan */ |
| 475 | "0.001%" , /* humidity */ |
| 476 | "0.001V" , /* in */ |
| 477 | NULL, /* intrusion */ |
| 478 | "0.001W" , /* power */ |
| 479 | "1Hz" , /* pwm */ |
| 480 | "0.001'C" , /* temp */ |
| 481 | }; |
| 482 | static const char *const hwmon_desc[HWMON_TYPE_MAX] = { |
| 483 | NULL, |
| 484 | "CPU core reference voltage" , /* cpu */ |
| 485 | "Current" , /* curr */ |
| 486 | "Cumulative energy use" , /* energy */ |
| 487 | "Fan" , /* fan */ |
| 488 | "Humidity" , /* humidity */ |
| 489 | "Voltage" , /* in */ |
| 490 | "Chassis intrusion detection" , /* intrusion */ |
| 491 | "Power use" , /* power */ |
| 492 | "Pulse width modulation fan control" , /* pwm */ |
| 493 | "Temperature" , /* temp */ |
| 494 | }; |
| 495 | char alias_buf[64]; |
| 496 | char desc_buf[256]; |
| 497 | char encoding_buf[128]; |
| 498 | union hwmon_pmu_event_key key = { |
| 499 | .type_and_num = cur->key, |
| 500 | }; |
| 501 | struct hwmon_pmu_event_value *value = cur->pvalue; |
| 502 | struct pmu_event_info info = { |
| 503 | .pmu = pmu, |
| 504 | .name = value->name, |
| 505 | .alias = alias_buf, |
| 506 | .scale_unit = hwmon_scale_units[key.type], |
| 507 | .desc = desc_buf, |
| 508 | .long_desc = NULL, |
| 509 | .encoding_desc = encoding_buf, |
| 510 | .topic = "hwmon" , |
| 511 | .pmu_name = pmu->name, |
| 512 | .event_type_desc = "Hwmon event" , |
| 513 | }; |
| 514 | int ret; |
| 515 | size_t len; |
| 516 | |
| 517 | len = snprintf(buf: alias_buf, size: sizeof(alias_buf), fmt: "%s%d" , |
| 518 | hwmon_type_strs[key.type], key.num); |
| 519 | if (!info.name) { |
| 520 | info.name = info.alias; |
| 521 | info.alias = NULL; |
| 522 | } |
| 523 | |
| 524 | len = snprintf(buf: desc_buf, size: sizeof(desc_buf), fmt: "%s in unit %s named %s." , |
| 525 | hwmon_desc[key.type], |
| 526 | pmu->name + 6, |
| 527 | value->label ?: info.name); |
| 528 | |
| 529 | len += hwmon_pmu__describe_items(hwm, out_buf: desc_buf + len, out_buf_len: sizeof(desc_buf) - len, |
| 530 | key, items: value->items, /*is_alarm=*/false); |
| 531 | |
| 532 | len += hwmon_pmu__describe_items(hwm, out_buf: desc_buf + len, out_buf_len: sizeof(desc_buf) - len, |
| 533 | key, items: value->alarm_items, /*is_alarm=*/true); |
| 534 | |
| 535 | snprintf(buf: encoding_buf, size: sizeof(encoding_buf), fmt: "%s/config=0x%lx/" , |
| 536 | pmu->name, cur->key); |
| 537 | |
| 538 | ret = cb(state, &info); |
| 539 | if (ret) |
| 540 | return ret; |
| 541 | } |
| 542 | return 0; |
| 543 | } |
| 544 | |
| 545 | size_t hwmon_pmu__num_events(struct perf_pmu *pmu) |
| 546 | { |
| 547 | struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); |
| 548 | |
| 549 | hwmon_pmu__read_events(pmu: hwm); |
| 550 | return hashmap__size(map: &hwm->events); |
| 551 | } |
| 552 | |
| 553 | bool hwmon_pmu__have_event(struct perf_pmu *pmu, const char *name) |
| 554 | { |
| 555 | struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); |
| 556 | enum hwmon_type type; |
| 557 | int number; |
| 558 | union hwmon_pmu_event_key key = { .type_and_num = 0 }; |
| 559 | struct hashmap_entry *cur; |
| 560 | size_t bkt; |
| 561 | |
| 562 | if (!parse_hwmon_filename(filename: name, type: &type, number: &number, /*item=*/NULL, /*is_alarm=*/NULL)) |
| 563 | return false; |
| 564 | |
| 565 | if (hwmon_pmu__read_events(pmu: hwm)) |
| 566 | return false; |
| 567 | |
| 568 | key.type = type; |
| 569 | key.num = number; |
| 570 | if (hashmap_find(map: &hwm->events, key: key.type_and_num, /*value=*/NULL)) |
| 571 | return true; |
| 572 | if (key.num != -1) |
| 573 | return false; |
| 574 | /* Item is of form <type>_ which means we should match <type>_<label>. */ |
| 575 | hashmap__for_each_entry((&hwm->events), cur, bkt) { |
| 576 | struct hwmon_pmu_event_value *value = cur->pvalue; |
| 577 | |
| 578 | key.type_and_num = cur->key; |
| 579 | if (key.type == type && value->name && !strcasecmp(s1: name, s2: value->name)) |
| 580 | return true; |
| 581 | } |
| 582 | return false; |
| 583 | } |
| 584 | |
| 585 | static int hwmon_pmu__config_term(const struct hwmon_pmu *hwm, |
| 586 | struct perf_event_attr *attr, |
| 587 | struct parse_events_term *term, |
| 588 | struct parse_events_error *err) |
| 589 | { |
| 590 | if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) { |
| 591 | enum hwmon_type type; |
| 592 | int number; |
| 593 | |
| 594 | if (parse_hwmon_filename(filename: term->config, type: &type, number: &number, |
| 595 | /*item=*/NULL, /*is_alarm=*/NULL)) { |
| 596 | if (number == -1) { |
| 597 | /* |
| 598 | * Item is of form <type>_ which means we should |
| 599 | * match <type>_<label>. |
| 600 | */ |
| 601 | struct hashmap_entry *cur; |
| 602 | size_t bkt; |
| 603 | |
| 604 | attr->config = 0; |
| 605 | hashmap__for_each_entry((&hwm->events), cur, bkt) { |
| 606 | union hwmon_pmu_event_key key = { |
| 607 | .type_and_num = cur->key, |
| 608 | }; |
| 609 | struct hwmon_pmu_event_value *value = cur->pvalue; |
| 610 | |
| 611 | if (key.type == type && value->name && |
| 612 | !strcasecmp(s1: term->config, s2: value->name)) { |
| 613 | attr->config = key.type_and_num; |
| 614 | break; |
| 615 | } |
| 616 | } |
| 617 | if (attr->config == 0) |
| 618 | return -EINVAL; |
| 619 | } else { |
| 620 | union hwmon_pmu_event_key key = { |
| 621 | .type_and_num = 0, |
| 622 | }; |
| 623 | |
| 624 | key.type = type; |
| 625 | key.num = number; |
| 626 | attr->config = key.type_and_num; |
| 627 | } |
| 628 | return 0; |
| 629 | } |
| 630 | } |
| 631 | if (err) { |
| 632 | char *err_str; |
| 633 | |
| 634 | parse_events_error__handle(err, idx: term->err_val, |
| 635 | str: asprintf(&err_str, |
| 636 | "unexpected hwmon event term (%s) %s" , |
| 637 | parse_events__term_type_str(term_type: term->type_term), |
| 638 | term->config) < 0 |
| 639 | ? strdup("unexpected hwmon event term" ) |
| 640 | : err_str, |
| 641 | NULL); |
| 642 | } |
| 643 | return -EINVAL; |
| 644 | } |
| 645 | |
| 646 | int hwmon_pmu__config_terms(const struct perf_pmu *pmu, |
| 647 | struct perf_event_attr *attr, |
| 648 | struct parse_events_terms *terms, |
| 649 | struct parse_events_error *err) |
| 650 | { |
| 651 | struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); |
| 652 | struct parse_events_term *term; |
| 653 | int ret; |
| 654 | |
| 655 | ret = hwmon_pmu__read_events(pmu: hwm); |
| 656 | if (ret) |
| 657 | return ret; |
| 658 | |
| 659 | list_for_each_entry(term, &terms->terms, list) { |
| 660 | if (hwmon_pmu__config_term(hwm, attr, term, err)) |
| 661 | return -EINVAL; |
| 662 | } |
| 663 | |
| 664 | return 0; |
| 665 | |
| 666 | } |
| 667 | |
| 668 | int hwmon_pmu__check_alias(struct parse_events_terms *terms, struct perf_pmu_info *info, |
| 669 | struct parse_events_error *err) |
| 670 | { |
| 671 | struct parse_events_term *term = |
| 672 | list_first_entry(&terms->terms, struct parse_events_term, list); |
| 673 | |
| 674 | if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) { |
| 675 | enum hwmon_type type; |
| 676 | int number; |
| 677 | |
| 678 | if (parse_hwmon_filename(filename: term->config, type: &type, number: &number, |
| 679 | /*item=*/NULL, /*is_alarm=*/NULL)) { |
| 680 | info->unit = hwmon_units[type]; |
| 681 | if (type == HWMON_TYPE_FAN || type == HWMON_TYPE_PWM || |
| 682 | type == HWMON_TYPE_INTRUSION) |
| 683 | info->scale = 1; |
| 684 | else |
| 685 | info->scale = 0.001; |
| 686 | } |
| 687 | return 0; |
| 688 | } |
| 689 | if (err) { |
| 690 | char *err_str; |
| 691 | |
| 692 | parse_events_error__handle(err, idx: term->err_val, |
| 693 | str: asprintf(&err_str, |
| 694 | "unexpected hwmon event term (%s) %s" , |
| 695 | parse_events__term_type_str(term_type: term->type_term), |
| 696 | term->config) < 0 |
| 697 | ? strdup("unexpected hwmon event term" ) |
| 698 | : err_str, |
| 699 | NULL); |
| 700 | } |
| 701 | return -EINVAL; |
| 702 | } |
| 703 | |
| 704 | int perf_pmus__read_hwmon_pmus(struct list_head *pmus) |
| 705 | { |
| 706 | char *line = NULL; |
| 707 | struct io_dirent64 *class_hwmon_ent; |
| 708 | struct io_dir class_hwmon_dir; |
| 709 | char buf[PATH_MAX]; |
| 710 | const char *sysfs = sysfs__mountpoint(); |
| 711 | |
| 712 | if (!sysfs) |
| 713 | return 0; |
| 714 | |
| 715 | scnprintf(buf, size: sizeof(buf), fmt: "%s/class/hwmon/" , sysfs); |
| 716 | io_dir__init(&class_hwmon_dir, open(buf, O_CLOEXEC | O_DIRECTORY | O_RDONLY)); |
| 717 | |
| 718 | if (class_hwmon_dir.dirfd < 0) |
| 719 | return 0; |
| 720 | |
| 721 | while ((class_hwmon_ent = io_dir__readdir(&class_hwmon_dir)) != NULL) { |
| 722 | size_t line_len; |
| 723 | int hwmon_dir, name_fd; |
| 724 | struct io io; |
| 725 | char buf2[128]; |
| 726 | |
| 727 | if (class_hwmon_ent->d_type != DT_LNK) |
| 728 | continue; |
| 729 | |
| 730 | scnprintf(buf, size: sizeof(buf), fmt: "%s/class/hwmon/%s" , sysfs, class_hwmon_ent->d_name); |
| 731 | hwmon_dir = open(buf, O_DIRECTORY); |
| 732 | if (hwmon_dir == -1) { |
| 733 | pr_debug("hwmon_pmu: not a directory: '%s/class/hwmon/%s'\n" , |
| 734 | sysfs, class_hwmon_ent->d_name); |
| 735 | continue; |
| 736 | } |
| 737 | name_fd = openat(hwmon_dir, "name" , O_RDONLY); |
| 738 | if (name_fd == -1) { |
| 739 | pr_debug("hwmon_pmu: failure to open '%s/class/hwmon/%s/name'\n" , |
| 740 | sysfs, class_hwmon_ent->d_name); |
| 741 | close(hwmon_dir); |
| 742 | continue; |
| 743 | } |
| 744 | io__init(&io, name_fd, buf2, sizeof(buf2)); |
| 745 | if (io__getline(&io, &line, &line_len) > 0 && line[line_len - 1] == '\n') |
| 746 | line[line_len - 1] = '\0'; |
| 747 | hwmon_pmu__new(pmus, hwmon_dir: buf, sysfs_name: class_hwmon_ent->d_name, name: line); |
| 748 | close(name_fd); |
| 749 | close(hwmon_dir); |
| 750 | } |
| 751 | free(line); |
| 752 | close(class_hwmon_dir.dirfd); |
| 753 | return 0; |
| 754 | } |
| 755 | |
| 756 | #define FD(e, x, y) (*(int *)xyarray__entry(e->core.fd, x, y)) |
| 757 | |
| 758 | int evsel__hwmon_pmu_open(struct evsel *evsel, |
| 759 | struct perf_thread_map *threads, |
| 760 | int start_cpu_map_idx, int end_cpu_map_idx) |
| 761 | { |
| 762 | struct hwmon_pmu *hwm = container_of(evsel->pmu, struct hwmon_pmu, pmu); |
| 763 | union hwmon_pmu_event_key key = { |
| 764 | .type_and_num = evsel->core.attr.config, |
| 765 | }; |
| 766 | int idx = 0, thread = 0, nthreads, err = 0; |
| 767 | int dir = open(hwm->hwmon_dir, O_CLOEXEC | O_DIRECTORY | O_RDONLY); |
| 768 | |
| 769 | if (dir < 0) |
| 770 | return -errno; |
| 771 | |
| 772 | nthreads = perf_thread_map__nr(threads); |
| 773 | for (idx = start_cpu_map_idx; idx < end_cpu_map_idx; idx++) { |
| 774 | for (thread = 0; thread < nthreads; thread++) { |
| 775 | char buf[64]; |
| 776 | int fd; |
| 777 | |
| 778 | snprintf(buf, size: sizeof(buf), fmt: "%s%d_input" , |
| 779 | hwmon_type_strs[key.type], key.num); |
| 780 | |
| 781 | fd = openat(dir, buf, O_RDONLY); |
| 782 | FD(evsel, idx, thread) = fd; |
| 783 | if (fd < 0) { |
| 784 | err = -errno; |
| 785 | goto out_close; |
| 786 | } |
| 787 | } |
| 788 | } |
| 789 | close(dir); |
| 790 | return 0; |
| 791 | out_close: |
| 792 | if (err) |
| 793 | threads->err_thread = thread; |
| 794 | |
| 795 | do { |
| 796 | while (--thread >= 0) { |
| 797 | if (FD(evsel, idx, thread) >= 0) |
| 798 | close(FD(evsel, idx, thread)); |
| 799 | FD(evsel, idx, thread) = -1; |
| 800 | } |
| 801 | thread = nthreads; |
| 802 | } while (--idx >= 0); |
| 803 | close(dir); |
| 804 | return err; |
| 805 | } |
| 806 | |
| 807 | int evsel__hwmon_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread) |
| 808 | { |
| 809 | char buf[32]; |
| 810 | int fd; |
| 811 | ssize_t len; |
| 812 | struct perf_counts_values *count, *old_count = NULL; |
| 813 | |
| 814 | if (evsel->prev_raw_counts) |
| 815 | old_count = perf_counts(counts: evsel->prev_raw_counts, cpu_map_idx, thread); |
| 816 | |
| 817 | count = perf_counts(counts: evsel->counts, cpu_map_idx, thread); |
| 818 | fd = FD(evsel, cpu_map_idx, thread); |
| 819 | len = pread(fd, buf, sizeof(buf), 0); |
| 820 | if (len <= 0) { |
| 821 | count->lost++; |
| 822 | return -EINVAL; |
| 823 | } |
| 824 | buf[len] = '\0'; |
| 825 | if (old_count) { |
| 826 | count->val = old_count->val + strtoll(buf, NULL, 10); |
| 827 | count->run = old_count->run + 1; |
| 828 | count->ena = old_count->ena + 1; |
| 829 | } else { |
| 830 | count->val = strtoll(buf, NULL, 10); |
| 831 | count->run++; |
| 832 | count->ena++; |
| 833 | } |
| 834 | return 0; |
| 835 | } |
| 836 | |