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