diff mbox series

[V7,02/21] hw/ast-bmc: Initialize ast lpc mctp binding

Message ID 20230511162446.10457-3-clombard@linux.ibm.com
State Superseded
Headers show
Series Implement MCTP and PLDM features | expand

Commit Message

Christophe Lombard May 11, 2023, 4:24 p.m. UTC
The Management Component Transport Protocol (MCTP) defines a communication
model intended to facilitate communication.

This patch initialize MCTP binding over LPC Bus interface.

Several steps must be performed:
- Initialize the MCTP core (mctp_init()).
- Initialize a hardware binding as AST LPC mode host (mctp_astlpc_init()).
- Register the hardware binding with the core (mctp_register_bus()), using
a predefined EID (Host default is 9).

To transmit a MCTP message, mctp_message_tx() is used.
To receive a MCTP message, a callback need to be provided and registered
through mctp_set_rx_all().

For the transfer of MCTP messages, two basics components are used:
- A window of the LPC FW address space, where reads and writes are
forwarded to BMC memory.
- An interrupt mechanism using the KCS interface.

hw/ast-bmc/ast-mctp.c is compilated if the compiler flag CONFIG_PLDM is
set.

Reviewed-by: Abhishek Singh Tomar <abhishek@linux.ibm.com>
Signed-off-by: Christophe Lombard <clombard@linux.ibm.com>
---
 hw/ast-bmc/Makefile.inc   |   7 +
 hw/ast-bmc/ast-mctp.c     | 422 ++++++++++++++++++++++++++++++++++++++
 include/ast.h             |  20 ++
 platforms/astbmc/common.c |  39 ++++
 4 files changed, 488 insertions(+)
 create mode 100644 hw/ast-bmc/ast-mctp.c
diff mbox series

Patch

diff --git a/hw/ast-bmc/Makefile.inc b/hw/ast-bmc/Makefile.inc
index e7ded0e8..546f2bc7 100644
--- a/hw/ast-bmc/Makefile.inc
+++ b/hw/ast-bmc/Makefile.inc
@@ -2,5 +2,12 @@ 
 SUBDIRS += hw/ast-bmc
 
 AST_BMC_OBJS  = ast-io.o ast-sf-ctrl.o
+
+ifeq ($(CONFIG_PLDM),1)
+CPPFLAGS += -I$(SRC)/libmctp/
+#CFLAGS += -DAST_MCTP_DEBUG
+AST_BMC_OBJS += ast-mctp.o
+endif
+
 AST_BMC = hw/ast-bmc/built-in.a
 $(AST_BMC): $(AST_BMC_OBJS:%=hw/ast-bmc/%)
