/* Copyright 2019 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. */ #define pr_fmt(fmt) "DUMP: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include "hdata/spira.h" /* XXX Ideally we should use HDAT provided data (proc_dump_area->thread_size). * But we are not getting this data durig boot. Hence lets reserve fixed * memory for architected registers data collection. */ #define ARCH_REGS_DATA_SIZE_PER_CHIP (512 * 1024) /* Actual address of MDST and MDDT table */ #define MDST_TABLE_BASE (SKIBOOT_BASE + MDST_TABLE_OFF) #define MDDT_TABLE_BASE (SKIBOOT_BASE + MDDT_TABLE_OFF) #define PROC_DUMP_AREA_BASE (SKIBOOT_BASE + PROC_DUMP_AREA_OFF) static struct spira_ntuple *ntuple_mdst; static struct spira_ntuple *ntuple_mddt; static struct spira_ntuple *ntuple_mdrt; static struct mpipl_metadata *mpipl_metadata; /* Dump metadata area */ static struct opal_mpipl_fadump *opal_mpipl_data; static struct opal_mpipl_fadump *opal_mpipl_cpu_data; /* * Number of tags passed by OPAL to kernel after MPIPL boot. * Currently it supports below tags: * - CPU register data area * - OPAL metadata area address * - Kernel passed tag during MPIPL registration * - Post MPIPL boot memory size */ #define MAX_OPAL_MPIPL_TAGS 0x04 static u64 opal_mpipl_tags[MAX_OPAL_MPIPL_TAGS]; static int opal_mpipl_max_tags = MAX_OPAL_MPIPL_TAGS; static u64 opal_dump_addr, opal_dump_size; static bool mpipl_enabled; static int opal_mpipl_add_entry(u8 region, u64 src, u64 dest, u64 size) { int i; int mdst_cnt = be16_to_cpu(ntuple_mdst->act_cnt); int mddt_cnt = be16_to_cpu(ntuple_mddt->act_cnt); struct mdst_table *mdst; struct mddt_table *mddt; if (mdst_cnt >= MDST_TABLE_SIZE / sizeof(struct mdst_table)) { prlog(PR_DEBUG, "MDST table is full\n"); return OPAL_RESOURCE; } if (mddt_cnt >= MDDT_TABLE_SIZE / sizeof(struct mddt_table)) { prlog(PR_DEBUG, "MDDT table is full\n"); return OPAL_RESOURCE; } /* Use relocated memory address */ mdst = (void *)(MDST_TABLE_BASE); mddt = (void *)(MDDT_TABLE_BASE); /* Check for duplicate entry */ for (i = 0; i < mdst_cnt; i++) { if (be64_to_cpu(mdst->addr) == (src | HRMOR_BIT)) { prlog(PR_DEBUG, "Duplicate source address : 0x%llx", src); return OPAL_PARAMETER; } mdst++; } for (i = 0; i < mddt_cnt; i++) { if (be64_to_cpu(mddt->addr) == (dest | HRMOR_BIT)) { prlog(PR_DEBUG, "Duplicate destination address : 0x%llx", dest); return OPAL_PARAMETER; } mddt++; } /* Add OPAL source address to MDST entry */ mdst->addr = cpu_to_be64(src | HRMOR_BIT); mdst->data_region = region; mdst->size = cpu_to_be32(size); ntuple_mdst->act_cnt = cpu_to_be16(mdst_cnt + 1); /* Add OPAL destination address to MDDT entry */ mddt->addr = cpu_to_be64(dest | HRMOR_BIT); mddt->data_region = region; mddt->size = cpu_to_be32(size); ntuple_mddt->act_cnt = cpu_to_be16(mddt_cnt + 1); prlog(PR_TRACE, "Added new entry. src : 0x%llx, dest : 0x%llx," " size : 0x%llx\n", src, dest, size); return OPAL_SUCCESS; } /* Remove entry from source (MDST) table */ static int opal_mpipl_remove_entry_mdst(bool remove_all, u8 region, u64 src) { bool found = false; int i, j; int mdst_cnt = be16_to_cpu(ntuple_mdst->act_cnt); struct mdst_table *tmp_mdst; struct mdst_table *mdst = (void *)(MDST_TABLE_BASE); for (i = 0; i < mdst_cnt;) { if (mdst->data_region != region) { mdst++; i++; continue; } if (remove_all != true && be64_to_cpu(mdst->addr) != (src | HRMOR_BIT)) { mdst++; i++; continue; } tmp_mdst = mdst; memset(tmp_mdst, 0, sizeof(struct mdst_table)); for (j = i; j < mdst_cnt - 1; j++) { memcpy((void *)tmp_mdst, (void *)(tmp_mdst + 1), sizeof(struct mdst_table)); tmp_mdst++; memset(tmp_mdst, 0, sizeof(struct mdst_table)); } mdst_cnt--; if (remove_all == false) { found = true; break; } } /* end - for loop */ ntuple_mdst->act_cnt = cpu_to_be16((u16)mdst_cnt); if (remove_all == false && found == false) { prlog(PR_DEBUG, "Source address [0x%llx] not found in MDST table\n", src); return OPAL_PARAMETER; } return OPAL_SUCCESS; } /* Remove entry from destination (MDDT) table */ static int opal_mpipl_remove_entry_mddt(bool remove_all, u8 region, u64 dest) { bool found = false; int i, j; int mddt_cnt = be16_to_cpu(ntuple_mddt->act_cnt); struct mddt_table *tmp_mddt; struct mddt_table *mddt = (void *)(MDDT_TABLE_BASE); for (i = 0; i < mddt_cnt;) { if (mddt->data_region != region) { mddt++; i++; continue; } if (remove_all != true && be64_to_cpu(mddt->addr) != (dest | HRMOR_BIT)) { mddt++; i++; continue; } tmp_mddt = mddt; memset(tmp_mddt, 0, sizeof(struct mddt_table)); for (j = i; j < mddt_cnt - 1; j++) { memcpy((void *)tmp_mddt, (void *)(tmp_mddt + 1), sizeof(struct mddt_table)); tmp_mddt++; memset(tmp_mddt, 0, sizeof(struct mddt_table)); } mddt_cnt--; if (remove_all == false) { found = true; break; } } /* end - for loop */ ntuple_mddt->act_cnt = cpu_to_be16((u16)mddt_cnt); if (remove_all == false && found == false) { prlog(PR_DEBUG, "Dest address [0x%llx] not found in MDDT table\n", dest); return OPAL_PARAMETER; } return OPAL_SUCCESS; } /* Register for OPAL dump. */ static void opal_mpipl_register(void) { u64 arch_regs_dest, arch_regs_size; struct proc_dump_area *proc_dump = (void *)(PROC_DUMP_AREA_BASE); /* Add OPAL reservation detail to MDST/MDDT table */ opal_mpipl_add_entry(DUMP_REGION_OPAL_MEMORY, SKIBOOT_BASE, opal_dump_addr, opal_dump_size); /* Thread size check */ if (proc_dump->thread_size != 0) { prlog(PR_INFO, "Thread register entry size is available, " "but not supported.\n"); } /* Reserve memory used to capture architected register state */ arch_regs_dest = opal_dump_addr + opal_dump_size; arch_regs_size = nr_chips() * ARCH_REGS_DATA_SIZE_PER_CHIP; proc_dump->alloc_addr = cpu_to_be64(arch_regs_dest | HRMOR_BIT); proc_dump->alloc_size = cpu_to_be32(arch_regs_size); prlog(PR_NOTICE, "Architected register dest addr : 0x%llx, " "size : 0x%llx\n", arch_regs_dest, arch_regs_size); } static int payload_mpipl_register(u64 src, u64 dest, u64 size) { if (!opal_addr_valid((void *)src)) { prlog(PR_DEBUG, "Invalid source address [0x%llx]\n", src); return OPAL_PARAMETER; } if (!opal_addr_valid((void *)dest)) { prlog(PR_DEBUG, "Invalid dest address [0x%llx]\n", dest); return OPAL_PARAMETER; } if (size <= 0) { prlog(PR_DEBUG, "Invalid size [0x%llx]\n", size); return OPAL_PARAMETER; } return opal_mpipl_add_entry(DUMP_REGION_KERNEL, src, dest, size); } static int payload_mpipl_unregister(u64 src, u64 dest) { int rc; /* Remove src from MDST table */ rc = opal_mpipl_remove_entry_mdst(false, DUMP_REGION_KERNEL, src); if (rc) return rc; /* Remove dest from MDDT table */ rc = opal_mpipl_remove_entry_mddt(false, DUMP_REGION_KERNEL, dest); return rc; } static int payload_mpipl_unregister_all(void) { opal_mpipl_remove_entry_mdst(true, DUMP_REGION_KERNEL, 0); opal_mpipl_remove_entry_mddt(true, DUMP_REGION_KERNEL, 0); return OPAL_SUCCESS; } static int64_t opal_mpipl_update(enum opal_mpipl_ops ops, u64 src, u64 dest, u64 size) { int rc; void *skiboot_constant_addr mdrt_table_base_addr = (void *) MDRT_TABLE_BASE; switch (ops) { case OPAL_MPIPL_ADD_RANGE: rc = payload_mpipl_register(src, dest, size); if (!rc) prlog(PR_NOTICE, "Payload registered for MPIPL\n"); break; case OPAL_MPIPL_REMOVE_RANGE: rc = payload_mpipl_unregister(src, dest); if (!rc) { prlog(PR_NOTICE, "Payload removed entry from MPIPL." "[src : 0x%llx, dest : 0x%llx]\n", src, dest); } break; case OPAL_MPIPL_REMOVE_ALL: rc = payload_mpipl_unregister_all(); if (!rc) prlog(PR_NOTICE, "Payload unregistered for MPIPL\n"); break; case OPAL_MPIPL_FREE_PRESERVED_MEMORY: /* Clear tags */ memset(&opal_mpipl_tags, 0, (sizeof(u64) * MAX_OPAL_MPIPL_TAGS)); opal_mpipl_max_tags = 0; /* Release memory */ free(opal_mpipl_data); opal_mpipl_data = NULL; free(opal_mpipl_cpu_data); opal_mpipl_cpu_data = NULL; /* Clear MDRT table */ memset(mdrt_table_base_addr, 0, MDRT_TABLE_SIZE); /* Set MDRT count to max allocated count */ ntuple_mdrt->act_cnt = cpu_to_be16(MDRT_TABLE_SIZE / sizeof(struct mdrt_table)); rc = OPAL_SUCCESS; prlog(PR_NOTICE, "Payload Invalidated MPIPL\n"); break; default: prlog(PR_DEBUG, "Unsupported MPIPL update operation : 0x%x\n", ops); rc = OPAL_PARAMETER; break; } return rc; } static int64_t opal_mpipl_register_tag(enum opal_mpipl_tags tag, uint64_t tag_val) { int rc = OPAL_SUCCESS; switch (tag) { case OPAL_MPIPL_TAG_BOOT_MEM: if (tag_val <= 0 || tag_val > top_of_ram) { prlog(PR_DEBUG, "Payload sent invalid boot mem size" " : 0x%llx\n", tag_val); rc = OPAL_PARAMETER; } else { mpipl_metadata->boot_mem_size = tag_val; prlog(PR_NOTICE, "Boot mem size : 0x%llx\n", tag_val); } break; case OPAL_MPIPL_TAG_KERNEL: mpipl_metadata->kernel_tag = tag_val; prlog(PR_NOTICE, "Payload sent metadata tag : 0x%llx\n", tag_val); break; default: prlog(PR_DEBUG, "Payload sent unsupported tag : 0x%x\n", tag); rc = OPAL_PARAMETER; break; } return rc; } static uint64_t opal_mpipl_query_tag(enum opal_mpipl_tags tag, __be64 *tag_val) { if (!opal_addr_valid(tag_val)) { prlog(PR_DEBUG, "Invalid tag address\n"); return OPAL_PARAMETER; } if (tag >= opal_mpipl_max_tags) return OPAL_PARAMETER; *tag_val = cpu_to_be64(opal_mpipl_tags[tag]); return OPAL_SUCCESS; } static inline void post_mpipl_get_preserved_tags(void) { if (mpipl_metadata->kernel_tag) opal_mpipl_tags[OPAL_MPIPL_TAG_KERNEL] = mpipl_metadata->kernel_tag; if (mpipl_metadata->boot_mem_size) opal_mpipl_tags[OPAL_MPIPL_TAG_BOOT_MEM] = mpipl_metadata->boot_mem_size; } static void post_mpipl_arch_regs_data(void) { struct proc_dump_area *proc_dump = (void *)(PROC_DUMP_AREA_BASE); if (proc_dump->dest_addr == 0) { prlog(PR_DEBUG, "Invalid CPU registers destination address\n"); return; } if (proc_dump->act_size == 0) { prlog(PR_DEBUG, "Invalid CPU registers destination size\n"); return; } opal_mpipl_cpu_data = zalloc(sizeof(struct opal_mpipl_fadump) + sizeof(struct opal_mpipl_region)); if (!opal_mpipl_cpu_data) { prlog(PR_ERR, "Failed to allocate memory\n"); return; } /* Fill CPU register details */ opal_mpipl_cpu_data->version = OPAL_MPIPL_VERSION; opal_mpipl_cpu_data->cpu_data_version = cpu_to_be32((u32)proc_dump->version); opal_mpipl_cpu_data->cpu_data_size = proc_dump->thread_size; opal_mpipl_cpu_data->region_cnt = cpu_to_be32(1); opal_mpipl_cpu_data->region[0].src = proc_dump->dest_addr & ~(cpu_to_be64(HRMOR_BIT)); opal_mpipl_cpu_data->region[0].dest = proc_dump->dest_addr & ~(cpu_to_be64(HRMOR_BIT)); opal_mpipl_cpu_data->region[0].size = cpu_to_be64(be32_to_cpu(proc_dump->act_size)); /* Update tag */ opal_mpipl_tags[OPAL_MPIPL_TAG_CPU] = (u64)opal_mpipl_cpu_data; } static void post_mpipl_get_opal_data(void) { struct mdrt_table *mdrt = (void *)(MDRT_TABLE_BASE); int i, j = 0, count = 0; int mdrt_cnt = be16_to_cpu(ntuple_mdrt->act_cnt); struct opal_mpipl_region *region; /* Count OPAL dump regions */ for (i = 0; i < mdrt_cnt; i++) { if (mdrt->data_region == DUMP_REGION_OPAL_MEMORY) count++; mdrt++; } if (count == 0) { prlog(PR_INFO, "OPAL dump is not available\n"); return; } opal_mpipl_data = zalloc(sizeof(struct opal_mpipl_fadump) + count * sizeof(struct opal_mpipl_region)); if (!opal_mpipl_data) { prlog(PR_ERR, "Failed to allocate memory\n"); return; } /* Fill OPAL dump details */ opal_mpipl_data->version = OPAL_MPIPL_VERSION; opal_mpipl_data->crashing_pir = cpu_to_be32(mpipl_metadata->crashing_pir); opal_mpipl_data->region_cnt = cpu_to_be32(count); region = opal_mpipl_data->region; mdrt = (void *)(MDRT_TABLE_BASE); for (i = 0; i < mdrt_cnt; i++) { if (mdrt->data_region != DUMP_REGION_OPAL_MEMORY) { mdrt++; continue; } region[j].src = mdrt->src_addr & ~(cpu_to_be64(HRMOR_BIT)); region[j].dest = mdrt->dest_addr & ~(cpu_to_be64(HRMOR_BIT)); region[j].size = cpu_to_be64(be32_to_cpu(mdrt->size)); prlog(PR_NOTICE, "OPAL reserved region %d - src : 0x%llx, " "dest : 0x%llx, size : 0x%llx\n", j, be64_to_cpu(region[j].src), be64_to_cpu(region[j].dest), be64_to_cpu(region[j].size)); mdrt++; j++; if (j == count) break; } opal_mpipl_tags[OPAL_MPIPL_TAG_OPAL] = (u64)opal_mpipl_data; } void opal_mpipl_save_crashing_pir(void) { if (!is_mpipl_enabled()) return; mpipl_metadata->crashing_pir = this_cpu()->pir; prlog(PR_NOTICE, "Crashing PIR = 0x%x\n", this_cpu()->pir); } void opal_mpipl_reserve_mem(void) { struct dt_node *opal_node, *dump_node; u64 arch_regs_dest, arch_regs_size; opal_node = dt_find_by_path(dt_root, "ibm,opal"); if (!opal_node) return; dump_node = dt_find_by_path(opal_node, "dump"); if (!dump_node) return; /* Calculcate and Reserve OPAL dump destination memory */ opal_dump_size = SKIBOOT_SIZE + (cpu_max_pir + 1) * STACK_SIZE; opal_dump_addr = SKIBOOT_BASE + opal_dump_size; mem_reserve_fw("ibm,firmware-dump", opal_dump_addr, opal_dump_size); /* Reserve memory to capture CPU register data */ arch_regs_dest = opal_dump_addr + opal_dump_size; arch_regs_size = nr_chips() * ARCH_REGS_DATA_SIZE_PER_CHIP; mem_reserve_fw("ibm,firmware-arch-registers", arch_regs_dest, arch_regs_size); } bool is_mpipl_enabled(void) { return mpipl_enabled; } void opal_mpipl_init(void) { void *skiboot_constant_addr mdst_base = (void *)MDST_TABLE_BASE; void *skiboot_constant_addr mddt_base = (void *)MDDT_TABLE_BASE; struct dt_node *dump_node; dump_node = dt_find_by_path(opal_node, "dump"); if (!dump_node) return; /* Get MDST and MDDT ntuple from SPIRAH */ ntuple_mdst = &(spirah.ntuples.mdump_src); ntuple_mddt = &(spirah.ntuples.mdump_dst); ntuple_mdrt = &(spirah.ntuples.mdump_res); /* Get metadata area pointer */ mpipl_metadata = (void *)(DUMP_METADATA_AREA_BASE); if (dt_find_property(dump_node, "mpipl-boot")) { disable_fast_reboot("MPIPL Boot"); post_mpipl_get_preserved_tags(); post_mpipl_get_opal_data(); post_mpipl_arch_regs_data(); } /* Clear OPAL metadata area */ if (sizeof(struct mpipl_metadata) > DUMP_METADATA_AREA_SIZE) { prlog(PR_ERR, "INSUFFICIENT OPAL METADATA AREA\n"); prlog(PR_ERR, "INCREASE OPAL MEDTADATA AREA SIZE\n"); assert(false); } memset(mpipl_metadata, 0, sizeof(struct mpipl_metadata)); /* Clear MDST and MDDT table */ memset(mdst_base, 0, MDST_TABLE_SIZE); ntuple_mdst->act_cnt = 0; memset(mddt_base, 0, MDDT_TABLE_SIZE); ntuple_mddt->act_cnt = 0; opal_mpipl_register(); /* Send OPAL relocated base address to SBE */ p9_sbe_send_relocated_base(SKIBOOT_BASE); /* OPAL API for MPIPL update */ opal_register(OPAL_MPIPL_UPDATE, opal_mpipl_update, 4); opal_register(OPAL_MPIPL_REGISTER_TAG, opal_mpipl_register_tag, 2); opal_register(OPAL_MPIPL_QUERY_TAG, opal_mpipl_query_tag, 2); /* Enable MPIPL */ mpipl_enabled = true; }