aboutsummaryrefslogtreecommitdiff
path: root/hw/fsp/fsp-codeupdate.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/fsp/fsp-codeupdate.c')
-rw-r--r--hw/fsp/fsp-codeupdate.c1197
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();
+}