| // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) | |
| #include "counts.h" | |
| #include "debug.h" | |
| #include "evsel.h" | |
| #include "hashmap.h" | |
| #include "hwmon_pmu.h" | |
| #include "pmu.h" | |
| #include <internal/xyarray.h> | |
| #include <internal/threadmap.h> | |
| #include <perf/threadmap.h> | |
| #include <sys/types.h> | |
| #include <assert.h> | |
| #include <ctype.h> | |
| #include <fcntl.h> | |
| #include <stddef.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <api/fs/fs.h> | |
| #include <api/io.h> | |
| #include <api/io_dir.h> | |
| #include <linux/kernel.h> | |
| #include <linux/string.h> | |
| #include <linux/zalloc.h> | |
| /** Strings that correspond to enum hwmon_type. */ | |
| static const char * const hwmon_type_strs[HWMON_TYPE_MAX] = { | |
| NULL, | |
| "cpu", | |
| "curr", | |
| "energy", | |
| "fan", | |
| "humidity", | |
| "in", | |
| "intrusion", | |
| "power", | |
| "pwm", | |
| "temp", | |
| }; | |
| #define LONGEST_HWMON_TYPE_STR "intrusion" | |
| /** Strings that correspond to enum hwmon_item. */ | |
| static const char * const hwmon_item_strs[HWMON_ITEM__MAX] = { | |
| NULL, | |
| "accuracy", | |
| "alarm", | |
| "auto_channels_temp", | |
| "average", | |
| "average_highest", | |
| "average_interval", | |
| "average_interval_max", | |
| "average_interval_min", | |
| "average_lowest", | |
| "average_max", | |
| "average_min", | |
| "beep", | |
| "cap", | |
| "cap_hyst", | |
| "cap_max", | |
| "cap_min", | |
| "crit", | |
| "crit_hyst", | |
| "div", | |
| "emergency", | |
| "emergency_hist", | |
| "enable", | |
| "fault", | |
| "freq", | |
| "highest", | |
| "input", | |
| "label", | |
| "lcrit", | |
| "lcrit_hyst", | |
| "lowest", | |
| "max", | |
| "max_hyst", | |
| "min", | |
| "min_hyst", | |
| "mod", | |
| "offset", | |
| "pulses", | |
| "rated_max", | |
| "rated_min", | |
| "reset_history", | |
| "target", | |
| "type", | |
| "vid", | |
| }; | |
| #define LONGEST_HWMON_ITEM_STR "average_interval_max" | |
| static const char *const hwmon_units[HWMON_TYPE_MAX] = { | |
| NULL, | |
| "V", /* cpu */ | |
| "A", /* curr */ | |
| "J", /* energy */ | |
| "rpm", /* fan */ | |
| "%", /* humidity */ | |
| "V", /* in */ | |
| "", /* intrusion */ | |
| "W", /* power */ | |
| "Hz", /* pwm */ | |
| "'C", /* temp */ | |
| }; | |
| struct hwmon_pmu { | |
| struct perf_pmu pmu; | |
| struct hashmap events; | |
| char *hwmon_dir; | |
| }; | |
| /** | |
| * struct hwmon_pmu_event_value: Value in hwmon_pmu->events. | |
| * | |
| * Hwmon files are of the form <type><number>_<item> and may have a suffix | |
| * _alarm. | |
| */ | |
| struct hwmon_pmu_event_value { | |
| /** @items: which item files are present. */ | |
| DECLARE_BITMAP(items, HWMON_ITEM__MAX); | |
| /** @alarm_items: which item files are present. */ | |
| DECLARE_BITMAP(alarm_items, HWMON_ITEM__MAX); | |
| /** @label: contents of <type><number>_label if present. */ | |
| char *label; | |
| /** @name: name computed from label of the form <type>_<label>. */ | |
| char *name; | |
| }; | |
| bool perf_pmu__is_hwmon(const struct perf_pmu *pmu) | |
| { | |
| return pmu && pmu->type >= PERF_PMU_TYPE_HWMON_START && | |
| pmu->type <= PERF_PMU_TYPE_HWMON_END; | |
| } | |
| bool evsel__is_hwmon(const struct evsel *evsel) | |
| { | |
| return perf_pmu__is_hwmon(evsel->pmu); | |
| } | |
| static size_t hwmon_pmu__event_hashmap_hash(long key, void *ctx __maybe_unused) | |
| { | |
| return ((union hwmon_pmu_event_key)key).type_and_num; | |
| } | |
| static bool hwmon_pmu__event_hashmap_equal(long key1, long key2, void *ctx __maybe_unused) | |
| { | |
| return ((union hwmon_pmu_event_key)key1).type_and_num == | |
| ((union hwmon_pmu_event_key)key2).type_and_num; | |
| } | |
| static int hwmon_strcmp(const void *a, const void *b) | |
| { | |
| const char *sa = a; | |
| const char * const *sb = b; | |
| return strcmp(sa, *sb); | |
| } | |
| bool parse_hwmon_filename(const char *filename, | |
| enum hwmon_type *type, | |
| int *number, | |
| enum hwmon_item *item, | |
| bool *alarm) | |
| { | |
| char fn_type[24]; | |
| const char * const *elem; | |
| const char *fn_item = NULL; | |
| size_t fn_item_len; | |
| assert(strlen(LONGEST_HWMON_TYPE_STR) < sizeof(fn_type)); | |
| strlcpy(fn_type, filename, sizeof(fn_type)); | |
| for (size_t i = 0; fn_type[i] != '\0'; i++) { | |
| if (fn_type[i] >= '0' && fn_type[i] <= '9') { | |
| fn_type[i] = '\0'; | |
| *number = strtoul(&filename[i], (char **)&fn_item, 10); | |
| if (*fn_item == '_') | |
| fn_item++; | |
| break; | |
| } | |
| if (fn_type[i] == '_') { | |
| fn_type[i] = '\0'; | |
| *number = -1; | |
| fn_item = &filename[i + 1]; | |
| break; | |
| } | |
| } | |
| if (fn_item == NULL || fn_type[0] == '\0' || (item != NULL && fn_item[0] == '\0')) { | |
| pr_debug3("hwmon_pmu: not a hwmon file '%s'\n", filename); | |
| return false; | |
| } | |
| elem = bsearch(&fn_type, hwmon_type_strs + 1, ARRAY_SIZE(hwmon_type_strs) - 1, | |
| sizeof(hwmon_type_strs[0]), hwmon_strcmp); | |
| if (!elem) { | |
| pr_debug3("hwmon_pmu: not a hwmon type '%s' in file name '%s'\n", | |
| fn_type, filename); | |
| return false; | |
| } | |
| *type = elem - &hwmon_type_strs[0]; | |
| if (!item) | |
| return true; | |
| *alarm = false; | |
| fn_item_len = strlen(fn_item); | |
| if (fn_item_len > 6 && !strcmp(&fn_item[fn_item_len - 6], "_alarm")) { | |
| assert(strlen(LONGEST_HWMON_ITEM_STR) < sizeof(fn_type)); | |
| /* fn_item_len - 5 strips "_alarm"; clamp to buffer size */ | |
| strlcpy(fn_type, fn_item, min_t(size_t, fn_item_len - 5, sizeof(fn_type))); | |
| fn_item = fn_type; | |
| *alarm = true; | |
| } | |
| elem = bsearch(fn_item, hwmon_item_strs + 1, ARRAY_SIZE(hwmon_item_strs) - 1, | |
| sizeof(hwmon_item_strs[0]), hwmon_strcmp); | |
| if (!elem) { | |
| pr_debug3("hwmon_pmu: not a hwmon item '%s' in file name '%s'\n", | |
| fn_item, filename); | |
| return false; | |
| } | |
| *item = elem - &hwmon_item_strs[0]; | |
| return true; | |
| } | |
| static void fix_name(char *p) | |
| { | |
| char *s = strchr(p, '\n'); | |
| if (s) | |
| *s = '\0'; | |
| while (*p != '\0') { | |
| if (strchr(" :,/\n\t", *p)) | |
| *p = '_'; | |
| else | |
| *p = tolower(*p); | |
| p++; | |
| } | |
| } | |
| static int hwmon_pmu__read_events(struct hwmon_pmu *pmu) | |
| { | |
| int err = 0; | |
| struct hashmap_entry *cur, *tmp; | |
| size_t bkt; | |
| struct io_dirent64 *ent; | |
| struct io_dir dir; | |
| if (pmu->pmu.sysfs_aliases_loaded) | |
| return 0; | |
| /* Use openat so that the directory contents are refreshed. */ | |
| io_dir__init(&dir, open(pmu->hwmon_dir, O_CLOEXEC | O_DIRECTORY | O_RDONLY)); | |
| if (dir.dirfd < 0) | |
| return -ENOENT; | |
| while ((ent = io_dir__readdir(&dir)) != NULL) { | |
| enum hwmon_type type; | |
| int number; | |
| enum hwmon_item item; | |
| bool alarm; | |
| union hwmon_pmu_event_key key = { .type_and_num = 0 }; | |
| struct hwmon_pmu_event_value *value; | |
| if (ent->d_type != DT_REG) | |
| continue; | |
| if (!parse_hwmon_filename(ent->d_name, &type, &number, &item, &alarm)) { | |
| pr_debug3("Not a hwmon file '%s'\n", ent->d_name); | |
| continue; | |
| } | |
| key.num = number; | |
| key.type = type; | |
| if (!hashmap__find(&pmu->events, key.type_and_num, &value)) { | |
| value = zalloc(sizeof(*value)); | |
| if (!value) { | |
| err = -ENOMEM; | |
| goto err_out; | |
| } | |
| err = hashmap__add(&pmu->events, key.type_and_num, value); | |
| if (err) { | |
| free(value); | |
| err = -ENOMEM; | |
| goto err_out; | |
| } | |
| } | |
| __set_bit(item, alarm ? value->alarm_items : value->items); | |
| if (item == HWMON_ITEM_LABEL) { | |
| char buf[128]; | |
| int fd = openat(dir.dirfd, ent->d_name, O_RDONLY); | |
| ssize_t read_len; | |
| if (fd < 0) | |
| continue; | |
| read_len = read(fd, buf, sizeof(buf) - 1); | |
| while (read_len > 0 && buf[read_len - 1] == '\n') | |
| read_len--; | |
| if (read_len <= 0) { | |
| close(fd); | |
| continue; | |
| } | |
| buf[read_len] = '\0'; | |
| if (buf[0] == '\0') { | |
| pr_debug("hwmon_pmu: empty label file %s %s\n", | |
| pmu->pmu.name, ent->d_name); | |
| close(fd); | |
| continue; | |
| } | |
| value->label = strdup(buf); | |
| if (!value->label) { | |
| pr_debug("hwmon_pmu: memory allocation failure\n"); | |
| close(fd); | |
| continue; | |
| } | |
| snprintf(buf, sizeof(buf), "%s_%s", hwmon_type_strs[type], value->label); | |
| fix_name(buf); | |
| value->name = strdup(buf); | |
| if (!value->name) | |
| pr_debug("hwmon_pmu: memory allocation failure\n"); | |
| close(fd); | |
| } | |
| } | |
| if (hashmap__size(&pmu->events) == 0) | |
| pr_debug2("hwmon_pmu: %s has no events\n", pmu->pmu.name); | |
| hashmap__for_each_entry_safe((&pmu->events), cur, tmp, bkt) { | |
| union hwmon_pmu_event_key key = { | |
| .type_and_num = cur->key, | |
| }; | |
| struct hwmon_pmu_event_value *value = cur->pvalue; | |
| if (!test_bit(HWMON_ITEM_INPUT, value->items)) { | |
| pr_debug("hwmon_pmu: %s removing event '%s%d' that has no input file\n", | |
| pmu->pmu.name, hwmon_type_strs[key.type], key.num); | |
| hashmap__delete(&pmu->events, key.type_and_num, &key, &value); | |
| zfree(&value->label); | |
| zfree(&value->name); | |
| free(value); | |
| } | |
| } | |
| pmu->pmu.sysfs_aliases_loaded = true; | |
| err_out: | |
| close(dir.dirfd); | |
| return err; | |
| } | |
| struct perf_pmu *hwmon_pmu__new(struct list_head *pmus, const char *hwmon_dir, | |
| const char *sysfs_name, const char *name) | |
| { | |
| char buf[64]; | |
| struct hwmon_pmu *hwm; | |
| __u32 type = PERF_PMU_TYPE_HWMON_START + strtoul(sysfs_name + 5, NULL, 10); | |
| if (type > PERF_PMU_TYPE_HWMON_END) { | |
| pr_err("Unable to encode hwmon type from %s in valid PMU type\n", sysfs_name); | |
| return NULL; | |
| } | |
| snprintf(buf, sizeof(buf), "hwmon_%s", name); | |
| fix_name(buf + 6); | |
| hwm = zalloc(sizeof(*hwm)); | |
| if (!hwm) | |
| return NULL; | |
| if (perf_pmu__init(&hwm->pmu, type, buf) != 0) { | |
| perf_pmu__delete(&hwm->pmu); | |
| return NULL; | |
| } | |
| hwm->hwmon_dir = strdup(hwmon_dir); | |
| if (!hwm->hwmon_dir) { | |
| perf_pmu__delete(&hwm->pmu); | |
| return NULL; | |
| } | |
| hwm->pmu.alias_name = strdup(sysfs_name); | |
| if (!hwm->pmu.alias_name) { | |
| perf_pmu__delete(&hwm->pmu); | |
| return NULL; | |
| } | |
| hwm->pmu.cpus = perf_cpu_map__new_int(0); | |
| if (!hwm->pmu.cpus) { | |
| perf_pmu__delete(&hwm->pmu); | |
| return NULL; | |
| } | |
| INIT_LIST_HEAD(&hwm->pmu.format); | |
| INIT_LIST_HEAD(&hwm->pmu.caps); | |
| hashmap__init(&hwm->events, hwmon_pmu__event_hashmap_hash, | |
| hwmon_pmu__event_hashmap_equal, /*ctx=*/NULL); | |
| list_add_tail(&hwm->pmu.list, pmus); | |
| return &hwm->pmu; | |
| } | |
| void hwmon_pmu__exit(struct perf_pmu *pmu) | |
| { | |
| struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); | |
| struct hashmap_entry *cur, *tmp; | |
| size_t bkt; | |
| hashmap__for_each_entry_safe((&hwm->events), cur, tmp, bkt) { | |
| struct hwmon_pmu_event_value *value = cur->pvalue; | |
| zfree(&value->label); | |
| zfree(&value->name); | |
| free(value); | |
| } | |
| hashmap__clear(&hwm->events); | |
| zfree(&hwm->hwmon_dir); | |
| } | |
| static size_t hwmon_pmu__describe_items(struct hwmon_pmu *hwm, char *out_buf, size_t out_buf_len, | |
| union hwmon_pmu_event_key key, | |
| const unsigned long *items, bool is_alarm) | |
| { | |
| size_t bit; | |
| char buf[64]; | |
| size_t len = 0; | |
| int dir = open(hwm->hwmon_dir, O_CLOEXEC | O_DIRECTORY | O_RDONLY); | |
| if (dir < 0) | |
| return 0; | |
| for_each_set_bit(bit, items, HWMON_ITEM__MAX) { | |
| int fd; | |
| if (bit == HWMON_ITEM_LABEL || bit == HWMON_ITEM_INPUT) | |
| continue; | |
| snprintf(buf, sizeof(buf), "%s%d_%s%s", | |
| hwmon_type_strs[key.type], | |
| key.num, | |
| hwmon_item_strs[bit], | |
| is_alarm ? "_alarm" : ""); | |
| fd = openat(dir, buf, O_RDONLY); | |
| if (fd >= 0) { | |
| ssize_t read_len = read(fd, buf, sizeof(buf) - 1); | |
| while (read_len > 0 && buf[read_len - 1] == '\n') | |
| read_len--; | |
| if (read_len > 0) { | |
| long long val; | |
| buf[read_len] = '\0'; | |
| val = strtoll(buf, /*endptr=*/NULL, 10); | |
| len += scnprintf(out_buf + len, out_buf_len - len, "%s%s%s=%g%s", | |
| len == 0 ? " " : ", ", | |
| hwmon_item_strs[bit], | |
| is_alarm ? "_alarm" : "", | |
| (double)val / 1000.0, | |
| hwmon_units[key.type]); | |
| } | |
| close(fd); | |
| } | |
| } | |
| close(dir); | |
| return len; | |
| } | |
| int hwmon_pmu__for_each_event(struct perf_pmu *pmu, void *state, pmu_event_callback cb) | |
| { | |
| struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); | |
| struct hashmap_entry *cur; | |
| size_t bkt; | |
| if (hwmon_pmu__read_events(hwm)) | |
| return false; | |
| hashmap__for_each_entry((&hwm->events), cur, bkt) { | |
| static const char *const hwmon_scale_units[HWMON_TYPE_MAX] = { | |
| NULL, | |
| "0.001V", /* cpu */ | |
| "0.001A", /* curr */ | |
| "0.001J", /* energy */ | |
| "1rpm", /* fan */ | |
| "0.001%", /* humidity */ | |
| "0.001V", /* in */ | |
| NULL, /* intrusion */ | |
| "0.001W", /* power */ | |
| "1Hz", /* pwm */ | |
| "0.001'C", /* temp */ | |
| }; | |
| static const char *const hwmon_desc[HWMON_TYPE_MAX] = { | |
| NULL, | |
| "CPU core reference voltage", /* cpu */ | |
| "Current", /* curr */ | |
| "Cumulative energy use", /* energy */ | |
| "Fan", /* fan */ | |
| "Humidity", /* humidity */ | |
| "Voltage", /* in */ | |
| "Chassis intrusion detection", /* intrusion */ | |
| "Power use", /* power */ | |
| "Pulse width modulation fan control", /* pwm */ | |
| "Temperature", /* temp */ | |
| }; | |
| char alias_buf[64]; | |
| char desc_buf[256]; | |
| char encoding_buf[128]; | |
| union hwmon_pmu_event_key key = { | |
| .type_and_num = cur->key, | |
| }; | |
| struct hwmon_pmu_event_value *value = cur->pvalue; | |
| struct pmu_event_info info = { | |
| .pmu = pmu, | |
| .name = value->name, | |
| .alias = alias_buf, | |
| .scale_unit = hwmon_scale_units[key.type], | |
| .desc = desc_buf, | |
| .long_desc = NULL, | |
| .encoding_desc = encoding_buf, | |
| .topic = "hwmon", | |
| .pmu_name = pmu->name, | |
| .event_type_desc = "Hwmon event", | |
| }; | |
| int ret; | |
| size_t len; | |
| scnprintf(alias_buf, sizeof(alias_buf), "%s%d", | |
| hwmon_type_strs[key.type], key.num); | |
| if (!info.name) { | |
| info.name = info.alias; | |
| info.alias = NULL; | |
| } | |
| len = scnprintf(desc_buf, sizeof(desc_buf), "%s in unit %s named %s.", | |
| hwmon_desc[key.type], | |
| pmu->name + 6, | |
| value->label ?: info.name); | |
| len += hwmon_pmu__describe_items(hwm, desc_buf + len, sizeof(desc_buf) - len, | |
| key, value->items, /*is_alarm=*/false); | |
| len += hwmon_pmu__describe_items(hwm, desc_buf + len, sizeof(desc_buf) - len, | |
| key, value->alarm_items, /*is_alarm=*/true); | |
| snprintf(encoding_buf, sizeof(encoding_buf), "%s/config=0x%lx/", | |
| pmu->name, cur->key); | |
| ret = cb(state, &info); | |
| if (ret) | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| size_t hwmon_pmu__num_events(struct perf_pmu *pmu) | |
| { | |
| struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); | |
| hwmon_pmu__read_events(hwm); | |
| return hashmap__size(&hwm->events); | |
| } | |
| bool hwmon_pmu__have_event(struct perf_pmu *pmu, const char *name) | |
| { | |
| struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); | |
| enum hwmon_type type; | |
| int number; | |
| union hwmon_pmu_event_key key = { .type_and_num = 0 }; | |
| struct hashmap_entry *cur; | |
| size_t bkt; | |
| if (!parse_hwmon_filename(name, &type, &number, /*item=*/NULL, /*is_alarm=*/NULL)) | |
| return false; | |
| if (hwmon_pmu__read_events(hwm)) | |
| return false; | |
| key.type = type; | |
| key.num = number; | |
| if (hashmap_find(&hwm->events, key.type_and_num, /*value=*/NULL)) | |
| return true; | |
| if (key.num != -1) | |
| return false; | |
| /* Item is of form <type>_ which means we should match <type>_<label>. */ | |
| hashmap__for_each_entry((&hwm->events), cur, bkt) { | |
| struct hwmon_pmu_event_value *value = cur->pvalue; | |
| key.type_and_num = cur->key; | |
| if (key.type == type && value->name && !strcasecmp(name, value->name)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static int hwmon_pmu__config_term(const struct hwmon_pmu *hwm, | |
| struct perf_event_attr *attr, | |
| struct parse_events_term *term, | |
| struct parse_events_error *err) | |
| { | |
| if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) { | |
| enum hwmon_type type; | |
| int number; | |
| if (parse_hwmon_filename(term->config, &type, &number, | |
| /*item=*/NULL, /*is_alarm=*/NULL)) { | |
| if (number == -1) { | |
| /* | |
| * Item is of form <type>_ which means we should | |
| * match <type>_<label>. | |
| */ | |
| struct hashmap_entry *cur; | |
| size_t bkt; | |
| attr->config = 0; | |
| hashmap__for_each_entry((&hwm->events), cur, bkt) { | |
| union hwmon_pmu_event_key key = { | |
| .type_and_num = cur->key, | |
| }; | |
| struct hwmon_pmu_event_value *value = cur->pvalue; | |
| if (key.type == type && value->name && | |
| !strcasecmp(term->config, value->name)) { | |
| attr->config = key.type_and_num; | |
| break; | |
| } | |
| } | |
| if (attr->config == 0) | |
| return -EINVAL; | |
| } else { | |
| union hwmon_pmu_event_key key = { | |
| .type_and_num = 0, | |
| }; | |
| key.type = type; | |
| key.num = number; | |
| attr->config = key.type_and_num; | |
| } | |
| return 0; | |
| } | |
| } | |
| if (err) { | |
| char *err_str; | |
| parse_events_error__handle(err, term->err_val, | |
| asprintf(&err_str, | |
| "unexpected hwmon event term (%s) %s", | |
| parse_events__term_type_str(term->type_term), | |
| term->config) < 0 | |
| ? strdup("unexpected hwmon event term") | |
| : err_str, | |
| NULL); | |
| } | |
| return -EINVAL; | |
| } | |
| int hwmon_pmu__config_terms(const struct perf_pmu *pmu, | |
| struct perf_event_attr *attr, | |
| struct parse_events_terms *terms, | |
| struct parse_events_error *err) | |
| { | |
| struct hwmon_pmu *hwm = container_of(pmu, struct hwmon_pmu, pmu); | |
| struct parse_events_term *term; | |
| int ret; | |
| ret = hwmon_pmu__read_events(hwm); | |
| if (ret) | |
| return ret; | |
| list_for_each_entry(term, &terms->terms, list) { | |
| if (hwmon_pmu__config_term(hwm, attr, term, err)) | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| int hwmon_pmu__check_alias(struct parse_events_terms *terms, struct perf_pmu_info *info, | |
| struct parse_events_error *err) | |
| { | |
| struct parse_events_term *term = | |
| list_first_entry(&terms->terms, struct parse_events_term, list); | |
| if (term->type_term == PARSE_EVENTS__TERM_TYPE_USER) { | |
| enum hwmon_type type; | |
| int number; | |
| if (parse_hwmon_filename(term->config, &type, &number, | |
| /*item=*/NULL, /*is_alarm=*/NULL)) { | |
| info->unit = hwmon_units[type]; | |
| if (type == HWMON_TYPE_FAN || type == HWMON_TYPE_PWM || | |
| type == HWMON_TYPE_INTRUSION) | |
| info->scale = 1; | |
| else | |
| info->scale = 0.001; | |
| } | |
| return 0; | |
| } | |
| if (err) { | |
| char *err_str; | |
| parse_events_error__handle(err, term->err_val, | |
| asprintf(&err_str, | |
| "unexpected hwmon event term (%s) %s", | |
| parse_events__term_type_str(term->type_term), | |
| term->config) < 0 | |
| ? strdup("unexpected hwmon event term") | |
| : err_str, | |
| NULL); | |
| } | |
| return -EINVAL; | |
| } | |
| int perf_pmus__read_hwmon_pmus(struct list_head *pmus) | |
| { | |
| char *line = NULL; | |
| struct io_dirent64 *class_hwmon_ent; | |
| struct io_dir class_hwmon_dir; | |
| char buf[PATH_MAX]; | |
| const char *sysfs = sysfs__mountpoint(); | |
| if (!sysfs) | |
| return 0; | |
| scnprintf(buf, sizeof(buf), "%s/class/hwmon/", sysfs); | |
| io_dir__init(&class_hwmon_dir, open(buf, O_CLOEXEC | O_DIRECTORY | O_RDONLY)); | |
| if (class_hwmon_dir.dirfd < 0) | |
| return 0; | |
| while ((class_hwmon_ent = io_dir__readdir(&class_hwmon_dir)) != NULL) { | |
| size_t line_len; | |
| int hwmon_dir, name_fd; | |
| struct io io; | |
| char buf2[128]; | |
| if (class_hwmon_ent->d_type != DT_LNK) | |
| continue; | |
| scnprintf(buf, sizeof(buf), "%s/class/hwmon/%s", sysfs, class_hwmon_ent->d_name); | |
| hwmon_dir = open(buf, O_DIRECTORY); | |
| if (hwmon_dir == -1) { | |
| pr_debug("hwmon_pmu: not a directory: '%s/class/hwmon/%s'\n", | |
| sysfs, class_hwmon_ent->d_name); | |
| continue; | |
| } | |
| name_fd = openat(hwmon_dir, "name", O_RDONLY); | |
| if (name_fd == -1) { | |
| pr_debug("hwmon_pmu: failure to open '%s/class/hwmon/%s/name'\n", | |
| sysfs, class_hwmon_ent->d_name); | |
| close(hwmon_dir); | |
| continue; | |
| } | |
| io__init(&io, name_fd, buf2, sizeof(buf2)); | |
| if (io__getline(&io, &line, &line_len) > 0 && line[line_len - 1] == '\n') | |
| line[line_len - 1] = '\0'; | |
| hwmon_pmu__new(pmus, buf, class_hwmon_ent->d_name, line); | |
| close(name_fd); | |
| close(hwmon_dir); | |
| } | |
| free(line); | |
| close(class_hwmon_dir.dirfd); | |
| return 0; | |
| } | |
| #define FD(e, x, y) (*(int *)xyarray__entry(e->core.fd, x, y)) | |
| int evsel__hwmon_pmu_open(struct evsel *evsel, | |
| struct perf_thread_map *threads, | |
| int start_cpu_map_idx, int end_cpu_map_idx) | |
| { | |
| struct hwmon_pmu *hwm = container_of(evsel->pmu, struct hwmon_pmu, pmu); | |
| union hwmon_pmu_event_key key = { | |
| .type_and_num = evsel->core.attr.config, | |
| }; | |
| int idx = 0, thread = 0, nthreads, err = 0; | |
| int dir = open(hwm->hwmon_dir, O_CLOEXEC | O_DIRECTORY | O_RDONLY); | |
| if (dir < 0) | |
| return -errno; | |
| nthreads = perf_thread_map__nr(threads); | |
| for (idx = start_cpu_map_idx; idx < end_cpu_map_idx; idx++) { | |
| for (thread = 0; thread < nthreads; thread++) { | |
| char buf[64]; | |
| int fd; | |
| snprintf(buf, sizeof(buf), "%s%d_input", | |
| hwmon_type_strs[key.type], key.num); | |
| fd = openat(dir, buf, O_RDONLY); | |
| FD(evsel, idx, thread) = fd; | |
| if (fd < 0) { | |
| err = -errno; | |
| goto out_close; | |
| } | |
| } | |
| } | |
| close(dir); | |
| return 0; | |
| out_close: | |
| if (err) | |
| threads->err_thread = thread; | |
| do { | |
| while (--thread >= 0) { | |
| if (FD(evsel, idx, thread) >= 0) | |
| close(FD(evsel, idx, thread)); | |
| FD(evsel, idx, thread) = -1; | |
| } | |
| thread = nthreads; | |
| } while (--idx >= 0); | |
| close(dir); | |
| return err; | |
| } | |
| int evsel__hwmon_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread) | |
| { | |
| char buf[32]; | |
| int fd; | |
| ssize_t len; | |
| struct perf_counts_values *count, *old_count = NULL; | |
| if (evsel->prev_raw_counts) | |
| old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread); | |
| count = perf_counts(evsel->counts, cpu_map_idx, thread); | |
| fd = FD(evsel, cpu_map_idx, thread); | |
| len = pread(fd, buf, sizeof(buf) - 1, 0); | |
| if (len <= 0) { | |
| count->lost++; | |
| return -EINVAL; | |
| } | |
| buf[len] = '\0'; | |
| if (old_count) { | |
| count->val = old_count->val + strtoll(buf, NULL, 10); | |
| count->run = old_count->run + 1; | |
| count->ena = old_count->ena + 1; | |
| } else { | |
| count->val = strtoll(buf, NULL, 10); | |
| count->run++; | |
| count->ena++; | |
| } | |
| return 0; | |
| } |