diff mbox series

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

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

Commit Message

Christophe Lombard Sept. 13, 2022, 10:26 a.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.

Signed-off-by: Christophe Lombard <clombard@linux.vnet.ibm.com>
---
 hw/ast-bmc/Makefile.inc |   7 +
 hw/ast-bmc/ast-mctp.c   | 386 ++++++++++++++++++++++++++++++++++++++++
 include/ast.h           |  11 ++
 3 files changed, 404 insertions(+)
 create mode 100644 hw/ast-bmc/ast-mctp.c

Comments

Abhishek Singh Tomar Sept. 29, 2022, 10:42 a.m. UTC | #1
On Tue, Sep 13, 2022 at 12:26:46PM +0200, Christophe Lombard wrote:
> 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.
> 
> Signed-off-by: Christophe Lombard <clombard@linux.vnet.ibm.com>
Reviewed-by: Abhishek Singh Tomar <abhishek@linux.ibm.com>
> ---
>  hw/ast-bmc/Makefile.inc |   7 +
>  hw/ast-bmc/ast-mctp.c   | 386 ++++++++++++++++++++++++++++++++++++++++
>  include/ast.h           |  11 ++
>  3 files changed, 404 insertions(+)
>  create mode 100644 hw/ast-bmc/ast-mctp.c
> 
> 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..1e7bdb51
> --- /dev/null
> +++ b/hw/ast-bmc/ast-mctp.c
> @@ -0,0 +1,386 @@
> +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
> +// Copyright 2022 IBM Corp.
> +
> +#define pr_fmt(fmt) "AST-MCTP: " fmt
> +
> +#include <lpc.h>
> +#include <interrupts.h>
> +#include <timer.h>
> +#include <timebase.h>
> +#include <debug_descriptor.h>
> +#include <ast.h>
> +#include <console.h>
> +#include <libmctp.h>
> +#include <libmctp-cmds.h>
> +#include <libmctp-log.h>
> +#include <libmctp-astlpc.h>
> +
> +/*
> + * For the EIDs: the valid range is 8-254.
> + * host default = 9
> + */
> +uint8_t HOST_EID = 9;
> +
> +struct timer ast_boot_timer;
> +static struct mctp *mctp;
> +
> +struct ast_mctp_backend {
> +	bool (*ready)(void);
> +	int (*binding)(void);
> +	void (*destroy)(void);
> +};
> +const struct ast_mctp_backend *mctp_backend;
> +
> +/*
> + * The AST LPC binding is described here:
> + *
> + * https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-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, tmp;
> +	int rc;
> +
> +	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;
> +
> +	rc = lpc_read(OPAL_LPC_IO, addr, &tmp, 1);
> +	if (!rc)
> +		*val = tmp & 0xff;
> +	else
> +		*val = 0xff;
> +
> +	mctp_prdebug("%s 0x%hhx from %s (%d)",
> +		     __func__, *val, reg ? "status" : "data", rc);
> +
> +	return rc;
> +}
> +
> +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;
> +
> +	mctp_prdebug("%s 0x%hhx to %s",
> +		     __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;
> +
> +	return lpc_write(OPAL_LPC_IO, addr, val, 1);
> +}
> +
> +static int astlpc_read(void *binding_data, void *buf, long offset,
> +		       size_t len)
> +{
> +	struct astlpc_ops_data *ops_data = binding_data;
> +
> +	mctp_prdebug("%s %zu bytes from 0x%lx (lpc: 0x%lx)",
> +		     __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;
> +
> +	mctp_prdebug("%s %zu bytes to offset 0x%lx (lpc: 0x%lx)",
> +		     __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,
> +};
> +
> +static struct mctp_binding_astlpc *astlpc;
> +static struct astlpc_ops_data *ops_data;
> +
> +/* Keyboard Controller Style (KCS) data register address */
> +#define KCS_DATA_REG 0xca2
> +
> +/* Keyboard Controller Style (KCS) status register address */
> +#define KCS_STATUS_REG 0xca3
> +
> +/* Serial IRQ number for KCS interface */
> +#define KCS_SERIAL_IRQ 11
> +
> +#define KCS_STATUS_BMC_READY 0x80
> +
> +bool irq_active;
> +
> +/* we need a poller to crank the mctp state machine during boot */
> +static void astlpc_poller(struct timer *t __unused,
> +			  void *data __unused, uint64_t now __unused)
> +{
> +	mctp_astlpc_poll(astlpc);
> +
> +	/* once booted we can rely on the KCS interrupt */
> +	if ((opal_booting() && !irq_active))
> +		schedule_timer(&ast_boot_timer, msecs_to_tb(5));
> +}
> +
> +/* at runtime the interrupt should handle it */
> +static void astlpc_interrupt(uint32_t chip_id __unused,
> +			     uint32_t irq_msk __unused)
> +{
> +	if (!irq_active) {
> +		mctp_prerr("IRQ Active! Disabling boot poller...");
> +		irq_active = true;
> +	}
> +
> +	mctp_astlpc_poll(astlpc);
> +}
> +
> +static struct lpc_client kcs_lpc_client = {
> +	.reset = NULL,
> +	.interrupt = astlpc_interrupt,
> +	.interrupts = LPC_IRQ(KCS_SERIAL_IRQ),
> +};
> +
> +static bool astlpc_ready(void)
> +{
> +	uint32_t tmp = 0;
> +
> +	/* Probe for MCTP support by reading the STAT register of KCS#4 */
> +	if (lpc_probe_read(OPAL_LPC_IO, KCS_STATUS_REG, &tmp, 1)) {
> +		mctp_prdebug("KCS4 probe failed, skipping MCTP init");
> +		return false;
> +	}
> +
> +	return !!(tmp & KCS_STATUS_BMC_READY); /* high bit indicates BMC is listening */
> +}
> +
> +#define DESIRED_MTU 32768
> +
> +static void astlpc_destroy(void)
> +{
> +	if (astlpc) {
> +		mctp_astlpc_destroy(astlpc);
> +		cancel_timer(&ast_boot_timer);
> +		astlpc = NULL;
> +	}
> +}
> +
> +static int astlpc_binding(void)
> +{
> +	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) {
> +		mctp_prerr("binding init failed");
> +		return OPAL_HARDWARE;
> +	}
> +
> +	/* register an lpc client so we get an interrupt */
> +	lpc_register_client(0, &kcs_lpc_client, IRQ_ATTR_TARGET_OPAL);
> +
> +	init_timer(&ast_boot_timer, astlpc_poller, astlpc);
> +	schedule_timer(&ast_boot_timer, msecs_to_tb(5));
> +
> +	/* 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)) {
> +		mctp_prerr("failed to register bus");
> +		goto err;
> +	}
> +
> +	return OPAL_SUCCESS;
> +
> +err:
> +	astlpc_destroy();
> +	free(ops_data);
> +
> +	return OPAL_HARDWARE;
> +}
> +
> +static const struct ast_mctp_backend astlpc_backend = {
> +	.ready = astlpc_ready,
> +	.binding = astlpc_binding,
> +	.destroy = astlpc_destroy,
> +};
> +
> +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_ready(void)
> +{
> +	if (!mctp_backend->ready())
> +		return OPAL_BUSY;
> +
> +	return OPAL_SUCCESS;
> +}
> +
> +int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len)
> +{
> +	uint8_t tag = 0;
> +
> +	return mctp_message_tx(mctp, eid, MCTP_MESSAGE_TO_DST, tag, msg, len);
> +}
> +
> +void ast_mctp_stop_polling(void)
> +{
> +	irq_active = true;
> +}
> +
> +#define HOST_MAX_INCOMING_MESSAGE_ALLOCATION 131072
> +
> +/*
> + * Initialize mctp binding for hbrt and provide interfaces for sending
> + * and receiving mctp messages.
> + */
> +int ast_mctp_init(uint8_t mode, void (*fn)(uint8_t src_eid, bool tag_owner,
> +		  uint8_t msg_tag, void *data, void *msg, size_t len))
> +{
> +	uint64_t start;
> +
> +	if (mode == MCTP_BINDING_ASTLPC_MODE)
> +		mctp_backend = &astlpc_backend;
> +	else {
> +		mctp_prerr("Unknown AST binding mode: %u", mode);
> +		return OPAL_PARAMETER;
> +	}
> +
> +	if (!mctp_backend->ready())
> +		return OPAL_HARDWARE;
> +
> +	/* 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/kcs -> (eid: 9) | skiboot |
> +	 * \-----/               pcie              \---------/
> +	 *           astlpc kcs / astpcie binding
> +	 */
> +	mctp = mctp_init();
> +	if (!mctp) {
> +		mctp_prerr("mctp init failed");
> +		return OPAL_HARDWARE;
> +	}
> +
> +#ifdef AST_MCTP_DEBUG
> +	/* Setup the trace hook */
> +	mctp_set_log_custom(mctp_log);
> +#endif
> +
> +	mctp_prinfo("libmctp initialized");
> +
> +	/* 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, fn, NULL);
> +
> +	/* Initialise the binding */
> +	if (mctp_backend->binding())
> +		goto err;
> +
> +	/* Initializing the channel requires running the poll function
> +	 * a few times to handle the initial setup since we need the
> +	 * BMC to reply.
> +	 *
> +	 * Unfortuntately there's no good way to determine if this
> +	 * worked or not so poll for a bit with a timeout. :(
> +	 *
> +	 * We have to check (bus->state == mctp_bus_state_constructed)
> +	 * otherwise mctp_message_tx() will reject the message.
> +	 */
> +	start = mftb();
> +	while (tb_compare(mftb(), start + msecs_to_tb(250)) == TB_ABEFOREB)
> +		time_wait_ms(10);
> +
> +	mctp_prwarn("%s - The mctp bus state should be correct now !!!",
> +		    __func__);
> +
> +	return OPAL_SUCCESS;
> +
> +err:
> +	mctp_prerr("Unable to initialize MCTP");
> +	mctp_destroy(mctp);
> +	mctp = NULL;
> +
> +	return OPAL_HARDWARE;
> +}
> +
> +void ast_mctp_exit(void)
> +{
> +	if (mctp_backend && mctp_backend->destroy)
> +		mctp_backend->destroy();
> +
> +	if (mctp)
> +		mctp_destroy(mctp);
> +
> +	mctp = NULL;
> +}
> diff --git a/include/ast.h b/include/ast.h
> index 5e932398..55eeae28 100644
> --- a/include/ast.h
> +++ b/include/ast.h
> @@ -91,6 +91,17 @@ 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);
> 
> +/* AST MCTP configuration */
> +#define MCTP_BINDING_ASTLPC_MODE 0
> +#define MCTP_BINDING_ASTPCIE_MODE 1
> +
> +int ast_mctp_ready(void);
> +int ast_mctp_init(uint8_t mode, void (*fn)(uint8_t src_eid, bool tag_owner,
> +		  uint8_t msg_tag, void *data, void *msg, size_t len));
> +void ast_mctp_exit(void);
> +void ast_mctp_stop_polling(void);
> +int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len);
> +
>  #endif /* __SKIBOOT__ */
> 
>  /*
> -- 
> 2.37.3
> 
> _______________________________________________
> Skiboot mailing list
> Skiboot@lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/skiboot
Frederic Barrat April 6, 2023, 5:16 p.m. UTC | #2
On 13/09/2022 12:26, Christophe Lombard wrote:
> 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.
> 
> Signed-off-by: Christophe Lombard <clombard@linux.vnet.ibm.com>
> ---
>   hw/ast-bmc/Makefile.inc |   7 +
>   hw/ast-bmc/ast-mctp.c   | 386 ++++++++++++++++++++++++++++++++++++++++
>   include/ast.h           |  11 ++
>   3 files changed, 404 insertions(+)
>   create mode 100644 hw/ast-bmc/ast-mctp.c
> 
> 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..1e7bdb51
> --- /dev/null
> +++ b/hw/ast-bmc/ast-mctp.c
> @@ -0,0 +1,386 @@
> +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
> +// Copyright 2022 IBM Corp.
> +
> +#define pr_fmt(fmt) "AST-MCTP: " fmt
> +
> +#include <lpc.h>
> +#include <interrupts.h>
> +#include <timer.h>
> +#include <timebase.h>
> +#include <debug_descriptor.h>
> +#include <ast.h>
> +#include <console.h>
> +#include <libmctp.h>
> +#include <libmctp-cmds.h>
> +#include <libmctp-log.h>
> +#include <libmctp-astlpc.h>
> +
> +/*
> + * For the EIDs: the valid range is 8-254.
> + * host default = 9
> + */
> +uint8_t HOST_EID = 9;
> +
> +struct timer ast_boot_timer;
> +static struct mctp *mctp;
> +
> +struct ast_mctp_backend {
> +	bool (*ready)(void);
> +	int (*binding)(void);
> +	void (*destroy)(void);
> +};
> +const struct ast_mctp_backend *mctp_backend;
> +
> +/*
> + * The AST LPC binding is described here:
> + *
> + * https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-astlpc.md


The correct URL is now:
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, tmp;
> +	int rc;
> +
> +	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;
> +
> +	rc = lpc_read(OPAL_LPC_IO, addr, &tmp, 1);
> +	if (!rc)
> +		*val = tmp & 0xff;
> +	else
> +		*val = 0xff;
> +
> +	mctp_prdebug("%s 0x%hhx from %s (%d)",
> +		     __func__, *val, reg ? "status" : "data", rc);
> +
> +	return rc;
> +}
> +
> +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;
> +
> +	mctp_prdebug("%s 0x%hhx to %s",
> +		     __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;
> +
> +	return lpc_write(OPAL_LPC_IO, addr, val, 1);
> +}
> +
> +static int astlpc_read(void *binding_data, void *buf, long offset,
> +		       size_t len)
> +{
> +	struct astlpc_ops_data *ops_data = binding_data;
> +
> +	mctp_prdebug("%s %zu bytes from 0x%lx (lpc: 0x%lx)",
> +		     __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;
> +
> +	mctp_prdebug("%s %zu bytes to offset 0x%lx (lpc: 0x%lx)",
> +		     __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,
> +};
> +
> +static struct mctp_binding_astlpc *astlpc;
> +static struct astlpc_ops_data *ops_data;
> +
> +/* Keyboard Controller Style (KCS) data register address */
> +#define KCS_DATA_REG 0xca2
> +
> +/* Keyboard Controller Style (KCS) status register address */
> +#define KCS_STATUS_REG 0xca3
> +
> +/* Serial IRQ number for KCS interface */
> +#define KCS_SERIAL_IRQ 11


