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