blob: 00f66abec2c9d1205a2c09bdcd446dbe1aa1e99c [file] [log] [blame]
// 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 <pldm.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);
/* The first byte defines the type of MCTP packet payload
* contained in the message data portion of the MCTP message.
* (See DSP0236 for more details about MCTP packet fields).
* For now we only support PLDM over MCTP.
*/
switch (*msg) {
case MCTP_MSG_TYPE_PLDM:
/* handle the PLDM message */
pldm_mctp_message_rx(eid, tag_owner, msg_tag,
msg + sizeof(uint8_t),
len - sizeof(uint8_t));
break;
default:
prlog(PR_ERR, "%s - not a pldm message type (type: %x)\n",
__func__, *msg);
}
}
/*
* 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;
}
}