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