blob: a29a80ae4c82fbf44cf83d9c35176c1f47fa4912 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
// Copyright 2022 IBM Corp.
#define pr_fmt(fmt) "PLDM: " fmt
#include <cpu.h>
#include <opal.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <libpldm/fru.h>
#include "pldm.h"
static void *fru_record_table;
static size_t fru_record_length;
static void *local_fru_record_table;
static size_t local_fru_table_length;
static bool fru_ready;
static char *bmc_version;
static void fru_init_complete(bool success)
{
/* Read not successful, error out and free the buffer */
if (!success) {
fru_ready = false;
if (fru_record_table != NULL) {
free(fru_record_table);
fru_record_length = 0;
}
return;
}
/* Mark ready */
fru_ready = true;
}
int pldm_fru_get_bmc_version(void *bv, int len)
{
if (bv == NULL)
return OPAL_PARAMETER;
if (bmc_version == NULL)
return OPAL_PARAMETER;
if (strlen(bmc_version) > (len + 1))
return OPAL_PARAMETER;
memcpy(bv, bmc_version, strlen(bmc_version) + 1);
return OPAL_SUCCESS;
}
static int get_fru_record_table_req(void **record_table_data,
size_t *record_table_length)
{
size_t data_size = PLDM_MSG_SIZE(struct pldm_get_fru_record_table_req);
uint8_t transfer_flag, completion_code;
size_t response_len, payload_len;
struct pldm_tx_data *tx = NULL;
uint32_t next_transfer_handle;
uint8_t *table_data;
size_t table_length;
void *response_msg;
int rc = OPAL_SUCCESS;
struct pldm_get_fru_record_table_req fru_record_table_req = {
.data_transfer_handle = 0, /* (0 if operation op is FIRSTPART) */
.transfer_operation_flag = PLDM_GET_FIRSTPART,
};
payload_len = sizeof(struct pldm_get_fru_record_table_req);
/* Encode the file table request */
tx = zalloc(sizeof(struct pldm_tx_data) + data_size);
if (!tx)
return OPAL_NO_MEM;
tx->data_size = data_size;
rc = encode_get_fru_record_table_req(
DEFAULT_INSTANCE_ID,
fru_record_table_req.data_transfer_handle,
fru_record_table_req.transfer_operation_flag,
(struct pldm_msg *)tx->data,
payload_len);
if (rc != PLDM_SUCCESS) {
prlog(PR_ERR, "Encode GetFruRecordTableReq Error, rc: %d\n", rc);
free(tx);
return OPAL_PARAMETER;
}
/* Send and get the response message bytes */
rc = pldm_requester_queue_and_wait(tx, &response_msg, &response_len);
if (rc) {
prlog(PR_ERR, "Communication Error, req: GetFruRecordTableReq, rc: %d\n", rc);
free(tx);
return rc;
}
/* Decode the message */
payload_len = response_len - sizeof(struct pldm_msg_hdr);
table_data = zalloc(payload_len);
if (!table_data) {
free(tx);
return OPAL_NO_MEM;
}
rc = decode_get_fru_record_table_resp(
response_msg,
payload_len,
&completion_code,
&next_transfer_handle,
&transfer_flag,
table_data,
&table_length);
if (rc != PLDM_SUCCESS || completion_code != PLDM_SUCCESS) {
prlog(PR_ERR, "Decode GetFruRecordTableReq Error, rc: %d, cc: %d\n",
rc, completion_code);
rc = OPAL_PARAMETER;
goto out;
}
/* we do not support multipart transfer */
if ((next_transfer_handle != PLDM_GET_NEXTPART) ||
(transfer_flag != PLDM_START_AND_END)) {
prlog(PR_ERR, "Transfert GetFruRecordTableReq not complete, "
"transfer_hndl: %d, transfer_flag: %d\n",
next_transfer_handle,
transfer_flag);
rc = OPAL_PARAMETER;
goto out;
}
*record_table_length = table_length;
*record_table_data = zalloc(table_length);
if (!record_table_data)
rc = OPAL_NO_MEM;
else
memcpy(*record_table_data, table_data, table_length);
out:
free(tx);
free(table_data);
free(response_msg);
return rc;
}
int pldm_fru_dt_add_bmc_version(void)
{
struct pldm_fru_record_data_format *data;
struct pldm_fru_record_tlv *tlv;
struct dt_node *dt_fw_version;
uint8_t *record_table;
int rc = OPAL_SUCCESS;
size_t record_size;
if (!fru_ready)
return OPAL_HARDWARE;
if (!fru_record_table)
return OPAL_HARDWARE;
dt_fw_version = dt_find_by_name(dt_root, "ibm,firmware-versions");
if (!dt_fw_version)
return OPAL_HARDWARE;
/* retrieve the bmc information with
* "FRU Record Set Identifier": 1,
* "FRU Record Type": "General(1)"
* "FRU Field Type": Version
*
* we can not know size of the record table got by options
* in advance, but it must be less than the source table. So
* it's safe to use sizeof the source table.
*/
record_table = zalloc(fru_record_length);
if (!record_table)
return OPAL_NO_MEM;
record_size = fru_record_length;
get_fru_record_by_option(
fru_record_table,
fru_record_length,
record_table,
&record_size,
1,
PLDM_FRU_RECORD_TYPE_GENERAL,
PLDM_FRU_FIELD_TYPE_VERSION);
if (record_size == 0) {
prlog(PR_ERR, "%s - no FRU type version found\n", __func__);
rc = OPAL_PARAMETER;
goto out;
}
/* get tlv value */
data = (struct pldm_fru_record_data_format *)record_table;
tlv = (struct pldm_fru_record_tlv *)data->tlvs;
prlog(PR_DEBUG, "%s - value: %s\n", __func__, tlv->value);
dt_add_property_string(dt_fw_version, "bmc-firmware-version",
tlv->value);
/* store the bmc version */
bmc_version = zalloc(tlv->length + 1);
if (!bmc_version)
rc = OPAL_NO_MEM;
else
memcpy(bmc_version, tlv->value, tlv->length);
out:
free(record_table);
return rc;
}
#define RECORD_SET_ID 100
void pldm_fru_set_local_table(uint32_t *table_length,
uint16_t *total_record_set_identifiers,
uint16_t *total_table_records)
{
struct pldm_fru_record_data_format *record;
struct pldm_fru_record_tlv *fru_tlv;
size_t fru_table_size, record_size;
char fru_product[] = "IBM, skiboot";
if (local_fru_record_table) {
*table_length = local_fru_table_length;
*total_record_set_identifiers = 1;
*total_table_records = 1;
return;
}
/* allocate fru table */
fru_table_size = sizeof(struct pldm_fru_record_data_format) +
sizeof(struct pldm_fru_record_tlv) +
strlen(fru_product);
local_fru_record_table = zalloc(fru_table_size);
if (!local_fru_record_table) {
prlog(PR_ERR, "%s: failed to allocate fru record table\n",
__func__);
return;
}
/* fill fru record data */
record = (struct pldm_fru_record_data_format *)local_fru_record_table;
record->record_set_id = htole16(RECORD_SET_ID);
record->record_type = PLDM_FRU_RECORD_TYPE_GENERAL;
record->num_fru_fields = 1;
record->encoding_type = PLDM_FRU_ENCODING_ASCII;
/* to start, set the size as the start of the TLV structs */
record_size = offsetof(struct pldm_fru_record_data_format, tlvs);
/* TLVs data */
fru_tlv = (struct pldm_fru_record_tlv *)(local_fru_record_table + record_size);
fru_tlv->type = PLDM_FRU_FIELD_TYPE_OTHER;
fru_tlv->length = strlen(fru_product);
memcpy(fru_tlv->value, fru_product, fru_tlv->length);
/* increment record_size by total size of this TLV */
record_size += (offsetof(struct pldm_fru_record_tlv, value) + fru_tlv->length);
*table_length = record_size;
*total_record_set_identifiers = 1;
*total_table_records = 1;
local_fru_table_length = *table_length;
}
int pldm_fru_get_local_table(void **fru_record_table_bytes,
uint32_t *fru_record_table_size)
{
if (!local_fru_record_table)
return OPAL_PARAMETER;
*fru_record_table_bytes = local_fru_record_table;
*fru_record_table_size = local_fru_table_length;
return OPAL_SUCCESS;
}
int pldm_fru_init(void)
{
int rc;
/* get fru record table */
rc = get_fru_record_table_req(&fru_record_table,
&fru_record_length);
if (rc)
goto err;
fru_init_complete(true);
prlog(PR_DEBUG, "%s - done\n", __func__);
return OPAL_SUCCESS;
err:
fru_init_complete(false);
return rc;
}