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. */
test_pmu_put(const char * dir,struct perf_pmu * hwm)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 */
test_pmu_get(char * dir,size_t sz)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 hwmon_dirfd = openat(test_dirfd, "hwmon1234", O_DIRECTORY);
97 if (hwmon_dirfd < 0) {
98 pr_err("Failed to open test hwmon directory \"%s/hwmon1234\"\n", dir);
99 goto err_out;
100 }
101 file = openat(hwmon_dirfd, "name", O_WRONLY | O_CREAT, 0600);
102 if (file < 0) {
103 pr_err("Failed to open for writing file \"name\"\n");
104 goto err_out;
105 }
106 len = strlen(test_hwmon_name_nl);
107 if (write(file, test_hwmon_name_nl, len) < len) {
108 close(file);
109 pr_err("Failed to write to 'name' file\n");
110 goto err_out;
111 }
112 close(file);
113
114 /* Create test hwmon files. */
115 for (size_t i = 0; i < ARRAY_SIZE(test_items); i++) {
116 const struct test_item *item = &test_items[i];
117
118 file = openat(hwmon_dirfd, item->name, O_WRONLY | O_CREAT, 0600);
119 if (file < 0) {
120 pr_err("Failed to open for writing file \"%s\"\n", item->name);
121 goto err_out;
122 }
123
124 if (write(file, item->value, strlen(item->value)) < 0) {
125 pr_err("Failed to write to file \"%s\"\n", item->name);
126 close(file);
127 goto err_out;
128 }
129 close(file);
130 }
131
132 /* Make the PMU reading the files created above. */
133 hwm = perf_pmus__add_test_hwmon_pmu(hwmon_dirfd, "hwmon1234", test_hwmon_name);
134 if (!hwm)
135 pr_err("Test hwmon creation failed\n");
136
137 err_out:
138 if (!hwm) {
139 test_pmu_put(dir, hwm);
140 if (hwmon_dirfd >= 0)
141 close(hwmon_dirfd);
142 }
143 if (test_dirfd >= 0)
144 close(test_dirfd);
145 return hwm;
146 }
147
do_test(size_t i,bool with_pmu,bool with_alias)148 static int do_test(size_t i, bool with_pmu, bool with_alias)
149 {
150 const char *test_event = with_alias ? test_events[i].alias : test_events[i].name;
151 struct evlist *evlist = evlist__new();
152 struct evsel *evsel;
153 struct parse_events_error err;
154 int ret;
155 char str[128];
156 bool found = false;
157
158 if (!evlist) {
159 pr_err("evlist allocation failed\n");
160 return TEST_FAIL;
161 }
162
163 if (with_pmu)
164 snprintf(str, sizeof(str), "hwmon_a_test_hwmon_pmu/%s/", test_event);
165 else
166 strlcpy(str, test_event, sizeof(str));
167
168 pr_debug("Testing '%s'\n", str);
169 parse_events_error__init(&err);
170 ret = parse_events(evlist, str, &err);
171 if (ret) {
172 pr_debug("FAILED %s:%d failed to parse event '%s', err %d\n",
173 __FILE__, __LINE__, str, ret);
174 parse_events_error__print(&err, str);
175 ret = TEST_FAIL;
176 goto out;
177 }
178
179 ret = TEST_OK;
180 if (with_pmu ? (evlist->core.nr_entries != 1) : (evlist->core.nr_entries < 1)) {
181 pr_debug("FAILED %s:%d Unexpected number of events for '%s' of %d\n",
182 __FILE__, __LINE__, str, evlist->core.nr_entries);
183 ret = TEST_FAIL;
184 goto out;
185 }
186
187 evlist__for_each_entry(evlist, evsel) {
188 if (!evsel->pmu || !evsel->pmu->name ||
189 strcmp(evsel->pmu->name, "hwmon_a_test_hwmon_pmu"))
190 continue;
191
192 if (evsel->core.attr.config != (u64)test_events[i].key.type_and_num) {
193 pr_debug("FAILED %s:%d Unexpected config for '%s', %lld != %ld\n",
194 __FILE__, __LINE__, str,
195 evsel->core.attr.config,
196 test_events[i].key.type_and_num);
197 ret = TEST_FAIL;
198 goto out;
199 }
200 found = true;
201 }
202
203 if (!found) {
204 pr_debug("FAILED %s:%d Didn't find hwmon event '%s' in parsed evsels\n",
205 __FILE__, __LINE__, str);
206 ret = TEST_FAIL;
207 }
208
209 out:
210 parse_events_error__exit(&err);
211 evlist__delete(evlist);
212 return ret;
213 }
214
test__hwmon_pmu(bool with_pmu)215 static int test__hwmon_pmu(bool with_pmu)
216 {
217 char dir[PATH_MAX];
218 struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir));
219 int ret = TEST_OK;
220
221 if (!pmu)
222 return TEST_FAIL;
223
224 for (size_t i = 0; i < ARRAY_SIZE(test_events); i++) {
225 ret = do_test(i, with_pmu, /*with_alias=*/false);
226
227 if (ret != TEST_OK)
228 break;
229
230 ret = do_test(i, with_pmu, /*with_alias=*/true);
231
232 if (ret != TEST_OK)
233 break;
234 }
235 test_pmu_put(dir, pmu);
236 return ret;
237 }
238
test__hwmon_pmu_without_pmu(struct test_suite * test __maybe_unused,int subtest __maybe_unused)239 static int test__hwmon_pmu_without_pmu(struct test_suite *test __maybe_unused,
240 int subtest __maybe_unused)
241 {
242 return test__hwmon_pmu(/*with_pmu=*/false);
243 }
244
test__hwmon_pmu_with_pmu(struct test_suite * test __maybe_unused,int subtest __maybe_unused)245 static int test__hwmon_pmu_with_pmu(struct test_suite *test __maybe_unused,
246 int subtest __maybe_unused)
247 {
248 return test__hwmon_pmu(/*with_pmu=*/true);
249 }
250
test__parse_hwmon_filename(struct test_suite * test __maybe_unused,int subtest __maybe_unused)251 static int test__parse_hwmon_filename(struct test_suite *test __maybe_unused,
252 int subtest __maybe_unused)
253 {
254 const struct hwmon_parse_test {
255 const char *filename;
256 enum hwmon_type type;
257 int number;
258 enum hwmon_item item;
259 bool alarm;
260 bool parse_ok;
261 } tests[] = {
262 {
263 .filename = "cpu0_accuracy",
264 .type = HWMON_TYPE_CPU,
265 .number = 0,
266 .item = HWMON_ITEM_ACCURACY,
267 .alarm = false,
268 .parse_ok = true,
269 },
270 {
271 .filename = "temp1_input",
272 .type = HWMON_TYPE_TEMP,
273 .number = 1,
274 .item = HWMON_ITEM_INPUT,
275 .alarm = false,
276 .parse_ok = true,
277 },
278 {
279 .filename = "fan2_vid",
280 .type = HWMON_TYPE_FAN,
281 .number = 2,
282 .item = HWMON_ITEM_VID,
283 .alarm = false,
284 .parse_ok = true,
285 },
286 {
287 .filename = "power3_crit_alarm",
288 .type = HWMON_TYPE_POWER,
289 .number = 3,
290 .item = HWMON_ITEM_CRIT,
291 .alarm = true,
292 .parse_ok = true,
293 },
294 {
295 .filename = "intrusion4_average_interval_min_alarm",
296 .type = HWMON_TYPE_INTRUSION,
297 .number = 4,
298 .item = HWMON_ITEM_AVERAGE_INTERVAL_MIN,
299 .alarm = true,
300 .parse_ok = true,
301 },
302 {
303 .filename = "badtype5_baditem",
304 .type = HWMON_TYPE_NONE,
305 .number = 5,
306 .item = HWMON_ITEM_NONE,
307 .alarm = false,
308 .parse_ok = false,
309 },
310 {
311 .filename = "humidity6_baditem",
312 .type = HWMON_TYPE_NONE,
313 .number = 6,
314 .item = HWMON_ITEM_NONE,
315 .alarm = false,
316 .parse_ok = false,
317 },
318 };
319
320 for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
321 enum hwmon_type type;
322 int number;
323 enum hwmon_item item;
324 bool alarm;
325
326 TEST_ASSERT_EQUAL("parse_hwmon_filename",
327 parse_hwmon_filename(
328 tests[i].filename,
329 &type,
330 &number,
331 &item,
332 &alarm),
333 tests[i].parse_ok
334 );
335 if (tests[i].parse_ok) {
336 TEST_ASSERT_EQUAL("parse_hwmon_filename type", type, tests[i].type);
337 TEST_ASSERT_EQUAL("parse_hwmon_filename number", number, tests[i].number);
338 TEST_ASSERT_EQUAL("parse_hwmon_filename item", item, tests[i].item);
339 TEST_ASSERT_EQUAL("parse_hwmon_filename alarm", alarm, tests[i].alarm);
340 }
341 }
342 return TEST_OK;
343 }
344
345 static struct test_case tests__hwmon_pmu[] = {
346 TEST_CASE("Basic parsing test", parse_hwmon_filename),
347 TEST_CASE("Parsing without PMU name", hwmon_pmu_without_pmu),
348 TEST_CASE("Parsing with PMU name", hwmon_pmu_with_pmu),
349 { .name = NULL, }
350 };
351
352 struct test_suite suite__hwmon_pmu = {
353 .desc = "Hwmon PMU",
354 .test_cases = tests__hwmon_pmu,
355 };
356