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 	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 
do_test(size_t i,bool with_pmu,bool with_alias)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 
test__hwmon_pmu(bool with_pmu)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 
test__hwmon_pmu_without_pmu(struct test_suite * test __maybe_unused,int subtest __maybe_unused)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 
test__hwmon_pmu_with_pmu(struct test_suite * test __maybe_unused,int subtest __maybe_unused)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 
test__parse_hwmon_filename(struct test_suite * test __maybe_unused,int subtest __maybe_unused)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