xref: /openbmc/phosphor-mboxd/test/mbox.c (revision acdbdd14)
1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright (C) 2018 IBM Corp.
3 
4 #define _GNU_SOURCE /* fallocate */
5 #include <assert.h>
6 #include <fcntl.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/mman.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
15 
16 #include "mbox.h"
17 #include "mboxd_flash.h"
18 #include "mboxd_lpc.h"
19 #include "mboxd_msg.h"
20 #include "mboxd_windows.h"
21 
22 #include "test/mbox.h"
23 
24 #define STEP 16
25 
dump_buf(const void * buf,size_t len)26 void dump_buf(const void *buf, size_t len)
27 {
28 	const uint8_t *buf8 = buf;
29 	int i;
30 
31 	for (i = 0; i < len; i += STEP) {
32 		int delta;
33 		int max;
34 		int j;
35 
36 		delta = len - i;
37 		max = delta > STEP ? STEP : delta;
38 
39 		printf("0x%08x:\t", i);
40 		for (j = 0; j < max; j++)
41 			printf("0x%02x, ", buf8[i + j]);
42 
43 		printf("\n");
44 	}
45 	printf("\n");
46 }
47 
48 /*
49  * Because we are using a file and not a pipe for the mbox file descriptor we
50  * need to handle a difference in behaviour. For a given command and response
51  * sequence the first 16 bytes of the file are occupied by the mbox command.
52  * The response occupies the following 14 bytes for a total of 30 bytes.
53  *
54  * We also have to ensure we lseek() to reset the file descriptor offset back
55  * to the start of the file before dispatching the mbox command.
56  */
57 
58 /* Macros for handling the pipe/file discrepancy */
59 #define RESPONSE_OFFSET	16
60 #define RESPONSE_SIZE	14
61 
mbox_cmp(struct mbox_context * context,const uint8_t * expected,size_t len)62 int mbox_cmp(struct mbox_context *context, const uint8_t *expected, size_t len)
63 {
64 	struct stat details;
65 	uint8_t *map;
66 	int rc;
67 	int fd;
68 
69 	fd = context->fds[MBOX_FD].fd;
70 	fstat(fd, &details);
71 
72 	map = mmap(NULL, details.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
73 	assert(map != MAP_FAILED);
74 	assert(details.st_size >= (RESPONSE_OFFSET + len));
75 
76 	rc = memcmp(expected, &map[RESPONSE_OFFSET], len);
77 
78 	if (rc != 0) {
79 		printf("\nMBOX state (%ld):\n", details.st_size);
80 		dump_buf(map, details.st_size);
81 		printf("Expected response (%lu):\n", len);
82 		dump_buf(expected, len);
83 	}
84 
85 	munmap(map, details.st_size);
86 
87 	return rc;
88 }
89 
mbox_rspcpy(struct mbox_context * context,struct mbox_msg * msg)90 void mbox_rspcpy(struct mbox_context *context, struct mbox_msg *msg)
91 {
92 	struct stat details;
93 	uint8_t *map;
94 	int fd;
95 
96 	fd = context->fds[MBOX_FD].fd;
97 	fstat(fd, &details);
98 
99 	map = mmap(NULL, details.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
100 	assert(map != MAP_FAILED);
101 	assert(details.st_size >= (RESPONSE_OFFSET + RESPONSE_SIZE));
102 
103 	memcpy(msg, &map[RESPONSE_OFFSET], RESPONSE_SIZE);
104 
105 	munmap(map, details.st_size);
106 }
107 
mbox_command_write(struct mbox_context * context,const uint8_t * command,size_t len)108 int mbox_command_write(struct mbox_context *context, const uint8_t *command,
109 		size_t len)
110 {
111 	size_t remaining;
112 	int rc;
113 	int fd;
114 
115 	fd = context->fds[MBOX_FD].fd;
116 	rc = lseek(fd, 0, SEEK_SET);
117 	if (rc != 0)
118 		return -1;
119 
120 	remaining = len;
121 	while (remaining > 0) {
122 		rc = write(fd, command, remaining);
123 		if (rc < 0)
124 			goto out;
125 		remaining -= rc;
126 	}
127 
128 	rc = lseek(fd, 0, SEEK_SET);
129 	if (rc != 0)
130 		return -1;
131 
132 out:
133 	return rc;
134 }
135 
mbox_command_dispatch(struct mbox_context * context,const uint8_t * command,size_t len)136 int mbox_command_dispatch(struct mbox_context *context, const uint8_t *command,
137 		size_t len)
138 {
139 	uint8_t status;
140 	int rc;
141 
142 	rc = mbox_command_write(context, command, len);
143 	if (rc < 0)
144 		return rc;
145 
146 	rc = dispatch_mbox(context);
147 	if (rc < 0)
148 		return -rc;
149 
150 	/*
151 	 * The aspeed-lpc-ctrl driver implements mailbox register access
152 	 * through the usual read()/write() chardev interface.
153 	 *
154 	 * The typical access sequence is:
155 	 *
156 	 * 1. Read all the registers out
157 	 * 2. Perform the action specified
158 	 * 3. Write a response to the registers.
159 	 *
160 	 * For the tests the "device" file descriptor is backed by a temporary
161 	 * file. The above sequence leads to a file-size of 30 bytes: 16 bytes
162 	 * for the issued command, followed by a 14-byte response.
163 	 *
164 	 * However, the typical access pattern isn't the only access pattern.
165 	 * Individual byte registers can be accessed by lseek()'ing on the
166 	 * device's file descriptor and issuing read() or write() as desired.
167 	 * The daemon uses this property to manage the BMC status byte, and the
168 	 * implementation cleans up after status byte operations by lseek()'ing
169 	 * back to offset zero.
170 	 *
171 	 * Thus for the BMC_EVENT_ACK command the file only reaches a size of
172 	 * 16 bytes; the daemon's response overwrites the first 14 bytes of the
173 	 * command injected by the tests.
174 	 *
175 	 * The consequence of this is that the response status byte can either
176 	 * appear at offset 13, or offset 29, depending on the command.
177 	 *
178 	 * However, regardless of what command is issued the response data is
179 	 * written to the device and the file descriptor is left in the
180 	 * post-write() state. This means the status byte can always be
181 	 * accessed relative to the current position by an lseek() of type
182 	 * SEEK_CUR for offset -1.
183 	 *
184 	 */
185 	rc = lseek(context->fds[MBOX_FD].fd, -1, SEEK_CUR);
186 	if (rc < 0)
187 		return rc;
188 
189 	rc = read(context->fds[MBOX_FD].fd, &status, sizeof(status));
190 	if (rc < 0)
191 		return rc;
192 
193 	return status;
194 }
195 
196 struct mbox_test_context {
197 	struct tmpf mbox;
198 	struct tmpf flash;
199 	struct tmpf lpc;
200 	struct mbox_context context;
201 } test;
202 
cleanup(void)203 void cleanup(void)
204 {
205 	tmpf_destroy(&test.mbox);
206 	tmpf_destroy(&test.flash);
207 	tmpf_destroy(&test.lpc);
208 }
209 
210 int __init_mbox_dev(struct mbox_context *context, const char *path);
211 int __init_lpc_dev(struct mbox_context *context, const char *path);
212 
mbox_create_test_context(int n_windows,size_t len)213 struct mbox_context *mbox_create_test_context(int n_windows, size_t len)
214 {
215 	int rc;
216 
217 	mbox_vlog = &mbox_log_console;
218 	verbosity = 2;
219 
220 	atexit(cleanup);
221 
222 	rc = tmpf_init(&test.mbox, "mbox-store.XXXXXX");
223 	assert(rc == 0);
224 
225 	rc = tmpf_init(&test.flash, "flash-store.XXXXXX");
226 	assert(rc == 0);
227 
228 	rc = tmpf_init(&test.lpc, "lpc-store.XXXXXX");
229 	assert(rc == 0);
230 
231 	test.context.windows.num = n_windows;
232 	test.context.windows.default_size = len;
233 
234 	/*
235 	 * We need to call __init_mbox_dev() to initialise the handler table.
236 	 * However, afterwards we need to discard the fd of the clearly useless
237 	 * /dev/null and replace it with our own fd for mbox device emulation
238 	 * by the test framework.
239 	 */
240 	__init_mbox_dev(&test.context, "/dev/null");
241 	rc = close(test.context.fds[MBOX_FD].fd);
242 	assert(rc == 0);
243 	test.context.fds[MBOX_FD].fd = test.mbox.fd;
244 
245 	rc = init_flash_dev(&test.context);
246 	assert(rc == 0);
247 
248 	rc = fallocate(test.flash.fd, 0, 0, test.context.mtd_info.size);
249 	assert(rc == 0);
250 
251 	rc = __init_lpc_dev(&test.context, test.lpc.path);
252 	assert(rc == 0);
253 
254 	rc = fallocate(test.lpc.fd, 0, 0, test.context.mem_size);
255 	assert(rc == 0);
256 
257 	rc = init_windows(&test.context);
258 	assert(rc == 0);
259 
260 	return rc ? NULL : &test.context;
261 }
262 
263 /* From ccan's container_of module, CC0 license */
264 #define container_of(member_ptr, containing_type, member)		\
265 	 ((containing_type *)						\
266 	  ((char *)(member_ptr)						\
267 	   - container_off(containing_type, member))			\
268 	  + check_types_match(*(member_ptr), ((containing_type *)0)->member))
269 
270 /* From ccan's container_of module, CC0 license */
271 #define container_off(containing_type, member)	\
272 		offsetof(containing_type, member)
273 
274 /* From ccan's check_type module, CC0 license */
275 #define check_type(expr, type)			\
276 	((typeof(expr) *)0 != (type *)0)
277 
278 /* From ccan's check_type module, CC0 license */
279 #define check_types_match(expr1, expr2)		\
280 	((typeof(expr1) *)0 != (typeof(expr2) *)0)
281 
mbox_set_mtd_data(struct mbox_context * context,const void * data,size_t len)282 int mbox_set_mtd_data(struct mbox_context *context, const void *data,
283 		size_t len)
284 {
285 	struct mbox_test_context *arg;
286 	void *map;
287 
288 	/* Sanity check */
289 	arg = container_of(context, struct mbox_test_context, context);
290 	if (&test != arg)
291 		return -1;
292 
293 	if (len > test.context.mtd_info.size)
294 		return -2;
295 
296 	map = mmap(NULL, test.context.mtd_info.size, PROT_WRITE, MAP_SHARED,
297 			test.flash.fd, 0);
298 	assert(map != MAP_FAILED);
299 	memcpy(map, data, len);
300 	munmap(map, test.context.mtd_info.size);
301 
302 	return 0;
303 }
304 
get_dev_mtd(void)305 char *get_dev_mtd(void)
306 {
307 	return strdup(test.flash.path);
308 }
309