|
7 | 7 | #include <linux/cpufeature.h> |
8 | 8 | #include <asm/coco.h> |
9 | 9 | #include <asm/tdx.h> |
| 10 | +#include <asm/vmx.h> |
10 | 11 |
|
11 | 12 | /* TDX module Call Leaf IDs */ |
12 | 13 | #define TDX_GET_INFO 1 |
@@ -36,6 +37,17 @@ void __tdx_hypercall_failed(void) |
36 | 37 | panic("TDVMCALL failed. TDX module bug?"); |
37 | 38 | } |
38 | 39 |
|
| 40 | +/* |
| 41 | + * The TDG.VP.VMCALL-Instruction-execution sub-functions are defined |
| 42 | + * independently from but are currently matched 1:1 with VMX EXIT_REASONs. |
| 43 | + * Reusing the KVM EXIT_REASON macros makes it easier to connect the host and |
| 44 | + * guest sides of these calls. |
| 45 | + */ |
| 46 | +static u64 hcall_func(u64 exit_reason) |
| 47 | +{ |
| 48 | + return exit_reason; |
| 49 | +} |
| 50 | + |
39 | 51 | /* |
40 | 52 | * Used for TDX guests to make calls directly to the TD module. This |
41 | 53 | * should only be used for calls that have no legitimate reason to fail |
@@ -74,6 +86,62 @@ static u64 get_cc_mask(void) |
74 | 86 | return BIT_ULL(gpa_width - 1); |
75 | 87 | } |
76 | 88 |
|
| 89 | +static u64 __cpuidle __halt(const bool irq_disabled, const bool do_sti) |
| 90 | +{ |
| 91 | + struct tdx_hypercall_args args = { |
| 92 | + .r10 = TDX_HYPERCALL_STANDARD, |
| 93 | + .r11 = hcall_func(EXIT_REASON_HLT), |
| 94 | + .r12 = irq_disabled, |
| 95 | + }; |
| 96 | + |
| 97 | + /* |
| 98 | + * Emulate HLT operation via hypercall. More info about ABI |
| 99 | + * can be found in TDX Guest-Host-Communication Interface |
| 100 | + * (GHCI), section 3.8 TDG.VP.VMCALL<Instruction.HLT>. |
| 101 | + * |
| 102 | + * The VMM uses the "IRQ disabled" param to understand IRQ |
| 103 | + * enabled status (RFLAGS.IF) of the TD guest and to determine |
| 104 | + * whether or not it should schedule the halted vCPU if an |
| 105 | + * IRQ becomes pending. E.g. if IRQs are disabled, the VMM |
| 106 | + * can keep the vCPU in virtual HLT, even if an IRQ is |
| 107 | + * pending, without hanging/breaking the guest. |
| 108 | + */ |
| 109 | + return __tdx_hypercall(&args, do_sti ? TDX_HCALL_ISSUE_STI : 0); |
| 110 | +} |
| 111 | + |
| 112 | +static bool handle_halt(void) |
| 113 | +{ |
| 114 | + /* |
| 115 | + * Since non safe halt is mainly used in CPU offlining |
| 116 | + * and the guest will always stay in the halt state, don't |
| 117 | + * call the STI instruction (set do_sti as false). |
| 118 | + */ |
| 119 | + const bool irq_disabled = irqs_disabled(); |
| 120 | + const bool do_sti = false; |
| 121 | + |
| 122 | + if (__halt(irq_disabled, do_sti)) |
| 123 | + return false; |
| 124 | + |
| 125 | + return true; |
| 126 | +} |
| 127 | + |
| 128 | +void __cpuidle tdx_safe_halt(void) |
| 129 | +{ |
| 130 | + /* |
| 131 | + * For do_sti=true case, __tdx_hypercall() function enables |
| 132 | + * interrupts using the STI instruction before the TDCALL. So |
| 133 | + * set irq_disabled as false. |
| 134 | + */ |
| 135 | + const bool irq_disabled = false; |
| 136 | + const bool do_sti = true; |
| 137 | + |
| 138 | + /* |
| 139 | + * Use WARN_ONCE() to report the failure. |
| 140 | + */ |
| 141 | + if (__halt(irq_disabled, do_sti)) |
| 142 | + WARN_ONCE(1, "HLT instruction emulation failed\n"); |
| 143 | +} |
| 144 | + |
77 | 145 | void tdx_get_ve_info(struct ve_info *ve) |
78 | 146 | { |
79 | 147 | struct tdx_module_output out; |
@@ -104,11 +172,32 @@ void tdx_get_ve_info(struct ve_info *ve) |
104 | 172 | ve->instr_info = upper_32_bits(out.r10); |
105 | 173 | } |
106 | 174 |
|
| 175 | +/* Handle the kernel #VE */ |
| 176 | +static bool virt_exception_kernel(struct pt_regs *regs, struct ve_info *ve) |
| 177 | +{ |
| 178 | + switch (ve->exit_reason) { |
| 179 | + case EXIT_REASON_HLT: |
| 180 | + return handle_halt(); |
| 181 | + default: |
| 182 | + pr_warn("Unexpected #VE: %lld\n", ve->exit_reason); |
| 183 | + return false; |
| 184 | + } |
| 185 | +} |
| 186 | + |
107 | 187 | bool tdx_handle_virt_exception(struct pt_regs *regs, struct ve_info *ve) |
108 | 188 | { |
109 | | - pr_warn("Unexpected #VE: %lld\n", ve->exit_reason); |
| 189 | + bool ret; |
| 190 | + |
| 191 | + if (user_mode(regs)) |
| 192 | + ret = false; |
| 193 | + else |
| 194 | + ret = virt_exception_kernel(regs, ve); |
| 195 | + |
| 196 | + /* After successful #VE handling, move the IP */ |
| 197 | + if (ret) |
| 198 | + regs->ip += ve->instr_len; |
110 | 199 |
|
111 | | - return false; |
| 200 | + return ret; |
112 | 201 | } |
113 | 202 |
|
114 | 203 | void __init tdx_early_init(void) |
|
0 commit comments