Skip to content
Permalink
Browse files
x86/tdx: Use direct paravirt call for wrmsrl
TDX normally handles MSR writes using the #VE exception, or directly
for some special MSRs. But there is at least one performance critical
MSR which triggers #VE, which is the TSC deadline MSR.  It gets
reprogrammed every timer interrupt, and also every idle exit. There
are noticeable slow downs by relying on #VE for this, since a #VE
requires at least 3 exits to the TDX module, which adds up in overhead.

Use a direct paravirt call for MSR writes. This will only be used for
wrmsrl(), some of the other MSR write paths still use #VE (but these
don't seem to be performance critical, so it shouldn't matter)

There is one complication that TDX has both context switched MSRs
(which always need to use WRMSR) and host supported MSRs (which need to
use TDCALL). Unfortunately the list of both is quite long and it would
be difficult to maintain a switch statement to distinguish them. For
most MSRs it doesn't really matter if there is an extra VE exception or
not because they are not performance critical. But it's important for a
few critical ones like TSC_DEADLINE, which needs to use TDCALL. So
enable the TDCALL fast path only for TSC_DEADLINE and keep using WRMSR
for all the others, which may or may not result in an extra VE
exception. If there are other performance critical host controlled MSRs
it can be added to the switch statement here later.

Signed-off-by: Andi Kleen <ak@linux.intel.com>
Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
  • Loading branch information
Andi Kleen authored and kiryl committed Jan 10, 2022
1 parent 5511bdc commit 95bf288c8d7a4e30ca710931bfb0deb2a6f020e7
Showing 1 changed file with 38 additions and 3 deletions.
@@ -464,8 +464,33 @@ static bool tdx_read_msr(unsigned int msr, u64 *val)
return true;
}

static bool tdx_write_msr(unsigned int msr, unsigned int low,
unsigned int high)
/*
* TDX has context switched MSRs and emulated MSRs. The emulated MSRs
* normally trigger a #VE, but that is expensive, which can be avoided
* by doing a direct TDCALL. Unfortunately, this cannot be done for all
* because some MSRs are "context switched" and need WRMSR.
*
* The list for this is unfortunately quite long. To avoid maintaining
* very long switch statements just do a fast path for the few critical
* MSRs that need TDCALL, currently only TSC_DEADLINE.
*
* More can be added as needed.
*
* The others will be handled by the #VE handler as needed.
* See 18.1 "MSR virtualization" in the TDX Module EAS
*/
static bool tdx_fast_tdcall_path_msr(unsigned int msr)
{
switch (msr) {
case MSR_IA32_TSC_DEADLINE:
return true;
default:
return false;
}
}

static bool __tdx_write_msr(unsigned int msr, unsigned int low,
unsigned int high)
{
u64 ret;

@@ -480,6 +505,14 @@ static bool tdx_write_msr(unsigned int msr, unsigned int low,
return ret ? false : true;
}

void notrace tdx_write_msr(unsigned int msr, u32 low, u32 high)
{
if (tdx_fast_tdcall_path_msr(msr))
__tdx_write_msr(msr, low, high);
else
native_write_msr(msr, low, high);
}

static bool tdx_handle_cpuid(struct pt_regs *regs)
{
struct tdx_hypercall_output out;
@@ -746,7 +779,7 @@ static bool tdx_virt_exception_kernel(struct pt_regs *regs, struct ve_info *ve)
}
break;
case EXIT_REASON_MSR_WRITE:
ret = tdx_write_msr(regs->cx, regs->ax, regs->dx);
ret = __tdx_write_msr(regs->cx, regs->ax, regs->dx);
break;
case EXIT_REASON_CPUID:
ret = tdx_handle_cpuid(regs);
@@ -846,6 +879,8 @@ void __init tdx_early_init(void)

swiotlb_force = SWIOTLB_FORCE;

pv_ops.cpu.write_msr = tdx_write_msr;

legacy_pic = &null_legacy_pic;

/*

0 comments on commit 95bf288

Please sign in to comment.