diff --git a/hw/ast-bmc/ast-mctp.c b/hw/ast-bmc/ast-mctp.c
new file mode 100644
index 00000000..9a9e0484
--- /dev/null
+++ b/hw/ast-bmc/ast-mctp.c
@@ -0,0 +1,422 @@ 
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+// Copyright 2022 IBM Corp.
+
+#define pr_fmt(fmt) "AST-MCTP: " fmt
+
+#include <lock.h>
+#include <lpc.h>
+#include <interrupts.h>
+#include <timer.h>
+#include <timebase.h>
+#include <debug_descriptor.h>
+#include <device.h>
+#include <ast.h>
+#include <console.h>
+#include <libmctp.h>
+#include <libmctp-cmds.h>
+#include <libmctp-log.h>
+#include <libmctp-astlpc.h>
+
+static struct mctp *mctp;
+static struct mctp_binding_astlpc *astlpc;
+static struct astlpc_ops_data *ops_data;
+static struct lock mctp_lock = LOCK_UNLOCKED;
+
+/* Keyboard Controller Style (KCS) data register address */
+#define KCS_DATA_REG 0xca2
+
+/* Keyboard Controller Style (KCS) status register address */
+#define KCS_STATUS_REG 0xca3
+
+#define KCS_STATUS_BMC_READY 0x80
+#define KCS_STATUS_OBF       0x01
+
+#define HOST_MAX_INCOMING_MESSAGE_ALLOCATION 131072
+#define DESIRED_MTU 32768
+
+/*
+ * The AST LPC binding is described here:
+ *
+ * https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-ibm-astlpc.md
+ *
+ * Most of the binding is implemented in libmctp, but we need to provide
+ * accessors for the LPC FW space (for the packet buffer) and for the KCS
+ * peripheral (for the interrupt mechanism).
+ */
+
+struct astlpc_ops_data {
+	uint16_t kcs_data_addr; /* LPC IO space offset for the data register */
+	uint16_t kcs_stat_addr;
+
+	/* address of the packet exchange buffer in FW space */
+	uint32_t lpc_fw_addr;
+};
+
+static int astlpc_kcs_reg_read(void *binding_data,
+			       enum mctp_binding_astlpc_kcs_reg reg,
+			       uint8_t *val)
+{
+	struct astlpc_ops_data *ops_data = binding_data;
+	uint32_t addr;
+
+	if (reg == MCTP_ASTLPC_KCS_REG_STATUS)
+		addr = ops_data->kcs_stat_addr;
+	else if (reg == MCTP_ASTLPC_KCS_REG_DATA)
+		addr = ops_data->kcs_data_addr;
+	else
+		return OPAL_PARAMETER;
+
+	*val = lpc_inb(addr);
+
+	prlog(PR_TRACE, "%s: 0x%hhx from %s\n",
+			__func__, *val, reg ? "status" : "data");
+
+	return OPAL_SUCCESS;
+}
+
+static int astlpc_kcs_reg_write(void *binding_data,
+				enum mctp_binding_astlpc_kcs_reg reg,
+				uint8_t val)
+{
+	struct astlpc_ops_data *ops_data = binding_data;
+	uint32_t addr;
+
+	prlog(PR_TRACE, "%s 0x%hhx to %s\n",
+			__func__, val, reg ? "status" : "data");
+
+	if (reg == MCTP_ASTLPC_KCS_REG_STATUS)
+		addr = ops_data->kcs_stat_addr;
+	else if (reg == MCTP_ASTLPC_KCS_REG_DATA)
+		addr = ops_data->kcs_data_addr;
+	else
+		return OPAL_PARAMETER;
+
+	lpc_outb(val, addr);
+
+	return OPAL_SUCCESS;
+}
+
+static int astlpc_read(void *binding_data, void *buf, long offset,
+		       size_t len)
+{
+	struct astlpc_ops_data *ops_data = binding_data;
+
+	prlog(PR_TRACE, "%s %zu bytes from 0x%lx (lpc: 0x%lx)\n",
+			__func__, len, offset,
+			ops_data->lpc_fw_addr + offset);
+	return lpc_fw_read(ops_data->lpc_fw_addr + offset, buf, len);
+}
+
+static int astlpc_write(void *binding_data, const void *buf,
+			long offset, size_t len)
+{
+	struct astlpc_ops_data *ops_data = binding_data;
+
+	prlog(PR_TRACE, "%s %zu bytes to offset 0x%lx (lpc: 0x%lx)\n",
+			__func__, len, offset,
+			ops_data->lpc_fw_addr + offset);
+	return lpc_fw_write(ops_data->lpc_fw_addr + offset, buf, len);
+}
+
+static const struct mctp_binding_astlpc_ops astlpc_ops = {
+	.kcs_read = astlpc_kcs_reg_read,
+	.kcs_write = astlpc_kcs_reg_write,
+	.lpc_read = astlpc_read,
+	.lpc_write = astlpc_write,
+};
+
+/* we need a poller to crank the mctp state machine during boot */
+static void astlpc_poller(void *data)
+{
+	struct mctp_binding_astlpc *astlpc = (struct mctp_binding_astlpc *)data;
+
+	if (astlpc)
+		mctp_astlpc_poll(astlpc);
+}
+
+/* at runtime the interrupt should handle it */
+static void astlpc_interrupt(uint32_t chip_id __unused,
+			     uint32_t irq_msk __unused)
+{
+	if (astlpc)
+		mctp_astlpc_poll(astlpc);
+}
+
+static struct lpc_client kcs_lpc_client = {
+	.reset = NULL,
+	.interrupt = astlpc_interrupt,
+};
+
+static void drain_odr(struct astlpc_ops_data *ops_data)
+{
+	uint8_t kcs_status, kcs_data;
+	uint8_t drain_counter = 255;
+
+	astlpc_kcs_reg_read(ops_data, MCTP_ASTLPC_KCS_REG_STATUS, &kcs_status);
+
+	while (--drain_counter && (kcs_status & KCS_STATUS_OBF)) {
+		astlpc_kcs_reg_read(ops_data, MCTP_ASTLPC_KCS_REG_DATA, &kcs_data);
+		time_wait_ms(5);
+		astlpc_kcs_reg_read(ops_data, MCTP_ASTLPC_KCS_REG_STATUS, &kcs_status);
+	}
+}
+
+static int astlpc_binding(void)
+{
+	struct mctp_bus *bus;
+	int counter = 0;
+
+	ops_data = zalloc(sizeof(struct astlpc_ops_data));
+	if (!ops_data)
+		return OPAL_NO_MEM;
+
+	/*
+	 * Current OpenBMC systems put the MCTP buffer 1MB down from
+	 * the end of the LPC FW range.
+	 *
+	 * The size of the FW range is: 0x1000_0000 so the window be at:
+	 *
+	 *   0x1000_0000 - 2**20 == 0xff00000
+	 */
+	ops_data->lpc_fw_addr = 0xff00000;
+
+	/* values chosen by the OpenBMC driver */
+	ops_data->kcs_data_addr = KCS_DATA_REG;
+	ops_data->kcs_stat_addr = KCS_STATUS_REG;
+
+	/* Initialise the binding */
+	astlpc = mctp_astlpc_init(MCTP_BINDING_ASTLPC_MODE_HOST,
+				  DESIRED_MTU,
+				  NULL,
+				  &astlpc_ops,
+				  ops_data);
+	if (!astlpc) {
+		prlog(PR_ERR, "binding init failed\n");
+		return OPAL_HARDWARE;
+	}
+
+	/* Read and discard any potentially stale messages in the ODR */
+	drain_odr(ops_data);
+
+	/* Register the binding to the LPC bus we are using for this
+	 * MCTP configuration.
+	 */
+	if (mctp_register_bus(mctp,
+			      mctp_binding_astlpc_core(astlpc),
+			      HOST_EID)) {
+		prlog(PR_ERR, "failed to register bus\n");
+		goto err;
+	}
+
+	/* lpc/kcs status register poller */
+	opal_add_poller(astlpc_poller, astlpc);
+
+	/* Don't start sending messages to the BMC until the bus has
+	 * been registered and tx has been enabled */
+	bus = mctp_binding_astlpc_core(astlpc)->bus;
+
+	while((bus == NULL) ||
+	      (mctp_bus_get_state(bus) == mctp_bus_state_constructed)) {
+		if(++counter >= 1000) {
+			prlog(PR_ERR, "failed to initialize MCTP channel\n");
+			goto err;
+		}
+		time_wait_ms(5);
+		/* Update bus pointer if it is a nullptr */
+		if (bus == NULL)
+			bus = mctp_binding_astlpc_core(astlpc)->bus;
+	}
+
+	return OPAL_SUCCESS;
+
+err:
+	opal_del_poller(astlpc_poller);
+	mctp_astlpc_destroy(astlpc);
+	free(ops_data);
+
+	return OPAL_HARDWARE;
+}
+
+static void *mctp_malloc(size_t size) { return malloc(size); }
+static void mctp_free(void *ptr) { return free(ptr); }
+static void *mctp_realloc(void *ptr, size_t size)
+{
+	return realloc(ptr, size);
+}
+
+#ifdef AST_MCTP_DEBUG
+char buffer[320];
+static void mctp_log(int log_lvl, const char *fmt, va_list va)
+{
+	snprintf(buffer, sizeof(buffer), "%s\n", fmt);
+	vprlog(log_lvl, buffer, va);
+}
+#endif
+
+int ast_mctp_message_tx(bool tag_owner, uint8_t msg_tag,
+			uint8_t *msg, int msg_len)
+{
+	int rc;
+
+	lock(&mctp_lock);
+
+	rc = mctp_message_tx(mctp, BMC_EID, tag_owner, msg_tag,
+			     msg, msg_len);
+	unlock(&mctp_lock);
+
+	/* --------------  WORKAROUND -------------- */
+
+	/* From kcs_bmc_aspeed.c
+	 *	The ASPEED KCS devices don't provide a BMC-side interrupt for
+	 *	the host reading the output data register (ODR). The act of the
+	 *	host reading ODR clears the output buffer full (OBF) flag in the
+	 *	status register (STR), informing the BMC it can transmit a
+	 *	subsequent byte.
+	 *	On the BMC side the KCS client must enable the OBE event *and*
+	 *	perform a subsequent read of STR anyway to avoid races - the
+	 *	polling provides a window for the host to read ODR if data was
+	 *	freshly written while minimising BMC-side latency.
+	 *
+	 *	Given we don't have an OBE IRQ, delay by polling briefly to see
+	 *	if we can observe such an event before returning to the caller.
+	 *	This is not incorrect because OBF may have already become clear
+	 *	before enabling the IRQ if we had one, under which circumstance
+	 *	no event will be propagated anyway.
+	 *
+	 *
+	 * [  261.510224532,7] PLDM: encode_and_queue_get_pdr_req - record_hndl: 0
+	 * [  261.510314132,7] AST-MCTP: write KCS data 0x1       <= Tx begin
+
+	 *		[147727.652282] misc raw-kcs3: Disabling IDR events for back-pressure
+	 *		[147727.652321] misc raw-kcs3: IDR read, waking waiters
+	 *		[147727.652383] misc raw-kcs3: Read status 0xc2       <= input buffer full
+	 *		[147727.652403] misc raw-kcs3: Waiting for IBF        <= probably read Tx begin
+	 *		[147727.652413] misc raw-kcs3: Woken by IBF, enabling IRQ
+	 *		[147727.652440] misc raw-kcs3: Read status 0xc0
+	 *		[147727.652456] misc raw-kcs3: Writing 0x2 to ODR      <= Rx complete
+	 *		[147727.655276] misc raw-kcs3: Read status 0xc1        <= output buffer full, from poll()
+
+	 * [  261.510376528,7] AST-MCTP: read KCS status 0xc2     <= input buffer full
+	 * [  262.001010946,7] AST-MCTP: read KCS status 0xc1     <= output buffer full
+	 * [  262.001067507,7] AST-MCTP: read KCS data 0x2        <= Rx complete
+
+	 *		[147728.156473] misc raw-kcs3: ODR writable, waking waiters
+	 *		[147728.156583] misc raw-kcs3: Read status 0xc0
+	 *		[147728.156608] misc raw-kcs3: Writing 0x1 to ODR
+	 *		[147728.156627] misc raw-kcs3: Read status 0xc1
+	 *		[147728.160802] misc raw-kcs3: Disabling IDR events for back-pressure
+
+	 * [  262.003679442,7] AST-MCTP: read KCS status 0xc1     <= output buffer full
+	 * [  262.003720291,7] AST-MCTP: read KCS data 0x1        <= Tx Begin
+	 * [  262.003779271,7] AST-MCTP: write KCS data 0x2       <= Tx Complete
+
+	 *		[147728.160833] misc raw-kcs3: IDR read, waking waiters
+	 *		[147728.160848] misc raw-kcs3: ODR writable, waking waiters
+	 *		[147728.160989] misc raw-kcs3: Read status 0xc2
+
+	 * [  262.003822967,7] PLDM: get_pdr_req_complete - record_hndl: 0
+	 * [  262.003941315,7] PLDM: get_pdr_req_complete - record_hndl: 0, next_record_hndl: 2, resp_cnt: 20
+	 */
+
+	/* To inform the BMC to transmit the data immediately, we must read,
+	 * immediately after sending the request, the ODR register to clear the
+	 * output buffer full (OBF) flag in the status register (STR).
+	 * Otherwise the bmc will only be awakened during the interruption, ie
+	 * every 500ms.
+	 */
+
+	for (int i = 0; i < 300; i++)
+		mctp_astlpc_poll(astlpc);
+
+	/* --------------  WORKAROUND -------------- */
+
+	return rc;
+}
+
+static void message_rx(uint8_t eid, bool tag_owner,
+		       uint8_t msg_tag, void *data __unused,
+		       void *vmsg, size_t len)
+{
+	uint8_t *msg = (uint8_t *)vmsg;
+
+	prlog(PR_TRACE, "message received: msg type: %x, len %zd "
+			"(eid: %d), rx tag %d owner %d \n",
+			 *msg, len, eid, tag_owner, msg_tag);
+}
+
+/*
+ * Initialize mctp binding for hbrt and provide interfaces for sending
+ * and receiving mctp messages.
+ */
+int ast_mctp_init(void)
+{
+	uint32_t kcs_serial_irq;
+	struct dt_node *n;
+
+	/* Search mctp node */
+	n = dt_find_compatible_node(dt_root, NULL, "mctp");
+	if (!n) {
+		prlog(PR_ERR, "No MCTP device\n");
+		return OPAL_PARAMETER;
+	}
+
+	/* skiboot's malloc/free/realloc are macros so they need
+	 * wrappers
+	 */
+	mctp_set_alloc_ops(mctp_malloc, mctp_free, mctp_realloc);
+
+	/*
+	 * /-----\                                        /---------\
+	 * | bmc | (eid: 8) <- lpc pcie / kcs -> (eid: 9) | skiboot |
+	 * \-----/                                        \---------/
+	 */
+	mctp = mctp_init();
+	if (!mctp) {
+		prlog(PR_ERR, "mctp init failed\n");
+		return OPAL_HARDWARE;
+	}
+
+#ifdef AST_MCTP_DEBUG
+	/* Setup the trace hook */
+	mctp_set_log_custom(mctp_log);
+#endif
+
+	/* Set the max message size to be large enough */
+	mctp_set_max_message_size(mctp, HOST_MAX_INCOMING_MESSAGE_ALLOCATION);
+
+	/* Setup the message rx callback */
+	mctp_set_rx_all(mctp, message_rx, NULL);
+
+	/* Initialize the binding */
+	if (astlpc_binding())
+		goto err;
+
+	/* register an lpc client so we get an interrupt */
+	kcs_serial_irq = dt_prop_get_u32(n, "interrupts");
+	kcs_lpc_client.interrupts = LPC_IRQ(kcs_serial_irq);
+	lpc_register_client(dt_get_chip_id(n), &kcs_lpc_client, IRQ_ATTR_TARGET_OPAL);
+
+	return OPAL_SUCCESS;
+
+err:
+	prlog(PR_ERR, "Unable to initialize MCTP\n");
+	opal_del_poller(astlpc_poller);
+	mctp_destroy(mctp);
+	mctp = NULL;
+
+	return OPAL_HARDWARE;
+}
+
+void ast_mctp_exit(void)
+{
+	if (astlpc) {
+		opal_del_poller(astlpc_poller);
+		mctp_astlpc_destroy(astlpc);
+		astlpc = NULL;
+	}
+
+	if (mctp) {
+		mctp_destroy(mctp);
+		mctp = NULL;
+	}
+}
diff --git a/include/ast.h b/include/ast.h
index 5e932398..50e3b932 100644
--- a/include/ast.h
+++ b/include/ast.h
@@ -91,6 +91,26 @@  void ast_setup_ibt(uint16_t io_base, uint8_t irq);
 /* MBOX configuration */
 void ast_setup_sio_mbox(uint16_t io_base, uint8_t irq);
 
