| // 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); |
| } |