diff options
Diffstat (limited to 'hw/fsp/fsp-codeupdate.c')
-rw-r--r-- | hw/fsp/fsp-codeupdate.c | 1197 |
1 files changed, 1197 insertions, 0 deletions
diff --git a/hw/fsp/fsp-codeupdate.c b/hw/fsp/fsp-codeupdate.c new file mode 100644 index 0000000..be705a4 --- /dev/null +++ b/hw/fsp/fsp-codeupdate.c @@ -0,0 +1,1197 @@ +/* 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 <codeupdate.h> +#include <device.h> +#include <ccan/endian/endian.h> +#include <fsp-elog.h> + +enum flash_state { + FLASH_STATE_ABSENT, + FLASH_STATE_INVALID, /* IPL side marker lid is invalid */ + FLASH_STATE_READING, + FLASH_STATE_READ, +}; + +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_GENERAL, OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_CU_FLASH, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE, + OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_CU_SG_LIST, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE, + OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_CU_COMMIT, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE, + OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_CU_MSG, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE, + OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_CU_NOTIFY, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE, + OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +DEFINE_LOG_ENTRY(OPAL_RC_CU_MARKER_LID, OPAL_PLATFORM_ERR_EVT, OPAL_CODEUPDATE, + OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA, NULL); + +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); + printf("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 + printf("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 + printf("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), + "FLASH: Error %d queueing param request\n", rc); + dec_in_flight_param(); + } +} + +static int64_t code_update_check_state(void) +{ + switch(flash_state) { + case FLASH_STATE_ABSENT: + return OPAL_HARDWARE; + case FLASH_STATE_INVALID: + 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'; + printf("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'; + printf("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; + + 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]; + + printf("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; + fsp_fetch_data_queue(flags, id, lid_id, + offset, buffer, &chunk, + fetch_lid_data_complete); + 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; + fsp_fetch_data_queue(flags, id, T_COM_MARKER_LID_ID, + 0, (void *)PSI_DMA_CODE_UPD, + &length, fetch_lid_data_complete); + 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) +{ + if (!fsp_present()) + return; + + printf("CUPD: Waiting read marker LID completion...\n"); + + while(flash_state == FLASH_STATE_READING) + fsp_poll(); + + printf("CUPD: Waiting in flight params completion...\n"); + while(in_flight_params) + fsp_poll(); + + 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 = sg->next) { + length = (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 > entry->length) { + lid_offset -= entry->length; + continue; + } + + /* + * SG list entry size can be more than 4k. + * Map only required pages, instead of + * mapping entire entry. + */ + map_act = entry->length; + map_size = entry->length; + + /* First TCE mapping */ + if (!tce_start) { + tce_start = PSI_DMA_CODE_UPD + + (lid_offset & 0xfff); + map_act = entry->length - lid_offset; + lid_offset &= ~0xfff; + map_size = 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, 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; + + lock(&flash_lock); + + /* 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 *)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) + goto out; + + /* Set next IPL side */ + if (code_update_set_ipl_side() != 0) + goto out; + + /* Start code update process */ + if (code_update_start() != 0) + 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); + + 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 size 0x%x is > max LID size \n", + be32_to_cpu(idx_entry->size)); + + goto abort_update; + } + + rc = get_lid_data(list, be32_to_cpu(idx_entry->size), + be32_to_cpu(idx_entry->offset)); + if (rc) + goto abort_update; + + rc = code_update_write_lid(be32_to_cpu(idx_entry->id), + be32_to_cpu(idx_entry->size)); + if (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); + + unlock(&flash_lock); + return rc; + +abort_update: + log_simple_error(&e_info(OPAL_RC_CU_FLASH), "CUPD: LID update failed " + "Aborting codeupdate! rc:%d", rc); + rc = code_update_complete(FSP_CMD_FLASH_ABORT); +out: + unlock(&flash_lock); + 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 = sg->next) { + length = (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)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; + } + length = (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)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; + printf("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; + printf("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; +} + +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); + + /* 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(); +} |