+/* MCTP configuration */
+
+/*
+ * EID is the MCTP endpoint ID, which aids in routing MCTP packets.
+ * For the EIDs: the valid range is 8-254.
+ * We are saying that BMC is EID 8 and Skiboot is HOST_EID 9
+ */
+#define BMC_EID  8
+#define HOST_EID 9
+
+enum mctp_msg_type {
+      MCTP_MSG_TYPE_CONTROL = 0x00,
+      MCTP_MSG_TYPE_PLDM    = 0x01,
+};
+
+int ast_mctp_message_tx(bool tag_owner, uint8_t msg_tag,
+			uint8_t *msg, int msg_len);
+int ast_mctp_init(void);
+void ast_mctp_exit(void);
+
 #endif /* __SKIBOOT__ */
 
 /*
diff --git a/platforms/astbmc/common.c b/platforms/astbmc/common.c
index 83ef70ad..cba01000 100644
--- a/platforms/astbmc/common.c
+++ b/platforms/astbmc/common.c
@@ -31,6 +31,11 @@ 
 #define MBOX_IO_COUNT 6
 #define MBOX_LPC_IRQ 9
 
+/* MCTP config */
+#define MCTP_IO_BASE	0xca2
+#define MCTP_IO_COUNT	2
+#define MCTP_LPC_IRQ	11
+
 void astbmc_ext_irq_serirq_cpld(unsigned int chip_id)
 {
 	lpc_all_interrupts(chip_id);
@@ -227,6 +232,37 @@  static void astbmc_fixup_dt_system_id(void)
 	dt_add_property_strings(dt_root, "system-id", "unavailable");
 }
 
