/* 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. */ /* * Sapphire dump design: * - During initialization we setup Memory Dump Source Table (MDST) table * which contains address, size pair. * - We send MDST table update notification to FSP via MBOX command. * - During Sapphire checkstop: * - FSP retrieves HWDUMP. * - FSP retrieves CEC memory based on MDST table. * - Once Sapphire reboot FSP sends new dump avialable notification via HDAT */ #include #include #include #include #include #include #include #include /* * Sapphire dump size * This is the maximum memory that FSP can retrieve during checkstop. * * Note: * Presently we are hardcoding this parameter. Eventually we need * new System parameter so that we can get max size dynamically. */ #define MAX_SAPPHIRE_DUMP_SIZE 0x1000000 DEFINE_LOG_ENTRY(OPAL_RC_DUMP_MDST_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_DUMP, OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, OPAL_NA, NULL); DEFINE_LOG_ENTRY(OPAL_RC_DUMP_MDST_UPDATE, OPAL_PLATFORM_ERR_EVT, OPAL_DUMP, OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_FAULT_RECTIFY_REBOOT, OPAL_NA, NULL); DEFINE_LOG_ENTRY(OPAL_RC_DUMP_MDST_ADD, OPAL_PLATFORM_ERR_EVT, OPAL_DUMP, OPAL_PLATFORM_FIRMWARE, OPAL_INFO, OPAL_NA, NULL); DEFINE_LOG_ENTRY(OPAL_RC_DUMP_MDST_REMOVE, OPAL_PLATFORM_ERR_EVT, OPAL_DUMP, OPAL_PLATFORM_FIRMWARE, OPAL_INFO, OPAL_NA, NULL); static struct dump_mdst_table *mdst_table; static struct dump_mdst_table *dump_mem_region; static int cur_mdst_entry; static int max_mdst_entry; static int cur_dump_size; /* * Presently both sizes are same.. But if someday FSP gives more space * than our TCE mapping then we need this validation.. * * Also once FSP implements MAX_SAPPHIRE_DUMP_SIZE system param, we can * move this validation to separate function. */ static int max_dump_size = MIN(MAX_SAPPHIRE_DUMP_SIZE, PSI_DMA_HYP_DUMP_SIZE); /* Protect MDST table entries */ static struct lock mdst_lock = LOCK_UNLOCKED; /* Not supported on P7 */ static inline bool fsp_mdst_supported(void) { return proc_gen >= proc_gen_p8; } static inline uint32_t get_dump_region_map_size(uint64_t addr, uint32_t size) { uint64_t start, end; start = addr & ~TCE_MASK; end = addr + size; end = (end + (TCE_MASK - 1)) & ~TCE_MASK; return (end - start); } static int dump_region_tce_map(void) { int i; uint32_t t_size = 0, size; uint64_t addr; for (i = 0; i < cur_mdst_entry; i++) { addr = dump_mem_region[i].addr & ~TCE_MASK; size = get_dump_region_map_size(dump_mem_region[i].addr, dump_mem_region[i].size); if (t_size + size > max_dump_size) break; /* TCE mapping */ fsp_tce_map(PSI_DMA_HYP_DUMP + t_size, (void *)addr, size); /* Add entry to MDST table */ mdst_table[i].type = dump_mem_region[i].type; mdst_table[i].size = dump_mem_region[i].size; mdst_table[i].addr = PSI_DMA_HYP_DUMP + t_size; /* TCE alignment adjustment */ mdst_table[i].addr += dump_mem_region[i].addr & 0xfff; t_size += size; } return i; } static inline void dump_region_tce_unmap(void) { fsp_tce_unmap(PSI_DMA_HYP_DUMP, PSI_DMA_HYP_DUMP_SIZE); } static void update_mdst_table_complete(struct fsp_msg *msg) { uint8_t status = (msg->resp->word1 >> 8) & 0xff; if (status) log_simple_error(&e_info(OPAL_RC_DUMP_MDST_UPDATE), "MDST: Update table MBOX command failed: " "0x%x\n", status); else printf("MDST: Table updated.\n"); fsp_freemsg(msg); } /* Send MDST table to FSP */ static int64_t fsp_update_mdst_table(void) { struct fsp_msg *msg; int count; int rc = OPAL_SUCCESS; if (cur_mdst_entry <= 0) { printf("MDST: Table is empty\n"); return OPAL_INTERNAL_ERROR; } lock(&mdst_lock); /* Unmap previous mapping */ dump_region_tce_unmap(); count = dump_region_tce_map(); msg = fsp_mkmsg(FSP_CMD_HYP_MDST_TABLE, 4, 0, PSI_DMA_MDST_TABLE, sizeof(*mdst_table) * count, sizeof(*mdst_table)); unlock(&mdst_lock); if (!msg) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_UPDATE), "MDST: Message allocation failed.!\n"); rc = OPAL_INTERNAL_ERROR; } else if (fsp_queue_msg(msg, update_mdst_table_complete)) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_UPDATE), "MDST: Failed to queue MDST table message.\n"); fsp_freemsg(msg); rc = OPAL_INTERNAL_ERROR; } return rc; } static int dump_region_del_entry(uint32_t id) { int i; uint32_t size; bool found = false; int rc = OPAL_SUCCESS; lock(&mdst_lock); for (i = 0; i < cur_mdst_entry; i++) { if (dump_mem_region[i].type != id) continue; found = true; break; } if (!found) { rc = OPAL_PARAMETER; goto del_out; } /* Adjust current dump size */ size = get_dump_region_map_size(dump_mem_region[i].addr, dump_mem_region[i].size); cur_dump_size -= size; for ( ; i < cur_mdst_entry - 1; i++) dump_mem_region[i] = dump_mem_region[i + 1]; dump_mem_region[i].type = 0; cur_mdst_entry--; del_out: unlock(&mdst_lock); return rc; } /* Add entry to MDST table */ static int __dump_region_add_entry(uint32_t id, uint64_t addr, uint32_t size) { int rc = OPAL_INTERNAL_ERROR; uint32_t act_size; /* Delete function takes lock before modifying table */ dump_region_del_entry(id); lock(&mdst_lock); if (cur_mdst_entry >= max_mdst_entry) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_ADD), "MDST: Table is full.\n"); goto out; } /* TCE alignment adjustment */ act_size = get_dump_region_map_size(addr, size); /* Make sure we don't cross dump size limit */ if (cur_dump_size + act_size > max_dump_size) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_ADD), "MDST: 0x%x is crossing max dump size (0x%x) limit.\n", cur_dump_size + act_size, max_dump_size); goto out; } /* Add entry to dump memory region table */ dump_mem_region[cur_mdst_entry].type = id; dump_mem_region[cur_mdst_entry].addr = addr; dump_mem_region[cur_mdst_entry].size = size; /* Update dump region count and dump size */ cur_mdst_entry++; cur_dump_size += act_size; printf("MDST: Addr = 0x%llx [size : 0x%x bytes] added to MDST table.\n", (uint64_t)addr, size); rc = OPAL_SUCCESS; out: unlock(&mdst_lock); return rc; } static int dump_region_add_entries(void) { int rc; /* Add console buffer */ rc = __dump_region_add_entry(DUMP_REGION_CONSOLE, INMEM_CON_START, INMEM_CON_LEN); if (rc) return rc; /* Add HBRT buffer */ rc = __dump_region_add_entry(DUMP_REGION_HBRT_LOG, HBRT_CON_START, HBRT_CON_LEN); return rc; } static int64_t fsp_opal_register_dump_region(uint32_t id, uint64_t addr, uint64_t size) { int rc = OPAL_SUCCESS; if (!fsp_present()) return OPAL_UNSUPPORTED; if (!fsp_mdst_supported()) { printf("MDST: Not supported\n"); return OPAL_UNSUPPORTED; } /* Validate memory region id */ if (id < DUMP_REGION_HOST_START || id > DUMP_REGION_HOST_END) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_ADD), "MDST: Invalid dump region id : 0x%x\n", id); return OPAL_PARAMETER; } if (size <= 0) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_ADD), "MDST: Invalid size : 0x%llx\n", size); return OPAL_PARAMETER; } rc = __dump_region_add_entry(id, addr, size); if (rc) return rc; /* Send updated MDST to FSP */ rc = fsp_update_mdst_table(); return rc; } static int64_t fsp_opal_unregister_dump_region(uint32_t id) { int rc = OPAL_SUCCESS; if (!fsp_present()) return OPAL_UNSUPPORTED; if (!fsp_mdst_supported()) { printf("MDST: Not supported\n"); return OPAL_UNSUPPORTED; } /* Validate memory region id */ if (id < DUMP_REGION_HOST_START || id > DUMP_REGION_HOST_END) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_REMOVE), "MDST: Invalid dump region id : 0x%x\n", id); return OPAL_PARAMETER; } rc = dump_region_del_entry(id); if (rc) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_REMOVE), "MDST: dump region id : 0x%x not found\n", id); return OPAL_PARAMETER; } /* Send updated MDST to FSP */ rc = fsp_update_mdst_table(); return rc; } /* TCE mapping */ static inline void mdst_table_tce_map(void) { fsp_tce_map(PSI_DMA_MDST_TABLE, mdst_table, PSI_DMA_MDST_TABLE_SIZE); } /* Initialize MDST table */ static int mdst_table_init(void) { dump_mem_region = memalign(TCE_PSIZE, PSI_DMA_MDST_TABLE_SIZE); if (!dump_mem_region) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_INIT), "MDST: Failed to allocate memory for dump " "memory region table.\n"); return -ENOMEM; } memset(dump_mem_region, 0, PSI_DMA_MDST_TABLE_SIZE); mdst_table = memalign(TCE_PSIZE, PSI_DMA_MDST_TABLE_SIZE); if (!mdst_table) { log_simple_error(&e_info(OPAL_RC_DUMP_MDST_INIT), "MDST: Failed to allocate memory for MDST table.\n"); return -ENOMEM; } memset(mdst_table, 0, PSI_DMA_MDST_TABLE_SIZE); mdst_table_tce_map(); max_mdst_entry = PSI_DMA_MDST_TABLE_SIZE / sizeof(*mdst_table); printf("MDST: Max entries in MDST table : %d\n", max_mdst_entry); return OPAL_SUCCESS; } /* * Handle FSP R/R event. */ static bool fsp_mdst_update_rr(uint32_t cmd_sub_mod, struct fsp_msg *msg __unused) { switch (cmd_sub_mod) { case FSP_RESET_START: return true; case FSP_RELOAD_COMPLETE: /* Send MDST to FSP */ fsp_update_mdst_table(); return true; } return false; } static struct fsp_client fsp_mdst_client_rr = { .message = fsp_mdst_update_rr, }; /* Initialize MDST table and send notification to FSP */ void fsp_mdst_table_init(void) { if (!fsp_present()) return; /* OPAL interface */ opal_register(OPAL_REGISTER_DUMP_REGION, fsp_opal_register_dump_region, 3); opal_register(OPAL_UNREGISTER_DUMP_REGION, fsp_opal_unregister_dump_region, 1); if (!fsp_mdst_supported()) return; /* Initiate MDST */ if (mdst_table_init() != OPAL_SUCCESS) return; /* * Ignore return code from mdst_table_add_entries so that * we can atleast capture partial dump. */ dump_region_add_entries(); fsp_update_mdst_table(); /* Register for Class AA (FSP R/R) */ fsp_register_client(&fsp_mdst_client_rr, FSP_MCLASS_RR_EVENT); }