These should probably be defined in platforms/astbmc/common.c and added 
to the device tree and the value read here from the device tree, like 
it's done for other LPC clients (UART, BT, MBOX).


> +
> +#define KCS_STATUS_BMC_READY 0x80
> +
> +bool irq_active;
> +
> +/* we need a poller to crank the mctp state machine during boot */
> +static void astlpc_poller(struct timer *t __unused,
> +			  void *data __unused, uint64_t now __unused)
> +{
> +	mctp_astlpc_poll(astlpc);
> +
> +	/* once booted we can rely on the KCS interrupt */
> +	if ((opal_booting() && !irq_active))


We shoult talk about interrupts, I'm pretty convinced the call to 
opal_booting() is useless, since external interrupts are not going to 
fire while we are in skiboot. But this could all be moot if we use an 
opal poller (see below).


> +		schedule_timer(&ast_boot_timer, msecs_to_tb(5));
> +}
> +
> +/* at runtime the interrupt should handle it */
> +static void astlpc_interrupt(uint32_t chip_id __unused,
> +			     uint32_t irq_msk __unused)
> +{
> +	if (!irq_active) {
> +		mctp_prerr("IRQ Active! Disabling boot poller...");
> +		irq_active = true;
> +	}
> +
> +	mctp_astlpc_poll(astlpc);
> +}
> +
> +static struct lpc_client kcs_lpc_client = {
> +	.reset = NULL,
> +	.interrupt = astlpc_interrupt,
> +	.interrupts = LPC_IRQ(KCS_SERIAL_IRQ),
> +};
> +
> +static bool astlpc_ready(void)
> +{
> +	uint32_t tmp = 0;
> +
> +	/* Probe for MCTP support by reading the STAT register of KCS#4 */
> +	if (lpc_probe_read(OPAL_LPC_IO, KCS_STATUS_REG, &tmp, 1)) {


We've talked about this, I've done some reading and I don't understand 
the need for lpc_probe_read() here. My understanding is that the "probe" 
version is more forgiving in case of errors (it temporarily masks the 
interrupt, does a read, then checks the error status register to report 
errors) and it is meant to be used with invalid addresses for example. 
It's not the case here. I've been running with a simple lpc_read() and 
it's working as expected for me.


> +		mctp_prdebug("KCS4 probe failed, skipping MCTP init");
> +		return false;
> +	}
> +
> +	return !!(tmp & KCS_STATUS_BMC_READY); /* high bit indicates BMC is listening */
> +}
> +
> +#define DESIRED_MTU 32768
> +
> +static void astlpc_destroy(void)
> +{
> +	if (astlpc) {
> +		mctp_astlpc_destroy(astlpc);
> +		cancel_timer(&ast_boot_timer);
> +		astlpc = NULL;
> +	}
> +}
> +
> +static int astlpc_binding(void)
> +{
> +	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;


The fw address is already defined in HDAT! Look for "mctp_base" in 
bmc_create_node(). Maybe it could be added to the device tree from HDAT 
and read here?


> +
> +	/* 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) {
> +		mctp_prerr("binding init failed");
> +		return OPAL_HARDWARE;
> +	}
> +
> +	/* register an lpc client so we get an interrupt */
> +	lpc_register_client(0, &kcs_lpc_client, IRQ_ATTR_TARGET_OPAL);
> +
> +	init_timer(&ast_boot_timer, astlpc_poller, astlpc);
> +	schedule_timer(&ast_boot_timer, msecs_to_tb(5));


I'm thinking we should convert this to an opal poller (by calling 
opal_add_poller()) instead of defining our own timer. It would call our 
polling function whenever opal_run_pollers() is called and we don't need 
to manage the timer (by re-arming it each time astlpc_poller() is called).


