diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-07-02 15:36:20 +1000 |
commit | 1d880992fd8c8457a2d990ac6622cfd58fb1b261 (patch) | |
tree | c4c843b12e96b5612c315db5a23c5da1a900618c /core/fast-reboot.c | |
download | skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.zip skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.gz skiboot-1d880992fd8c8457a2d990ac6622cfd58fb1b261.tar.bz2 |
Initial commit of Open Source release
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'core/fast-reboot.c')
-rw-r--r-- | core/fast-reboot.c | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/core/fast-reboot.c b/core/fast-reboot.c new file mode 100644 index 0000000..49b80b6 --- /dev/null +++ b/core/fast-reboot.c @@ -0,0 +1,346 @@ +/* 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. + */ + +#include <skiboot.h> +#include <cpu.h> +#include <fsp.h> +#include <psi.h> +#include <opal.h> +#include <xscom.h> +#include <interrupts.h> +#include <cec.h> +#include <timebase.h> +#include <memory.h> +#include <pci.h> +#include <chip.h> + +/* + * To get control of all threads, we sreset them via XSCOM after + * patching the 0x100 vector. This will work as long as the target + * HRMOR is 0. If Linux ever uses HRMOR, we'll have to consider + * a more messy approach. + * + * The SCOM register we want is called "Core RAS Control" in the doc + * and EX0.EC.PC.TCTL_GENERATE#0.TCTL.DIRECT_CONTROLS in the SCOM list + * + * Bits in there change from CPU rev to CPU rev but the bit we care + * about, bit 60 "sreset_request" appears to have stuck to the same + * place in both P7 and P7+. The register also has the same SCOM + * address + */ +#define EX0_TCTL_DIRECT_CONTROLS0 0x08010400 +#define EX0_TCTL_DIRECT_CONTROLS1 0x08010440 +#define EX0_TCTL_DIRECT_CONTROLS2 0x08010480 +#define EX0_TCTL_DIRECT_CONTROLS3 0x080104c0 +#define TCTL_DC_SRESET_REQUEST PPC_BIT(60) + +/* Flag tested by the OPAL entry code */ +uint8_t reboot_in_progress; +static struct cpu_thread *resettor, *resettee; + +static void flush_caches(void) +{ + uint64_t base = SKIBOOT_BASE; + uint64_t end = base + SKIBOOT_SIZE; + + /* Not sure what the effect of sreset is on cores, so let's + * shoot a series of dcbf's on all cachelines that make up + * our core memory just in case... + */ + while(base < end) { + asm volatile("dcbf 0,%0" : : "r" (base) : "memory"); + base += 128; + } + sync(); +} + +static bool do_reset_core_p7(struct cpu_thread *cpu) +{ + uint32_t xscom_addr, chip; + uint64_t ctl; + int rc; + + /* Add the Core# */ + xscom_addr = EX0_TCTL_DIRECT_CONTROLS0; + xscom_addr |= ((cpu->pir >> 2) & 7) << 24; + + chip = pir_to_chip_id(cpu->pir); + + ctl = TCTL_DC_SRESET_REQUEST; + rc = xscom_write(chip, xscom_addr, ctl); + rc |= xscom_write(chip, xscom_addr + 0x40, ctl); + rc |= xscom_write(chip, xscom_addr + 0x80, ctl); + rc |= xscom_write(chip, xscom_addr + 0xc0, ctl); + if (rc) { + prerror("RESET: Error %d resetting CPU 0x%04x\n", + rc, cpu->pir); + return false; + } + return true; +} + +static void fast_reset_p7(void) +{ + struct cpu_thread *cpu; + + resettee = this_cpu(); + resettor = NULL; + + /* Pick up a candidate resettor. We do that before we flush + * the caches + */ + for_each_cpu(cpu) { + /* + * Some threads might still be in skiboot. + * + * But because we deal with entire cores and we don't want + * to special case things, we are just going to reset them + * too making the assumption that this is safe, they are + * holding no locks. This can only be true if they don't + * have jobs scheduled which is hopefully the case. + */ + if (cpu->state != cpu_state_os && + cpu->state != cpu_state_active) + continue; + + /* + * Only hit cores and only if they aren't on the same core + * as ourselves + */ + if (cpu_get_thread0(cpu) == cpu_get_thread0(this_cpu()) || + cpu->pir & 0x3) + continue; + + /* Pick up one of those guys as our "resettor". It will be + * in charge of resetting this CPU. We avoid resetting + * ourselves, not sure how well it would do with SCOM + */ + resettor = cpu; + break; + } + + if (!resettor) { + printf("RESET: Can't find a resettor !\n"); + return; + } + printf("RESET: Resetting from 0x%04x, resettor 0x%04x\n", + this_cpu()->pir, resettor->pir); + + printf("RESET: Flushing caches...\n"); + + /* Is that necessary ? */ + flush_caches(); + + /* Reset everybody except self and except resettor */ + for_each_cpu(cpu) { + if (cpu->state != cpu_state_os && + cpu->state != cpu_state_active) + continue; + if (cpu_get_thread0(cpu) == cpu_get_thread0(this_cpu()) || + cpu->pir & 0x3) + continue; + if (cpu_get_thread0(cpu) == cpu_get_thread0(resettor)) + continue; + + printf("RESET: Resetting CPU 0x%04x...\n", cpu->pir); + + if (!do_reset_core_p7(cpu)) + return; + } + + /* Reset the resettor last because it's going to kill me ! */ + printf("RESET: Resetting CPU 0x%04x...\n", resettor->pir); + if (!do_reset_core_p7(resettor)) + return; + + /* Don't return */ + for (;;) + ; +} + +void fast_reset(void) +{ + uint32_t pvr = mfspr(SPR_PVR); + extern uint32_t fast_reset_patch_start; + extern uint32_t fast_reset_patch_end; + uint32_t *dst, *src; + + printf("RESET: Fast reboot request !\n"); + + /* XXX We need a way to ensure that no other CPU is in skiboot + * holding locks (via the OPAL APIs) and if they are, we need + * for them to get out + */ + reboot_in_progress = 1; + time_wait_ms(200); + + /* Copy reset trampoline */ + printf("RESET: Copying reset trampoline...\n"); + src = &fast_reset_patch_start; + dst = (uint32_t *)0x100; + while(src < &fast_reset_patch_end) + *(dst++) = *(src++); + sync_icache(); + + switch(PVR_TYPE(pvr)) { + case PVR_TYPE_P7: + case PVR_TYPE_P7P: + fast_reset_p7(); + } +} + +static void cleanup_cpu_state(void) +{ + if (cpu_is_thread0(this_cpu())) { + cleanup_tlb(); + init_shared_sprs(); + } + init_replicated_sprs(); + reset_cpu_icp(); +} + +#ifdef FAST_REBOOT_CLEARS_MEMORY +static void fast_mem_clear(uint64_t start, uint64_t end) +{ + printf("MEMORY: Clearing %llx..%llx\n", start, end); + + while(start < end) { + asm volatile("dcbz 0,%0" : : "r" (start) : "memory"); + start += 128; + } +} + +static void memory_reset(void) +{ + struct address_range *i; + uint64_t skistart = SKIBOOT_BASE; + uint64_t skiend = SKIBOOT_BASE + SKIBOOT_SIZE; + + printf("MEMORY: Clearing ...\n"); + + list_for_each(&address_ranges, i, list) { + uint64_t start = cleanup_addr(i->arange->start); + uint64_t end = cleanup_addr(i->arange->end); + + if (start >= skiend || end <= skistart) + fast_mem_clear(start, end); + else { + if (start < skistart) + fast_mem_clear(start, skistart); + if (end > skiend) + fast_mem_clear(skiend, end); + } + } +} +#endif /* FAST_REBOOT_CLEARS_MEMORY */ + +/* Entry from asm after a fast reset */ +void fast_reboot(void); + +void fast_reboot(void) +{ + static volatile bool fast_boot_release; + struct cpu_thread *cpu; + + printf("INIT: CPU PIR 0x%04x reset in\n", this_cpu()->pir); + + /* If this CPU was chosen as the resettor, it must reset the + * resettee (the one that initiated the whole process + */ + if (this_cpu() == resettor) + do_reset_core_p7(resettee); + + /* Are we the original boot CPU ? If not, we spin waiting + * for a relase signal from CPU 1, then we clean ourselves + * up and go processing jobs. + */ + if (this_cpu() != boot_cpu) { + this_cpu()->state = cpu_state_present; + while (!fast_boot_release) { + smt_very_low(); + sync(); + } + smt_medium(); + cleanup_cpu_state(); + __secondary_cpu_entry(); + } + + /* We are the original boot CPU, wait for secondaries to + * be captured + */ + for_each_cpu(cpu) { + if (cpu == this_cpu()) + continue; + + /* XXX Add a callin timeout ? */ + while (cpu->state != cpu_state_present) { + smt_very_low(); + sync(); + } + smt_medium(); + } + + printf("INIT: Releasing secondaries...\n"); + + /* Release everybody */ + fast_boot_release = true; + sync(); + + /* Wait for them to respond */ + for_each_cpu(cpu) { + if (cpu == this_cpu()) + continue; + + /* XXX Add a callin timeout ? */ + while (cpu->state == cpu_state_present) { + smt_very_low(); + sync(); + } + } + + printf("INIT: All done, resetting everything else...\n"); + + /* Clear release flag for next time */ + fast_boot_release = false; + reboot_in_progress = 0; + + /* Cleanup ourselves */ + cleanup_cpu_state(); + + /* Set our state to active */ + this_cpu()->state = cpu_state_active; + + /* Poke the consoles (see comments in the code there) */ + fsp_console_reset(); + + /* Reset/EOI the PSI interrupt */ + psi_irq_reset(); + + /* Remove all PCI devices */ + pci_reset(); + + /* Reset IO Hubs */ + cec_reset(); + + /* Re-Initialize all discovered PCI slots */ + pci_init_slots(); + + /* Clear memory */ +#ifdef FAST_REBOOT_CLEARS_MEMORY + memory_reset(); +#endif + load_and_boot_kernel(true); +} |