// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later // Copyright 2022 IBM Corp. #define pr_fmt(fmt) "PLDM: " fmt #include #include #include #include #include #include #include #include #include "pldm.h" #define BMC_VERSION_LENGTH 100 /* * Response data from IPMI Get device ID command (As defined in * Section 20.1 Get Device ID Command - IPMI standard spec). */ struct ipmi_dev_id { uint8_t dev_id; uint8_t dev_revision; uint8_t fw_rev1; uint8_t fw_rev2; uint8_t ipmi_ver; uint8_t add_dev_support; uint8_t manufactur_id[3]; uint8_t product_id[2]; uint8_t aux_fw_rev[4]; }; static struct lock msgq_lock = LOCK_UNLOCKED; static struct list_head msgq = LIST_HEAD_INIT(msgq); uint64_t opal_event_ipmi_recv; static void opal_send_complete(struct ipmi_msg *msg) { lock(&msgq_lock); list_add_tail(&msgq, &msg->link); opal_update_pending_evt(opal_event_ipmi_recv, opal_event_ipmi_recv); unlock(&msgq_lock); } #define GUID_SIZE 16 static int opal_get_tid_req(struct opal_ipmi_msg *opal_ipmi_msg, uint64_t msg_len __unused) { struct ipmi_msg *opal_ipmi_response = NULL; uint8_t guid[GUID_SIZE]; int bmc_tid; opal_ipmi_response = zalloc(sizeof(struct ipmi_msg) + GUID_SIZE); opal_ipmi_response->netfn = IPMI_NETFN_RETURN_CODE(opal_ipmi_msg->netfn); opal_ipmi_response->cmd = opal_ipmi_msg->cmd; memset(&guid, 0, GUID_SIZE); /* First byte of guid contains bmc tid */ bmc_tid = pldm_base_get_bmc_tid(); if (bmc_tid == -1) { opal_ipmi_response->resp_size = 0; opal_ipmi_response->cc = IPMI_ERR_UNSPECIFIED; goto out; } guid[0] = (uint8_t)bmc_tid; memcpy(opal_ipmi_response->data, guid, GUID_SIZE); opal_ipmi_response->resp_size = GUID_SIZE; opal_ipmi_response->cc = IPMI_CC_NO_ERROR; out: opal_send_complete(opal_ipmi_response); return OPAL_SUCCESS; } static int parse_bmc_version(uint8_t *bmc_version, struct ipmi_dev_id *devid) { uint8_t *ptr; uint8_t temp; prlog(PR_TRACE, "%s - bmc version: %s len=%d\n", __func__, bmc_version, (int)strlen(bmc_version)); /* * parse bmc version string to find fw_rev1 and fw_rev2 * Firmware Name is in format : * fw1030.00-2.8-1030.2233.20220819a (NL1030_007) * so fw_rev1 = 10 * fw_rev2 = 20 * aux_fw_rev = "007" */ ptr = strstr(bmc_version, "NL"); if (ptr == NULL || strlen(ptr) < 8) return OPAL_PARAMETER; ptr += 2; /* * Convert first two byte to * fw_rev1 and net 2byte to fw_rev2 */ temp = ptr[2]; ptr[2] = '\0'; devid->fw_rev1 = (uint8_t)atoi(ptr); ptr += 2; ptr[0] = temp; temp = ptr[2]; ptr[2] = '\0'; devid->fw_rev2 = (uint8_t)atoi(ptr); ptr += 2; ptr[0] = temp; /* Aux version is truncated to 4 char only */ if (*ptr == '_') strncpy(devid->aux_fw_rev, ptr + 1, 3); prlog(PR_TRACE, "BMC Version major->%d minor->%d aux->%.4s\n", devid->fw_rev1, devid->fw_rev2, devid->aux_fw_rev); return OPAL_SUCCESS; } static int opal_get_bmc_info(struct opal_ipmi_msg *opal_ipmi_msg, uint64_t msg_len __unused) { struct ipmi_msg *opal_ipmi_response = NULL; char bmc_version[BMC_VERSION_LENGTH]; struct ipmi_dev_id devid; int rc; opal_ipmi_response = zalloc(sizeof(struct ipmi_msg) + sizeof(struct ipmi_dev_id)); opal_ipmi_response->resp_size = sizeof(struct ipmi_dev_id); opal_ipmi_response->netfn = IPMI_NETFN_RETURN_CODE(opal_ipmi_msg->netfn); opal_ipmi_response->cmd = opal_ipmi_msg->cmd; opal_ipmi_response->cc = IPMI_CC_NO_ERROR; memset(&devid, 0, sizeof(devid)); devid.ipmi_ver = OPAL_IPMI_MSG_FORMAT_VERSION_1; /* retrieve bmc version */ rc = pldm_fru_get_bmc_version(bmc_version, BMC_VERSION_LENGTH); if (rc) { opal_ipmi_response->resp_size = 0; opal_ipmi_response->cc = IPMI_ERR_UNSPECIFIED; goto out; } /* parse the bmc version */ rc = parse_bmc_version(bmc_version, &devid); if (rc) { prlog(PR_ERR, "%s: Failed to parse BMC version, bmc version: %s\n", __func__, bmc_version); opal_ipmi_response->resp_size = 0; opal_ipmi_response->cc = IPMI_ERR_UNSPECIFIED; goto out; } memcpy(opal_ipmi_response->data, &devid, sizeof(devid)); out: opal_send_complete(opal_ipmi_response); return OPAL_SUCCESS; } static int64_t opal_ipmi_send(uint64_t interface __unused, struct opal_ipmi_msg *opal_ipmi_msg, uint64_t msg_len) { int16_t ipmi_code; if (opal_ipmi_msg->version != OPAL_IPMI_MSG_FORMAT_VERSION_1) { prerror("OPAL IPMI: Incorrect version\n"); return OPAL_UNSUPPORTED; } msg_len -= sizeof(struct opal_ipmi_msg); if (msg_len > IPMI_MAX_REQ_SIZE) { prerror("OPAL IPMI: Invalid request length\n"); return OPAL_PARAMETER; } ipmi_code = IPMI_CODE(opal_ipmi_msg->netfn >> 2, opal_ipmi_msg->cmd); if ((ipmi_code == IPMI_CHASSIS_GET_SYS_BOOT_OPT_CMD) || (opal_ipmi_msg->cmd == IPMI_CODE(opal_ipmi_msg->netfn >> 2, 0x1a)) || (opal_ipmi_msg->cmd == IPMI_CODE(opal_ipmi_msg->netfn >> 2, 0x42))) { prerror("OPAL IPMI: Command not supported, code: %d, " "cmd: 0x%x netfn: 0x%x\n", ipmi_code, opal_ipmi_msg->cmd, opal_ipmi_msg->netfn >> 2); return OPAL_UNSUPPORTED; } prlog(PR_TRACE, "%s - cmd: 0x%02x netfn: 0x%02x len: 0x%02llx\n", __func__, opal_ipmi_msg->cmd, opal_ipmi_msg->netfn >> 2, msg_len); switch (opal_ipmi_msg->cmd) { case IPMI_GET_DEVICE_ID_CMD: return opal_get_bmc_info(opal_ipmi_msg, msg_len); case IPMI_GET_DEVICE_GUID_CMD: return opal_get_tid_req(opal_ipmi_msg, msg_len); } prerror("OPAL IPMI: Command not supported, cmd: 0x%x netfn: 0x%x\n", opal_ipmi_msg->cmd, opal_ipmi_msg->netfn >> 2); return OPAL_UNSUPPORTED; } static int64_t opal_ipmi_recv(uint64_t interface, struct opal_ipmi_msg *opal_ipmi_msg, __be64 *msg_len) { struct ipmi_msg *msg; int64_t rc; lock(&msgq_lock); msg = list_top(&msgq, struct ipmi_msg, link); if (!msg) { rc = OPAL_EMPTY; goto out_unlock; } if (opal_ipmi_msg->version != OPAL_IPMI_MSG_FORMAT_VERSION_1) { prerror("OPAL IPMI: Incorrect version\n"); rc = OPAL_UNSUPPORTED; goto out_del_msg; } if (interface != IPMI_DEFAULT_INTERFACE) { prerror("IPMI: Invalid interface 0x%llx in %s\n", interface, __func__); rc = OPAL_PARAMETER; goto out_del_msg; } if (be64_to_cpu(*msg_len) - sizeof(struct opal_ipmi_msg) < msg->resp_size + 1) { rc = OPAL_RESOURCE; goto out_del_msg; } list_del(&msg->link); if (list_empty(&msgq)) opal_update_pending_evt(opal_event_ipmi_recv, 0); unlock(&msgq_lock); opal_ipmi_msg->cmd = msg->cmd; opal_ipmi_msg->netfn = msg->netfn; opal_ipmi_msg->data[0] = msg->cc; memcpy(&opal_ipmi_msg->data[1], msg->data, msg->resp_size); prlog(PR_TRACE, "%s - cmd: 0x%02x netfn: 0x%02x resp_size: 0x%02x\n", __func__, msg->cmd, msg->netfn >> 2, msg->resp_size); /* Add one as the completion code is returned in the message data */ *msg_len = cpu_to_be64(msg->resp_size + sizeof(struct opal_ipmi_msg) + 1); free(msg); return OPAL_SUCCESS; out_del_msg: list_del(&msg->link); if (list_empty(&msgq)) opal_update_pending_evt(opal_event_ipmi_recv, 0); free(msg); out_unlock: unlock(&msgq_lock); return rc; } void pldm_opal_init(void) { struct dt_node *opal_ipmi, *opal_event = NULL; opal_ipmi = dt_new(opal_node, "ipmi"); dt_add_property_strings(opal_ipmi, "compatible", "ibm,opal-ipmi"); dt_add_property_cells(opal_ipmi, "ibm,ipmi-interface-id", IPMI_DEFAULT_INTERFACE); opal_event_ipmi_recv = opal_dynamic_event_alloc(); dt_add_property_cells(opal_ipmi, "interrupts", ilog2(opal_event_ipmi_recv)); opal_event = dt_find_by_name(opal_node, "event"); if (opal_event) dt_add_property_cells(opal_ipmi, "interrupt-parent", opal_event->phandle); opal_register(OPAL_IPMI_SEND, opal_ipmi_send, 3); opal_register(OPAL_IPMI_RECV, opal_ipmi_recv, 3); }