blob: 9e600f3f6c8a808c869d0c58a38b5ae32526d644 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/*
* Conduit for IPMI messages to/from FSP
*
* Copyright 2014-2019 IBM Corp.
*/
#include <errorlog.h>
#include <fsp.h>
#include <ipmi.h>
#include <lock.h>
#include <opal-api.h>
/*
* Under the hood, FSP IPMI component implements the KCS (Keyboard Controller
* Style) interface
*
* KCS interface request message format
*
* BYTE 1 BYTE 2 BYTE 3:N
* -------------------------------------
* | NetFn/LUN | Cmd | Data |
* -------------------------------------
*
* KCS interface response message format
*
* BYTE 1 BYTE 2 BYTE 3 BYTE 4:N
* ------------------------------------------------
* | NetFn/LUN | Cmd | CompCode | Data |
* ------------------------------------------------
*/
#define FSP_IPMI_REQ_MIN_LEN 2 /* NetFn + Cmd */
#define FSP_IPMI_RESP_MIN_LEN 3 /* NetFn + Cmd + Completion code */
DEFINE_LOG_ENTRY(OPAL_RC_IPMI_REQ, OPAL_PLATFORM_ERR_EVT, OPAL_IPMI,
OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
OPAL_NA);
DEFINE_LOG_ENTRY(OPAL_RC_IPMI_RESP, OPAL_PLATFORM_ERR_EVT, OPAL_IPMI,
OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
OPAL_NA);
DEFINE_LOG_ENTRY(OPAL_RC_IPMI_DMA_ERROR_RESP, OPAL_PLATFORM_ERR_EVT, OPAL_IPMI,
OPAL_PLATFORM_FIRMWARE, OPAL_INFO,
OPAL_NA);
struct fsp_ipmi_msg {
struct list_node link;
struct ipmi_msg ipmi_msg;
};
static struct fsp_ipmi {
struct list_head msg_queue;
void *ipmi_req_buf;
void *ipmi_resp_buf;
/* There can only be one outstanding request whose reference is stored
* in 'cur_msg' and the 'lock' protects against the concurrent updates
* of it through request and response. The same 'lock' also protects
* the list manipulation.
*/
struct fsp_ipmi_msg *cur_msg;
struct lock lock;
} fsp_ipmi;
static int fsp_ipmi_send_request(void);
static void fsp_ipmi_cmd_done(uint8_t cmd, uint8_t netfn, uint8_t cc)
{
struct fsp_ipmi_msg *fsp_ipmi_msg = fsp_ipmi.cur_msg;
lock(&fsp_ipmi.lock);
if (fsp_ipmi.cur_msg == NULL) {
unlock(&fsp_ipmi.lock);
return;
}
list_del(&fsp_ipmi_msg->link);
fsp_ipmi.cur_msg = NULL;
unlock(&fsp_ipmi.lock);
ipmi_cmd_done(cmd, netfn, cc, &fsp_ipmi_msg->ipmi_msg);
}
static void fsp_ipmi_req_complete(struct fsp_msg *msg)
{
uint8_t status = (msg->resp->word1 >> 8) & 0xff;
uint32_t length = fsp_msg_get_data_word(msg->resp, 0);
struct fsp_ipmi_msg *fsp_ipmi_msg = msg->user_data;
struct ipmi_msg *ipmi_msg;
fsp_freemsg(msg);
if (status != FSP_STATUS_SUCCESS) {
assert(fsp_ipmi_msg == fsp_ipmi.cur_msg);
ipmi_msg = &fsp_ipmi_msg->ipmi_msg;
if (length != (ipmi_msg->req_size + FSP_IPMI_REQ_MIN_LEN))
prlog(PR_DEBUG, "IPMI: Length mismatch in req completion "
"(%d, %d)\n", ipmi_msg->req_size, length);
log_simple_error(&e_info(OPAL_RC_IPMI_REQ), "IPMI: Request "
"failed with status:0x%02x\n", status);
/* FSP will not send the response now, so clear the current
* outstanding request
*/
fsp_ipmi_cmd_done(ipmi_msg->cmd,
IPMI_NETFN_RETURN_CODE(ipmi_msg->netfn),
IPMI_ERR_UNSPECIFIED);
/* Send the next request in the queue */
fsp_ipmi_send_request();
}
}
static int fsp_ipmi_send_request(void)
{
uint8_t *req_buf = fsp_ipmi.ipmi_req_buf;
struct ipmi_msg *ipmi_msg;
struct fsp_msg *msg;
int rc;
if (fsp_in_rr())
return OPAL_BUSY;
lock(&fsp_ipmi.lock);
/* An outstanding request is still pending */
if (fsp_ipmi.cur_msg) {
unlock(&fsp_ipmi.lock);
return OPAL_SUCCESS;
}
fsp_ipmi.cur_msg = list_top(&fsp_ipmi.msg_queue, struct fsp_ipmi_msg,
link);
unlock(&fsp_ipmi.lock);
if (!fsp_ipmi.cur_msg)
return OPAL_SUCCESS;
ipmi_msg = &fsp_ipmi.cur_msg->ipmi_msg;
prlog(PR_TRACE, "IPMI: Send request, netfn:0x%02x, cmd:0x%02x, "
"req_len:%d\n", ipmi_msg->netfn, ipmi_msg->cmd, ipmi_msg->req_size);
/* KCS request message format */
*req_buf++ = ipmi_msg->netfn; /* BYTE 1 */
*req_buf++ = ipmi_msg->cmd; /* BYTE 2 */
if (ipmi_msg->req_size)
memcpy(req_buf, ipmi_msg->data, ipmi_msg->req_size);
msg = fsp_mkmsg(FSP_CMD_FETCH_PLAT_DATA, 5, 0, PSI_DMA_PLAT_REQ_BUF,
0, PSI_DMA_PLAT_RESP_BUF,
ipmi_msg->req_size + FSP_IPMI_REQ_MIN_LEN);
if (!msg) {
log_simple_error(&e_info(OPAL_RC_IPMI_REQ), "IPMI: Failed to "
"allocate request message\n");
fsp_ipmi_cmd_done(ipmi_msg->cmd,
IPMI_NETFN_RETURN_CODE(ipmi_msg->netfn),
IPMI_ERR_UNSPECIFIED);
return OPAL_NO_MEM;
}
msg->user_data = fsp_ipmi.cur_msg;
rc = fsp_queue_msg(msg, fsp_ipmi_req_complete);
if (rc) {
log_simple_error(&e_info(OPAL_RC_IPMI_REQ), "IPMI: Failed to "
"queue request message (%d)\n", rc);
fsp_freemsg(msg);
fsp_ipmi_cmd_done(ipmi_msg->cmd,
IPMI_NETFN_RETURN_CODE(ipmi_msg->netfn),
IPMI_ERR_UNSPECIFIED);
return OPAL_INTERNAL_ERROR;
}
return OPAL_SUCCESS;
}
static struct ipmi_msg *fsp_ipmi_alloc_msg(size_t req_size, size_t resp_size)
{
struct fsp_ipmi_msg *fsp_ipmi_msg;
struct ipmi_msg *ipmi_msg;
fsp_ipmi_msg = zalloc(sizeof(*fsp_ipmi_msg) + MAX(req_size, resp_size));
if (!fsp_ipmi_msg)
return NULL;
ipmi_msg = &fsp_ipmi_msg->ipmi_msg;
ipmi_msg->req_size = req_size;
ipmi_msg->resp_size = resp_size;
ipmi_msg->data = (uint8_t *)(fsp_ipmi_msg + 1);
return ipmi_msg;
}
static void fsp_ipmi_free_msg(struct ipmi_msg *ipmi_msg)
{
struct fsp_ipmi_msg *fsp_ipmi_msg = container_of(ipmi_msg,
struct fsp_ipmi_msg, ipmi_msg);
free(fsp_ipmi_msg);
}
static int fsp_ipmi_queue_msg(struct ipmi_msg *ipmi_msg)
{
struct fsp_ipmi_msg *fsp_ipmi_msg = container_of(ipmi_msg,
struct fsp_ipmi_msg, ipmi_msg);
if (fsp_in_rr())
return OPAL_BUSY;
lock(&fsp_ipmi.lock);
list_add_tail(&fsp_ipmi.msg_queue, &fsp_ipmi_msg->link);
unlock(&fsp_ipmi.lock);
return fsp_ipmi_send_request();
}
static int fsp_ipmi_queue_msg_head(struct ipmi_msg *ipmi_msg)
{
struct fsp_ipmi_msg *fsp_ipmi_msg = container_of(ipmi_msg,
struct fsp_ipmi_msg, ipmi_msg);
if (fsp_in_rr())
return OPAL_BUSY;
lock(&fsp_ipmi.lock);
list_add(&fsp_ipmi.msg_queue, &fsp_ipmi_msg->link);
unlock(&fsp_ipmi.lock);
return fsp_ipmi_send_request();
}
static int fsp_ipmi_dequeue_msg(struct ipmi_msg *ipmi_msg)
{
struct fsp_ipmi_msg *fsp_ipmi_msg = container_of(ipmi_msg,
struct fsp_ipmi_msg, ipmi_msg);
lock(&fsp_ipmi.lock);
list_del_from(&fsp_ipmi.msg_queue, &fsp_ipmi_msg->link);
unlock(&fsp_ipmi.lock);
return 0;
}
static bool fsp_ipmi_poll(void)
{
/* fsp_opal_poll poller checks command responses */
opal_run_pollers();
return !list_empty(&fsp_ipmi.msg_queue);
}
static struct ipmi_backend fsp_ipmi_backend = {
.alloc_msg = fsp_ipmi_alloc_msg,
.free_msg = fsp_ipmi_free_msg,
.queue_msg = fsp_ipmi_queue_msg,
.queue_msg_head = fsp_ipmi_queue_msg_head,
.dequeue_msg = fsp_ipmi_dequeue_msg,
.poll = fsp_ipmi_poll,
};
static bool fsp_ipmi_rr_notify(uint32_t cmd_sub_mod,
struct fsp_msg *msg __unused)
{
struct ipmi_msg *ipmi_msg;
switch (cmd_sub_mod) {
case FSP_RESET_START:
return true;
case FSP_RELOAD_COMPLETE:
/*
* We will not get response for outstanding request. Send error
* message to caller and start sending new ipmi messages.
*/
if (fsp_ipmi.cur_msg) {
ipmi_msg = &fsp_ipmi.cur_msg->ipmi_msg;
fsp_ipmi_cmd_done(ipmi_msg->cmd,
IPMI_NETFN_RETURN_CODE(ipmi_msg->netfn),
IPMI_ERR_UNSPECIFIED);
}
fsp_ipmi_send_request();
return true;
}
return false;
}
static struct fsp_client fsp_ipmi_client_rr = {
.message = fsp_ipmi_rr_notify,
};
static bool fsp_ipmi_send_response(uint32_t cmd)
{
struct fsp_msg *resp;
int rc;
resp = fsp_mkmsg(cmd, 0);
if (!resp) {
log_simple_error(&e_info(OPAL_RC_IPMI_RESP), "IPMI: Failed to "
"allocate response message\n");
return false;
}
rc = fsp_queue_msg(resp, fsp_freemsg);
if (rc) {
fsp_freemsg(resp);
log_simple_error(&e_info(OPAL_RC_IPMI_RESP), "IPMI: Failed to "
"queue response message\n");
return false;
}
return true;
}
static bool fsp_ipmi_read_response(struct fsp_msg *msg)
{
uint8_t *resp_buf = fsp_ipmi.ipmi_resp_buf;
uint32_t status = fsp_msg_get_data_word(msg, 3);
uint32_t length = fsp_msg_get_data_word(msg, 2);
struct ipmi_msg *ipmi_msg;
uint8_t netfn, cmd, cc;
assert(fsp_ipmi.cur_msg);
ipmi_msg = &fsp_ipmi.cur_msg->ipmi_msg;
/* Response TCE token */
assert(fsp_msg_get_data_word(msg, 1) == PSI_DMA_PLAT_RESP_BUF);
if (status != FSP_STATUS_SUCCESS) {
if(status == FSP_STATUS_DMA_ERROR)
log_simple_error(&e_info(OPAL_RC_IPMI_DMA_ERROR_RESP), "IPMI: Received "
"DMA ERROR response from FSP, this may be due to FSP "
"is in termination state:0x%02x\n", status);
else
log_simple_error(&e_info(OPAL_RC_IPMI_RESP), "IPMI: FSP response "
"received with bad status:0x%02x\n", status);
fsp_ipmi_cmd_done(ipmi_msg->cmd,
IPMI_NETFN_RETURN_CODE(ipmi_msg->netfn),
IPMI_ERR_UNSPECIFIED);
return fsp_ipmi_send_response(FSP_RSP_PLAT_DATA |
FSP_STATUS_SUCCESS);
}
/* KCS response message format */
netfn = *resp_buf++;
cmd = *resp_buf++;
cc = *resp_buf++;
length -= FSP_IPMI_RESP_MIN_LEN;
prlog(PR_TRACE, "IPMI: fsp response received, netfn:0x%02x, cmd:0x%02x,"
" cc:0x%02x, length:%d\n", netfn, cmd, cc, length);
if (length > ipmi_msg->resp_size) {
prlog(PR_DEBUG, "IPMI: Length mismatch in response (%d, %d)\n",
length, ipmi_msg->resp_size);
length = ipmi_msg->resp_size; /* Truncate */
cc = IPMI_ERR_MSG_TRUNCATED;
}
ipmi_msg->resp_size = length;
if (length)
memcpy(ipmi_msg->data, resp_buf, length);
fsp_ipmi_cmd_done(cmd, netfn, cc);
return fsp_ipmi_send_response(FSP_RSP_PLAT_DATA);
}
static bool fsp_ipmi_response(uint32_t cmd_sub_mod, struct fsp_msg *msg)
{
bool rc;
switch (cmd_sub_mod) {
case FSP_CMD_SEND_PLAT_DATA:
prlog(PR_TRACE, "FSP_CMD_SEND_PLAT_DATA command received\n");
rc = fsp_ipmi_read_response(msg);
break;
default:
return false;
};
/* If response sent successfully, pick the next request */
if (rc == true)
fsp_ipmi_send_request();
return rc;
}
static struct fsp_client fsp_ipmi_client = {
.message = fsp_ipmi_response,
};
void fsp_ipmi_init(void)
{
fsp_tce_map(PSI_DMA_PLAT_REQ_BUF, fsp_ipmi.ipmi_req_buf,
PSI_DMA_PLAT_REQ_BUF_SIZE);
fsp_tce_map(PSI_DMA_PLAT_RESP_BUF, fsp_ipmi.ipmi_resp_buf,
PSI_DMA_PLAT_RESP_BUF_SIZE);
list_head_init(&fsp_ipmi.msg_queue);
init_lock(&fsp_ipmi.lock);
fsp_register_client(&fsp_ipmi_client, FSP_MCLASS_FETCH_SPDATA);
fsp_register_client(&fsp_ipmi_client_rr, FSP_MCLASS_RR_EVENT);
ipmi_register_backend(&fsp_ipmi_backend);
}