Skip to content

Commit 2e67035

Browse files
xinli-intelbp3tk0v
authored andcommitted
x86/entry: Add fred_entry_from_kvm() for VMX to handle IRQ/NMI
In IRQ/NMI induced VM exits, KVM VMX needs to execute the respective handlers, which requires the software to create a FRED stack frame, and use it to invoke the handlers. Add fred_irq_entry_from_kvm() for this job. Export fred_entry_from_kvm() because VMX can be compiled as a module. Suggested-by: Sean Christopherson <seanjc@google.com> Suggested-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Xin Li <xin3.li@intel.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de> Tested-by: Shan Kang <shan.kang@intel.com> Link: https://lore.kernel.org/r/20231205105030.8698-32-xin3.li@intel.com
1 parent 2333f3c commit 2e67035

3 files changed

Lines changed: 109 additions & 0 deletions

File tree

arch/x86/entry/entry_64_fred.S

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
* The actual FRED entry points.
44
*/
55

6+
#include <linux/export.h>
7+
68
#include <asm/asm.h>
79
#include <asm/fred.h>
10+
#include <asm/segment.h>
811

912
#include "calling.h"
1013

@@ -52,3 +55,77 @@ SYM_CODE_START_NOALIGN(asm_fred_entrypoint_kernel)
5255
FRED_EXIT
5356
ERETS
5457
SYM_CODE_END(asm_fred_entrypoint_kernel)
58+
59+
#if IS_ENABLED(CONFIG_KVM_INTEL)
60+
SYM_FUNC_START(asm_fred_entry_from_kvm)
61+
push %rbp
62+
mov %rsp, %rbp
63+
64+
UNWIND_HINT_SAVE
65+
66+
/*
67+
* Both IRQ and NMI from VMX can be handled on current task stack
68+
* because there is no need to protect from reentrancy and the call
69+
* stack leading to this helper is effectively constant and shallow
70+
* (relatively speaking). Do the same when FRED is active, i.e., no
71+
* need to check current stack level for a stack switch.
72+
*
73+
* Emulate the FRED-defined redzone and stack alignment.
74+
*/
75+
sub $(FRED_CONFIG_REDZONE_AMOUNT << 6), %rsp
76+
and $FRED_STACK_FRAME_RSP_MASK, %rsp
77+
78+
/*
79+
* Start to push a FRED stack frame, which is always 64 bytes:
80+
*
81+
* +--------+-----------------+
82+
* | Bytes | Usage |
83+
* +--------+-----------------+
84+
* | 63:56 | Reserved |
85+
* | 55:48 | Event Data |
86+
* | 47:40 | SS + Event Info |
87+
* | 39:32 | RSP |
88+
* | 31:24 | RFLAGS |
89+
* | 23:16 | CS + Aux Info |
90+
* | 15:8 | RIP |
91+
* | 7:0 | Error Code |
92+
* +--------+-----------------+
93+
*/
94+
push $0 /* Reserved, must be 0 */
95+
push $0 /* Event data, 0 for IRQ/NMI */
96+
push %rdi /* fred_ss handed in by the caller */
97+
push %rbp
98+
pushf
99+
mov $__KERNEL_CS, %rax
100+
push %rax
101+
102+
/*
103+
* Unlike the IDT event delivery, FRED _always_ pushes an error code
104+
* after pushing the return RIP, thus the CALL instruction CANNOT be
105+
* used here to push the return RIP, otherwise there is no chance to
106+
* push an error code before invoking the IRQ/NMI handler.
107+
*
108+
* Use LEA to get the return RIP and push it, then push an error code.
109+
*/
110+
lea 1f(%rip), %rax
111+
push %rax /* Return RIP */
112+
push $0 /* Error code, 0 for IRQ/NMI */
113+
114+
PUSH_AND_CLEAR_REGS clear_bp=0 unwind_hint=0
115+
movq %rsp, %rdi /* %rdi -> pt_regs */
116+
call __fred_entry_from_kvm /* Call the C entry point */
117+
POP_REGS
118+
ERETS
119+
1:
120+
/*
121+
* Objtool doesn't understand what ERETS does, this hint tells it that
122+
* yes, we'll reach here and with what stack state. A save/restore pair
123+
* isn't strictly needed, but it's the simplest form.
124+
*/
125+
UNWIND_HINT_RESTORE
126+
pop %rbp
127+
RET
128+
129+
SYM_FUNC_END(asm_fred_entry_from_kvm)
130+
EXPORT_SYMBOL_GPL(asm_fred_entry_from_kvm);
131+
#endif

arch/x86/entry/entry_fred.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,17 @@ __visible noinstr void fred_entry_from_kernel(struct pt_regs *regs)
257257

258258
return fred_bad_type(regs, error_code);
259259
}
260+
261+
#if IS_ENABLED(CONFIG_KVM_INTEL)
262+
__visible noinstr void __fred_entry_from_kvm(struct pt_regs *regs)
263+
{
264+
switch (regs->fred_ss.type) {
265+
case EVENT_TYPE_EXTINT:
266+
return fred_extint(regs);
267+
case EVENT_TYPE_NMI:
268+
return fred_exc_nmi(regs);
269+
default:
270+
WARN_ON_ONCE(1);
271+
}
272+
}
273+
#endif

arch/x86/include/asm/fred.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <linux/const.h>
1010

1111
#include <asm/asm.h>
12+
#include <asm/trapnr.h>
1213

1314
/*
1415
* FRED event return instruction opcodes for ERET{S,U}; supported in
@@ -62,12 +63,29 @@ static __always_inline unsigned long fred_event_data(struct pt_regs *regs)
6263

6364
void asm_fred_entrypoint_user(void);
6465
void asm_fred_entrypoint_kernel(void);
66+
void asm_fred_entry_from_kvm(struct fred_ss);
6567

6668
__visible void fred_entry_from_user(struct pt_regs *regs);
6769
__visible void fred_entry_from_kernel(struct pt_regs *regs);
70+
__visible void __fred_entry_from_kvm(struct pt_regs *regs);
71+
72+
/* Can be called from noinstr code, thus __always_inline */
73+
static __always_inline void fred_entry_from_kvm(unsigned int type, unsigned int vector)
74+
{
75+
struct fred_ss ss = {
76+
.ss =__KERNEL_DS,
77+
.type = type,
78+
.vector = vector,
79+
.nmi = type == EVENT_TYPE_NMI,
80+
.lm = 1,
81+
};
82+
83+
asm_fred_entry_from_kvm(ss);
84+
}
6885

6986
#else /* CONFIG_X86_FRED */
7087
static __always_inline unsigned long fred_event_data(struct pt_regs *regs) { return 0; }
88+
static __always_inline void fred_entry_from_kvm(unsigned int type, unsigned int vector) { }
7189
#endif /* CONFIG_X86_FRED */
7290
#endif /* !__ASSEMBLY__ */
7391

0 commit comments

Comments
 (0)