> +
> +	/* 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)) {
> +		mctp_prerr("failed to register bus");
> +		goto err;
> +	}
> +
> +	return OPAL_SUCCESS;
> +
> +err:


The timer needs to be cleaned up on the error path.


> +	astlpc_destroy();
> +	free(ops_data);
> +
> +	return OPAL_HARDWARE;
> +}
> +
> +static const struct ast_mctp_backend astlpc_backend = {
> +	.ready = astlpc_ready,
> +	.binding = astlpc_binding,
> +	.destroy = astlpc_destroy,
> +};
> +
> +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


I don't really like how logging is done in this file. We always reuse 
the libmctp trace macro, which means:
- if we don't define AST_MCTP_DEBUG, we get no traces at all, not even 
errors.
- if we define AST_MCTP_DEBUG, then we also get the traces from libmctp, 
which floods the in-memory skiboot trace buffer with traces such as:
[  371.500175890,7] AST-MCTP: astlpc_kcs_reg_read 0xc0 from status (0)
[  371.500219079,7] astlpc: host: mctp_astlpc_poll: status: 0xc0

Why not use the usual prlog() for traces from this file?
We can keep AST_MCTP_DEBUG just as a mean to control the libmctp tracing 
only.


> +
> +int ast_mctp_ready(void)
> +{
> +	if (!mctp_backend->ready())


That's an exported API, so it would be better to check if mctp_backend 
and mctp_backend->ready are non-NULL before calling it.


> +		return OPAL_BUSY;
> +
> +	return OPAL_SUCCESS;
> +}
> +
> +int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len)
> +{
> +	uint8_t tag = 0;
> +
> +	return mctp_message_tx(mctp, eid, MCTP_MESSAGE_TO_DST, tag, msg, len);
> +}
> +
> +void ast_mctp_stop_polling(void)
> +{


This function looks like dead code. Leftover from debug?


> +	irq_active = true;
> +}
> +
> +#define HOST_MAX_INCOMING_MESSAGE_ALLOCATION 131072
> +
> +/*
> + * Initialize mctp binding for hbrt and provide interfaces for sending
> + * and receiving mctp messages.
> + */
> +int ast_mctp_init(uint8_t mode, void (*fn)(uint8_t src_eid, bool tag_owner,
> +		  uint8_t msg_tag, void *data, void *msg, size_t len))
> +{
> +	uint64_t start;
> +
> +	if (mode == MCTP_BINDING_ASTLPC_MODE)
> +		mctp_backend = &astlpc_backend;
> +	else {
> +		mctp_prerr("Unknown AST binding mode: %u", mode);
> +		return OPAL_PARAMETER;
> +	}
> +
> +	if (!mctp_backend->ready())
> +		return OPAL_HARDWARE;
> +
> +	/* 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/kcs -> (eid: 9) | skiboot |
> +	 * \-----/               pcie              \---------/
> +	 *           astlpc kcs / astpcie binding
> +	 */
> +	mctp = mctp_init();
> +	if (!mctp) {
> +		mctp_prerr("mctp init failed");
> +		return OPAL_HARDWARE;
> +	}
> +
> +#ifdef AST_MCTP_DEBUG
> +	/* Setup the trace hook */
> +	mctp_set_log_custom(mctp_log);
> +#endif
> +
> +	mctp_prinfo("libmctp initialized");
> +
> +	/* 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, fn, NULL);
> +
> +	/* Initialise the binding */
> +	if (mctp_backend->binding())
> +		goto err;
> +
> +	/* Initializing the channel requires running the poll function
> +	 * a few times to handle the initial setup since we need the
> +	 * BMC to reply.
> +	 *
> +	 * Unfortuntately there's no good way to determine if this
> +	 * worked or not so poll for a bit with a timeout. :(
> +	 *
> +	 * We have to check (bus->state == mctp_bus_state_constructed)
> +	 * otherwise mctp_message_tx() will reject the message.
> +	 */
> +	start = mftb();
> +	while (tb_compare(mftb(), start + msecs_to_tb(250)) == TB_ABEFOREB)
> +		time_wait_ms(10);


Why not a simple time_wait_ms() for the full duration? It would call the 
opal_run_pollers(), which check the timers.

   Fred



> +
> +	mctp_prwarn("%s - The mctp bus state should be correct now !!!",
> +		    __func__);
> +
> +	return OPAL_SUCCESS;
> +
> +err:
> +	mctp_prerr("Unable to initialize MCTP");
> +	mctp_destroy(mctp);
> +	mctp = NULL;
> +
> +	return OPAL_HARDWARE;
> +}
> +
> +void ast_mctp_exit(void)
> +{
> +	if (mctp_backend && mctp_backend->destroy)
> +		mctp_backend->destroy();
> +
> +	if (mctp)
> +		mctp_destroy(mctp);
> +
> +	mctp = NULL;
> +}
> diff --git a/include/ast.h b/include/ast.h
> index 5e932398..55eeae28 100644
> --- a/include/ast.h
> +++ b/include/ast.h
> @@ -91,6 +91,17 @@ 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);
>   
> +/* AST MCTP configuration */
> +#define MCTP_BINDING_ASTLPC_MODE 0
> +#define MCTP_BINDING_ASTPCIE_MODE 1
> +
> +int ast_mctp_ready(void);
> +int ast_mctp_init(uint8_t mode, void (*fn)(uint8_t src_eid, bool tag_owner,
> +		  uint8_t msg_tag, void *data, void *msg, size_t len));
> +void ast_mctp_exit(void);
> +void ast_mctp_stop_polling(void);
> +int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len);
> +
>   #endif /* __SKIBOOT__ */
>   
>   /*
Christophe Lombard April 12, 2023, 7:45 a.m. UTC | #3
Le 06/04/2023 à 19:16, Frederic Barrat a écrit :
>
>
> On 13/09/2022 12:26, Christophe Lombard wrote:
>> 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.
>>
>> Signed-off-by: Christophe Lombard <clombard@linux.vnet.ibm.com>
>> ---
>>   hw/ast-bmc/Makefile.inc |   7 +
>>   hw/ast-bmc/ast-mctp.c   | 386 ++++++++++++++++++++++++++++++++++++++++
>>   include/ast.h           |  11 ++
>>   3 files changed, 404 insertions(+)
>>   create mode 100644 hw/ast-bmc/ast-mctp.c
>>
>> 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..1e7bdb51
>> --- /dev/null
>> +++ b/hw/ast-bmc/ast-mctp.c
>> @@ -0,0 +1,386 @@
>> +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
>> +// Copyright 2022 IBM Corp.
>> +
>> +#define pr_fmt(fmt) "AST-MCTP: " fmt
>> +
>> +#include <lpc.h>
>> +#include <interrupts.h>
>> +#include <timer.h>
>> +#include <timebase.h>
>> +#include <debug_descriptor.h>
>> +#include <ast.h>
>> +#include <console.h>
>> +#include <libmctp.h>
>> +#include <libmctp-cmds.h>
>> +#include <libmctp-log.h>
>> +#include <libmctp-astlpc.h>
>> +
>> +/*
>> + * For the EIDs: the valid range is 8-254.
>> + * host default = 9
>> + */
>> +uint8_t HOST_EID = 9;
>> +
>> +struct timer ast_boot_timer;
>> +static struct mctp *mctp;
>> +
>> +struct ast_mctp_backend {
>> +    bool (*ready)(void);
>> +    int (*binding)(void);
>> +    void (*destroy)(void);
>> +};
>> +const struct ast_mctp_backend *mctp_backend;
>> +
>> +/*
>> + * The AST LPC binding is described here:
>> + *
>> + * 
>> https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-astlpc.md
>
>
> The correct URL is now:
> https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-ibm-astlpc.md 
>
>

Thanks.

>
>> + *
>> + * 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, tmp;
>> +    int rc;
>> +
>> +    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;
>> +
>> +    rc = lpc_read(OPAL_LPC_IO, addr, &tmp, 1);
>> +    if (!rc)
>> +        *val = tmp & 0xff;
>> +    else
>> +        *val = 0xff;
>> +
>> +    mctp_prdebug("%s 0x%hhx from %s (%d)",
>> +             __func__, *val, reg ? "status" : "data", rc);
>> +
>> +    return rc;
>> +}
>> +
>> +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;
>> +
>> +    mctp_prdebug("%s 0x%hhx to %s",
>> +             __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;
>> +
>> +    return lpc_write(OPAL_LPC_IO, addr, val, 1);
>> +}
>> +
>> +static int astlpc_read(void *binding_data, void *buf, long offset,
>> +               size_t len)
>> +{
>> +    struct astlpc_ops_data *ops_data = binding_data;
>> +
>> +    mctp_prdebug("%s %zu bytes from 0x%lx (lpc: 0x%lx)",
>> +             __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;
>> +
>> +    mctp_prdebug("%s %zu bytes to offset 0x%lx (lpc: 0x%lx)",
>> +             __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,
>> +};
>> +
>> +static struct mctp_binding_astlpc *astlpc;
>> +static struct astlpc_ops_data *ops_data;
>> +
>> +/* Keyboard Controller Style (KCS) data register address */
>> +#define KCS_DATA_REG 0xca2
>> +
>> +/* Keyboard Controller Style (KCS) status register address */
>> +#define KCS_STATUS_REG 0xca3
>> +
>> +/* Serial IRQ number for KCS interface */
>> +#define KCS_SERIAL_IRQ 11
>
>
> These should probably be defined in platforms/astbmc/common.c and 
> added to the device tree and the value read here from the device tree, 
> like it's done for other LPC clients (UART, BT, MBOX).
>

