/* 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 #include #include #include #include #include #include #include #include #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: * MIcurrent-T-imagecurrent-P-image<0x0A> * MInew-T-imagenew-P-image<0x0A> * MLcurrent-T-imagecurrent-P-image<0x0A> * MLnew-T-imagenew-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(); }