diff options
author | Christophe Lombard <clombard@linux.ibm.com> | 2023-08-29 11:23:19 +0200 |
---|---|---|
committer | Reza Arbab <arbab@linux.ibm.com> | 2023-09-12 14:22:11 -0500 |
commit | 4fc06a6cb136a0acaef03e603bb906a6e6c48788 (patch) | |
tree | 4a532dbcbfd67687706350fa669fb2bbc579eae0 | |
parent | 66c38f45e9fc079dc47c8865ee6a27da5e7cdc0f (diff) | |
download | skiboot-4fc06a6cb136a0acaef03e603bb906a6e6c48788.zip skiboot-4fc06a6cb136a0acaef03e603bb906a6e6c48788.tar.gz skiboot-4fc06a6cb136a0acaef03e603bb906a6e6c48788.tar.bz2 |
hw/ast-bmc: Initialize ast lpc mctp binding
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>
Signed-off-by: Reza Arbab <arbab@linux.ibm.com>
-rw-r--r-- | hw/ast-bmc/Makefile.inc | 7 | ||||
-rw-r--r-- | hw/ast-bmc/ast-mctp.c | 364 | ||||
-rw-r--r-- | include/ast.h | 20 | ||||
-rw-r--r-- | platforms/astbmc/common.c | 41 |
4 files changed, 432 insertions, 0 deletions
diff --git a/hw/ast-bmc/Makefile.inc b/hw/ast-bmc/Makefile.inc index e7ded0e..546f2bc 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 0000000..c2efb7b --- /dev/null +++ b/hw/ast-bmc/ast-mctp.c @@ -0,0 +1,364 @@ +// 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 + +#define TX_POLL_MAX 5 + +/* + * 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); + + 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: + 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) +{ + unsigned long stop_time; + int rc = OPAL_SUCCESS; + + lock(&mctp_lock); + + rc = mctp_message_tx(mctp, BMC_EID, tag_owner, msg_tag, + msg, msg_len); + unlock(&mctp_lock); + + /* do not poll when we respond to a BMC request */ + if (tag_owner) + return rc; + + /* read the Rx_complete command out of the ODR */ + stop_time = mftb() + msecs_to_tb(TX_POLL_MAX); + while (mftb() < stop_time && !mctp_astlpc_tx_done(astlpc)) + mctp_astlpc_poll(astlpc); + + 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"); + mctp_destroy(mctp); + mctp = NULL; + + return OPAL_HARDWARE; +} + +void ast_mctp_exit(void) +{ + if (astlpc) { + mctp_astlpc_destroy(astlpc); + astlpc = NULL; + } + + if (mctp) { + mctp_destroy(mctp); + mctp = NULL; + } +} diff --git a/include/ast.h b/include/ast.h index 5e93239..79efe4f 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 bfbba2d..6697230 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"); } +#ifdef CONFIG_PLDM +static void astbmc_fixup_dt_mctp(struct dt_node *lpc) +{ + struct dt_node *mctp; + char namebuf[32]; + + 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); +} +#endif + static void astbmc_fixup_dt_bt(struct dt_node *lpc) { struct dt_node *bt; @@ -404,6 +440,11 @@ static void astbmc_fixup_dt(void) /* BT is not in HB either */ astbmc_fixup_dt_bt(primary_lpc); +#ifdef CONFIG_PLDM + /* Fixup the MCTP, that might be missing from HB */ + astbmc_fixup_dt_mctp(primary_lpc); +#endif + /* The pel logging code needs a system-id property to work so make sure we have one. */ astbmc_fixup_dt_system_id(); |