+static void astbmc_fixup_dt_mctp(struct dt_node *lpc)
+{
+	struct dt_node *mctp;
+	char namebuf[32];
+
+	return;
+
+	if (!lpc)
+		return;
+
+	/* First check if the mbox interface is already there */
+	dt_for_each_child(lpc, mctp) {
+		if (dt_node_is_compatible(mctp, "mctp"))
+			return;
+	}
+
+	snprintf(namebuf, sizeof(namebuf), "mctp@i%x", MCTP_IO_BASE);
+	mctp = dt_new(lpc, namebuf);
+
+	dt_add_property_cells(mctp, "reg",
+			      1, /* IO space */
+			      MCTP_IO_BASE, MCTP_IO_COUNT);
+	dt_add_property_strings(mctp, "compatible", "mctp");
+
+	/* Mark it as reserved to avoid Linux trying to claim it */
+	dt_add_property_strings(mctp, "status", "reserved");
+
+	dt_add_property_cells(mctp, "interrupts", MCTP_LPC_IRQ);
+	dt_add_property_cells(mctp, "interrupt-parent", lpc->phandle);
+}
+
 static void astbmc_fixup_dt_bt(struct dt_node *lpc)
 {
 	struct dt_node *bt;
@@ -404,6 +440,9 @@  static void astbmc_fixup_dt(void)
 	/* BT is not in HB either */
 	astbmc_fixup_dt_bt(primary_lpc);
 
+	/* Fixup the MCTP, that might be missing from HB */
+	astbmc_fixup_dt_mctp(primary_lpc);
+
 	/* The pel logging code needs a system-id property to work so
 	   make sure we have one. */
 	astbmc_fixup_dt_system_id();