162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2006-2008, IBM Corporation. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#undef DEBUG 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/types.h> 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/smp.h> 1262306a36Sopenharmony_ci#include <linux/reboot.h> 1362306a36Sopenharmony_ci#include <linux/kexec.h> 1462306a36Sopenharmony_ci#include <linux/crash_dump.h> 1562306a36Sopenharmony_ci#include <linux/of.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <asm/kexec.h> 1862306a36Sopenharmony_ci#include <asm/reg.h> 1962306a36Sopenharmony_ci#include <asm/io.h> 2062306a36Sopenharmony_ci#include <asm/machdep.h> 2162306a36Sopenharmony_ci#include <asm/rtas.h> 2262306a36Sopenharmony_ci#include <asm/cell-regs.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include "ras.h" 2562306a36Sopenharmony_ci#include "pervasive.h" 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic void dump_fir(int cpu) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci struct cbe_pmd_regs __iomem *pregs = cbe_get_cpu_pmd_regs(cpu); 3062306a36Sopenharmony_ci struct cbe_iic_regs __iomem *iregs = cbe_get_cpu_iic_regs(cpu); 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci if (pregs == NULL) 3362306a36Sopenharmony_ci return; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci /* Todo: do some nicer parsing of bits and based on them go down 3662306a36Sopenharmony_ci * to other sub-units FIRs and not only IIC 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_ci printk(KERN_ERR "Global Checkstop FIR : 0x%016llx\n", 3962306a36Sopenharmony_ci in_be64(&pregs->checkstop_fir)); 4062306a36Sopenharmony_ci printk(KERN_ERR "Global Recoverable FIR : 0x%016llx\n", 4162306a36Sopenharmony_ci in_be64(&pregs->checkstop_fir)); 4262306a36Sopenharmony_ci printk(KERN_ERR "Global MachineCheck FIR : 0x%016llx\n", 4362306a36Sopenharmony_ci in_be64(&pregs->spec_att_mchk_fir)); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci if (iregs == NULL) 4662306a36Sopenharmony_ci return; 4762306a36Sopenharmony_ci printk(KERN_ERR "IOC FIR : 0x%016llx\n", 4862306a36Sopenharmony_ci in_be64(&iregs->ioc_fir)); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ciDEFINE_INTERRUPT_HANDLER(cbe_system_error_exception) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci int cpu = smp_processor_id(); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci printk(KERN_ERR "System Error Interrupt on CPU %d !\n", cpu); 5762306a36Sopenharmony_ci dump_fir(cpu); 5862306a36Sopenharmony_ci dump_stack(); 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ciDEFINE_INTERRUPT_HANDLER(cbe_maintenance_exception) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci int cpu = smp_processor_id(); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci /* 6662306a36Sopenharmony_ci * Nothing implemented for the maintenance interrupt at this point 6762306a36Sopenharmony_ci */ 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci printk(KERN_ERR "Unhandled Maintenance interrupt on CPU %d !\n", cpu); 7062306a36Sopenharmony_ci dump_stack(); 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ciDEFINE_INTERRUPT_HANDLER(cbe_thermal_exception) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci int cpu = smp_processor_id(); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci /* 7862306a36Sopenharmony_ci * Nothing implemented for the thermal interrupt at this point 7962306a36Sopenharmony_ci */ 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci printk(KERN_ERR "Unhandled Thermal interrupt on CPU %d !\n", cpu); 8262306a36Sopenharmony_ci dump_stack(); 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic int cbe_machine_check_handler(struct pt_regs *regs) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci int cpu = smp_processor_id(); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci printk(KERN_ERR "Machine Check Interrupt on CPU %d !\n", cpu); 9062306a36Sopenharmony_ci dump_fir(cpu); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci /* No recovery from this code now, lets continue */ 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistruct ptcal_area { 9762306a36Sopenharmony_ci struct list_head list; 9862306a36Sopenharmony_ci int nid; 9962306a36Sopenharmony_ci int order; 10062306a36Sopenharmony_ci struct page *pages; 10162306a36Sopenharmony_ci}; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic LIST_HEAD(ptcal_list); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic int ptcal_start_tok, ptcal_stop_tok; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic int __init cbe_ptcal_enable_on_node(int nid, int order) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci struct ptcal_area *area; 11062306a36Sopenharmony_ci int ret = -ENOMEM; 11162306a36Sopenharmony_ci unsigned long addr; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci if (is_kdump_kernel()) 11462306a36Sopenharmony_ci rtas_call(ptcal_stop_tok, 1, 1, NULL, nid); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci area = kmalloc(sizeof(*area), GFP_KERNEL); 11762306a36Sopenharmony_ci if (!area) 11862306a36Sopenharmony_ci goto out_err; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci area->nid = nid; 12162306a36Sopenharmony_ci area->order = order; 12262306a36Sopenharmony_ci area->pages = __alloc_pages_node(area->nid, 12362306a36Sopenharmony_ci GFP_KERNEL|__GFP_THISNODE, 12462306a36Sopenharmony_ci area->order); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci if (!area->pages) { 12762306a36Sopenharmony_ci printk(KERN_WARNING "%s: no page on node %d\n", 12862306a36Sopenharmony_ci __func__, area->nid); 12962306a36Sopenharmony_ci goto out_free_area; 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci /* 13362306a36Sopenharmony_ci * We move the ptcal area to the middle of the allocated 13462306a36Sopenharmony_ci * page, in order to avoid prefetches in memcpy and similar 13562306a36Sopenharmony_ci * functions stepping on it. 13662306a36Sopenharmony_ci */ 13762306a36Sopenharmony_ci addr = __pa(page_address(area->pages)) + (PAGE_SIZE >> 1); 13862306a36Sopenharmony_ci printk(KERN_DEBUG "%s: enabling PTCAL on node %d address=0x%016lx\n", 13962306a36Sopenharmony_ci __func__, area->nid, addr); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci ret = -EIO; 14262306a36Sopenharmony_ci if (rtas_call(ptcal_start_tok, 3, 1, NULL, area->nid, 14362306a36Sopenharmony_ci (unsigned int)(addr >> 32), 14462306a36Sopenharmony_ci (unsigned int)(addr & 0xffffffff))) { 14562306a36Sopenharmony_ci printk(KERN_ERR "%s: error enabling PTCAL on node %d!\n", 14662306a36Sopenharmony_ci __func__, nid); 14762306a36Sopenharmony_ci goto out_free_pages; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci list_add(&area->list, &ptcal_list); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci return 0; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ciout_free_pages: 15562306a36Sopenharmony_ci __free_pages(area->pages, area->order); 15662306a36Sopenharmony_ciout_free_area: 15762306a36Sopenharmony_ci kfree(area); 15862306a36Sopenharmony_ciout_err: 15962306a36Sopenharmony_ci return ret; 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic int __init cbe_ptcal_enable(void) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci const u32 *size; 16562306a36Sopenharmony_ci struct device_node *np; 16662306a36Sopenharmony_ci int order, found_mic = 0; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci np = of_find_node_by_path("/rtas"); 16962306a36Sopenharmony_ci if (!np) 17062306a36Sopenharmony_ci return -ENODEV; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci size = of_get_property(np, "ibm,cbe-ptcal-size", NULL); 17362306a36Sopenharmony_ci if (!size) { 17462306a36Sopenharmony_ci of_node_put(np); 17562306a36Sopenharmony_ci return -ENODEV; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci pr_debug("%s: enabling PTCAL, size = 0x%x\n", __func__, *size); 17962306a36Sopenharmony_ci order = get_order(*size); 18062306a36Sopenharmony_ci of_node_put(np); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* support for malta device trees, with be@/mic@ nodes */ 18362306a36Sopenharmony_ci for_each_node_by_type(np, "mic-tm") { 18462306a36Sopenharmony_ci cbe_ptcal_enable_on_node(of_node_to_nid(np), order); 18562306a36Sopenharmony_ci found_mic = 1; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci if (found_mic) 18962306a36Sopenharmony_ci return 0; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* support for older device tree - use cpu nodes */ 19262306a36Sopenharmony_ci for_each_node_by_type(np, "cpu") { 19362306a36Sopenharmony_ci const u32 *nid = of_get_property(np, "node-id", NULL); 19462306a36Sopenharmony_ci if (!nid) { 19562306a36Sopenharmony_ci printk(KERN_ERR "%s: node %pOF is missing node-id?\n", 19662306a36Sopenharmony_ci __func__, np); 19762306a36Sopenharmony_ci continue; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci cbe_ptcal_enable_on_node(*nid, order); 20062306a36Sopenharmony_ci found_mic = 1; 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci return found_mic ? 0 : -ENODEV; 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic int cbe_ptcal_disable(void) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci struct ptcal_area *area, *tmp; 20962306a36Sopenharmony_ci int ret = 0; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci pr_debug("%s: disabling PTCAL\n", __func__); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci list_for_each_entry_safe(area, tmp, &ptcal_list, list) { 21462306a36Sopenharmony_ci /* disable ptcal on this node */ 21562306a36Sopenharmony_ci if (rtas_call(ptcal_stop_tok, 1, 1, NULL, area->nid)) { 21662306a36Sopenharmony_ci printk(KERN_ERR "%s: error disabling PTCAL " 21762306a36Sopenharmony_ci "on node %d!\n", __func__, 21862306a36Sopenharmony_ci area->nid); 21962306a36Sopenharmony_ci ret = -EIO; 22062306a36Sopenharmony_ci continue; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci /* ensure we can access the PTCAL area */ 22462306a36Sopenharmony_ci memset(page_address(area->pages), 0, 22562306a36Sopenharmony_ci 1 << (area->order + PAGE_SHIFT)); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci /* clean up */ 22862306a36Sopenharmony_ci list_del(&area->list); 22962306a36Sopenharmony_ci __free_pages(area->pages, area->order); 23062306a36Sopenharmony_ci kfree(area); 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci return ret; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic int cbe_ptcal_notify_reboot(struct notifier_block *nb, 23762306a36Sopenharmony_ci unsigned long code, void *data) 23862306a36Sopenharmony_ci{ 23962306a36Sopenharmony_ci return cbe_ptcal_disable(); 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic void cbe_ptcal_crash_shutdown(void) 24362306a36Sopenharmony_ci{ 24462306a36Sopenharmony_ci cbe_ptcal_disable(); 24562306a36Sopenharmony_ci} 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_cistatic struct notifier_block cbe_ptcal_reboot_notifier = { 24862306a36Sopenharmony_ci .notifier_call = cbe_ptcal_notify_reboot 24962306a36Sopenharmony_ci}; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci#ifdef CONFIG_PPC_IBM_CELL_RESETBUTTON 25262306a36Sopenharmony_cistatic int sysreset_hack; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic int __init cbe_sysreset_init(void) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci struct cbe_pmd_regs __iomem *regs; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci sysreset_hack = of_machine_is_compatible("IBM,CBPLUS-1.0"); 25962306a36Sopenharmony_ci if (!sysreset_hack) 26062306a36Sopenharmony_ci return 0; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci regs = cbe_get_cpu_pmd_regs(0); 26362306a36Sopenharmony_ci if (!regs) 26462306a36Sopenharmony_ci return 0; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci /* Enable JTAG system-reset hack */ 26762306a36Sopenharmony_ci out_be32(®s->fir_mode_reg, 26862306a36Sopenharmony_ci in_be32(®s->fir_mode_reg) | 26962306a36Sopenharmony_ci CBE_PMD_FIR_MODE_M8); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci return 0; 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_cidevice_initcall(cbe_sysreset_init); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ciint cbe_sysreset_hack(void) 27662306a36Sopenharmony_ci{ 27762306a36Sopenharmony_ci struct cbe_pmd_regs __iomem *regs; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci /* 28062306a36Sopenharmony_ci * The BMC can inject user triggered system reset exceptions, 28162306a36Sopenharmony_ci * but cannot set the system reset reason in srr1, 28262306a36Sopenharmony_ci * so check an extra register here. 28362306a36Sopenharmony_ci */ 28462306a36Sopenharmony_ci if (sysreset_hack && (smp_processor_id() == 0)) { 28562306a36Sopenharmony_ci regs = cbe_get_cpu_pmd_regs(0); 28662306a36Sopenharmony_ci if (!regs) 28762306a36Sopenharmony_ci return 0; 28862306a36Sopenharmony_ci if (in_be64(®s->ras_esc_0) & 0x0000ffff) { 28962306a36Sopenharmony_ci out_be64(®s->ras_esc_0, 0); 29062306a36Sopenharmony_ci return 0; 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci } 29362306a36Sopenharmony_ci return 1; 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci#endif /* CONFIG_PPC_IBM_CELL_RESETBUTTON */ 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_cistatic int __init cbe_ptcal_init(void) 29862306a36Sopenharmony_ci{ 29962306a36Sopenharmony_ci int ret; 30062306a36Sopenharmony_ci ptcal_start_tok = rtas_function_token(RTAS_FN_IBM_CBE_START_PTCAL); 30162306a36Sopenharmony_ci ptcal_stop_tok = rtas_function_token(RTAS_FN_IBM_CBE_STOP_PTCAL); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci if (ptcal_start_tok == RTAS_UNKNOWN_SERVICE 30462306a36Sopenharmony_ci || ptcal_stop_tok == RTAS_UNKNOWN_SERVICE) 30562306a36Sopenharmony_ci return -ENODEV; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci ret = register_reboot_notifier(&cbe_ptcal_reboot_notifier); 30862306a36Sopenharmony_ci if (ret) 30962306a36Sopenharmony_ci goto out1; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci ret = crash_shutdown_register(&cbe_ptcal_crash_shutdown); 31262306a36Sopenharmony_ci if (ret) 31362306a36Sopenharmony_ci goto out2; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci return cbe_ptcal_enable(); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ciout2: 31862306a36Sopenharmony_ci unregister_reboot_notifier(&cbe_ptcal_reboot_notifier); 31962306a36Sopenharmony_ciout1: 32062306a36Sopenharmony_ci printk(KERN_ERR "Can't disable PTCAL, so not enabling\n"); 32162306a36Sopenharmony_ci return ret; 32262306a36Sopenharmony_ci} 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ciarch_initcall(cbe_ptcal_init); 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_civoid __init cbe_ras_init(void) 32762306a36Sopenharmony_ci{ 32862306a36Sopenharmony_ci unsigned long hid0; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci /* 33162306a36Sopenharmony_ci * Enable System Error & thermal interrupts and wakeup conditions 33262306a36Sopenharmony_ci */ 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci hid0 = mfspr(SPRN_HID0); 33562306a36Sopenharmony_ci hid0 |= HID0_CBE_THERM_INT_EN | HID0_CBE_THERM_WAKEUP | 33662306a36Sopenharmony_ci HID0_CBE_SYSERR_INT_EN | HID0_CBE_SYSERR_WAKEUP; 33762306a36Sopenharmony_ci mtspr(SPRN_HID0, hid0); 33862306a36Sopenharmony_ci mb(); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci /* 34162306a36Sopenharmony_ci * Install machine check handler. Leave setting of precise mode to 34262306a36Sopenharmony_ci * what the firmware did for now 34362306a36Sopenharmony_ci */ 34462306a36Sopenharmony_ci ppc_md.machine_check_exception = cbe_machine_check_handler; 34562306a36Sopenharmony_ci mb(); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci /* 34862306a36Sopenharmony_ci * For now, we assume that IOC_FIR is already set to forward some 34962306a36Sopenharmony_ci * error conditions to the System Error handler. If that is not true 35062306a36Sopenharmony_ci * then it will have to be fixed up here. 35162306a36Sopenharmony_ci */ 35262306a36Sopenharmony_ci} 353