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