mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-22 18:44:44 +08:00
d6a4f70909
On PowerNV platforms, when a CPU is offline, we put it into nap mode. It's possible that the CPU wakes up from nap mode while it is still offline due to a stray IPI. A misdirected device interrupt could also potentially cause it to wake up. In that circumstance, we need to clear the interrupt so that the CPU can go back to nap mode. In the past the clearing of the interrupt was accomplished by briefly enabling interrupts and allowing the normal interrupt handling code (do_IRQ() etc.) to handle the interrupt. This has the problem that this code calls irq_enter() and irq_exit(), which call functions such as account_system_vtime() which use RCU internally. Use of RCU is not permitted on offline CPUs and will trigger errors if RCU checking is enabled. To avoid calling into any generic code which might use RCU, we adopt a different method of clearing interrupts on offline CPUs. Since we are on the PowerNV platform, we know that the system interrupt controller is a XICS being driven directly (i.e. not via hcalls) by the kernel. Hence this adds a new icp_native_flush_interrupt() function to the native-mode XICS driver and arranges to call that when an offline CPU is woken from nap. This new function reads the interrupt from the XICS. If it is an IPI, it clears the IPI; if it is a device interrupt, it prints a warning and disables the source. Then it does the end-of-interrupt processing for the interrupt. The other thing that briefly enabling interrupts did was to check and clear the irq_happened flag in this CPU's PACA. Therefore, after flushing the interrupt from the XICS, we also clear all bits except the PACA_IRQ_HARD_DIS (interrupts are hard disabled) bit from the irq_happened flag. The PACA_IRQ_HARD_DIS flag is set by power7_nap() and is left set to indicate that interrupts are hard disabled. This means we then have to ignore that flag in power7_nap(), which is reasonable since it doesn't indicate that any interrupt event needs servicing. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
245 lines
5.3 KiB
ArmAsm
245 lines
5.3 KiB
ArmAsm
/*
|
|
* This file contains the power_save function for Power7 CPUs.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/threads.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/page.h>
|
|
#include <asm/cputable.h>
|
|
#include <asm/thread_info.h>
|
|
#include <asm/ppc_asm.h>
|
|
#include <asm/asm-offsets.h>
|
|
#include <asm/ppc-opcode.h>
|
|
#include <asm/hw_irq.h>
|
|
#include <asm/kvm_book3s_asm.h>
|
|
#include <asm/opal.h>
|
|
|
|
#undef DEBUG
|
|
|
|
/* Idle state entry routines */
|
|
|
|
#define IDLE_STATE_ENTER_SEQ(IDLE_INST) \
|
|
/* Magic NAP/SLEEP/WINKLE mode enter sequence */ \
|
|
std r0,0(r1); \
|
|
ptesync; \
|
|
ld r0,0(r1); \
|
|
1: cmp cr0,r0,r0; \
|
|
bne 1b; \
|
|
IDLE_INST; \
|
|
b .
|
|
|
|
.text
|
|
|
|
/*
|
|
* Pass requested state in r3:
|
|
* 0 - nap
|
|
* 1 - sleep
|
|
*
|
|
* To check IRQ_HAPPENED in r4
|
|
* 0 - don't check
|
|
* 1 - check
|
|
*/
|
|
_GLOBAL(power7_powersave_common)
|
|
/* Use r3 to pass state nap/sleep/winkle */
|
|
/* NAP is a state loss, we create a regs frame on the
|
|
* stack, fill it up with the state we care about and
|
|
* stick a pointer to it in PACAR1. We really only
|
|
* need to save PC, some CR bits and the NV GPRs,
|
|
* but for now an interrupt frame will do.
|
|
*/
|
|
mflr r0
|
|
std r0,16(r1)
|
|
stdu r1,-INT_FRAME_SIZE(r1)
|
|
std r0,_LINK(r1)
|
|
std r0,_NIP(r1)
|
|
|
|
#ifndef CONFIG_SMP
|
|
/* Make sure FPU, VSX etc... are flushed as we may lose
|
|
* state when going to nap mode
|
|
*/
|
|
bl discard_lazy_cpu_state
|
|
#endif /* CONFIG_SMP */
|
|
|
|
/* Hard disable interrupts */
|
|
mfmsr r9
|
|
rldicl r9,r9,48,1
|
|
rotldi r9,r9,16
|
|
mtmsrd r9,1 /* hard-disable interrupts */
|
|
|
|
/* Check if something happened while soft-disabled */
|
|
lbz r0,PACAIRQHAPPENED(r13)
|
|
andi. r0,r0,~PACA_IRQ_HARD_DIS@l
|
|
beq 1f
|
|
cmpwi cr0,r4,0
|
|
beq 1f
|
|
addi r1,r1,INT_FRAME_SIZE
|
|
ld r0,16(r1)
|
|
mtlr r0
|
|
blr
|
|
|
|
1: /* We mark irqs hard disabled as this is the state we'll
|
|
* be in when returning and we need to tell arch_local_irq_restore()
|
|
* about it
|
|
*/
|
|
li r0,PACA_IRQ_HARD_DIS
|
|
stb r0,PACAIRQHAPPENED(r13)
|
|
|
|
/* We haven't lost state ... yet */
|
|
li r0,0
|
|
stb r0,PACA_NAPSTATELOST(r13)
|
|
|
|
/* Continue saving state */
|
|
SAVE_GPR(2, r1)
|
|
SAVE_NVGPRS(r1)
|
|
mfcr r4
|
|
std r4,_CCR(r1)
|
|
std r9,_MSR(r1)
|
|
std r1,PACAR1(r13)
|
|
|
|
_GLOBAL(power7_enter_nap_mode)
|
|
#ifdef CONFIG_KVM_BOOK3S_HV_POSSIBLE
|
|
/* Tell KVM we're napping */
|
|
li r4,KVM_HWTHREAD_IN_NAP
|
|
stb r4,HSTATE_HWTHREAD_STATE(r13)
|
|
#endif
|
|
cmpwi cr0,r3,1
|
|
beq 2f
|
|
IDLE_STATE_ENTER_SEQ(PPC_NAP)
|
|
/* No return */
|
|
2: IDLE_STATE_ENTER_SEQ(PPC_SLEEP)
|
|
/* No return */
|
|
|
|
_GLOBAL(power7_idle)
|
|
/* Now check if user or arch enabled NAP mode */
|
|
LOAD_REG_ADDRBASE(r3,powersave_nap)
|
|
lwz r4,ADDROFF(powersave_nap)(r3)
|
|
cmpwi 0,r4,0
|
|
beqlr
|
|
li r3, 1
|
|
/* fall through */
|
|
|
|
_GLOBAL(power7_nap)
|
|
mr r4,r3
|
|
li r3,0
|
|
b power7_powersave_common
|
|
/* No return */
|
|
|
|
_GLOBAL(power7_sleep)
|
|
li r3,1
|
|
li r4,1
|
|
b power7_powersave_common
|
|
/* No return */
|
|
|
|
/*
|
|
* Make opal call in realmode. This is a generic function to be called
|
|
* from realmode from reset vector. It handles endianess.
|
|
*
|
|
* r13 - paca pointer
|
|
* r1 - stack pointer
|
|
* r3 - opal token
|
|
*/
|
|
opal_call_realmode:
|
|
mflr r12
|
|
std r12,_LINK(r1)
|
|
ld r2,PACATOC(r13)
|
|
/* Set opal return address */
|
|
LOAD_REG_ADDR(r0,return_from_opal_call)
|
|
mtlr r0
|
|
/* Handle endian-ness */
|
|
li r0,MSR_LE
|
|
mfmsr r12
|
|
andc r12,r12,r0
|
|
mtspr SPRN_HSRR1,r12
|
|
mr r0,r3 /* Move opal token to r0 */
|
|
LOAD_REG_ADDR(r11,opal)
|
|
ld r12,8(r11)
|
|
ld r2,0(r11)
|
|
mtspr SPRN_HSRR0,r12
|
|
hrfid
|
|
|
|
return_from_opal_call:
|
|
FIXUP_ENDIAN
|
|
ld r0,_LINK(r1)
|
|
mtlr r0
|
|
blr
|
|
|
|
#define CHECK_HMI_INTERRUPT \
|
|
mfspr r0,SPRN_SRR1; \
|
|
BEGIN_FTR_SECTION_NESTED(66); \
|
|
rlwinm r0,r0,45-31,0xf; /* extract wake reason field (P8) */ \
|
|
FTR_SECTION_ELSE_NESTED(66); \
|
|
rlwinm r0,r0,45-31,0xe; /* P7 wake reason field is 3 bits */ \
|
|
ALT_FTR_SECTION_END_NESTED_IFSET(CPU_FTR_ARCH_207S, 66); \
|
|
cmpwi r0,0xa; /* Hypervisor maintenance ? */ \
|
|
bne 20f; \
|
|
/* Invoke opal call to handle hmi */ \
|
|
ld r2,PACATOC(r13); \
|
|
ld r1,PACAR1(r13); \
|
|
std r3,ORIG_GPR3(r1); /* Save original r3 */ \
|
|
li r3,OPAL_HANDLE_HMI; /* Pass opal token argument*/ \
|
|
bl opal_call_realmode; \
|
|
ld r3,ORIG_GPR3(r1); /* Restore original r3 */ \
|
|
20: nop;
|
|
|
|
|
|
_GLOBAL(power7_wakeup_tb_loss)
|
|
ld r2,PACATOC(r13);
|
|
ld r1,PACAR1(r13)
|
|
|
|
BEGIN_FTR_SECTION
|
|
CHECK_HMI_INTERRUPT
|
|
END_FTR_SECTION_IFSET(CPU_FTR_HVMODE)
|
|
/* Time base re-sync */
|
|
li r3,OPAL_RESYNC_TIMEBASE
|
|
bl opal_call_realmode;
|
|
|
|
/* TODO: Check r3 for failure */
|
|
|
|
REST_NVGPRS(r1)
|
|
REST_GPR(2, r1)
|
|
ld r3,_CCR(r1)
|
|
ld r4,_MSR(r1)
|
|
ld r5,_NIP(r1)
|
|
addi r1,r1,INT_FRAME_SIZE
|
|
mtcr r3
|
|
mfspr r3,SPRN_SRR1 /* Return SRR1 */
|
|
mtspr SPRN_SRR1,r4
|
|
mtspr SPRN_SRR0,r5
|
|
rfid
|
|
|
|
_GLOBAL(power7_wakeup_loss)
|
|
ld r1,PACAR1(r13)
|
|
BEGIN_FTR_SECTION
|
|
CHECK_HMI_INTERRUPT
|
|
END_FTR_SECTION_IFSET(CPU_FTR_HVMODE)
|
|
REST_NVGPRS(r1)
|
|
REST_GPR(2, r1)
|
|
ld r3,_CCR(r1)
|
|
ld r4,_MSR(r1)
|
|
ld r5,_NIP(r1)
|
|
addi r1,r1,INT_FRAME_SIZE
|
|
mtcr r3
|
|
mtspr SPRN_SRR1,r4
|
|
mtspr SPRN_SRR0,r5
|
|
rfid
|
|
|
|
_GLOBAL(power7_wakeup_noloss)
|
|
lbz r0,PACA_NAPSTATELOST(r13)
|
|
cmpwi r0,0
|
|
bne power7_wakeup_loss
|
|
BEGIN_FTR_SECTION
|
|
CHECK_HMI_INTERRUPT
|
|
END_FTR_SECTION_IFSET(CPU_FTR_HVMODE)
|
|
ld r1,PACAR1(r13)
|
|
ld r4,_MSR(r1)
|
|
ld r5,_NIP(r1)
|
|
addi r1,r1,INT_FRAME_SIZE
|
|
mtspr SPRN_SRR1,r4
|
|
mtspr SPRN_SRR0,r5
|
|
rfid
|