1321 lines
33 KiB
C
1321 lines
33 KiB
C
/* Copyright 2013-2014 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
|
|
#include <skiboot.h>
|
|
#include <fsp.h>
|
|
#include <fsp-sysparam.h>
|
|
#include <lock.h>
|
|
#include <device.h>
|
|
#include <ccan/endian/endian.h>
|
|
#include <errorlog.h>
|
|
#include <opal-api.h>
|
|
#include <timebase.h>
|
|
|
|
#include "fsp-codeupdate.h"
|
|
|
|
enum flash_state {
|
|
FLASH_STATE_ABSENT,
|
|
FLASH_STATE_INVALID, /* IPL side marker lid is invalid */
|
|
FLASH_STATE_READING,
|
|
FLASH_STATE_READ,
|
|
FLASH_STATE_ABORT,
|
|
};
|
|
|
|
enum lid_fetch_side {
|
|
FETCH_T_SIDE_ONLY,
|
|
FETCH_P_SIDE_ONLY,
|
|
FETCH_BOTH_SIDE,
|
|
};
|
|
|
|
static enum flash_state flash_state = FLASH_STATE_INVALID;
|
|
static enum lid_fetch_side lid_fetch_side = FETCH_BOTH_SIDE;
|
|
|
|
/* Image buffers */
|
|
static struct opal_sg_list *image_data;
|
|
static uint32_t tce_start;
|
|
static void *lid_data;
|
|
static char validate_buf[VALIDATE_BUF_SIZE];
|
|
|
|
/* TCE buffer lock */
|
|
static struct lock flash_lock = LOCK_UNLOCKED;
|
|
|
|
/* FW VPD data */
|
|
static struct fw_image_vpd fw_vpd[2];
|
|
|
|
/* Code update related sys parameters */
|
|
static uint32_t ipl_side;
|
|
static uint32_t hmc_managed;
|
|
static uint32_t update_policy;
|
|
static uint32_t in_flight_params;
|
|
|
|
/* If non-NULL, this gets called just before rebooting */
|
|
int (*fsp_flash_term_hook)(void);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_CU_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE,
|
|
OPAL_PLATFORM_FIRMWARE,
|
|
OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_CU_FLASH, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE,
|
|
OPAL_PLATFORM_FIRMWARE,
|
|
OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_CU_SG_LIST, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE,
|
|
OPAL_PLATFORM_FIRMWARE,
|
|
OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_CU_COMMIT, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE,
|
|
OPAL_PLATFORM_FIRMWARE,
|
|
OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_CU_MSG, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE,
|
|
OPAL_PLATFORM_FIRMWARE,
|
|
OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_CU_NOTIFY, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE,
|
|
OPAL_PLATFORM_FIRMWARE,
|
|
OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, OPAL_NA);
|
|
|
|
DEFINE_LOG_ENTRY(OPAL_RC_CU_MARKER_LID, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE,
|
|
OPAL_PLATFORM_FIRMWARE,
|
|
OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, OPAL_NA);
|
|
|
|
static inline void code_update_tce_map(uint32_t tce_offset,
|
|
void *buffer, uint32_t size)
|
|
{
|
|
uint32_t tlen = ALIGN_UP(size, TCE_PSIZE);
|
|
|
|
fsp_tce_map(PSI_DMA_CODE_UPD + tce_offset, buffer, tlen);
|
|
}
|
|
|
|
static inline void code_update_tce_unmap(uint32_t size)
|
|
{
|
|
fsp_tce_unmap(PSI_DMA_CODE_UPD, size);
|
|
}
|
|
|
|
static inline void set_def_fw_version(uint32_t side)
|
|
{
|
|
strncpy(fw_vpd[side].mi_keyword, FW_VERSION_UNKNOWN, MI_KEYWORD_SIZE);
|
|
strncpy(fw_vpd[side].ext_fw_id, FW_VERSION_UNKNOWN, ML_KEYWORD_SIZE);
|
|
}
|
|
|
|
/*
|
|
* Get IPL side
|
|
*/
|
|
static void get_ipl_side(void)
|
|
{
|
|
struct dt_node *iplp;
|
|
const char *side = NULL;
|
|
|
|
iplp = dt_find_by_path(dt_root, "ipl-params/ipl-params");
|
|
if (iplp)
|
|
side = dt_prop_get_def(iplp, "cec-ipl-side", NULL);
|
|
prlog(PR_NOTICE, "CUPD: IPL SIDE = %s\n", side);
|
|
|
|
if (!side || !strcmp(side, "temp"))
|
|
ipl_side = FW_IPL_SIDE_TEMP;
|
|
else
|
|
ipl_side = FW_IPL_SIDE_PERM;
|
|
}
|
|
|
|
|
|
/*
|
|
* Helper routines to retrieve code update related
|
|
* system parameters from FSP.
|
|
*/
|
|
|
|
static void inc_in_flight_param(void)
|
|
{
|
|
lock(&flash_lock);
|
|
in_flight_params++;
|
|
unlock(&flash_lock);
|
|
}
|
|
|
|
static void dec_in_flight_param(void)
|
|
{
|
|
lock(&flash_lock);
|
|
assert(in_flight_params > 0);
|
|
in_flight_params--;
|
|
unlock(&flash_lock);
|
|
}
|
|
|
|
static void got_code_update_policy(uint32_t param_id __unused, int err_len,
|
|
void *data __unused)
|
|
{
|
|
if (err_len != 4) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_INIT), "CUPD: Error "
|
|
"retrieving code update policy: %d\n", err_len);
|
|
} else
|
|
prlog(PR_NOTICE, "CUPD: Code update policy from FSP: %d\n",
|
|
update_policy);
|
|
|
|
dec_in_flight_param();
|
|
}
|
|
|
|
static void get_code_update_policy(void)
|
|
{
|
|
int rc;
|
|
|
|
inc_in_flight_param();
|
|
rc = fsp_get_sys_param(SYS_PARAM_FLASH_POLICY, &update_policy, 4,
|
|
got_code_update_policy, NULL);
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_INIT),
|
|
"CUPD: Error %d queueing param request\n", rc);
|
|
dec_in_flight_param();
|
|
}
|
|
}
|
|
|
|
static void got_platform_hmc_managed(uint32_t param_id __unused, int err_len,
|
|
void *data __unused)
|
|
{
|
|
if (err_len != 4) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_INIT), "CUPD: Error "
|
|
"retrieving hmc managed status: %d\n", err_len);
|
|
} else
|
|
prlog(PR_NOTICE, "CUPD: HMC managed status from FSP: %d\n",
|
|
hmc_managed);
|
|
|
|
dec_in_flight_param();
|
|
}
|
|
|
|
static void get_platform_hmc_managed(void)
|
|
{
|
|
int rc;
|
|
|
|
inc_in_flight_param();
|
|
rc = fsp_get_sys_param(SYS_PARAM_HMC_MANAGED, &hmc_managed, 4,
|
|
got_platform_hmc_managed, NULL);
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_INIT),
|
|
"CUPD: Error %d queueing param request\n", rc);
|
|
dec_in_flight_param();
|
|
}
|
|
}
|
|
|
|
static bool fw_ipl_side_update_notify(struct fsp_msg *msg)
|
|
{
|
|
u32 param_id = msg->data.words[0];
|
|
int dlen = msg->data.words[1] & 0xffff;
|
|
uint32_t state = msg->data.words[2];
|
|
|
|
if (param_id != SYS_PARAM_FW_IPL_SIDE)
|
|
return false;
|
|
|
|
if (dlen != 4) {
|
|
prlog(PR_DEBUG,
|
|
"CUPD: Invalid sysparams notify len : 0x%x\n", dlen);
|
|
return false;
|
|
}
|
|
|
|
prlog(PR_NOTICE, "CUPD: FW IPL side changed. Disable fast reboot\n");
|
|
prlog(PR_NOTICE, "CUPD: Next IPL side : %s\n",
|
|
state == FW_IPL_SIDE_TEMP ? "temp" : "perm");
|
|
|
|
disable_fast_reboot("FSP IPL Side Change");
|
|
return true;
|
|
}
|
|
|
|
static int64_t code_update_check_state(void)
|
|
{
|
|
switch(flash_state) {
|
|
case FLASH_STATE_ABSENT:
|
|
return OPAL_HARDWARE;
|
|
case FLASH_STATE_INVALID:
|
|
case FLASH_STATE_ABORT:
|
|
return OPAL_INTERNAL_ERROR;
|
|
case FLASH_STATE_READING:
|
|
return OPAL_BUSY;
|
|
default:
|
|
break;
|
|
}
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Get common marker LID additional data section
|
|
*/
|
|
static void *get_adf_sec_data(struct com_marker_adf_sec *adf_sec,
|
|
uint32_t name)
|
|
{
|
|
struct com_marker_adf_header *adf_header;
|
|
int i;
|
|
|
|
adf_header = (void *)adf_sec->adf_data;
|
|
for (i = 0; i < be32_to_cpu(adf_sec->adf_cnt); i++) {
|
|
if (be32_to_cpu(adf_header->name) == name)
|
|
return adf_header;
|
|
|
|
adf_header = (void *)adf_header + be32_to_cpu(adf_header->size);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Parse common marker LID to get FW version details
|
|
*
|
|
* Note:
|
|
* At present, we are parsing "Service Pack Nomenclature ADF"
|
|
* section only. If we are adding FW IP support, then we have
|
|
* to parse "Firmware IP Protection ADF" as well.
|
|
*/
|
|
static void parse_marker_lid(uint32_t side)
|
|
{
|
|
struct com_marker_header *header;
|
|
struct com_marker_mi_section *mi_sec;
|
|
struct com_marker_adf_sec *adf_sec;
|
|
struct com_marker_adf_sp *adf_sp;
|
|
|
|
header = (void *)lid_data;
|
|
|
|
/* Get MI details */
|
|
mi_sec = (void *)header + be32_to_cpu(header->MI_offset);
|
|
/*
|
|
* If Marker LID is invalid, then FSP will return a Marker
|
|
* LID with ASCII zeros for the entire MI keyword.
|
|
*/
|
|
if (mi_sec->mi_keyword[0] == '0')
|
|
return;
|
|
|
|
strncpy(fw_vpd[side].mi_keyword, mi_sec->mi_keyword, MI_KEYWORD_SIZE);
|
|
fw_vpd[side].mi_keyword[MI_KEYWORD_SIZE - 1] = '\0';
|
|
prlog(PR_NOTICE, "CUPD: %s side MI Keyword = %s\n",
|
|
side == 0x00 ? "P" : "T", fw_vpd[side].mi_keyword);
|
|
|
|
/* Get ML details */
|
|
adf_sec = (void *)header + be32_to_cpu(mi_sec->adf_offset);
|
|
adf_sp = get_adf_sec_data(adf_sec, ADF_NAME_SP);
|
|
if (!adf_sp)
|
|
return;
|
|
|
|
strncpy(fw_vpd[side].ext_fw_id,
|
|
(void *)adf_sp + be32_to_cpu(adf_sp->sp_name_offset),
|
|
ML_KEYWORD_SIZE);
|
|
fw_vpd[side].ext_fw_id[ML_KEYWORD_SIZE - 1] = '\0';
|
|
prlog(PR_NOTICE, "CUPD: %s side ML Keyword = %s\n",
|
|
side == 0x00 ? "P" : "T", fw_vpd[side].ext_fw_id);
|
|
}
|
|
|
|
static void validate_com_marker_lid(void)
|
|
{
|
|
if (!strncmp(fw_vpd[ipl_side].mi_keyword, FW_VERSION_UNKNOWN,
|
|
sizeof(FW_VERSION_UNKNOWN))) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_MARKER_LID),
|
|
"CUPD: IPL side Marker LID is not valid\n");
|
|
flash_state = FLASH_STATE_INVALID;
|
|
return;
|
|
}
|
|
|
|
flash_state = FLASH_STATE_READ;
|
|
}
|
|
|
|
static void fetch_lid_data_complete(struct fsp_msg *msg)
|
|
{
|
|
void *buffer;
|
|
size_t length, chunk;
|
|
uint32_t lid_id, offset;
|
|
uint16_t id;
|
|
uint8_t flags, status;
|
|
int rc;
|
|
|
|
status = (msg->resp->word1 >> 8) & 0xff;
|
|
flags = (msg->data.words[0] >> 16) & 0xff;
|
|
id = msg->data.words[0] & 0xffff;
|
|
lid_id = msg->data.words[1];
|
|
offset = msg->resp->data.words[1];
|
|
length = msg->resp->data.words[2];
|
|
|
|
prlog(PR_NOTICE, "CUPD: Marker LID id : size : status = "
|
|
"0x%x : 0x%x : 0x%x\n",
|
|
msg->data.words[1], msg->resp->data.words[2], status);
|
|
|
|
fsp_freemsg(msg);
|
|
|
|
switch (status) {
|
|
case FSP_STATUS_SUCCESS: /* Read complete, parse VPD */
|
|
parse_marker_lid(lid_id == P_COM_MARKER_LID_ID ? 0 : 1);
|
|
break;
|
|
case FSP_STATUS_MORE_DATA: /* More data left */
|
|
offset += length;
|
|
chunk = MARKER_LID_SIZE - offset;
|
|
if (chunk > 0) {
|
|
buffer = (void *)PSI_DMA_CODE_UPD + offset;
|
|
rc = fsp_fetch_data_queue(flags, id, lid_id,
|
|
offset, buffer, &chunk,
|
|
fetch_lid_data_complete);
|
|
|
|
/* If queue msg fails, then continue with marker LID
|
|
* validation hoping that we have at least boot side
|
|
* information.
|
|
*/
|
|
if (rc == OPAL_SUCCESS)
|
|
return;
|
|
}
|
|
break;
|
|
default: /* Fetch LID call failed */
|
|
break;
|
|
}
|
|
|
|
/* If required, fetch T side marker LID */
|
|
if (lid_id == P_COM_MARKER_LID_ID &&
|
|
lid_fetch_side == FETCH_BOTH_SIDE) {
|
|
length = MARKER_LID_SIZE;
|
|
rc = fsp_fetch_data_queue(flags, id, T_COM_MARKER_LID_ID,
|
|
0, (void *)PSI_DMA_CODE_UPD,
|
|
&length, fetch_lid_data_complete);
|
|
|
|
/* If queue msg fails, then continue with marker LID
|
|
* validation hoping that we have at least boot side
|
|
* information.
|
|
*/
|
|
if (rc == OPAL_SUCCESS)
|
|
return;
|
|
}
|
|
|
|
lock(&flash_lock);
|
|
|
|
/* Validate marker LID data */
|
|
validate_com_marker_lid();
|
|
/* TCE unmap */
|
|
code_update_tce_unmap(MARKER_LID_SIZE);
|
|
|
|
unlock(&flash_lock);
|
|
}
|
|
|
|
static void fetch_com_marker_lid(void)
|
|
{
|
|
size_t length = MARKER_LID_SIZE;
|
|
uint32_t lid_id;
|
|
int rc;
|
|
|
|
/* Read in progress? */
|
|
rc = code_update_check_state();
|
|
if (rc == OPAL_HARDWARE || rc == OPAL_BUSY)
|
|
return;
|
|
|
|
if (lid_fetch_side == FETCH_T_SIDE_ONLY) {
|
|
lid_id = T_COM_MARKER_LID_ID;
|
|
set_def_fw_version(FW_IPL_SIDE_TEMP);
|
|
} else if (lid_fetch_side == FETCH_P_SIDE_ONLY) {
|
|
lid_id = P_COM_MARKER_LID_ID;
|
|
set_def_fw_version(FW_IPL_SIDE_PERM);
|
|
} else {
|
|
lid_id = P_COM_MARKER_LID_ID;
|
|
set_def_fw_version(FW_IPL_SIDE_PERM);
|
|
set_def_fw_version(FW_IPL_SIDE_TEMP);
|
|
}
|
|
|
|
code_update_tce_map(0, lid_data, length);
|
|
rc = fsp_fetch_data_queue(0x00, 0x05, lid_id, 0,
|
|
(void *)PSI_DMA_CODE_UPD, &length,
|
|
fetch_lid_data_complete);
|
|
if (!rc)
|
|
flash_state = FLASH_STATE_READING;
|
|
else
|
|
flash_state = FLASH_STATE_INVALID;
|
|
}
|
|
|
|
/*
|
|
* Add MI and ML keyword details into DT
|
|
*/
|
|
#define FW_VER_SIZE 64
|
|
static void add_opal_firmware_version(void)
|
|
{
|
|
struct dt_node *dt_fw;
|
|
char buffer[FW_VER_SIZE];
|
|
int offset;
|
|
|
|
dt_fw = dt_find_by_path(dt_root, "ibm,opal/firmware");
|
|
if (!dt_fw)
|
|
return;
|
|
|
|
/* MI version */
|
|
offset = snprintf(buffer, FW_VER_SIZE, "MI %s %s",
|
|
fw_vpd[FW_IPL_SIDE_TEMP].mi_keyword,
|
|
fw_vpd[FW_IPL_SIDE_PERM].mi_keyword);
|
|
if (ipl_side == FW_IPL_SIDE_TEMP)
|
|
snprintf(buffer + offset, FW_VER_SIZE - offset,
|
|
" %s", fw_vpd[FW_IPL_SIDE_TEMP].mi_keyword);
|
|
else
|
|
snprintf(buffer + offset, FW_VER_SIZE - offset,
|
|
" %s", fw_vpd[FW_IPL_SIDE_PERM].mi_keyword);
|
|
|
|
dt_add_property(dt_fw, "mi-version", buffer, strlen(buffer));
|
|
|
|
/* ML version */
|
|
offset = snprintf(buffer, FW_VER_SIZE, "ML %s %s",
|
|
fw_vpd[FW_IPL_SIDE_TEMP].ext_fw_id,
|
|
fw_vpd[FW_IPL_SIDE_PERM].ext_fw_id);
|
|
if (ipl_side == FW_IPL_SIDE_TEMP)
|
|
snprintf(buffer + offset, FW_VER_SIZE - offset,
|
|
" %s", fw_vpd[FW_IPL_SIDE_TEMP].ext_fw_id);
|
|
else
|
|
snprintf(buffer + offset, FW_VER_SIZE - offset,
|
|
" %s", fw_vpd[FW_IPL_SIDE_PERM].ext_fw_id);
|
|
|
|
dt_add_property(dt_fw, "ml-version", buffer, strlen(buffer));
|
|
}
|
|
|
|
/*
|
|
* This is called right before starting the payload (Linux) to
|
|
* ensure the common marker LID read and parsing has happened
|
|
* before we transfer control.
|
|
*/
|
|
void fsp_code_update_wait_vpd(bool is_boot)
|
|
{
|
|
int waited = 0;
|
|
|
|
if (!fsp_present())
|
|
return;
|
|
|
|
prlog(PR_NOTICE, "CUPD: Waiting read marker LID"
|
|
" and in flight parsm completion...\n");
|
|
|
|
lock(&flash_lock);
|
|
while(true) {
|
|
if (!(flash_state == FLASH_STATE_READING || in_flight_params))
|
|
break;
|
|
unlock(&flash_lock);
|
|
time_wait_ms(5);
|
|
waited+=5;
|
|
lock(&flash_lock);
|
|
}
|
|
unlock(&flash_lock);
|
|
|
|
if (waited)
|
|
prlog(PR_DEBUG, "CUPD: fsp_code_update_wait_vpd %d\n", waited);
|
|
|
|
if (is_boot)
|
|
add_opal_firmware_version();
|
|
}
|
|
|
|
static int code_update_start(void)
|
|
{
|
|
struct fsp_msg *msg;
|
|
int rc;
|
|
uint16_t comp = 0x00; /* All components */
|
|
uint8_t side = OPAL_COMMIT_TMP_SIDE; /* Temporary side */
|
|
|
|
msg = fsp_mkmsg(FSP_CMD_FLASH_START, 1, side << 16 | comp);
|
|
if (!msg) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_MSG),
|
|
"CUPD: CMD_FLASH_START message allocation failed !\n");
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
if (fsp_sync_msg(msg, false)) {
|
|
fsp_freemsg(msg);
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
rc = (msg->resp->word1 >> 8) & 0xff;
|
|
fsp_freemsg(msg);
|
|
return rc;
|
|
}
|
|
|
|
static int code_update_write_lid(uint32_t lid_id, uint32_t size)
|
|
{
|
|
struct fsp_msg *msg;
|
|
int rc, n_pairs = 1;
|
|
|
|
msg = fsp_mkmsg(FSP_CMD_FLASH_WRITE, 5, lid_id,
|
|
n_pairs, 0, tce_start, size);
|
|
if (!msg) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_MSG),
|
|
"CUPD: CMD_FLASH_WRITE message allocation failed !\n");
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
if (fsp_sync_msg(msg, false)) {
|
|
fsp_freemsg(msg);
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
rc = (msg->resp->word1 >> 8) & 0xff;
|
|
fsp_freemsg(msg);
|
|
return rc;
|
|
}
|
|
|
|
static int code_update_del_lid(uint32_t lid_id)
|
|
{
|
|
struct fsp_msg *msg;
|
|
int rc;
|
|
|
|
msg = fsp_mkmsg(FSP_CMD_FLASH_DEL, 1, lid_id);
|
|
if (!msg) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_MSG),
|
|
"CUPD: CMD_FLASH_DEL message allocation failed !\n");
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
if (fsp_sync_msg(msg, false)) {
|
|
fsp_freemsg(msg);
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
rc = (msg->resp->word1 >> 8) & 0xff;
|
|
fsp_freemsg(msg);
|
|
return rc;
|
|
}
|
|
|
|
static int code_update_complete(uint32_t cmd)
|
|
{
|
|
struct fsp_msg *msg;
|
|
int rc;
|
|
|
|
msg = fsp_mkmsg(cmd, 0);
|
|
if (!msg) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_MSG),
|
|
"CUPD: CUPD COMPLETE message allocation failed !\n");
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
if (fsp_sync_msg(msg, false)) {
|
|
fsp_freemsg(msg);
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
rc = (msg->resp->word1 >> 8) & 0xff;
|
|
fsp_freemsg(msg);
|
|
return rc;
|
|
}
|
|
|
|
static int code_update_swap_side(void)
|
|
{
|
|
struct fsp_msg *msg;
|
|
int rc;
|
|
|
|
msg = fsp_mkmsg(FSP_CMD_FLASH_SWAP, 0);
|
|
if (!msg) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_MSG),
|
|
"CUPD: CMD_FLASH_SWAP message allocation failed !\n");
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (fsp_sync_msg(msg, false)) {
|
|
fsp_freemsg(msg);
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
rc = (msg->resp->word1 >> 8) & 0xff;
|
|
fsp_freemsg(msg);
|
|
return rc;
|
|
}
|
|
|
|
static int code_update_set_ipl_side(void)
|
|
{
|
|
struct fsp_msg *msg;
|
|
uint8_t side = FW_IPL_SIDE_TEMP; /* Next IPL side */
|
|
int rc;
|
|
|
|
msg = fsp_mkmsg(FSP_CMD_SET_IPL_SIDE, 1, side << 16);
|
|
if (!msg) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_MSG),
|
|
"CUPD: CMD_SET_IPL_SIDE message allocation failed!\n");
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
if (fsp_sync_msg(msg, false)) {
|
|
fsp_freemsg(msg);
|
|
log_simple_error(&e_info(OPAL_RC_CU_MSG),
|
|
"CUPD: Setting next IPL side failed!\n");
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
rc = (msg->resp->word1 >> 8) & 0xff;
|
|
fsp_freemsg(msg);
|
|
return rc;
|
|
}
|
|
|
|
static void code_update_commit_complete(struct fsp_msg *msg)
|
|
{
|
|
int rc;
|
|
uint8_t type;
|
|
|
|
rc = (msg->resp->word1 >> 8) & 0xff;
|
|
type = (msg->word1 >> 8) & 0xff;
|
|
fsp_freemsg(msg);
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_COMMIT),
|
|
"CUPD: Code update commit failed, err 0x%x\n", rc);
|
|
return;
|
|
}
|
|
|
|
/* Reset cached VPD data */
|
|
lock(&flash_lock);
|
|
|
|
/* Find commit type */
|
|
if (type == 0x01) {
|
|
lid_fetch_side = FETCH_P_SIDE_ONLY;
|
|
} else if (type == 0x02)
|
|
lid_fetch_side = FETCH_T_SIDE_ONLY;
|
|
else
|
|
lid_fetch_side = FETCH_BOTH_SIDE;
|
|
|
|
fetch_com_marker_lid();
|
|
|
|
unlock(&flash_lock);
|
|
}
|
|
|
|
static int code_update_commit(uint32_t cmd)
|
|
{
|
|
struct fsp_msg *msg;
|
|
|
|
msg = fsp_mkmsg(cmd, 0);
|
|
if (!msg) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_MSG),
|
|
"CUPD: COMMIT message allocation failed !\n");
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
if (fsp_queue_msg(msg, code_update_commit_complete)) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_COMMIT),
|
|
"CUPD: Failed to queue code update commit message\n");
|
|
fsp_freemsg(msg);
|
|
return OPAL_INTERNAL_ERROR;
|
|
}
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Inband code update is allowed?
|
|
*/
|
|
static int64_t validate_inband_policy(void)
|
|
{
|
|
/* Quirk:
|
|
* If the code update policy is out-of-band, but the system
|
|
* is not HMC-managed, then inband update is allowed.
|
|
*/
|
|
if (hmc_managed != PLATFORM_HMC_MANAGED)
|
|
return 0;
|
|
if (update_policy == INBAND_UPDATE_ALLOWED)
|
|
return 0;
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Validate magic Number
|
|
*/
|
|
static int64_t validate_magic_num(uint16_t magic)
|
|
{
|
|
if (magic != IMAGE_MAGIC_NUMBER)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Compare MI keyword to make sure candidate image
|
|
* is valid for this platform.
|
|
*/
|
|
static int64_t validate_image_version(struct update_image_header *header,
|
|
uint32_t *result)
|
|
{
|
|
struct fw_image_vpd vpd;
|
|
int t_valid = 0, p_valid = 0, cton_ver = -1, ptot_ver = -1;
|
|
|
|
/* Valid flash image level? */
|
|
if (strncmp(fw_vpd[0].mi_keyword, FW_VERSION_UNKNOWN,
|
|
sizeof(FW_VERSION_UNKNOWN)) != 0)
|
|
p_valid = 1;
|
|
|
|
if (strncmp(fw_vpd[1].mi_keyword, FW_VERSION_UNKNOWN,
|
|
sizeof(FW_VERSION_UNKNOWN)) != 0)
|
|
t_valid = 1;
|
|
|
|
/* Validate with IPL side image */
|
|
vpd = fw_vpd[ipl_side];
|
|
|
|
/* Validate platform identifier (first two char of MI keyword) */
|
|
if (strncmp(vpd.mi_keyword, header->mi_keyword_data, 2) != 0) {
|
|
*result = VALIDATE_INVALID_IMG;
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/* Don't flash different FW series (like P7 image on P8) */
|
|
if (vpd.mi_keyword[2] != header->mi_keyword_data[2]) {
|
|
*result = VALIDATE_INVALID_IMG;
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/* Get current to new version difference */
|
|
cton_ver = strncmp(vpd.mi_keyword + 3, header->mi_keyword_data + 3, 6);
|
|
|
|
/* Get P to T version difference */
|
|
if (t_valid && p_valid)
|
|
ptot_ver = strncmp(fw_vpd[0].mi_keyword + 3,
|
|
fw_vpd[1].mi_keyword + 3, 6);
|
|
|
|
/* Update validation result */
|
|
if (ipl_side == FW_IPL_SIDE_TEMP) {
|
|
if (!ptot_ver && cton_ver > 0) /* downgrade T side */
|
|
*result = VALIDATE_TMP_UPDATE_DL;
|
|
else if (!ptot_ver && cton_ver <= 0) /* upgrade T side */
|
|
*result = VALIDATE_TMP_UPDATE;
|
|
else if (cton_ver > 0) /* Implied commit & downgrade T side */
|
|
*result = VALIDATE_TMP_COMMIT_DL;
|
|
else /* Implied commit & upgrade T side */
|
|
*result = VALIDATE_TMP_COMMIT;
|
|
} else {
|
|
if (!t_valid) /* Current unknown */
|
|
*result = VALIDATE_CUR_UNKNOWN;
|
|
else if (cton_ver > 0) /* downgrade FW version */
|
|
*result = VALIDATE_TMP_UPDATE_DL;
|
|
else /* upgrade FW version */
|
|
*result = VALIDATE_TMP_UPDATE;
|
|
}
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Validate candidate image
|
|
*/
|
|
static int validate_candidate_image(uint64_t buffer,
|
|
uint32_t size, uint32_t *result)
|
|
{
|
|
struct update_image_header *header;
|
|
int rc = OPAL_PARAMETER;
|
|
|
|
if (size < VALIDATE_BUF_SIZE)
|
|
goto out;
|
|
|
|
rc = code_update_check_state();
|
|
if (rc != OPAL_SUCCESS)
|
|
goto out;
|
|
|
|
if (validate_inband_policy() != 0) {
|
|
*result = VALIDATE_FLASH_AUTH;
|
|
rc = OPAL_SUCCESS;
|
|
goto out;
|
|
}
|
|
|
|
memcpy(validate_buf, (void *)buffer, VALIDATE_BUF_SIZE);
|
|
header = (struct update_image_header *)validate_buf;
|
|
|
|
if (validate_magic_num(be32_to_cpu(header->magic)) != 0) {
|
|
*result = VALIDATE_INVALID_IMG;
|
|
rc = OPAL_SUCCESS;
|
|
goto out;
|
|
}
|
|
rc = validate_image_version(header, result);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static int validate_out_buf_mi_data(void *buffer, int offset, uint32_t result)
|
|
{
|
|
struct update_image_header *header = (void *)validate_buf;
|
|
|
|
/* Current T & P side MI data */
|
|
offset += snprintf(buffer + offset, VALIDATE_BUF_SIZE - offset,
|
|
"MI %s %s\n",
|
|
fw_vpd[1].mi_keyword, fw_vpd[0].mi_keyword);
|
|
|
|
/* New T & P side MI data */
|
|
offset += snprintf(buffer + offset, VALIDATE_BUF_SIZE - offset,
|
|
"MI %s", header->mi_keyword_data);
|
|
if (result == VALIDATE_TMP_COMMIT_DL ||
|
|
result == VALIDATE_TMP_COMMIT)
|
|
offset += snprintf(buffer + offset,
|
|
VALIDATE_BUF_SIZE - offset,
|
|
" %s\n", fw_vpd[1].mi_keyword);
|
|
else
|
|
offset += snprintf(buffer + offset,
|
|
VALIDATE_BUF_SIZE - offset,
|
|
" %s\n", fw_vpd[0].mi_keyword);
|
|
return offset;
|
|
}
|
|
|
|
static int validate_out_buf_ml_data(void *buffer, int offset, uint32_t result)
|
|
{
|
|
struct update_image_header *header = (void *)validate_buf;
|
|
/* Candidate image ML data */
|
|
char *ext_fw_id = (void *)header->data;
|
|
|
|
/* Current T & P side ML data */
|
|
offset += snprintf(buffer + offset, VALIDATE_BUF_SIZE - offset,
|
|
"ML %s %s\n",
|
|
fw_vpd[1].ext_fw_id, fw_vpd[0].ext_fw_id);
|
|
|
|
/* New T & P side ML data */
|
|
offset += snprintf(buffer + offset, VALIDATE_BUF_SIZE - offset,
|
|
"ML %s", ext_fw_id);
|
|
if (result == VALIDATE_TMP_COMMIT_DL ||
|
|
result == VALIDATE_TMP_COMMIT)
|
|
offset += snprintf(buffer + offset,
|
|
VALIDATE_BUF_SIZE - offset,
|
|
" %s\n", fw_vpd[1].ext_fw_id);
|
|
else
|
|
offset += snprintf(buffer + offset,
|
|
VALIDATE_BUF_SIZE - offset,
|
|
" %s\n", fw_vpd[0].ext_fw_id);
|
|
|
|
return offset;
|
|
}
|
|
|
|
/*
|
|
* Copy LID data to TCE buffer
|
|
*/
|
|
static int get_lid_data(struct opal_sg_list *list,
|
|
int lid_size, int lid_offset)
|
|
{
|
|
struct opal_sg_list *sg;
|
|
struct opal_sg_entry *entry;
|
|
int length, num_entries, i, buf_pos = 0;
|
|
int map_act, map_size;
|
|
bool last = false;
|
|
|
|
/* Reset TCE start address */
|
|
tce_start = 0;
|
|
|
|
for (sg = list; sg; sg = (struct opal_sg_list*)be64_to_cpu(sg->next)) {
|
|
length = (be64_to_cpu(sg->length) & ~(SG_LIST_VERSION << 56)) - 16;
|
|
num_entries = length / sizeof(struct opal_sg_entry);
|
|
if (num_entries <= 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < num_entries; i++) {
|
|
entry = &sg->entry[i];
|
|
|
|
/*
|
|
* Continue until we get data block which
|
|
* contains LID data
|
|
*/
|
|
if (lid_offset > be64_to_cpu(entry->length)) {
|
|
lid_offset -= be64_to_cpu(entry->length);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* SG list entry size can be more than 4k.
|
|
* Map only required pages, instead of
|
|
* mapping entire entry.
|
|
*/
|
|
map_act = be64_to_cpu(entry->length);
|
|
map_size = be64_to_cpu(entry->length);
|
|
|
|
/* First TCE mapping */
|
|
if (!tce_start) {
|
|
tce_start = PSI_DMA_CODE_UPD +
|
|
(lid_offset & 0xfff);
|
|
map_act = be64_to_cpu(entry->length) - lid_offset;
|
|
lid_offset &= ~0xfff;
|
|
map_size = be64_to_cpu(entry->length) - lid_offset;
|
|
}
|
|
|
|
/* Check pending LID size to map */
|
|
if (lid_size <= map_act) {
|
|
/* (map_size - map_act) gives page
|
|
* start to tce offset difference.
|
|
* This is required when LID size
|
|
* is <= 4k.
|
|
*/
|
|
map_size = (map_size - map_act) + lid_size;
|
|
last = true;
|
|
}
|
|
|
|
/* Ajust remaining size to map */
|
|
lid_size -= map_act;
|
|
|
|
/* TCE mapping */
|
|
code_update_tce_map(buf_pos,
|
|
(void*)(be64_to_cpu(entry->data)
|
|
+ lid_offset),
|
|
map_size);
|
|
buf_pos += map_size;
|
|
/* Reset LID offset count */
|
|
lid_offset = 0;
|
|
|
|
if (last)
|
|
return OPAL_SUCCESS;
|
|
}
|
|
} /* outer loop */
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* If IPL side is T, then swap P & T sides to add
|
|
* new fix to T side.
|
|
*/
|
|
static int validate_ipl_side(void)
|
|
{
|
|
if (ipl_side == FW_IPL_SIDE_PERM)
|
|
return 0;
|
|
return code_update_swap_side();
|
|
}
|
|
|
|
static int64_t fsp_opal_validate_flash(uint64_t buffer,
|
|
uint32_t *size, uint32_t *result)
|
|
{
|
|
int64_t rc = 0;
|
|
int offset;
|
|
|
|
lock(&flash_lock);
|
|
|
|
rc = validate_candidate_image(buffer, *size, result);
|
|
/* Fill output buffer
|
|
*
|
|
* Format:
|
|
* MI<sp>current-T-image<sp>current-P-image<0x0A>
|
|
* MI<sp>new-T-image<sp>new-P-image<0x0A>
|
|
* ML<sp>current-T-image<sp>current-P-image<0x0A>
|
|
* ML<sp>new-T-image<sp>new-P-image<0x0A>
|
|
*/
|
|
if (!rc && (*result != VALIDATE_FLASH_AUTH &&
|
|
*result != VALIDATE_INVALID_IMG)) {
|
|
/* Clear output buffer */
|
|
memset((void *)buffer, 0, VALIDATE_BUF_SIZE);
|
|
|
|
offset = validate_out_buf_mi_data((void *)buffer, 0, *result);
|
|
offset += validate_out_buf_ml_data((void *)buffer,
|
|
offset, *result);
|
|
*size = offset;
|
|
}
|
|
|
|
unlock(&flash_lock);
|
|
return rc;
|
|
}
|
|
|
|
/* Commit/Reject T side image */
|
|
static int64_t fsp_opal_manage_flash(uint8_t op)
|
|
{
|
|
uint32_t cmd;
|
|
int rc;
|
|
|
|
lock(&flash_lock);
|
|
rc = code_update_check_state();
|
|
unlock(&flash_lock);
|
|
|
|
if (rc != OPAL_SUCCESS)
|
|
return rc;
|
|
|
|
if (op != OPAL_REJECT_TMP_SIDE && op != OPAL_COMMIT_TMP_SIDE)
|
|
return OPAL_PARAMETER;
|
|
|
|
if ((op == OPAL_COMMIT_TMP_SIDE && ipl_side == FW_IPL_SIDE_PERM) ||
|
|
(op == OPAL_REJECT_TMP_SIDE && ipl_side == FW_IPL_SIDE_TEMP))
|
|
return OPAL_ACTIVE_SIDE_ERR;
|
|
|
|
if (op == OPAL_COMMIT_TMP_SIDE)
|
|
cmd = FSP_CMD_FLASH_NORMAL;
|
|
else
|
|
cmd = FSP_CMD_FLASH_REMOVE;
|
|
|
|
return code_update_commit(cmd);
|
|
}
|
|
|
|
static int fsp_flash_firmware(void)
|
|
{
|
|
struct update_image_header *header;
|
|
struct lid_index_entry *idx_entry;
|
|
struct opal_sg_list *list;
|
|
struct opal_sg_entry *entry;
|
|
int rc, i;
|
|
|
|
/* Make sure no outstanding LID read is in progress */
|
|
rc = code_update_check_state();
|
|
if (rc == OPAL_BUSY)
|
|
fsp_code_update_wait_vpd(false);
|
|
|
|
/* Get LID Index */
|
|
list = image_data;
|
|
if (!list)
|
|
goto out;
|
|
entry = &list->entry[0];
|
|
header = (struct update_image_header *)be64_to_cpu(entry->data);
|
|
idx_entry = (void *)header + be16_to_cpu(header->lid_index_offset);
|
|
|
|
/* FIXME:
|
|
* At present we depend on FSP to validate CRC for
|
|
* individual LIDs. Calculate and validate individual
|
|
* LID CRC here.
|
|
*/
|
|
|
|
if (validate_ipl_side() != 0) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_FLASH), "CUPD: "
|
|
"Rename (Swap T and P) failed!\n");
|
|
goto out;
|
|
}
|
|
|
|
/* Set next IPL side */
|
|
if (code_update_set_ipl_side() != 0) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_FLASH), "CUPD: "
|
|
"Setting next IPL side failed!\n");
|
|
goto out;
|
|
}
|
|
|
|
/* Start code update process */
|
|
if (code_update_start() != 0) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_FLASH), "CUPD: "
|
|
"Code update start failed!\n");
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Delete T side LIDs before writing.
|
|
*
|
|
* Note:
|
|
* - Applicable for FWv >= 760.
|
|
* - Current Code Update design is to ignore
|
|
* any delete lid failure, and continue with
|
|
* the update.
|
|
*/
|
|
rc = code_update_del_lid(DEL_UPD_SIDE_LIDS);
|
|
|
|
if (rc)
|
|
prlog(PR_TRACE, "CUPD: Failed to delete LIDs (%d). This is okay, continuing..", rc);
|
|
|
|
for (i = 0; i < be16_to_cpu(header->number_lids); i++) {
|
|
if (be32_to_cpu(idx_entry->size) > LID_MAX_SIZE) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_FLASH), "CUPD: LID"
|
|
" (0x%x) size 0x%x is > max LID size (0x%x).\n",
|
|
be32_to_cpu(idx_entry->id),
|
|
be32_to_cpu(idx_entry->size), LID_MAX_SIZE);
|
|
goto abort_update;
|
|
}
|
|
|
|
rc = get_lid_data(list, be32_to_cpu(idx_entry->size),
|
|
be32_to_cpu(idx_entry->offset));
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_FLASH), "CUPD: "
|
|
"Failed to parse LID from firmware image."
|
|
" (rc : %d).\n", rc);
|
|
goto abort_update;
|
|
}
|
|
|
|
rc = code_update_write_lid(be32_to_cpu(idx_entry->id),
|
|
be32_to_cpu(idx_entry->size));
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_FLASH), "CUPD: "
|
|
"Failed to write LID to FSP. (rc : %d).\n", rc);
|
|
goto abort_update;
|
|
}
|
|
|
|
/* Unmap TCE */
|
|
code_update_tce_unmap(PSI_DMA_CODE_UPD_SIZE);
|
|
|
|
/* Next LID index */
|
|
idx_entry = (void *)idx_entry + sizeof(struct lid_index_entry);
|
|
}
|
|
|
|
/* Code update completed */
|
|
rc = code_update_complete(FSP_CMD_FLASH_COMPLETE);
|
|
|
|
return rc;
|
|
|
|
abort_update:
|
|
rc = code_update_complete(FSP_CMD_FLASH_ABORT);
|
|
if (rc)
|
|
log_simple_error(&e_info(OPAL_RC_CU_FLASH), "CUPD: "
|
|
"Code update abort command failed. (rc : %d).", rc);
|
|
|
|
out:
|
|
return -1;
|
|
}
|
|
|
|
static int64_t validate_sglist(struct opal_sg_list *list)
|
|
{
|
|
struct opal_sg_list *sg;
|
|
struct opal_sg_entry *prev_entry, *entry;
|
|
int length, num_entries, i;
|
|
|
|
prev_entry = NULL;
|
|
for (sg = list; sg; sg = (struct opal_sg_list*)be64_to_cpu(sg->next)) {
|
|
length = (be64_to_cpu(sg->length) & ~(SG_LIST_VERSION << 56)) - 16;
|
|
num_entries = length / sizeof(struct opal_sg_entry);
|
|
if (num_entries <= 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < num_entries; i++) {
|
|
entry = &sg->entry[i];
|
|
|
|
/* All entries must be aligned */
|
|
if (((uint64_t)be64_to_cpu(entry->data)) & 0xfff)
|
|
return OPAL_PARAMETER;
|
|
|
|
/* All non-terminal entries size must be aligned */
|
|
if (prev_entry && (prev_entry->length & 0xfff))
|
|
return OPAL_PARAMETER;
|
|
|
|
prev_entry = entry;
|
|
}
|
|
}
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
static int64_t fsp_opal_update_flash(struct opal_sg_list *list)
|
|
{
|
|
struct opal_sg_entry *entry;
|
|
int length, num_entries, result = 0, rc = OPAL_PARAMETER;
|
|
|
|
/* Ensure that the sg list honors our alignment requirements */
|
|
rc = validate_sglist(list);
|
|
if (rc) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_SG_LIST),
|
|
"CUPD: sglist fails alignment requirements\n");
|
|
return rc;
|
|
}
|
|
|
|
lock(&flash_lock);
|
|
if (!list) { /* Cancel update request */
|
|
fsp_flash_term_hook = NULL;
|
|
image_data = NULL;
|
|
rc = OPAL_SUCCESS;
|
|
goto out;
|
|
}
|
|
|
|
disable_fast_reboot("FSP Code Update");
|
|
|
|
length = (be64_to_cpu(list->length) & ~(SG_LIST_VERSION << 56)) - 16;
|
|
num_entries = length / sizeof(struct opal_sg_entry);
|
|
if (num_entries <= 0)
|
|
goto out;
|
|
|
|
/* Validate image header */
|
|
entry = &list->entry[0];
|
|
rc = validate_candidate_image((uint64_t)be64_to_cpu(entry->data),
|
|
VALIDATE_BUF_SIZE, &result);
|
|
if (!rc && (result != VALIDATE_FLASH_AUTH &&
|
|
result != VALIDATE_INVALID_IMG)) {
|
|
image_data = list;
|
|
fsp_flash_term_hook = fsp_flash_firmware;
|
|
goto out;
|
|
}
|
|
|
|
/* Adjust return code */
|
|
if (result == VALIDATE_FLASH_AUTH)
|
|
rc = OPAL_FLASH_NO_AUTH;
|
|
else if (result == VALIDATE_INVALID_IMG)
|
|
rc = OPAL_INVALID_IMAGE;
|
|
|
|
out:
|
|
unlock(&flash_lock);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Code Update notifications
|
|
*
|
|
* Note: At present we just ACK these notifications.
|
|
* Reset cached VPD data if we are going to support
|
|
* concurrent image maint in future.
|
|
*/
|
|
static bool code_update_notify(uint32_t cmd_sub_mod, struct fsp_msg *msg)
|
|
{
|
|
int rc;
|
|
uint32_t cmd;
|
|
|
|
switch(cmd_sub_mod) {
|
|
case FSP_CMD_FLASH_CACHE:
|
|
cmd = FSP_CMD_FLASH_CACHE_RSP;
|
|
prlog(PR_NOTICE, "CUPD: Update LID cache event [data = 0x%x]\n",
|
|
msg->data.words[0]);
|
|
break;
|
|
case FSP_CMD_FLASH_OUTC:
|
|
case FSP_CMD_FLASH_OUTR:
|
|
case FSP_CMD_FLASH_OUTS:
|
|
cmd = FSP_CMD_FLASH_OUT_RSP;
|
|
prlog(PR_NOTICE, "CUPD: Out of band commit notify "
|
|
"[Type = 0x%x]\n", (msg->word1 >> 8) & 0xff);
|
|
break;
|
|
default:
|
|
log_simple_error(&e_info(OPAL_RC_CU_NOTIFY), "CUPD: Unknown "
|
|
"notification [cmd = 0x%x]\n", cmd_sub_mod);
|
|
return false;
|
|
}
|
|
|
|
rc = fsp_queue_msg(fsp_mkmsg(cmd, 0), fsp_freemsg);
|
|
if (rc)
|
|
log_simple_error(&e_info(OPAL_RC_CU_NOTIFY), "CUPD: Failed to "
|
|
"queue code update notification response :%d\n", rc);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Handle FSP R/R event.
|
|
*
|
|
* Note:
|
|
* If FSP R/R happens during code update, then entire system reboots
|
|
* and comes up with P side image (and T side image will be invalid).
|
|
* Hence we don't need to handle R/R during code update.
|
|
*
|
|
* Also if FSP R/R happens in init path (while retrieving in_flight_params)
|
|
* then system fails to continue booting (because we have not yet loaded
|
|
* all required data/LID from FSP). Hence we don't need to handle R/R
|
|
* for system params.
|
|
*/
|
|
static bool fsp_code_update_rr(uint32_t cmd_sub_mod,
|
|
struct fsp_msg *msg __unused)
|
|
{
|
|
switch (cmd_sub_mod) {
|
|
case FSP_RESET_START:
|
|
lock(&flash_lock);
|
|
|
|
if (code_update_check_state() == OPAL_BUSY)
|
|
flash_state = FLASH_STATE_ABORT;
|
|
|
|
unlock(&flash_lock);
|
|
return true;
|
|
case FSP_RELOAD_COMPLETE:
|
|
lock(&flash_lock);
|
|
|
|
/* Lets try to parse marker LID again, if we failed
|
|
* to parse marker LID last time.
|
|
*/
|
|
if (code_update_check_state() == OPAL_INTERNAL_ERROR)
|
|
fetch_com_marker_lid();
|
|
|
|
unlock(&flash_lock);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static struct fsp_client fsp_cupd_client_rr = {
|
|
.message = fsp_code_update_rr,
|
|
};
|
|
|
|
static struct fsp_client fsp_get_notify = {
|
|
.message = code_update_notify,
|
|
};
|
|
|
|
void fsp_code_update_init(void)
|
|
{
|
|
if (!fsp_present()) {
|
|
flash_state = FLASH_STATE_ABSENT;
|
|
return;
|
|
}
|
|
|
|
/* OPAL interface */
|
|
opal_register(OPAL_FLASH_VALIDATE, fsp_opal_validate_flash, 3);
|
|
opal_register(OPAL_FLASH_MANAGE, fsp_opal_manage_flash, 1);
|
|
opal_register(OPAL_FLASH_UPDATE, fsp_opal_update_flash, 1);
|
|
|
|
/* register Code Update Class D3 */
|
|
fsp_register_client(&fsp_get_notify, FSP_MCLASS_CODE_UPDATE);
|
|
/* Register for Class AA (FSP R/R) */
|
|
fsp_register_client(&fsp_cupd_client_rr, FSP_MCLASS_RR_EVENT);
|
|
|
|
/* Register for firmware IPL side update notification */
|
|
sysparam_add_update_notifier(fw_ipl_side_update_notify);
|
|
|
|
/* Flash hook */
|
|
fsp_flash_term_hook = NULL;
|
|
|
|
/* Fetch various code update related sys parameters */
|
|
get_ipl_side();
|
|
get_code_update_policy();
|
|
get_platform_hmc_managed();
|
|
|
|
/* Fetch common marker LID */
|
|
lid_data = memalign(TCE_PSIZE, MARKER_LID_SIZE);
|
|
if (!lid_data) {
|
|
log_simple_error(&e_info(OPAL_RC_CU_INIT),
|
|
"CUPD: Failed to allocate memory for marker LID\n");
|
|
flash_state = FLASH_STATE_ABSENT;
|
|
return;
|
|
}
|
|
fetch_com_marker_lid();
|
|
}
|