141df5928SHari Bathini // SPDX-License-Identifier: GPL-2.0-or-later 241df5928SHari Bathini /* 341df5928SHari Bathini * Firmware-Assisted Dump support on POWER platform (OPAL). 441df5928SHari Bathini * 541df5928SHari Bathini * Copyright 2019, Hari Bathini, IBM Corporation. 641df5928SHari Bathini */ 741df5928SHari Bathini 841df5928SHari Bathini #define pr_fmt(fmt) "opal fadump: " fmt 941df5928SHari Bathini 1041df5928SHari Bathini #include <linux/string.h> 1141df5928SHari Bathini #include <linux/seq_file.h> 1241df5928SHari Bathini #include <linux/of_fdt.h> 1341df5928SHari Bathini #include <linux/libfdt.h> 14742a265aSHari Bathini #include <linux/mm.h> 1541df5928SHari Bathini 16742a265aSHari Bathini #include <asm/page.h> 1741df5928SHari Bathini #include <asm/opal.h> 1841df5928SHari Bathini #include <asm/fadump-internal.h> 1941df5928SHari Bathini 20742a265aSHari Bathini #include "opal-fadump.h" 21742a265aSHari Bathini 22742a265aSHari Bathini static struct opal_fadump_mem_struct *opal_fdm; 23742a265aSHari Bathini 24a20a8fa4SHari Bathini static int opal_fadump_unregister(struct fw_dump *fadump_conf); 25a20a8fa4SHari Bathini 26a20a8fa4SHari Bathini static void opal_fadump_update_config(struct fw_dump *fadump_conf, 27a20a8fa4SHari Bathini const struct opal_fadump_mem_struct *fdm) 28a20a8fa4SHari Bathini { 2951bba8edSHari Bathini pr_debug("Boot memory regions count: %d\n", fdm->region_cnt); 3051bba8edSHari Bathini 31a20a8fa4SHari Bathini /* 32a20a8fa4SHari Bathini * The destination address of the first boot memory region is the 33a20a8fa4SHari Bathini * destination address of boot memory regions. 34a20a8fa4SHari Bathini */ 35a20a8fa4SHari Bathini fadump_conf->boot_mem_dest_addr = fdm->rgn[0].dest; 36a20a8fa4SHari Bathini pr_debug("Destination address of boot memory regions: %#016llx\n", 37a20a8fa4SHari Bathini fadump_conf->boot_mem_dest_addr); 38a20a8fa4SHari Bathini 39a20a8fa4SHari Bathini fadump_conf->fadumphdr_addr = fdm->fadumphdr_addr; 40a20a8fa4SHari Bathini } 41a20a8fa4SHari Bathini 42742a265aSHari Bathini /* Initialize kernel metadata */ 43742a265aSHari Bathini static void opal_fadump_init_metadata(struct opal_fadump_mem_struct *fdm) 44742a265aSHari Bathini { 45742a265aSHari Bathini fdm->version = OPAL_FADUMP_VERSION; 46742a265aSHari Bathini fdm->region_cnt = 0; 47742a265aSHari Bathini fdm->registered_regions = 0; 48742a265aSHari Bathini fdm->fadumphdr_addr = 0; 49742a265aSHari Bathini } 50742a265aSHari Bathini 5141df5928SHari Bathini static u64 opal_fadump_init_mem_struct(struct fw_dump *fadump_conf) 5241df5928SHari Bathini { 5351bba8edSHari Bathini int max_copy_size, cur_size, size; 5451bba8edSHari Bathini u64 src_addr, dest_addr; 55742a265aSHari Bathini 56742a265aSHari Bathini opal_fdm = __va(fadump_conf->kernel_metadata); 57742a265aSHari Bathini opal_fadump_init_metadata(opal_fdm); 58742a265aSHari Bathini 5951bba8edSHari Bathini /* 6051bba8edSHari Bathini * Firmware supports 32-bit field for size. Align it to PAGE_SIZE 6151bba8edSHari Bathini * and request firmware to copy multiple kernel boot memory regions. 6251bba8edSHari Bathini */ 6351bba8edSHari Bathini max_copy_size = _ALIGN_DOWN(U32_MAX, PAGE_SIZE); 6451bba8edSHari Bathini 6551bba8edSHari Bathini /* Boot memory regions */ 6651bba8edSHari Bathini src_addr = 0; 6751bba8edSHari Bathini dest_addr = fadump_conf->reserve_dump_area_start; 6851bba8edSHari Bathini size = fadump_conf->boot_memory_size; 6951bba8edSHari Bathini while (size) { 7051bba8edSHari Bathini cur_size = size > max_copy_size ? max_copy_size : size; 7151bba8edSHari Bathini 7251bba8edSHari Bathini opal_fdm->rgn[opal_fdm->region_cnt].src = src_addr; 7351bba8edSHari Bathini opal_fdm->rgn[opal_fdm->region_cnt].dest = dest_addr; 7451bba8edSHari Bathini opal_fdm->rgn[opal_fdm->region_cnt].size = cur_size; 7551bba8edSHari Bathini 7651bba8edSHari Bathini opal_fdm->region_cnt++; 7751bba8edSHari Bathini dest_addr += cur_size; 7851bba8edSHari Bathini src_addr += cur_size; 7951bba8edSHari Bathini size -= cur_size; 8051bba8edSHari Bathini } 81742a265aSHari Bathini 82742a265aSHari Bathini /* 83742a265aSHari Bathini * Kernel metadata is passed to f/w and retrieved in capture kerenl. 84742a265aSHari Bathini * So, use it to save fadump header address instead of calculating it. 85742a265aSHari Bathini */ 86742a265aSHari Bathini opal_fdm->fadumphdr_addr = (opal_fdm->rgn[0].dest + 87742a265aSHari Bathini fadump_conf->boot_memory_size); 88742a265aSHari Bathini 89a20a8fa4SHari Bathini opal_fadump_update_config(fadump_conf, opal_fdm); 90a20a8fa4SHari Bathini 9151bba8edSHari Bathini return dest_addr; 92742a265aSHari Bathini } 93742a265aSHari Bathini 94742a265aSHari Bathini static u64 opal_fadump_get_metadata_size(void) 95742a265aSHari Bathini { 96742a265aSHari Bathini return PAGE_ALIGN(sizeof(struct opal_fadump_mem_struct)); 97742a265aSHari Bathini } 98742a265aSHari Bathini 99742a265aSHari Bathini static int opal_fadump_setup_metadata(struct fw_dump *fadump_conf) 100742a265aSHari Bathini { 101742a265aSHari Bathini int err = 0; 102742a265aSHari Bathini s64 ret; 103742a265aSHari Bathini 104742a265aSHari Bathini /* 105742a265aSHari Bathini * Use the last page(s) in FADump memory reservation for 106742a265aSHari Bathini * kernel metadata. 107742a265aSHari Bathini */ 108742a265aSHari Bathini fadump_conf->kernel_metadata = (fadump_conf->reserve_dump_area_start + 109742a265aSHari Bathini fadump_conf->reserve_dump_area_size - 110742a265aSHari Bathini opal_fadump_get_metadata_size()); 111742a265aSHari Bathini pr_info("Kernel metadata addr: %llx\n", fadump_conf->kernel_metadata); 112742a265aSHari Bathini 113742a265aSHari Bathini /* Initialize kernel metadata before registering the address with f/w */ 114742a265aSHari Bathini opal_fdm = __va(fadump_conf->kernel_metadata); 115742a265aSHari Bathini opal_fadump_init_metadata(opal_fdm); 116742a265aSHari Bathini 117742a265aSHari Bathini /* 118742a265aSHari Bathini * Register metadata address with f/w. Can be retrieved in 119742a265aSHari Bathini * the capture kernel. 120742a265aSHari Bathini */ 121742a265aSHari Bathini ret = opal_mpipl_register_tag(OPAL_MPIPL_TAG_KERNEL, 122742a265aSHari Bathini fadump_conf->kernel_metadata); 123742a265aSHari Bathini if (ret != OPAL_SUCCESS) { 124742a265aSHari Bathini pr_err("Failed to set kernel metadata tag!\n"); 125742a265aSHari Bathini err = -EPERM; 126742a265aSHari Bathini } 127742a265aSHari Bathini 128742a265aSHari Bathini return err; 12941df5928SHari Bathini } 13041df5928SHari Bathini 13141df5928SHari Bathini static int opal_fadump_register(struct fw_dump *fadump_conf) 13241df5928SHari Bathini { 133a20a8fa4SHari Bathini s64 rc = OPAL_PARAMETER; 134a20a8fa4SHari Bathini int i, err = -EIO; 135a20a8fa4SHari Bathini 136a20a8fa4SHari Bathini for (i = 0; i < opal_fdm->region_cnt; i++) { 137a20a8fa4SHari Bathini rc = opal_mpipl_update(OPAL_MPIPL_ADD_RANGE, 138a20a8fa4SHari Bathini opal_fdm->rgn[i].src, 139a20a8fa4SHari Bathini opal_fdm->rgn[i].dest, 140a20a8fa4SHari Bathini opal_fdm->rgn[i].size); 141a20a8fa4SHari Bathini if (rc != OPAL_SUCCESS) 142a20a8fa4SHari Bathini break; 143a20a8fa4SHari Bathini 144a20a8fa4SHari Bathini opal_fdm->registered_regions++; 145a20a8fa4SHari Bathini } 146a20a8fa4SHari Bathini 147a20a8fa4SHari Bathini switch (rc) { 148a20a8fa4SHari Bathini case OPAL_SUCCESS: 149a20a8fa4SHari Bathini pr_info("Registration is successful!\n"); 150a20a8fa4SHari Bathini fadump_conf->dump_registered = 1; 151a20a8fa4SHari Bathini err = 0; 152a20a8fa4SHari Bathini break; 153a20a8fa4SHari Bathini case OPAL_RESOURCE: 154a20a8fa4SHari Bathini /* If MAX regions limit in f/w is hit, warn and proceed. */ 155a20a8fa4SHari Bathini pr_warn("%d regions could not be registered for MPIPL as MAX limit is reached!\n", 156a20a8fa4SHari Bathini (opal_fdm->region_cnt - opal_fdm->registered_regions)); 157a20a8fa4SHari Bathini fadump_conf->dump_registered = 1; 158a20a8fa4SHari Bathini err = 0; 159a20a8fa4SHari Bathini break; 160a20a8fa4SHari Bathini case OPAL_PARAMETER: 161a20a8fa4SHari Bathini pr_err("Failed to register. Parameter Error(%lld).\n", rc); 162a20a8fa4SHari Bathini break; 163a20a8fa4SHari Bathini case OPAL_HARDWARE: 164a20a8fa4SHari Bathini pr_err("Support not available.\n"); 165a20a8fa4SHari Bathini fadump_conf->fadump_supported = 0; 166a20a8fa4SHari Bathini fadump_conf->fadump_enabled = 0; 167a20a8fa4SHari Bathini break; 168a20a8fa4SHari Bathini default: 169a20a8fa4SHari Bathini pr_err("Failed to register. Unknown Error(%lld).\n", rc); 170a20a8fa4SHari Bathini break; 171a20a8fa4SHari Bathini } 172a20a8fa4SHari Bathini 173a20a8fa4SHari Bathini /* 174a20a8fa4SHari Bathini * If some regions were registered before OPAL_MPIPL_ADD_RANGE 175a20a8fa4SHari Bathini * OPAL call failed, unregister all regions. 176a20a8fa4SHari Bathini */ 177a20a8fa4SHari Bathini if ((err < 0) && (opal_fdm->registered_regions > 0)) 178a20a8fa4SHari Bathini opal_fadump_unregister(fadump_conf); 179a20a8fa4SHari Bathini 180a20a8fa4SHari Bathini return err; 18141df5928SHari Bathini } 18241df5928SHari Bathini 18341df5928SHari Bathini static int opal_fadump_unregister(struct fw_dump *fadump_conf) 18441df5928SHari Bathini { 185a20a8fa4SHari Bathini s64 rc; 186a20a8fa4SHari Bathini 187a20a8fa4SHari Bathini rc = opal_mpipl_update(OPAL_MPIPL_REMOVE_ALL, 0, 0, 0); 188a20a8fa4SHari Bathini if (rc) { 189a20a8fa4SHari Bathini pr_err("Failed to un-register - unexpected Error(%lld).\n", rc); 19041df5928SHari Bathini return -EIO; 19141df5928SHari Bathini } 19241df5928SHari Bathini 193a20a8fa4SHari Bathini opal_fdm->registered_regions = 0; 194a20a8fa4SHari Bathini fadump_conf->dump_registered = 0; 195a20a8fa4SHari Bathini return 0; 196a20a8fa4SHari Bathini } 197a20a8fa4SHari Bathini 19841df5928SHari Bathini static int opal_fadump_invalidate(struct fw_dump *fadump_conf) 19941df5928SHari Bathini { 20041df5928SHari Bathini return -EIO; 20141df5928SHari Bathini } 20241df5928SHari Bathini 2032790d01dSHari Bathini static void opal_fadump_cleanup(struct fw_dump *fadump_conf) 2042790d01dSHari Bathini { 2052790d01dSHari Bathini s64 ret; 2062790d01dSHari Bathini 2072790d01dSHari Bathini ret = opal_mpipl_register_tag(OPAL_MPIPL_TAG_KERNEL, 0); 2082790d01dSHari Bathini if (ret != OPAL_SUCCESS) 2092790d01dSHari Bathini pr_warn("Could not reset (%llu) kernel metadata tag!\n", ret); 2102790d01dSHari Bathini } 2112790d01dSHari Bathini 21241df5928SHari Bathini static int __init opal_fadump_process(struct fw_dump *fadump_conf) 21341df5928SHari Bathini { 21441df5928SHari Bathini return -EINVAL; 21541df5928SHari Bathini } 21641df5928SHari Bathini 21741df5928SHari Bathini static void opal_fadump_region_show(struct fw_dump *fadump_conf, 21841df5928SHari Bathini struct seq_file *m) 21941df5928SHari Bathini { 220742a265aSHari Bathini const struct opal_fadump_mem_struct *fdm_ptr = opal_fdm; 221742a265aSHari Bathini u64 dumped_bytes = 0; 222742a265aSHari Bathini int i; 223742a265aSHari Bathini 224742a265aSHari Bathini for (i = 0; i < fdm_ptr->region_cnt; i++) { 225742a265aSHari Bathini seq_printf(m, "DUMP: Src: %#016llx, Dest: %#016llx, ", 226742a265aSHari Bathini fdm_ptr->rgn[i].src, fdm_ptr->rgn[i].dest); 227742a265aSHari Bathini seq_printf(m, "Size: %#llx, Dumped: %#llx bytes\n", 228742a265aSHari Bathini fdm_ptr->rgn[i].size, dumped_bytes); 229742a265aSHari Bathini } 23041df5928SHari Bathini } 23141df5928SHari Bathini 23241df5928SHari Bathini static void opal_fadump_trigger(struct fadump_crash_info_header *fdh, 23341df5928SHari Bathini const char *msg) 23441df5928SHari Bathini { 23541df5928SHari Bathini int rc; 23641df5928SHari Bathini 23741df5928SHari Bathini rc = opal_cec_reboot2(OPAL_REBOOT_MPIPL, msg); 23841df5928SHari Bathini if (rc == OPAL_UNSUPPORTED) { 23941df5928SHari Bathini pr_emerg("Reboot type %d not supported.\n", 24041df5928SHari Bathini OPAL_REBOOT_MPIPL); 24141df5928SHari Bathini } else if (rc == OPAL_HARDWARE) 24241df5928SHari Bathini pr_emerg("No backend support for MPIPL!\n"); 24341df5928SHari Bathini } 24441df5928SHari Bathini 24541df5928SHari Bathini static struct fadump_ops opal_fadump_ops = { 24641df5928SHari Bathini .fadump_init_mem_struct = opal_fadump_init_mem_struct, 247742a265aSHari Bathini .fadump_get_metadata_size = opal_fadump_get_metadata_size, 248742a265aSHari Bathini .fadump_setup_metadata = opal_fadump_setup_metadata, 24941df5928SHari Bathini .fadump_register = opal_fadump_register, 25041df5928SHari Bathini .fadump_unregister = opal_fadump_unregister, 25141df5928SHari Bathini .fadump_invalidate = opal_fadump_invalidate, 2522790d01dSHari Bathini .fadump_cleanup = opal_fadump_cleanup, 25341df5928SHari Bathini .fadump_process = opal_fadump_process, 25441df5928SHari Bathini .fadump_region_show = opal_fadump_region_show, 25541df5928SHari Bathini .fadump_trigger = opal_fadump_trigger, 25641df5928SHari Bathini }; 25741df5928SHari Bathini 25841df5928SHari Bathini void __init opal_fadump_dt_scan(struct fw_dump *fadump_conf, u64 node) 25941df5928SHari Bathini { 26041df5928SHari Bathini unsigned long dn; 26141df5928SHari Bathini 26241df5928SHari Bathini /* 26341df5928SHari Bathini * Check if Firmware-Assisted Dump is supported. if yes, check 26441df5928SHari Bathini * if dump has been initiated on last reboot. 26541df5928SHari Bathini */ 26641df5928SHari Bathini dn = of_get_flat_dt_subnode_by_name(node, "dump"); 26741df5928SHari Bathini if (dn == -FDT_ERR_NOTFOUND) { 26841df5928SHari Bathini pr_debug("FADump support is missing!\n"); 26941df5928SHari Bathini return; 27041df5928SHari Bathini } 27141df5928SHari Bathini 27241df5928SHari Bathini if (!of_flat_dt_is_compatible(dn, "ibm,opal-dump")) { 27341df5928SHari Bathini pr_err("Support missing for this f/w version!\n"); 27441df5928SHari Bathini return; 27541df5928SHari Bathini } 27641df5928SHari Bathini 27741df5928SHari Bathini fadump_conf->ops = &opal_fadump_ops; 27841df5928SHari Bathini fadump_conf->fadump_supported = 1; 27941df5928SHari Bathini } 280