Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

powerpc/powernv: Handle irq_happened flag correctly in off-line loop

This fixes a bug where it is possible for an off-line CPU to fail to go
into a low-power state (nap/sleep/winkle), and to become unresponsive to
requests from the KVM subsystem to wake up and run a VCPU. What can
happen is that a maskable interrupt of some kind (external, decrementer,
hypervisor doorbell, or HMI) after we have called local_irq_disable() at
the beginning of pnv_smp_cpu_kill_self() and before interrupts are
hard-disabled inside power7_nap/sleep/winkle(). In this situation, the
pending event is marked in the irq_happened flag in the PACA. This
pending event prevents power7_nap/sleep/winkle from going to the
requested low-power state; instead they return immediately. We don't
deal with any of these pending event flags in the off-line loop in
pnv_smp_cpu_kill_self() because power7_nap et al. return 0 in this case,
so we will have srr1 == 0, and none of the processing to clear
interrupts or doorbells will be done.

Usually, the most obvious symptom of this is that a KVM guest will fail
with a console message saying "KVM: couldn't grab cpu N".

This fixes the problem by making sure we handle the irq_happened flags
properly. First, we hard-disable before the off-line loop. Once we have
hard-disabled, the irq_happened flags can't change underneath us. We
unconditionally clear the DEC and HMI flags: there is no processing of
timer interrupts while off-line, and the necessary HMI processing is all
done in lower-level code. We leave the EE and DBELL flags alone for the
first iteration of the loop, so that we won't fail to respond to a
split-core request that came in just before hard-disabling. Within the
loop, we handle external interrupts if the EE bit is set in irq_happened
as well as if the low-power state was interrupted by an external
interrupt. (We don't need to do the msgclr for a pending doorbell in
irq_happened, because doorbells are edge-triggered and don't remain
pending in hardware.) Then we clear both the EE and DBELL flags, and
once clear, they cannot be set again (until this CPU comes online again,
that is).

This also fixes the debug check to not be done when we just ran a KVM
guest or when the sleep didn't happen because of a pending event in
irq_happened.

Signed-off-by: Paul Mackerras <paulus@samba.org>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>

authored by

Paul Mackerras and committed by
Michael Ellerman
53c656c4 23316316

+24 -5
+24 -5
arch/powerpc/platforms/powernv/smp.c
··· 171 171 * so clear LPCR:PECE1. We keep PECE2 enabled. 172 172 */ 173 173 mtspr(SPRN_LPCR, mfspr(SPRN_LPCR) & ~(u64)LPCR_PECE1); 174 + 175 + /* 176 + * Hard-disable interrupts, and then clear irq_happened flags 177 + * that we can safely ignore while off-line, since they 178 + * are for things for which we do no processing when off-line 179 + * (or in the case of HMI, all the processing we need to do 180 + * is done in lower-level real-mode code). 181 + */ 182 + hard_irq_disable(); 183 + local_paca->irq_happened &= ~(PACA_IRQ_DEC | PACA_IRQ_HMI); 184 + 174 185 while (!generic_check_cpu_restart(cpu)) { 186 + /* 187 + * Clear IPI flag, since we don't handle IPIs while 188 + * offline, except for those when changing micro-threading 189 + * mode, which are handled explicitly below, and those 190 + * for coming online, which are handled via 191 + * generic_check_cpu_restart() calls. 192 + */ 193 + kvmppc_set_host_ipi(cpu, 0); 175 194 176 195 ppc64_runlatch_off(); 177 196 ··· 215 196 * having finished executing in a KVM guest, then srr1 216 197 * contains 0. 217 198 */ 218 - if ((srr1 & wmask) == SRR1_WAKEEE) { 199 + if (((srr1 & wmask) == SRR1_WAKEEE) || 200 + (local_paca->irq_happened & PACA_IRQ_EE)) { 219 201 icp_native_flush_interrupt(); 220 - local_paca->irq_happened &= PACA_IRQ_HARD_DIS; 221 - smp_mb(); 222 202 } else if ((srr1 & wmask) == SRR1_WAKEHDBELL) { 223 203 unsigned long msg = PPC_DBELL_TYPE(PPC_DBELL_SERVER); 224 204 asm volatile(PPC_MSGCLR(%0) : : "r" (msg)); 225 - kvmppc_set_host_ipi(cpu, 0); 226 205 } 206 + local_paca->irq_happened &= ~(PACA_IRQ_EE | PACA_IRQ_DBELL); 207 + smp_mb(); 227 208 228 209 if (cpu_core_split_required()) 229 210 continue; 230 211 231 - if (!generic_check_cpu_restart(cpu)) 212 + if (srr1 && !generic_check_cpu_restart(cpu)) 232 213 DBG("CPU%d Unexpected exit while offline !\n", cpu); 233 214 } 234 215 mtspr(SPRN_LPCR, mfspr(SPRN_LPCR) | LPCR_PECE1);