ok will do.

>
>> +
>> +#define KCS_STATUS_BMC_READY 0x80
>> +
>> +bool irq_active;
>> +
>> +/* we need a poller to crank the mctp state machine during boot */
>> +static void astlpc_poller(struct timer *t __unused,
>> +              void *data __unused, uint64_t now __unused)
>> +{
>> +    mctp_astlpc_poll(astlpc);
>> +
>> +    /* once booted we can rely on the KCS interrupt */
>> +    if ((opal_booting() && !irq_active))
>
>
> We shoult talk about interrupts, I'm pretty convinced the call to 
> opal_booting() is useless, since external interrupts are not going to 
> fire while we are in skiboot. But this could all be moot if we use an 
> opal poller (see below).
>
>
>> + schedule_timer(&ast_boot_timer, msecs_to_tb(5));
>> +}
>> +
>> +/* at runtime the interrupt should handle it */
>> +static void astlpc_interrupt(uint32_t chip_id __unused,
>> +                 uint32_t irq_msk __unused)
>> +{
>> +    if (!irq_active) {
>> +        mctp_prerr("IRQ Active! Disabling boot poller...");
>> +        irq_active = true;
>> +    }
>> +
>> +    mctp_astlpc_poll(astlpc);
>> +}
>> +
>> +static struct lpc_client kcs_lpc_client = {
>> +    .reset = NULL,
>> +    .interrupt = astlpc_interrupt,
>> +    .interrupts = LPC_IRQ(KCS_SERIAL_IRQ),
>> +};
>> +
>> +static bool astlpc_ready(void)
>> +{
>> +    uint32_t tmp = 0;
>> +
>> +    /* Probe for MCTP support by reading the STAT register of KCS#4 */
>> +    if (lpc_probe_read(OPAL_LPC_IO, KCS_STATUS_REG, &tmp, 1)) {
>
>
> We've talked about this, I've done some reading and I don't understand 
> the need for lpc_probe_read() here. My understanding is that the 
> "probe" version is more forgiving in case of errors (it temporarily 
> masks the interrupt, does a read, then checks the error status 
> register to report errors) and it is meant to be used with invalid 
> addresses for example. It's not the case here. I've been running with 
> a simple lpc_read() and it's working as expected for me.
>
>

you have probably reason. We will use lpc_read() instead.

>> +        mctp_prdebug("KCS4 probe failed, skipping MCTP init");
>> +        return false;
>> +    }
>> +
>> +    return !!(tmp & KCS_STATUS_BMC_READY); /* high bit indicates BMC 
>> is listening */
>> +}
>> +
>> +#define DESIRED_MTU 32768
>> +
>> +static void astlpc_destroy(void)
>> +{
>> +    if (astlpc) {
>> +        mctp_astlpc_destroy(astlpc);
>> +        cancel_timer(&ast_boot_timer);
>> +        astlpc = NULL;
>> +    }
>> +}
>> +
>> +static int astlpc_binding(void)
>> +{
>> +    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;
>
>
> The fw address is already defined in HDAT! Look for "mctp_base" in 
> bmc_create_node(). Maybe it could be added to the device tree from 
> HDAT and read here?
>
>

Thanks.

>> +
>> +    /* 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) {
>> +        mctp_prerr("binding init failed");
>> +        return OPAL_HARDWARE;
>> +    }
>> +
>> +    /* register an lpc client so we get an interrupt */
>> +    lpc_register_client(0, &kcs_lpc_client, IRQ_ATTR_TARGET_OPAL);
>> +
>> +    init_timer(&ast_boot_timer, astlpc_poller, astlpc);
>> +    schedule_timer(&ast_boot_timer, msecs_to_tb(5));
>
>
> I'm thinking we should convert this to an opal poller (by calling 
> opal_add_poller()) instead of defining our own timer. It would call 
> our polling function whenever opal_run_pollers() is called and we 
> don't need to manage the timer (by re-arming it each time 
> astlpc_poller() is called).
>
>
>> +
>> +    /* 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)) {
>> +        mctp_prerr("failed to register bus");
>> +        goto err;
>> +    }
>> +
>> +    return OPAL_SUCCESS;
>> +
>> +err:
>
>
> The timer needs to be cleaned up on the error path.
>

Good catch!

>
>> +    astlpc_destroy();
>> +    free(ops_data);
>> +
>> +    return OPAL_HARDWARE;
>> +}
>> +
>> +static const struct ast_mctp_backend astlpc_backend = {
>> +    .ready = astlpc_ready,
>> +    .binding = astlpc_binding,
>> +    .destroy = astlpc_destroy,
>> +};
>> +
>> +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
>
>
> I don't really like how logging is done in this file. We always reuse 
> the libmctp trace macro, which means:
> - if we don't define AST_MCTP_DEBUG, we get no traces at all, not even 
> errors.
> - if we define AST_MCTP_DEBUG, then we also get the traces from 
> libmctp, which floods the in-memory skiboot trace buffer with traces 
> such as:
> [  371.500175890,7] AST-MCTP: astlpc_kcs_reg_read 0xc0 from status (0)
> [  371.500219079,7] astlpc: host: mctp_astlpc_poll: status: 0xc0
>
> Why not use the usual prlog() for traces from this file?
> We can keep AST_MCTP_DEBUG just as a mean to control the libmctp 
> tracing only.
>

ast-mctp.c is intrinsically linked to libmctp. For this reason we used 
the same way of tracing.
But no problem to use prolog.

>
>> +
>> +int ast_mctp_ready(void)
>> +{
>> +    if (!mctp_backend->ready())
>
>
> That's an exported API, so it would be better to check if mctp_backend 
> and mctp_backend->ready are non-NULL before calling it.
>

Correct.

>
>> +        return OPAL_BUSY;
>> +
>> +    return OPAL_SUCCESS;
>> +}
>> +
>> +int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len)
>> +{
>> +    uint8_t tag = 0;
>> +
>> +    return mctp_message_tx(mctp, eid, MCTP_MESSAGE_TO_DST, tag, msg, 
>> len);
>> +}
>> +
>> +void ast_mctp_stop_polling(void)
>> +{
>
>
> This function looks like dead code. Leftover from debug?
>

Certainly, yes !

>
>> +    irq_active = true;
>> +}
>> +
>> +#define HOST_MAX_INCOMING_MESSAGE_ALLOCATION 131072
>> +
>> +/*
>> + * Initialize mctp binding for hbrt and provide interfaces for sending
>> + * and receiving mctp messages.
>> + */
>> +int ast_mctp_init(uint8_t mode, void (*fn)(uint8_t src_eid, bool 
>> tag_owner,
>> +          uint8_t msg_tag, void *data, void *msg, size_t len))
>> +{
>> +    uint64_t start;
>> +
>> +    if (mode == MCTP_BINDING_ASTLPC_MODE)
>> +        mctp_backend = &astlpc_backend;
>> +    else {
>> +        mctp_prerr("Unknown AST binding mode: %u", mode);
>> +        return OPAL_PARAMETER;
>> +    }
>> +
>> +    if (!mctp_backend->ready())
>> +        return OPAL_HARDWARE;
>> +
>> +    /* 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/kcs -> (eid: 9) | skiboot |
>> +     * \-----/               pcie              \---------/
>> +     *           astlpc kcs / astpcie binding
>> +     */
>> +    mctp = mctp_init();
>> +    if (!mctp) {
>> +        mctp_prerr("mctp init failed");
>> +        return OPAL_HARDWARE;
>> +    }
>> +
>> +#ifdef AST_MCTP_DEBUG
>> +    /* Setup the trace hook */
>> +    mctp_set_log_custom(mctp_log);
>> +#endif
>> +
>> +    mctp_prinfo("libmctp initialized");
>> +
>> +    /* 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, fn, NULL);
>> +
>> +    /* Initialise the binding */
>> +    if (mctp_backend->binding())
>> +        goto err;
>> +
>> +    /* Initializing the channel requires running the poll function
>> +     * a few times to handle the initial setup since we need the
>> +     * BMC to reply.
>> +     *
>> +     * Unfortuntately there's no good way to determine if this
>> +     * worked or not so poll for a bit with a timeout. :(
>> +     *
>> +     * We have to check (bus->state == mctp_bus_state_constructed)
>> +     * otherwise mctp_message_tx() will reject the message.
>> +     */
>> +    start = mftb();
>> +    while (tb_compare(mftb(), start + msecs_to_tb(250)) == TB_ABEFOREB)
>> +        time_wait_ms(10);
>
>
> Why not a simple time_wait_ms() for the full duration? It would call 
> the opal_run_pollers(), which check the timers.
>
>   Fred
>
>
>
>> +
>> +    mctp_prwarn("%s - The mctp bus state should be correct now !!!",
>> +            __func__);
>> +
>> +    return OPAL_SUCCESS;
>> +
>> +err:
>> +    mctp_prerr("Unable to initialize MCTP");
>> +    mctp_destroy(mctp);
>> +    mctp = NULL;
>> +
>> +    return OPAL_HARDWARE;
>> +}
>> +
>> +void ast_mctp_exit(void)
>> +{
>> +    if (mctp_backend && mctp_backend->destroy)
>> +        mctp_backend->destroy();
>> +
>> +    if (mctp)
>> +        mctp_destroy(mctp);
>> +
>> +    mctp = NULL;
>> +}
>> diff --git a/include/ast.h b/include/ast.h
>> index 5e932398..55eeae28 100644
>> --- a/include/ast.h
>> +++ b/include/ast.h
>> @@ -91,6 +91,17 @@ 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);
>>   +/* AST MCTP configuration */
>> +#define MCTP_BINDING_ASTLPC_MODE 0
>> +#define MCTP_BINDING_ASTPCIE_MODE 1
>> +
>> +int ast_mctp_ready(void);
>> +int ast_mctp_init(uint8_t mode, void (*fn)(uint8_t src_eid, bool 
>> tag_owner,
>> +          uint8_t msg_tag, void *data, void *msg, size_t len));
>> +void ast_mctp_exit(void);
>> +void ast_mctp_stop_polling(void);
>> +int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len);
>> +
>>   #endif /* __SKIBOOT__ */
>>     /*
Joel Stanley April 13, 2023, 6:11 a.m. UTC | #4
On Thu, 6 Apr 2023 at 17:16, Frederic Barrat <fbarrat@linux.ibm.com> wrote:
>
>
>
> On 13/09/2022 12:26, Christophe Lombard wrote:
> > 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.
> >
> > Signed-off-by: Christophe Lombard <clombard@linux.vnet.ibm.com>
> > ---
> >   hw/ast-bmc/Makefile.inc |   7 +
> >   hw/ast-bmc/ast-mctp.c   | 386 ++++++++++++++++++++++++++++++++++++++++
> >   include/ast.h           |  11 ++
> >   3 files changed, 404 insertions(+)
> >   create mode 100644 hw/ast-bmc/ast-mctp.c
> >
> > 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..1e7bdb51
> > --- /dev/null
> > +++ b/hw/ast-bmc/ast-mctp.c
> > @@ -0,0 +1,386 @@
> > +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
> > +// Copyright 2022 IBM Corp.
> > +
> > +#define pr_fmt(fmt) "AST-MCTP: " fmt
> > +
> > +#include <lpc.h>
> > +#include <interrupts.h>
> > +#include <timer.h>
> > +#include <timebase.h>
> > +#include <debug_descriptor.h>
> > +#include <ast.h>
> > +#include <console.h>
> > +#include <libmctp.h>
> > +#include <libmctp-cmds.h>
> > +#include <libmctp-log.h>
> > +#include <libmctp-astlpc.h>
> > +
> > +/*
> > + * For the EIDs: the valid range is 8-254.
> > + * host default = 9
> > + */
> > +uint8_t HOST_EID = 9;
> > +
> > +struct timer ast_boot_timer;
> > +static struct mctp *mctp;
> > +
> > +struct ast_mctp_backend {
> > +     bool (*ready)(void);
> > +     int (*binding)(void);
> > +     void (*destroy)(void);
> > +};
> > +const struct ast_mctp_backend *mctp_backend;
> > +
> > +/*
> > + * The AST LPC binding is described here:
> > + *
> > + * https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-astlpc.md
>
>
> The correct URL is now:
> https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-ibm-astlpc.md

Perhaps use a tag, so it doesn't go away the next time someone
reorganises the files:

https://github.com/openbmc/libmctp/blob/v0.11/docs/bindings/vendor-ibm-astlpc.md

> > +#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
>
>
> I don't really like how logging is done in this file. We always reuse
> the libmctp trace macro, which means:
> - if we don't define AST_MCTP_DEBUG, we get no traces at all, not even
> errors.
> - if we define AST_MCTP_DEBUG, then we also get the traces from libmctp,
> which floods the in-memory skiboot trace buffer with traces such as:
> [  371.500175890,7] AST-MCTP: astlpc_kcs_reg_read 0xc0 from status (0)
> [  371.500219079,7] astlpc: host: mctp_astlpc_poll: status: 0xc0
>
> Why not use the usual prlog() for traces from this file?

+1

> We can keep AST_MCTP_DEBUG just as a mean to control the libmctp tracing
> only.
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..1e7bdb51
--- /dev/null
+++ b/hw/ast-bmc/ast-mctp.c
@@ -0,0 +1,386 @@ 
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+// Copyright 2022 IBM Corp.
+
+#define pr_fmt(fmt) "AST-MCTP: " fmt
+
+#include <lpc.h>
+#include <interrupts.h>
+#include <timer.h>
+#include <timebase.h>
+#include <debug_descriptor.h>
+#include <ast.h>
+#include <console.h>
+#include <libmctp.h>
+#include <libmctp-cmds.h>
+#include <libmctp-log.h>
+#include <libmctp-astlpc.h>
+
+/*
+ * For the EIDs: the valid range is 8-254.
+ * host default = 9
+ */
+uint8_t HOST_EID = 9;
+
+struct timer ast_boot_timer;
+static struct mctp *mctp;
+
+struct ast_mctp_backend {
+	bool (*ready)(void);
+	int (*binding)(void);
+	void (*destroy)(void);
+};
+const struct ast_mctp_backend *mctp_backend;
+
+/*
+ * The AST LPC binding is described here:
+ *
+ * https://github.com/openbmc/libmctp/blob/master/docs/bindings/vendor-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, tmp;
+	int rc;
+
+	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;
+
+	rc = lpc_read(OPAL_LPC_IO, addr, &tmp, 1);
+	if (!rc)
+		*val = tmp & 0xff;
+	else
+		*val = 0xff;
+
+	mctp_prdebug("%s 0x%hhx from %s (%d)",
+		     __func__, *val, reg ? "status" : "data", rc);
+
+	return rc;
+}
+
+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;
+
+	mctp_prdebug("%s 0x%hhx to %s",
+		     __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;
+
+	return lpc_write(OPAL_LPC_IO, addr, val, 1);
+}
+
+static int astlpc_read(void *binding_data, void *buf, long offset,
+		       size_t len)
+{
+	struct astlpc_ops_data *ops_data = binding_data;
+
+	mctp_prdebug("%s %zu bytes from 0x%lx (lpc: 0x%lx)",
+		     __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;
+
+	mctp_prdebug("%s %zu bytes to offset 0x%lx (lpc: 0x%lx)",
+		     __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,
+};
+
+static struct mctp_binding_astlpc *astlpc;
+static struct astlpc_ops_data *ops_data;
+
+/* Keyboard Controller Style (KCS) data register address */
+#define KCS_DATA_REG 0xca2
+
+/* Keyboard Controller Style (KCS) status register address */
+#define KCS_STATUS_REG 0xca3
+
+/* Serial IRQ number for KCS interface */
+#define KCS_SERIAL_IRQ 11
+
+#define KCS_STATUS_BMC_READY 0x80
+
+bool irq_active;
+
+/* we need a poller to crank the mctp state machine during boot */
+static void astlpc_poller(struct timer *t __unused,
+			  void *data __unused, uint64_t now __unused)
+{
+	mctp_astlpc_poll(astlpc);
+
+	/* once booted we can rely on the KCS interrupt */
+	if ((opal_booting() && !irq_active))
+		schedule_timer(&ast_boot_timer, msecs_to_tb(5));
+}
+
+/* at runtime the interrupt should handle it */
+static void astlpc_interrupt(uint32_t chip_id __unused,
+			     uint32_t irq_msk __unused)
+{
+	if (!irq_active) {
+		mctp_prerr("IRQ Active! Disabling boot poller...");
+		irq_active = true;
+	}
+
+	mctp_astlpc_poll(astlpc);
+}
+
+static struct lpc_client kcs_lpc_client = {
+	.reset = NULL,
+	.interrupt = astlpc_interrupt,
+	.interrupts = LPC_IRQ(KCS_SERIAL_IRQ),
+};
+
+static bool astlpc_ready(void)
+{
+	uint32_t tmp = 0;
+
+	/* Probe for MCTP support by reading the STAT register of KCS#4 */
+	if (lpc_probe_read(OPAL_LPC_IO, KCS_STATUS_REG, &tmp, 1)) {
+		mctp_prdebug("KCS4 probe failed, skipping MCTP init");
+		return false;
+	}
+
+	return !!(tmp & KCS_STATUS_BMC_READY); /* high bit indicates BMC is listening */
+}
+
+#define DESIRED_MTU 32768
+
+static void astlpc_destroy(void)
+{
+	if (astlpc) {
+		mctp_astlpc_destroy(astlpc);
+		cancel_timer(&ast_boot_timer);
+		astlpc = NULL;
+	}
+}
+
+static int astlpc_binding(void)
+{
+	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) {
+		mctp_prerr("binding init failed");
+		return OPAL_HARDWARE;
+	}
+
+	/* register an lpc client so we get an interrupt */
+	lpc_register_client(0, &kcs_lpc_client, IRQ_ATTR_TARGET_OPAL);
+
+	init_timer(&ast_boot_timer, astlpc_poller, astlpc);
+	schedule_timer(&ast_boot_timer, msecs_to_tb(5));
+
+	/* 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)) {
+		mctp_prerr("failed to register bus");
+		goto err;
+	}
+
+	return OPAL_SUCCESS;
+
+err:
+	astlpc_destroy();
+	free(ops_data);
+
+	return OPAL_HARDWARE;
+}
+
+static const struct ast_mctp_backend astlpc_backend = {
+	.ready = astlpc_ready,
+	.binding = astlpc_binding,
+	.destroy = astlpc_destroy,
+};
+
+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_ready(void)
+{
+	if (!mctp_backend->ready())
+		return OPAL_BUSY;
+
+	return OPAL_SUCCESS;
+}
+
+int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len)
+{
+	uint8_t tag = 0;
+
+	return mctp_message_tx(mctp, eid, MCTP_MESSAGE_TO_DST, tag, msg, len);
+}
+
+void ast_mctp_stop_polling(void)
+{
+	irq_active = true;
+}
+
+#define HOST_MAX_INCOMING_MESSAGE_ALLOCATION 131072
+
+/*
+ * Initialize mctp binding for hbrt and provide interfaces for sending
+ * and receiving mctp messages.
+ */
+int ast_mctp_init(uint8_t mode, void (*fn)(uint8_t src_eid, bool tag_owner,
+		  uint8_t msg_tag, void *data, void *msg, size_t len))
+{
+	uint64_t start;
+
+	if (mode == MCTP_BINDING_ASTLPC_MODE)
+		mctp_backend = &astlpc_backend;
+	else {
+		mctp_prerr("Unknown AST binding mode: %u", mode);
+		return OPAL_PARAMETER;
+	}
+
+	if (!mctp_backend->ready())
+		return OPAL_HARDWARE;
+
+	/* 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/kcs -> (eid: 9) | skiboot |
+	 * \-----/               pcie              \---------/
+	 *           astlpc kcs / astpcie binding
+	 */
+	mctp = mctp_init();
+	if (!mctp) {
+		mctp_prerr("mctp init failed");
+		return OPAL_HARDWARE;
+	}
+
+#ifdef AST_MCTP_DEBUG
+	/* Setup the trace hook */
+	mctp_set_log_custom(mctp_log);
+#endif
+
+	mctp_prinfo("libmctp initialized");
+
+	/* 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, fn, NULL);
+
+	/* Initialise the binding */
+	if (mctp_backend->binding())
+		goto err;
+
+	/* Initializing the channel requires running the poll function
+	 * a few times to handle the initial setup since we need the
+	 * BMC to reply.
+	 *
+	 * Unfortuntately there's no good way to determine if this
+	 * worked or not so poll for a bit with a timeout. :(
+	 *
+	 * We have to check (bus->state == mctp_bus_state_constructed)
+	 * otherwise mctp_message_tx() will reject the message.
+	 */
+	start = mftb();
+	while (tb_compare(mftb(), start + msecs_to_tb(250)) == TB_ABEFOREB)
+		time_wait_ms(10);
+
+	mctp_prwarn("%s - The mctp bus state should be correct now !!!",
+		    __func__);
+
+	return OPAL_SUCCESS;
+
+err:
+	mctp_prerr("Unable to initialize MCTP");
+	mctp_destroy(mctp);
+	mctp = NULL;
+
+	return OPAL_HARDWARE;
+}
+
+void ast_mctp_exit(void)
+{
+	if (mctp_backend && mctp_backend->destroy)
+		mctp_backend->destroy();
+
+	if (mctp)
+		mctp_destroy(mctp);
+
+	mctp = NULL;
+}
diff --git a/include/ast.h b/include/ast.h
index 5e932398..55eeae28 100644
--- a/include/ast.h
+++ b/include/ast.h
@@ -91,6 +91,17 @@  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);
 
+/* AST MCTP configuration */
+#define MCTP_BINDING_ASTLPC_MODE 0
+#define MCTP_BINDING_ASTPCIE_MODE 1
+
+int ast_mctp_ready(void);
+int ast_mctp_init(uint8_t mode, void (*fn)(uint8_t src_eid, bool tag_owner,
+		  uint8_t msg_tag, void *data, void *msg, size_t len));
+void ast_mctp_exit(void);
+void ast_mctp_stop_polling(void);
+int ast_mctp_message_tx(uint8_t eid, uint8_t *msg, int len);
+
 #endif /* __SKIBOOT__ */
 
 /*