xref: /openbmc/libmctp/astlpc.c (revision 04b81fc7)
1 /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
2 
3 #include <assert.h>
4 #include <endian.h>
5 #include <err.h>
6 #include <stdbool.h>
7 #include <stdlib.h>
8 #include <string.h>
9 
10 #define pr_fmt(x) "astlpc: " x
11 
12 #if HAVE_CONFIG_H
13 #include "config.h"
14 #endif
15 
16 #include "libmctp.h"
17 #include "libmctp-alloc.h"
18 #include "libmctp-log.h"
19 #include "libmctp-astlpc.h"
20 
21 #ifdef MCTP_HAVE_FILEIO
22 
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <sys/ioctl.h>
26 #include <sys/mman.h>
27 #include <linux/aspeed-lpc-ctrl.h>
28 
29 /* kernel interface */
30 static const char *kcs_path = "/dev/mctp0";
31 static const char *lpc_path = "/dev/aspeed-lpc-ctrl";
32 
33 #endif
34 
35 struct mctp_binding_astlpc {
36 	struct mctp_binding	binding;
37 
38 	union {
39 		void			*lpc_map;
40 		struct mctp_lpcmap_hdr	*lpc_hdr;
41 	};
42 
43 	/* direct ops data */
44 	struct mctp_binding_astlpc_ops	ops;
45 	void			*ops_data;
46 	struct mctp_lpcmap_hdr	*priv_hdr;
47 
48 	/* fileio ops data */
49 	void			*lpc_map_base;
50 	int			kcs_fd;
51 	uint8_t			kcs_status;
52 
53 	bool			running;
54 
55 	/* temporary transmit buffer */
56 	uint8_t			txbuf[256];
57 };
58 
59 #ifndef container_of
60 #define container_of(ptr, type, member) \
61 	(type *)((char *)(ptr) - (char *)&((type *)0)->member)
62 #endif
63 
64 #define binding_to_astlpc(b) \
65 	container_of(b, struct mctp_binding_astlpc, binding)
66 
67 #define MCTP_MAGIC	0x4d435450
68 #define BMC_VER_MIN	1
69 #define BMC_VER_CUR	1
70 
71 struct mctp_lpcmap_hdr {
72 	uint32_t	magic;
73 
74 	uint16_t	bmc_ver_min;
75 	uint16_t	bmc_ver_cur;
76 	uint16_t	host_ver_min;
77 	uint16_t	host_ver_cur;
78 	uint16_t	negotiated_ver;
79 	uint16_t	pad0;
80 
81 	uint32_t	rx_offset;
82 	uint32_t	rx_size;
83 	uint32_t	tx_offset;
84 	uint32_t	tx_size;
85 } __attribute__((packed));
86 
87 /* layout of TX/RX areas */
88 static const uint32_t	rx_offset = 0x100;
89 static const uint32_t	rx_size   = 0x100;
90 static const uint32_t	tx_offset = 0x200;
91 static const uint32_t	tx_size   = 0x100;
92 
93 #define LPC_WIN_SIZE                (1 * 1024 * 1024)
94 
95 enum {
96 	KCS_REG_DATA = 0,
97 	KCS_REG_STATUS = 1,
98 };
99 
100 #define KCS_STATUS_BMC_READY		0x80
101 #define KCS_STATUS_CHANNEL_ACTIVE	0x40
102 #define KCS_STATUS_IBF			0x02
103 #define KCS_STATUS_OBF			0x01
104 
105 static bool lpc_direct(struct mctp_binding_astlpc *astlpc)
106 {
107 	return astlpc->lpc_map != NULL;
108 }
109 
110 static int mctp_astlpc_kcs_set_status(struct mctp_binding_astlpc *astlpc,
111 		uint8_t status)
112 {
113 	uint8_t data;
114 	int rc;
115 
116 	/* Since we're setting the status register, we want the other endpoint
117 	 * to be interrupted. However, some hardware may only raise a host-side
118 	 * interrupt on an ODR event.
119 	 * So, write a dummy value of 0xff to ODR, which will ensure that an
120 	 * interrupt is triggered, and can be ignored by the host.
121 	 */
122 	data = 0xff;
123 	status |= KCS_STATUS_OBF;
124 
125 	rc = astlpc->ops.kcs_write(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_DATA,
126 			data);
127 	if (rc) {
128 		mctp_prwarn("KCS dummy data write failed");
129 		return -1;
130 	}
131 
132 
133 	rc = astlpc->ops.kcs_write(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_STATUS,
134 			status);
135 	if (rc) {
136 		mctp_prwarn("KCS status write failed");
137 		return -1;
138 	}
139 	return 0;
140 }
141 
142 static int mctp_astlpc_kcs_send(struct mctp_binding_astlpc *astlpc,
143 		uint8_t data)
144 {
145 	uint8_t status;
146 	int rc;
147 
148 	for (;;) {
149 		rc = astlpc->ops.kcs_read(astlpc->ops_data,
150 				MCTP_ASTLPC_KCS_REG_STATUS, &status);
151 		if (rc) {
152 			mctp_prwarn("KCS status read failed");
153 			return -1;
154 		}
155 		if (!(status & KCS_STATUS_OBF))
156 			break;
157 		/* todo: timeout */
158 	}
159 
160 	rc = astlpc->ops.kcs_write(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_DATA,
161 			data);
162 	if (rc) {
163 		mctp_prwarn("KCS data write failed");
164 		return -1;
165 	}
166 
167 	return 0;
168 }
169 
170 static int mctp_binding_astlpc_tx(struct mctp_binding *b,
171 		struct mctp_pktbuf *pkt)
172 {
173 	struct mctp_binding_astlpc *astlpc = binding_to_astlpc(b);
174 	uint32_t len;
175 
176 	len = mctp_pktbuf_size(pkt);
177 	if (len > rx_size - 4) {
178 		mctp_prwarn("invalid TX len 0x%x", len);
179 		return -1;
180 	}
181 
182 	if (lpc_direct(astlpc)) {
183 		*(uint32_t *)(astlpc->lpc_map + rx_offset) = htobe32(len);
184 		memcpy(astlpc->lpc_map + rx_offset + 4, mctp_pktbuf_hdr(pkt),
185 				len);
186 	} else {
187 		uint32_t tmp = htobe32(len);
188 		astlpc->ops.lpc_write(astlpc->ops_data, &tmp, rx_offset,
189 				sizeof(tmp));
190 		astlpc->ops.lpc_write(astlpc->ops_data, mctp_pktbuf_hdr(pkt),
191 				rx_offset + 4, len);
192 	}
193 
194 	mctp_binding_set_tx_enabled(b, false);
195 
196 	mctp_astlpc_kcs_send(astlpc, 0x1);
197 	return 0;
198 }
199 
200 static void mctp_astlpc_init_channel(struct mctp_binding_astlpc *astlpc)
201 {
202 	/* todo: actual version negotiation */
203 	if (lpc_direct(astlpc)) {
204 		astlpc->lpc_hdr->negotiated_ver = htobe16(1);
205 	} else {
206 		uint16_t ver = htobe16(1);
207 		astlpc->ops.lpc_write(astlpc->ops_data, &ver,
208 				offsetof(struct mctp_lpcmap_hdr,
209 					negotiated_ver),
210 				sizeof(ver));
211 	}
212 	mctp_astlpc_kcs_set_status(astlpc,
213 			KCS_STATUS_BMC_READY | KCS_STATUS_CHANNEL_ACTIVE |
214 			KCS_STATUS_OBF);
215 
216 	mctp_binding_set_tx_enabled(&astlpc->binding, true);
217 }
218 
219 static void mctp_astlpc_rx_start(struct mctp_binding_astlpc *astlpc)
220 {
221 	struct mctp_pktbuf *pkt;
222 	uint32_t len;
223 
224 	if (lpc_direct(astlpc)) {
225 		len = *(uint32_t *)(astlpc->lpc_map + tx_offset);
226 	} else {
227 		astlpc->ops.lpc_read(astlpc->ops_data, &len,
228 				tx_offset, sizeof(len));
229 	}
230 	len = be32toh(len);
231 
232 	if (len > tx_size - 4) {
233 		mctp_prwarn("invalid RX len 0x%x", len);
234 		return;
235 	}
236 
237 	if (len > astlpc->binding.pkt_size) {
238 		mctp_prwarn("invalid RX len 0x%x", len);
239 		return;
240 	}
241 
242 	pkt = mctp_pktbuf_alloc(&astlpc->binding, len);
243 	if (!pkt)
244 		goto out_complete;
245 
246 	if (lpc_direct(astlpc)) {
247 		memcpy(mctp_pktbuf_hdr(pkt),
248 				astlpc->lpc_map + tx_offset + 4, len);
249 	} else {
250 		astlpc->ops.lpc_read(astlpc->ops_data, mctp_pktbuf_hdr(pkt),
251 				tx_offset + 4, len);
252 	}
253 
254 	mctp_bus_rx(&astlpc->binding, pkt);
255 
256 out_complete:
257 	mctp_astlpc_kcs_send(astlpc, 0x2);
258 }
259 
260 static void mctp_astlpc_tx_complete(struct mctp_binding_astlpc *astlpc)
261 {
262 	mctp_binding_set_tx_enabled(&astlpc->binding, true);
263 }
264 
265 int mctp_astlpc_poll(struct mctp_binding_astlpc *astlpc)
266 {
267 	uint8_t status, data;
268 	int rc;
269 
270 	rc = astlpc->ops.kcs_read(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_STATUS,
271 			&status);
272 	if (rc) {
273 		mctp_prwarn("KCS read error");
274 		return -1;
275 	}
276 
277 	if (!(status & KCS_STATUS_IBF))
278 		return 0;
279 
280 	rc = astlpc->ops.kcs_read(astlpc->ops_data, MCTP_ASTLPC_KCS_REG_DATA,
281 			&data);
282 	if (rc) {
283 		mctp_prwarn("KCS data read error");
284 		return -1;
285 	}
286 
287 	switch (data) {
288 	case 0x0:
289 		mctp_astlpc_init_channel(astlpc);
290 		break;
291 	case 0x1:
292 		mctp_astlpc_rx_start(astlpc);
293 		break;
294 	case 0x2:
295 		mctp_astlpc_tx_complete(astlpc);
296 		break;
297 	case 0xff:
298 		/* reserved value for dummy data writes; do nothing */
299 		break;
300 	default:
301 		mctp_prwarn("unknown message 0x%x", data);
302 	}
303 	return 0;
304 }
305 
306 static int mctp_astlpc_init_bmc(struct mctp_binding_astlpc *astlpc)
307 {
308 	struct mctp_lpcmap_hdr *hdr;
309 	uint8_t status;
310 	int rc;
311 
312 	if (lpc_direct(astlpc))
313 		hdr = astlpc->lpc_hdr;
314 	else
315 		hdr = astlpc->priv_hdr;
316 
317 	hdr->magic = htobe32(MCTP_MAGIC);
318 	hdr->bmc_ver_min = htobe16(BMC_VER_MIN);
319 	hdr->bmc_ver_cur = htobe16(BMC_VER_CUR);
320 
321 	hdr->rx_offset = htobe32(rx_offset);
322 	hdr->rx_size = htobe32(rx_size);
323 	hdr->tx_offset = htobe32(tx_offset);
324 	hdr->tx_size = htobe32(tx_size);
325 
326 	if (!lpc_direct(astlpc))
327 		astlpc->ops.lpc_write(astlpc->ops_data, hdr, 0, sizeof(*hdr));
328 
329 	/* set status indicating that the BMC is now active */
330 	status = KCS_STATUS_BMC_READY | KCS_STATUS_OBF;
331 	rc = astlpc->ops.kcs_write(astlpc->ops_data,
332 			MCTP_ASTLPC_KCS_REG_STATUS, status);
333 	if (rc) {
334 		mctp_prwarn("KCS write failed");
335 	}
336 
337 	return rc;
338 }
339 
340 static int mctp_binding_astlpc_start(struct mctp_binding *b)
341 {
342 	struct mctp_binding_astlpc *astlpc = container_of(b,
343 			struct mctp_binding_astlpc, binding);
344 
345 	return mctp_astlpc_init_bmc(astlpc);
346 }
347 
348 /* allocate and basic initialisation */
349 static struct mctp_binding_astlpc *__mctp_astlpc_init(void)
350 {
351 	struct mctp_binding_astlpc *astlpc;
352 
353 	astlpc = __mctp_alloc(sizeof(*astlpc));
354 	memset(astlpc, 0, sizeof(*astlpc));
355 	astlpc->binding.name = "astlpc";
356 	astlpc->binding.version = 1;
357 	astlpc->binding.tx = mctp_binding_astlpc_tx;
358 	astlpc->binding.start = mctp_binding_astlpc_start;
359 	astlpc->binding.pkt_size = MCTP_PACKET_SIZE(MCTP_BTU);
360 	astlpc->binding.pkt_pad = 0;
361 	astlpc->lpc_map = NULL;
362 
363 	return astlpc;
364 }
365 
366 struct mctp_binding *mctp_binding_astlpc_core(struct mctp_binding_astlpc *b)
367 {
368 	return &b->binding;
369 }
370 
371 struct mctp_binding_astlpc *mctp_astlpc_init_ops(
372 		struct mctp_binding_astlpc_ops *ops,
373 		void *ops_data, void *lpc_map)
374 {
375 	struct mctp_binding_astlpc *astlpc;
376 
377 	astlpc = __mctp_astlpc_init();
378 	if (!astlpc)
379 		return NULL;
380 
381 	memcpy(&astlpc->ops, ops, sizeof(astlpc->ops));
382 	astlpc->ops_data = ops_data;
383 	astlpc->lpc_map = lpc_map;
384 
385 	/* In indirect mode, we keep a separate buffer of header data.
386 	 * We need to sync this through the lpc_read/lpc_write ops.
387 	 */
388 	if (!astlpc->lpc_map)
389 		astlpc->priv_hdr = __mctp_alloc(sizeof(*astlpc->priv_hdr));
390 
391 	return astlpc;
392 }
393 
394 #ifdef MCTP_HAVE_FILEIO
395 static int mctp_astlpc_init_fileio_lpc(struct mctp_binding_astlpc *astlpc)
396 {
397 	struct aspeed_lpc_ctrl_mapping map = {
398 		.window_type = ASPEED_LPC_CTRL_WINDOW_MEMORY,
399 		.window_id = 0, /* There's only one */
400 		.flags = 0,
401 		.addr = 0,
402 		.offset = 0,
403 		.size = 0
404 	};
405 	int fd, rc;
406 
407 	fd = open(lpc_path, O_RDWR | O_SYNC);
408 	if (fd < 0) {
409 		mctp_prwarn("LPC open (%s) failed", lpc_path);
410 		return -1;
411 	}
412 
413 	rc = ioctl(fd, ASPEED_LPC_CTRL_IOCTL_GET_SIZE, &map);
414 	if (rc) {
415 		mctp_prwarn("LPC GET_SIZE failed");
416 		close(fd);
417 		return -1;
418 	}
419 
420 	astlpc->lpc_map_base = mmap(NULL, map.size, PROT_READ | PROT_WRITE,
421 			MAP_SHARED, fd, 0);
422 	if (astlpc->lpc_map_base == MAP_FAILED) {
423 		mctp_prwarn("LPC mmap failed");
424 		rc = -1;
425 	} else {
426 		astlpc->lpc_map = astlpc->lpc_map_base +
427 			map.size - LPC_WIN_SIZE;
428 	}
429 
430 	close(fd);
431 
432 	return rc;
433 }
434 
435 static int mctp_astlpc_init_fileio_kcs(struct mctp_binding_astlpc *astlpc)
436 {
437 	astlpc->kcs_fd = open(kcs_path, O_RDWR);
438 	if (astlpc->kcs_fd < 0)
439 		return -1;
440 
441 	return 0;
442 }
443 
444 static int __mctp_astlpc_fileio_kcs_read(void *arg,
445 		enum mctp_binding_astlpc_kcs_reg reg, uint8_t *val)
446 {
447 	struct mctp_binding_astlpc *astlpc = arg;
448 	off_t offset = reg;
449 	int rc;
450 
451 	rc = pread(astlpc->kcs_fd, val, 1, offset);
452 
453 	return rc == 1 ? 0 : -1;
454 }
455 
456 static int __mctp_astlpc_fileio_kcs_write(void *arg,
457 		enum mctp_binding_astlpc_kcs_reg reg, uint8_t val)
458 {
459 	struct mctp_binding_astlpc *astlpc = arg;
460 	off_t offset = reg;
461 	int rc;
462 
463 	rc = pwrite(astlpc->kcs_fd, &val, 1, offset);
464 
465 	return rc == 1 ? 0 : -1;
466 }
467 
468 int mctp_astlpc_get_fd(struct mctp_binding_astlpc *astlpc)
469 {
470 	return astlpc->kcs_fd;
471 }
472 
473 struct mctp_binding_astlpc *mctp_astlpc_init_fileio(void)
474 {
475 	struct mctp_binding_astlpc *astlpc;
476 	int rc;
477 
478 	astlpc = __mctp_astlpc_init();
479 	if (!astlpc)
480 		return NULL;
481 
482 	/* Set internal operations for kcs. We use direct accesses to the lpc
483 	 * map area */
484 	astlpc->ops.kcs_read = __mctp_astlpc_fileio_kcs_read;
485 	astlpc->ops.kcs_write = __mctp_astlpc_fileio_kcs_write;
486 	astlpc->ops_data = astlpc;
487 
488 	rc = mctp_astlpc_init_fileio_lpc(astlpc);
489 	if (rc) {
490 		free(astlpc);
491 		return NULL;
492 	}
493 
494 	rc = mctp_astlpc_init_fileio_kcs(astlpc);
495 	if (rc) {
496 		free(astlpc);
497 		return NULL;
498 	}
499 
500 	return astlpc;
501 }
502 #else
503 struct mctp_binding_astlpc * __attribute__((const))
504 	mctp_astlpc_init_fileio(void)
505 {
506 	warnx("Missing support for file IO");
507 	return NULL;
508 }
509 
510 int __attribute__((const)) mctp_astlpc_get_fd(
511 		struct mctp_binding_astlpc *astlpc __attribute__((unused)))
512 {
513 	warnx("Missing support for file IO");
514 	return -1;
515 }
516 #endif
517