1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 #include "hwmon_pmu.h" 3 4 #include <errno.h> 5 #include <inttypes.h> 6 7 #include <fcntl.h> 8 #include <linux/compiler.h> 9 #include <linux/kernel.h> 10 #include <linux/string.h> 11 #include <sys/stat.h> 12 13 #include "debug.h" 14 #include "evlist.h" 15 #include "parse-events.h" 16 #include "pmus.h" 17 #include "tests.h" 18 19 static const struct test_event { 20 const char *name; 21 const char *alias; 22 union hwmon_pmu_event_key key; 23 } test_events[] = { 24 { 25 "temp_test_hwmon_event1", 26 "temp1", 27 .key = { 28 .num = 1, 29 .type = 10 30 }, 31 }, 32 { 33 "temp_test_hwmon_event2", 34 "temp2", 35 .key = { 36 .num = 2, 37 .type = 10 38 }, 39 }, 40 }; 41 42 /* Cleanup test PMU directory. */ 43 static int test_pmu_put(const char *dir, struct perf_pmu *hwm) 44 { 45 char buf[PATH_MAX + 20]; 46 int ret; 47 48 if (scnprintf(buf, sizeof(buf), "rm -fr %s", dir) < 0) { 49 pr_err("Failure to set up buffer for \"%s\"\n", dir); 50 return -EINVAL; 51 } 52 ret = system(buf); 53 if (ret) 54 pr_err("Failure to \"%s\"\n", buf); 55 56 list_del(&hwm->list); 57 perf_pmu__delete(hwm); 58 return ret; 59 } 60 61 /* 62 * Prepare test PMU directory data, normally exported by kernel at 63 * /sys/class/hwmon/hwmon<number>/. Give as input a buffer to hold the file 64 * path, the result is PMU loaded using that directory. 65 */ 66 static struct perf_pmu *test_pmu_get(char *dir, size_t sz) 67 { 68 const char *test_hwmon_name_nl = "A test hwmon PMU\n"; 69 const char *test_hwmon_name = "A test hwmon PMU"; 70 /* Simulated hwmon items. */ 71 const struct test_item { 72 const char *name; 73 const char *value; 74 } test_items[] = { 75 { "temp1_label", "test hwmon event1\n", }, 76 { "temp1_input", "40000\n", }, 77 { "temp2_label", "test hwmon event2\n", }, 78 { "temp2_input", "50000\n", }, 79 }; 80 int hwmon_dirfd = -1, test_dirfd = -1, file; 81 struct perf_pmu *hwm = NULL; 82 ssize_t len; 83 84 /* Create equivalent of sysfs mount point. */ 85 scnprintf(dir, sz, "/tmp/perf-hwmon-pmu-test-XXXXXX"); 86 if (!mkdtemp(dir)) { 87 pr_err("mkdtemp failed\n"); 88 dir[0] = '\0'; 89 return NULL; 90 } 91 test_dirfd = open(dir, O_PATH|O_DIRECTORY); 92 if (test_dirfd < 0) { 93 pr_err("Failed to open test directory \"%s\"\n", dir); 94 goto err_out; 95 } 96 97 /* Create the test hwmon directory and give it a name. */ 98 if (mkdirat(test_dirfd, "hwmon1234", 0755) < 0) { 99 pr_err("Failed to mkdir hwmon directory\n"); 100 goto err_out; 101 } 102 strncat(dir, "/hwmon1234", sz - strlen(dir)); 103 hwmon_dirfd = open(dir, O_PATH|O_DIRECTORY); 104 if (hwmon_dirfd < 0) { 105 pr_err("Failed to open test hwmon directory \"%s\"\n", dir); 106 goto err_out; 107 } 108 file = openat(hwmon_dirfd, "name", O_WRONLY | O_CREAT, 0600); 109 if (file < 0) { 110 pr_err("Failed to open for writing file \"name\"\n"); 111 goto err_out; 112 } 113 len = strlen(test_hwmon_name_nl); 114 if (write(file, test_hwmon_name_nl, len) < len) { 115 close(file); 116 pr_err("Failed to write to 'name' file\n"); 117 goto err_out; 118 } 119 close(file); 120 121 /* Create test hwmon files. */ 122 for (size_t i = 0; i < ARRAY_SIZE(test_items); i++) { 123 const struct test_item *item = &test_items[i]; 124 125 file = openat(hwmon_dirfd, item->name, O_WRONLY | O_CREAT, 0600); 126 if (file < 0) { 127 pr_err("Failed to open for writing file \"%s\"\n", item->name); 128 goto err_out; 129 } 130 131 if (write(file, item->value, strlen(item->value)) < 0) { 132 pr_err("Failed to write to file \"%s\"\n", item->name); 133 close(file); 134 goto err_out; 135 } 136 close(file); 137 } 138 139 /* Make the PMU reading the files created above. */ 140 hwm = perf_pmus__add_test_hwmon_pmu(dir, "hwmon1234", test_hwmon_name); 141 if (!hwm) 142 pr_err("Test hwmon creation failed\n"); 143 144 err_out: 145 if (!hwm) { 146 test_pmu_put(dir, hwm); 147 } 148 if (test_dirfd >= 0) 149 close(test_dirfd); 150 if (hwmon_dirfd >= 0) 151 close(hwmon_dirfd); 152 return hwm; 153 } 154 155 static int do_test(size_t i, bool with_pmu, bool with_alias) 156 { 157 const char *test_event = with_alias ? test_events[i].alias : test_events[i].name; 158 struct evlist *evlist = evlist__new(); 159 struct evsel *evsel; 160 struct parse_events_error err; 161 int ret; 162 char str[128]; 163 bool found = false; 164 165 if (!evlist) { 166 pr_err("evlist allocation failed\n"); 167 return TEST_FAIL; 168 } 169 170 if (with_pmu) 171 snprintf(str, sizeof(str), "hwmon_a_test_hwmon_pmu/%s/", test_event); 172 else 173 strlcpy(str, test_event, sizeof(str)); 174 175 pr_debug("Testing '%s'\n", str); 176 parse_events_error__init(&err); 177 ret = parse_events(evlist, str, &err); 178 if (ret) { 179 pr_debug("FAILED %s:%d failed to parse event '%s', err %d\n", 180 __FILE__, __LINE__, str, ret); 181 parse_events_error__print(&err, str); 182 ret = TEST_FAIL; 183 goto out; 184 } 185 186 ret = TEST_OK; 187 if (with_pmu ? (evlist__nr_entries(evlist) != 1) 188 : (evlist__nr_entries(evlist) < 1)) { 189 pr_debug("FAILED %s:%d Unexpected number of events for '%s' of %d\n", 190 __FILE__, __LINE__, str, evlist__nr_entries(evlist)); 191 ret = TEST_FAIL; 192 goto out; 193 } 194 195 evlist__for_each_entry(evlist, evsel) { 196 if (!evsel->pmu || !evsel->pmu->name || 197 strcmp(evsel->pmu->name, "hwmon_a_test_hwmon_pmu")) 198 continue; 199 200 if (evsel->core.attr.config != (u64)test_events[i].key.type_and_num) { 201 pr_debug("FAILED %s:%d Unexpected config for '%s', %" PRIu64 " != %ld\n", 202 __FILE__, __LINE__, str, 203 (uint64_t)evsel->core.attr.config, 204 test_events[i].key.type_and_num); 205 ret = TEST_FAIL; 206 goto out; 207 } 208 found = true; 209 } 210 211 if (!found) { 212 pr_debug("FAILED %s:%d Didn't find hwmon event '%s' in parsed evsels\n", 213 __FILE__, __LINE__, str); 214 ret = TEST_FAIL; 215 } 216 217 out: 218 parse_events_error__exit(&err); 219 evlist__put(evlist); 220 return ret; 221 } 222 223 static int test__hwmon_pmu(bool with_pmu) 224 { 225 char dir[PATH_MAX]; 226 struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir)); 227 int ret = TEST_OK; 228 229 if (!pmu) 230 return TEST_FAIL; 231 232 for (size_t i = 0; i < ARRAY_SIZE(test_events); i++) { 233 ret = do_test(i, with_pmu, /*with_alias=*/false); 234 235 if (ret != TEST_OK) 236 break; 237 238 ret = do_test(i, with_pmu, /*with_alias=*/true); 239 240 if (ret != TEST_OK) 241 break; 242 } 243 test_pmu_put(dir, pmu); 244 return ret; 245 } 246 247 static int test__hwmon_pmu_without_pmu(struct test_suite *test __maybe_unused, 248 int subtest __maybe_unused) 249 { 250 return test__hwmon_pmu(/*with_pmu=*/false); 251 } 252 253 static int test__hwmon_pmu_with_pmu(struct test_suite *test __maybe_unused, 254 int subtest __maybe_unused) 255 { 256 return test__hwmon_pmu(/*with_pmu=*/true); 257 } 258 259 static int test__parse_hwmon_filename(struct test_suite *test __maybe_unused, 260 int subtest __maybe_unused) 261 { 262 const struct hwmon_parse_test { 263 const char *filename; 264 enum hwmon_type type; 265 int number; 266 enum hwmon_item item; 267 bool alarm; 268 bool parse_ok; 269 } tests[] = { 270 { 271 .filename = "cpu0_accuracy", 272 .type = HWMON_TYPE_CPU, 273 .number = 0, 274 .item = HWMON_ITEM_ACCURACY, 275 .alarm = false, 276 .parse_ok = true, 277 }, 278 { 279 .filename = "temp1_input", 280 .type = HWMON_TYPE_TEMP, 281 .number = 1, 282 .item = HWMON_ITEM_INPUT, 283 .alarm = false, 284 .parse_ok = true, 285 }, 286 { 287 .filename = "fan2_vid", 288 .type = HWMON_TYPE_FAN, 289 .number = 2, 290 .item = HWMON_ITEM_VID, 291 .alarm = false, 292 .parse_ok = true, 293 }, 294 { 295 .filename = "power3_crit_alarm", 296 .type = HWMON_TYPE_POWER, 297 .number = 3, 298 .item = HWMON_ITEM_CRIT, 299 .alarm = true, 300 .parse_ok = true, 301 }, 302 { 303 .filename = "intrusion4_average_interval_min_alarm", 304 .type = HWMON_TYPE_INTRUSION, 305 .number = 4, 306 .item = HWMON_ITEM_AVERAGE_INTERVAL_MIN, 307 .alarm = true, 308 .parse_ok = true, 309 }, 310 { 311 .filename = "badtype5_baditem", 312 .type = HWMON_TYPE_NONE, 313 .number = 5, 314 .item = HWMON_ITEM_NONE, 315 .alarm = false, 316 .parse_ok = false, 317 }, 318 { 319 .filename = "humidity6_baditem", 320 .type = HWMON_TYPE_NONE, 321 .number = 6, 322 .item = HWMON_ITEM_NONE, 323 .alarm = false, 324 .parse_ok = false, 325 }, 326 }; 327 328 for (size_t i = 0; i < ARRAY_SIZE(tests); i++) { 329 enum hwmon_type type; 330 int number; 331 enum hwmon_item item; 332 bool alarm; 333 334 TEST_ASSERT_EQUAL("parse_hwmon_filename", 335 parse_hwmon_filename( 336 tests[i].filename, 337 &type, 338 &number, 339 &item, 340 &alarm), 341 tests[i].parse_ok 342 ); 343 if (tests[i].parse_ok) { 344 TEST_ASSERT_EQUAL("parse_hwmon_filename type", type, tests[i].type); 345 TEST_ASSERT_EQUAL("parse_hwmon_filename number", number, tests[i].number); 346 TEST_ASSERT_EQUAL("parse_hwmon_filename item", item, tests[i].item); 347 TEST_ASSERT_EQUAL("parse_hwmon_filename alarm", alarm, tests[i].alarm); 348 } 349 } 350 return TEST_OK; 351 } 352 353 static struct test_case tests__hwmon_pmu[] = { 354 TEST_CASE("Basic parsing test", parse_hwmon_filename), 355 TEST_CASE("Parsing without PMU name", hwmon_pmu_without_pmu), 356 TEST_CASE("Parsing with PMU name", hwmon_pmu_with_pmu), 357 { .name = NULL, } 358 }; 359 360 struct test_suite suite__hwmon_pmu = { 361 .desc = "Hwmon PMU", 362 .test_cases = tests__hwmon_pmu, 363 }; 364