|  | // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later | 
|  | /* | 
|  | * Full IPL is slow, let's cheat! | 
|  | * | 
|  | * Copyright 2013-2019 IBM Corp. | 
|  | */ | 
|  |  | 
|  | #include <skiboot.h> | 
|  | #include <slw.h> | 
|  | #include <cpu.h> | 
|  | #include <console.h> | 
|  | #include <fsp.h> | 
|  | #include <psi.h> | 
|  | #include <opal.h> | 
|  | #include <mem_region.h> | 
|  | #include <xscom.h> | 
|  | #include <interrupts.h> | 
|  | #include <cec.h> | 
|  | #include <timebase.h> | 
|  | #include <pci.h> | 
|  | #include <xive.h> | 
|  | #include <chip.h> | 
|  | #include <chiptod.h> | 
|  | #include <ipmi.h> | 
|  | #include <direct-controls.h> | 
|  | #include <nvram.h> | 
|  |  | 
|  | /* Flag tested by the OPAL entry code */ | 
|  | static volatile bool fast_boot_release; | 
|  | static volatile bool spr_set_release; | 
|  | static volatile bool nmi_mce_release; | 
|  |  | 
|  | static void wait_on(volatile bool *cond) | 
|  | { | 
|  | sync(); | 
|  | if (!*cond) { | 
|  | smt_lowest(); | 
|  | while (!*cond) | 
|  | barrier(); | 
|  | smt_medium(); | 
|  | } | 
|  | sync(); | 
|  | } | 
|  |  | 
|  | static bool cpu_state_wait_all_others(enum cpu_thread_state state, | 
|  | unsigned long timeout_tb) | 
|  | { | 
|  | struct cpu_thread *cpu; | 
|  | unsigned long end = mftb() + timeout_tb; | 
|  |  | 
|  | sync(); | 
|  | for_each_ungarded_cpu(cpu) { | 
|  | if (cpu == this_cpu()) | 
|  | continue; | 
|  |  | 
|  | if (cpu->state != state) { | 
|  | smt_lowest(); | 
|  | while (cpu->state != state) { | 
|  | barrier(); | 
|  |  | 
|  | if (timeout_tb && (tb_compare(mftb(), end) == TB_AAFTERB)) { | 
|  | smt_medium(); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | smt_medium(); | 
|  | } | 
|  | } | 
|  | sync(); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static const char *fast_reboot_disabled = NULL; | 
|  |  | 
|  | void disable_fast_reboot(const char *reason) | 
|  | { | 
|  | if (fast_reboot_disabled) | 
|  | return; | 
|  |  | 
|  | prlog(PR_NOTICE, "RESET: Fast reboot disabled: %s\n", reason); | 
|  | fast_reboot_disabled = reason; | 
|  | } | 
|  |  | 
|  | void add_fast_reboot_dt_entries(void) | 
|  | { | 
|  | dt_check_del_prop(opal_node, "fast-reboot"); | 
|  |  | 
|  | if (fast_reboot_disabled) { | 
|  | dt_add_property_string(opal_node, "fast-reboot", fast_reboot_disabled); | 
|  | } else { | 
|  | dt_add_property_string(opal_node, "fast-reboot", "okay"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * This is called by the reboot CPU after all other CPUs have been | 
|  | * quiesced and stopped, to perform various sanity checks on firmware | 
|  | * data (and potentially hardware), to determine whether the fast | 
|  | * reboot should go ahead. | 
|  | */ | 
|  | static bool fast_reboot_sanity_check(void) | 
|  | { | 
|  | if (!mem_check_all()) { | 
|  | disable_fast_reboot("Inconsistent firmware data"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!verify_romem()) { | 
|  | disable_fast_reboot("Inconsistent firmware romem checksum"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void fast_reboot(void) | 
|  | { | 
|  | static int fast_reboot_count = 0; | 
|  |  | 
|  | if (chip_quirk(QUIRK_NO_DIRECT_CTL)) { | 
|  | prlog(PR_DEBUG, | 
|  | "RESET: Fast reboot disabled by quirk\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Ensure all other CPUs have left OPAL calls. | 
|  | */ | 
|  | if (!opal_quiesce(QUIESCE_HOLD, -1)) { | 
|  | disable_fast_reboot("OPAL quiesce timeout"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (fast_reboot_disabled && | 
|  | nvram_query_eq_dangerous("force-fast-reset", "1")) { | 
|  | /* Do fast reboot even if it's been disabled */ | 
|  | prlog(PR_NOTICE, "RESET: Ignoring fast reboot disabled: %s\n", | 
|  | fast_reboot_disabled); | 
|  | } else if (fast_reboot_disabled) { | 
|  | prlog(PR_NOTICE, "RESET: Fast reboot disabled: %s\n", | 
|  | fast_reboot_disabled); | 
|  | opal_quiesce(QUIESCE_RESUME, -1); | 
|  | return; | 
|  | } | 
|  |  | 
|  | prlog(PR_NOTICE, "RESET: Initiating fast reboot %d...\n", ++fast_reboot_count); | 
|  | fast_boot_release = false; | 
|  | spr_set_release = false; | 
|  | nmi_mce_release = false; | 
|  | sync(); | 
|  |  | 
|  | /* Put everybody in stop except myself */ | 
|  | if (sreset_all_prepare()) { | 
|  | prlog(PR_NOTICE, "RESET: Fast reboot failed to prepare " | 
|  | "secondaries for system reset\n"); | 
|  | opal_quiesce(QUIESCE_RESUME, -1); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!fast_reboot_sanity_check()) { | 
|  | opal_quiesce(QUIESCE_RESUME, -1); | 
|  | return; | 
|  | } | 
|  |  | 
|  | cpu_set_sreset_enable(false); | 
|  | cpu_set_ipi_enable(false); | 
|  |  | 
|  | /* | 
|  | * The fast reboot sreset vector has FIXUP_ENDIAN, so secondaries can | 
|  | * cope with a wrong HILE setting. | 
|  | */ | 
|  | copy_sreset_vector_fast_reboot(); | 
|  |  | 
|  | /* | 
|  | * There is no point clearing special wakeup or un-quiesce due to | 
|  | * failure after this point, because we will be going to full IPL. | 
|  | * Less cleanup work means less opportunity to fail. | 
|  | */ | 
|  |  | 
|  | /* Send everyone else to 0x100 */ | 
|  | if (sreset_all_others() != OPAL_SUCCESS) { | 
|  | prlog(PR_NOTICE, "RESET: Fast reboot failed to system reset " | 
|  | "secondaries\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Ensure all the sresets get through */ | 
|  | if (!cpu_state_wait_all_others(cpu_state_fast_reboot_entry, msecs_to_tb(1000))) { | 
|  | prlog(PR_NOTICE, "RESET: Fast reboot timed out waiting for " | 
|  | "secondaries to call in\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | prlog(PR_DEBUG, "RESET: Releasing special wakeups...\n"); | 
|  | sreset_all_finish(); | 
|  |  | 
|  | /* This resets our quiesce state ready to enter the new kernel. */ | 
|  | opal_quiesce(QUIESCE_RESUME_FAST_REBOOT, -1); | 
|  |  | 
|  | console_complete_flush(); | 
|  |  | 
|  | mtmsrd(0, 1); /* Clear MSR[RI] for 0x100 reset */ | 
|  | asm volatile("ba	0x100\n\t" : : : "memory"); | 
|  | for (;;) | 
|  | ; | 
|  | } | 
|  |  | 
|  | void __noreturn enter_nap(void); | 
|  |  | 
|  | static void check_split_core(void) | 
|  | { | 
|  | struct cpu_thread *cpu; | 
|  | u64 mask, hid0; | 
|  |  | 
|  | hid0 = mfspr(SPR_HID0); | 
|  | mask = SPR_HID0_POWER8_4LPARMODE | SPR_HID0_POWER8_2LPARMODE; | 
|  |  | 
|  | if ((hid0 & mask) == 0) | 
|  | return; | 
|  |  | 
|  | prlog(PR_INFO, "RESET: CPU 0x%04x is split !\n", this_cpu()->pir); | 
|  |  | 
|  | /* If it's a secondary thread, just send it to nap */ | 
|  | if (this_cpu()->pir & 7) { | 
|  | /* Prepare to be woken up */ | 
|  | icp_prep_for_pm(); | 
|  | /* Setup LPCR to wakeup on external interrupts only */ | 
|  | mtspr(SPR_LPCR, ((mfspr(SPR_LPCR) & ~SPR_LPCR_P8_PECE) | | 
|  | SPR_LPCR_P8_PECE2)); | 
|  | isync(); | 
|  | /* Go to nap (doesn't return) */ | 
|  | enter_nap(); | 
|  | } | 
|  |  | 
|  | prlog(PR_INFO, "RESET: Primary, unsplitting... \n"); | 
|  |  | 
|  | /* Trigger unsplit operation and update SLW image */ | 
|  | hid0 &= ~SPR_HID0_POWER8_DYNLPARDIS; | 
|  | set_hid0(hid0); | 
|  | opal_slw_set_reg(this_cpu()->pir, SPR_HID0, hid0); | 
|  |  | 
|  | /* Wait for unsplit */ | 
|  | while (mfspr(SPR_HID0) & mask) | 
|  | cpu_relax(); | 
|  |  | 
|  | /* Now the guys are sleeping, wake'em up. They will come back | 
|  | * via reset and continue the fast reboot process normally. | 
|  | * No need to wait. | 
|  | */ | 
|  | prlog(PR_INFO, "RESET: Waking unsplit secondaries... \n"); | 
|  |  | 
|  | for_each_cpu(cpu) { | 
|  | if (!cpu_is_sibling(cpu, this_cpu()) || (cpu == this_cpu())) | 
|  | continue; | 
|  | icp_kick_cpu(cpu); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void cleanup_cpu_state(void) | 
|  | { | 
|  | struct cpu_thread *cpu = this_cpu(); | 
|  |  | 
|  | if (proc_gen == proc_gen_p9) | 
|  | xive_cpu_reset(); | 
|  | else if (proc_gen == proc_gen_p10 || proc_gen == proc_gen_p11) | 
|  | xive2_cpu_reset(); | 
|  |  | 
|  | /* Per core cleanup */ | 
|  | if (cpu_is_thread0(cpu) || cpu_is_core_chiplet_primary(cpu)) { | 
|  | /* Shared SPRs whacked back to normal */ | 
|  |  | 
|  | /* XXX Update the SLW copies ! Also dbl check HIDs etc... */ | 
|  | init_shared_sprs(); | 
|  |  | 
|  | #ifdef CONFIG_P8 | 
|  | if (proc_gen == proc_gen_p8) { | 
|  | /* If somebody was in fast_sleep, we may have a | 
|  | * workaround to undo | 
|  | */ | 
|  | if (cpu->in_fast_sleep) { | 
|  | prlog(PR_DEBUG, "RESET: CPU 0x%04x in fast sleep" | 
|  | " undoing workarounds...\n", cpu->pir); | 
|  | fast_sleep_exit(); | 
|  | } | 
|  |  | 
|  | /* The TLB surely contains garbage. | 
|  | * P9 clears TLBs in cpu_fast_reboot_complete | 
|  | */ | 
|  | cleanup_local_tlb(); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* And we might have lost TB sync */ | 
|  | chiptod_wakeup_resync(); | 
|  | } | 
|  |  | 
|  | /* Per-thread additional cleanup */ | 
|  | init_replicated_sprs(); | 
|  |  | 
|  | // XXX Cleanup SLW, check HIDs ... | 
|  | } | 
|  |  | 
|  | /* Entry from asm after a fast reset */ | 
|  | void __noreturn fast_reboot_entry(void); | 
|  |  | 
|  | void __noreturn fast_reboot_entry(void) | 
|  | { | 
|  | struct cpu_thread *cpu = this_cpu(); | 
|  | void *skiboot_constant_addr kerneal_load_base_addr = KERNEL_LOAD_BASE; | 
|  | void *skiboot_constant_addr initramfs_load_base_addr = INITRAMFS_LOAD_BASE; | 
|  |  | 
|  | if (proc_gen == proc_gen_p8) { | 
|  | /* We reset our ICP first ! Otherwise we might get stray | 
|  | * interrupts when unsplitting | 
|  | */ | 
|  | reset_cpu_icp(); | 
|  |  | 
|  | /* If we are split, we need to unsplit. Since that can send us | 
|  | * to NAP, which will come back via reset, we do it now | 
|  | */ | 
|  | check_split_core(); | 
|  | } | 
|  |  | 
|  | /* Until SPRs (notably HID[HILE]) are set and new exception vectors | 
|  | * installed, nobody should take machine checks. Try to do minimal | 
|  | * work between these points. | 
|  | */ | 
|  | disable_machine_check(); | 
|  | mtmsrd(0, 1); /* Clear RI */ | 
|  |  | 
|  | sync(); | 
|  | cpu->state = cpu_state_fast_reboot_entry; | 
|  | sync(); | 
|  | if (cpu == boot_cpu) { | 
|  | cpu_state_wait_all_others(cpu_state_fast_reboot_entry, 0); | 
|  | spr_set_release = true; | 
|  | } else { | 
|  | wait_on(&spr_set_release); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Reset SPRs */ | 
|  | if (cpu_is_thread0(cpu)) | 
|  | init_shared_sprs(); | 
|  | init_replicated_sprs(); | 
|  |  | 
|  | if (cpu == boot_cpu) { | 
|  | /* Restore skiboot vectors */ | 
|  | copy_exception_vectors(); | 
|  | copy_sreset_vector(); | 
|  | patch_traps(true); | 
|  | } | 
|  |  | 
|  | /* Must wait for others to because shared SPRs like HID0 are only set | 
|  | * by thread0, so can't enable machine checks until those have been | 
|  | * set. | 
|  | */ | 
|  | sync(); | 
|  | cpu->state = cpu_state_present; | 
|  | sync(); | 
|  | if (cpu == boot_cpu) { | 
|  | cpu_state_wait_all_others(cpu_state_present, 0); | 
|  | nmi_mce_release = true; | 
|  | } else { | 
|  | wait_on(&nmi_mce_release); | 
|  | } | 
|  |  | 
|  | /* At this point skiboot exception vectors are in place and all | 
|  | * cores/threads have SPRs set for running skiboot. | 
|  | */ | 
|  | enable_machine_check(); | 
|  | mtmsrd(MSR_RI, 1); | 
|  |  | 
|  | cleanup_cpu_state(); | 
|  |  | 
|  | prlog(PR_DEBUG, "RESET: CPU 0x%04x reset in\n", cpu->pir); | 
|  |  | 
|  | /* The original boot CPU (not the fast reboot initiator) takes | 
|  | * command. Secondaries wait for the signal then go to their secondary | 
|  | * entry point. | 
|  | */ | 
|  | if (cpu != boot_cpu) { | 
|  | wait_on(&fast_boot_release); | 
|  |  | 
|  | __secondary_cpu_entry(); | 
|  | } | 
|  |  | 
|  | if (proc_gen == proc_gen_p9) | 
|  | xive_reset(); | 
|  | else if (proc_gen == proc_gen_p10 || proc_gen == proc_gen_p11) | 
|  | xive2_reset(); | 
|  |  | 
|  | /* Let the CPU layer do some last minute global cleanups */ | 
|  | cpu_fast_reboot_complete(); | 
|  |  | 
|  | /* We can now do NAP mode */ | 
|  | cpu_set_sreset_enable(true); | 
|  | cpu_set_ipi_enable(true); | 
|  |  | 
|  | prlog(PR_INFO, "RESET: Releasing secondaries...\n"); | 
|  |  | 
|  | /* Release everybody */ | 
|  | sync(); | 
|  | fast_boot_release = true; | 
|  | sync(); | 
|  | cpu->state = cpu_state_active; | 
|  | sync(); | 
|  |  | 
|  | /* Wait for them to respond */ | 
|  | cpu_state_wait_all_others(cpu_state_active, 0); | 
|  |  | 
|  | sync(); | 
|  |  | 
|  | prlog(PR_INFO, "RESET: All done, cleaning up...\n"); | 
|  |  | 
|  | /* Clear release flag for next time */ | 
|  | fast_boot_release = false; | 
|  |  | 
|  | if (!chip_quirk(QUIRK_MAMBO_CALLOUTS) && !chip_quirk(QUIRK_QEMU)) { | 
|  | /* | 
|  | * mem_region_clear_unused avoids these preload regions | 
|  | * so it can run along side image preloading. Clear these | 
|  | * regions now to catch anything not overwritten by | 
|  | * preload. | 
|  | * | 
|  | * Simulators may have embedded payload here, so don't clear | 
|  | * these ranges for them. | 
|  | */ | 
|  | memset(kerneal_load_base_addr, 0, KERNEL_LOAD_SIZE); | 
|  | memset(initramfs_load_base_addr, 0, INITRAMFS_LOAD_SIZE); | 
|  | } | 
|  |  | 
|  | /* Start preloading kernel and ramdisk */ | 
|  | start_preload_kernel(); | 
|  |  | 
|  | /* Start clearing memory */ | 
|  | start_mem_region_clear_unused(); | 
|  |  | 
|  | if (platform.fast_reboot_init) | 
|  | platform.fast_reboot_init(); | 
|  |  | 
|  | if (proc_gen == proc_gen_p8) { | 
|  | /* XXX */ | 
|  | /* Reset/EOI the PSI interrupt */ | 
|  | psi_irq_reset(); | 
|  | } | 
|  |  | 
|  | /* update pci nvram settings */ | 
|  | pci_nvram_init(); | 
|  |  | 
|  | /* Remove all PCI devices */ | 
|  | if (pci_reset()) { | 
|  | prlog(PR_NOTICE, "RESET: Fast reboot failed to reset PCI\n"); | 
|  |  | 
|  | /* | 
|  | * Can't return to caller here because we're past no-return. | 
|  | * Attempt an IPL here which is what the caller would do. | 
|  | */ | 
|  | if (platform.cec_reboot) | 
|  | platform.cec_reboot(); | 
|  | for (;;) | 
|  | ; | 
|  | } | 
|  |  | 
|  | ipmi_set_fw_progress_sensor(IPMI_FW_PCI_INIT); | 
|  |  | 
|  | wait_mem_region_clear_unused(); | 
|  |  | 
|  | /* Load and boot payload */ | 
|  | load_and_boot_kernel(true); | 
|  | } |