1// SPDX-License-Identifier: GPL-2.0-only
2#define _GNU_SOURCE
3#include <errno.h>
4#include <fcntl.h>
5#include <stdlib.h>
6#include <string.h>
7#include <sys/ioctl.h>
8#include <unistd.h>
9
10#include <asm/papr-vpd.h>
11
12#include "utils.h"
13
14#define DEVPATH "/dev/papr-vpd"
15
16static int dev_papr_vpd_open_close(void)
17{
18	const int devfd = open(DEVPATH, O_RDONLY);
19
20	SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
21		    DEVPATH " not present");
22
23	FAIL_IF(devfd < 0);
24	FAIL_IF(close(devfd) != 0);
25
26	return 0;
27}
28
29static int dev_papr_vpd_get_handle_all(void)
30{
31	const int devfd = open(DEVPATH, O_RDONLY);
32	struct papr_location_code lc = { .str = "", };
33	off_t size;
34	int fd;
35
36	SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
37		    DEVPATH " not present");
38
39	FAIL_IF(devfd < 0);
40
41	errno = 0;
42	fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
43	FAIL_IF(errno != 0);
44	FAIL_IF(fd < 0);
45
46	FAIL_IF(close(devfd) != 0);
47
48	size = lseek(fd, 0, SEEK_END);
49	FAIL_IF(size <= 0);
50
51	void *buf = malloc((size_t)size);
52	FAIL_IF(!buf);
53
54	ssize_t consumed = pread(fd, buf, size, 0);
55	FAIL_IF(consumed != size);
56
57	/* Ensure EOF */
58	FAIL_IF(read(fd, buf, size) != 0);
59	FAIL_IF(close(fd));
60
61	/* Verify that the buffer looks like VPD */
62	static const char needle[] = "System VPD";
63	FAIL_IF(!memmem(buf, size, needle, strlen(needle)));
64
65	return 0;
66}
67
68static int dev_papr_vpd_get_handle_byte_at_a_time(void)
69{
70	const int devfd = open(DEVPATH, O_RDONLY);
71	struct papr_location_code lc = { .str = "", };
72	int fd;
73
74	SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
75		    DEVPATH " not present");
76
77	FAIL_IF(devfd < 0);
78
79	errno = 0;
80	fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
81	FAIL_IF(errno != 0);
82	FAIL_IF(fd < 0);
83
84	FAIL_IF(close(devfd) != 0);
85
86	size_t consumed = 0;
87	while (1) {
88		ssize_t res;
89		char c;
90
91		errno = 0;
92		res = read(fd, &c, sizeof(c));
93		FAIL_IF(res > sizeof(c));
94		FAIL_IF(res < 0);
95		FAIL_IF(errno != 0);
96		consumed += res;
97		if (res == 0)
98			break;
99	}
100
101	FAIL_IF(consumed != lseek(fd, 0, SEEK_END));
102
103	FAIL_IF(close(fd));
104
105	return 0;
106}
107
108
109static int dev_papr_vpd_unterm_loc_code(void)
110{
111	const int devfd = open(DEVPATH, O_RDONLY);
112	struct papr_location_code lc = {};
113	int fd;
114
115	SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
116		    DEVPATH " not present");
117
118	FAIL_IF(devfd < 0);
119
120	/*
121	 * Place a non-null byte in every element of loc_code; the
122	 * driver should reject this input.
123	 */
124	memset(lc.str, 'x', ARRAY_SIZE(lc.str));
125
126	errno = 0;
127	fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
128	FAIL_IF(fd != -1);
129	FAIL_IF(errno != EINVAL);
130
131	FAIL_IF(close(devfd) != 0);
132	return 0;
133}
134
135static int dev_papr_vpd_null_handle(void)
136{
137	const int devfd = open(DEVPATH, O_RDONLY);
138	int rc;
139
140	SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
141		    DEVPATH " not present");
142
143	FAIL_IF(devfd < 0);
144
145	errno = 0;
146	rc = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, NULL);
147	FAIL_IF(rc != -1);
148	FAIL_IF(errno != EFAULT);
149
150	FAIL_IF(close(devfd) != 0);
151	return 0;
152}
153
154static int papr_vpd_close_handle_without_reading(void)
155{
156	const int devfd = open(DEVPATH, O_RDONLY);
157	struct papr_location_code lc = { .str = "", };
158	int fd;
159
160	SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
161		    DEVPATH " not present");
162
163	FAIL_IF(devfd < 0);
164
165	errno = 0;
166	fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
167	FAIL_IF(errno != 0);
168	FAIL_IF(fd < 0);
169
170	/* close the handle without reading it */
171	FAIL_IF(close(fd) != 0);
172
173	FAIL_IF(close(devfd) != 0);
174	return 0;
175}
176
177static int papr_vpd_reread(void)
178{
179	const int devfd = open(DEVPATH, O_RDONLY);
180	struct papr_location_code lc = { .str = "", };
181	int fd;
182
183	SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
184		    DEVPATH " not present");
185
186	FAIL_IF(devfd < 0);
187
188	errno = 0;
189	fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
190	FAIL_IF(errno != 0);
191	FAIL_IF(fd < 0);
192
193	FAIL_IF(close(devfd) != 0);
194
195	const off_t size = lseek(fd, 0, SEEK_END);
196	FAIL_IF(size <= 0);
197
198	char *bufs[2];
199
200	for (size_t i = 0; i < ARRAY_SIZE(bufs); ++i) {
201		bufs[i] = malloc(size);
202		FAIL_IF(!bufs[i]);
203		ssize_t consumed = pread(fd, bufs[i], size, 0);
204		FAIL_IF(consumed != size);
205	}
206
207	FAIL_IF(memcmp(bufs[0], bufs[1], size));
208
209	FAIL_IF(close(fd) != 0);
210
211	return 0;
212}
213
214static int get_system_loc_code(struct papr_location_code *lc)
215{
216	static const char system_id_path[] = "/sys/firmware/devicetree/base/system-id";
217	static const char model_path[] = "/sys/firmware/devicetree/base/model";
218	char *system_id;
219	char *model;
220	int err = -1;
221
222	if (read_file_alloc(model_path, &model, NULL))
223		return err;
224
225	if (read_file_alloc(system_id_path, &system_id, NULL))
226		goto free_model;
227
228	char *mtm;
229	int sscanf_ret = sscanf(model, "IBM,%ms", &mtm);
230	if (sscanf_ret != 1)
231		goto free_system_id;
232
233	char *plant_and_seq;
234	if (sscanf(system_id, "IBM,%*c%*c%ms", &plant_and_seq) != 1)
235		goto free_mtm;
236	/*
237	 * Replace - with . to build location code.
238	 */
239	char *sep = strchr(mtm, '-');
240	if (!sep)
241		goto free_mtm;
242	else
243		*sep = '.';
244
245	snprintf(lc->str, sizeof(lc->str),
246		 "U%s.%s", mtm, plant_and_seq);
247	err = 0;
248
249	free(plant_and_seq);
250free_mtm:
251	free(mtm);
252free_system_id:
253	free(system_id);
254free_model:
255	free(model);
256	return err;
257}
258
259static int papr_vpd_system_loc_code(void)
260{
261	struct papr_location_code lc;
262	const int devfd = open(DEVPATH, O_RDONLY);
263	off_t size;
264	int fd;
265
266	SKIP_IF_MSG(devfd < 0 && errno == ENOENT,
267		    DEVPATH " not present");
268	SKIP_IF_MSG(get_system_loc_code(&lc),
269		    "Cannot determine system location code");
270
271	FAIL_IF(devfd < 0);
272
273	errno = 0;
274	fd = ioctl(devfd, PAPR_VPD_IOC_CREATE_HANDLE, &lc);
275	FAIL_IF(errno != 0);
276	FAIL_IF(fd < 0);
277
278	FAIL_IF(close(devfd) != 0);
279
280	size = lseek(fd, 0, SEEK_END);
281	FAIL_IF(size <= 0);
282
283	void *buf = malloc((size_t)size);
284	FAIL_IF(!buf);
285
286	ssize_t consumed = pread(fd, buf, size, 0);
287	FAIL_IF(consumed != size);
288
289	/* Ensure EOF */
290	FAIL_IF(read(fd, buf, size) != 0);
291	FAIL_IF(close(fd));
292
293	/* Verify that the buffer looks like VPD */
294	static const char needle[] = "System VPD";
295	FAIL_IF(!memmem(buf, size, needle, strlen(needle)));
296
297	return 0;
298}
299
300struct vpd_test {
301	int (*function)(void);
302	const char *description;
303};
304
305static const struct vpd_test vpd_tests[] = {
306	{
307		.function = dev_papr_vpd_open_close,
308		.description = "open/close " DEVPATH,
309	},
310	{
311		.function = dev_papr_vpd_unterm_loc_code,
312		.description = "ensure EINVAL on unterminated location code",
313	},
314	{
315		.function = dev_papr_vpd_null_handle,
316		.description = "ensure EFAULT on bad handle addr",
317	},
318	{
319		.function = dev_papr_vpd_get_handle_all,
320		.description = "get handle for all VPD"
321	},
322	{
323		.function = papr_vpd_close_handle_without_reading,
324		.description = "close handle without consuming VPD"
325	},
326	{
327		.function = dev_papr_vpd_get_handle_byte_at_a_time,
328		.description = "read all VPD one byte at a time"
329	},
330	{
331		.function = papr_vpd_reread,
332		.description = "ensure re-read yields same results"
333	},
334	{
335		.function = papr_vpd_system_loc_code,
336		.description = "get handle for system VPD"
337	},
338};
339
340int main(void)
341{
342	size_t fails = 0;
343
344	for (size_t i = 0; i < ARRAY_SIZE(vpd_tests); ++i) {
345		const struct vpd_test *t = &vpd_tests[i];
346
347		if (test_harness(t->function, t->description))
348			++fails;
349	}
350
351	return fails == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
352}
353