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