x86/tdx: Port I/O: Add runtime hypercalls
TDX hypervisors cannot emulate instructions directly. This includes port I/O which is normally emulated in the hypervisor. All port I/O instructions inside TDX trigger the #VE exception in the guest and would be normally emulated there. Use a hypercall to emulate port I/O. Extend the tdx_handle_virt_exception() and add support to handle the #VE due to port I/O instructions. String I/O operations are not supported in TDX. Unroll them by declaring CC_ATTR_GUEST_UNROLL_STRING_IO confidential computing attribute. == Userspace Implications == The ioperm() facility allows userspace access to I/O instructions like inb/outb. Among other things, this allows writing userspace device drivers. This series has no special handling for ioperm(). Users will be able to successfully request I/O permissions but will induce a #VE on their first I/O instruction which leads SIGSEGV. If this is undesirable users can enable kernel lockdown feature with 'lockdown=integrity' kernel command line option. It makes ioperm() fail. More robust handling of this situation (denying ioperm() in all TDX guests) will be addressed in follow-on work. Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com> Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com> Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com> Reviewed-by: Andi Kleen <ak@linux.intel.com> Reviewed-by: Dan Williams <dan.j.williams@intel.com> Reviewed-by: Dave Hansen <dave.hansen@linux.intel.com> Reviewed-by: Thomas Gleixner <tglx@linutronix.de> Link: https://lkml.kernel.org/r/20220405232939.73860-18-kirill.shutemov@linux.intel.com
This commit is contained in:
parent
4c5b9aac6c
commit
0314994883
@ -18,7 +18,12 @@ static u64 cc_mask __ro_after_init;
|
||||
|
||||
static bool intel_cc_platform_has(enum cc_attr attr)
|
||||
{
|
||||
return false;
|
||||
switch (attr) {
|
||||
case CC_ATTR_GUEST_UNROLL_STRING_IO:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -19,6 +19,16 @@
|
||||
#define EPT_READ 0
|
||||
#define EPT_WRITE 1
|
||||
|
||||
/* Port I/O direction */
|
||||
#define PORT_READ 0
|
||||
#define PORT_WRITE 1
|
||||
|
||||
/* See Exit Qualification for I/O Instructions in VMX documentation */
|
||||
#define VE_IS_IO_IN(e) ((e) & BIT(3))
|
||||
#define VE_GET_IO_SIZE(e) (((e) & GENMASK(2, 0)) + 1)
|
||||
#define VE_GET_PORT_NUM(e) ((e) >> 16)
|
||||
#define VE_IS_IO_STRING(e) ((e) & BIT(4))
|
||||
|
||||
/*
|
||||
* Wrapper for standard use of __tdx_hypercall with no output aside from
|
||||
* return code.
|
||||
@ -341,6 +351,73 @@ static bool handle_mmio(struct pt_regs *regs, struct ve_info *ve)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool handle_in(struct pt_regs *regs, int size, int port)
|
||||
{
|
||||
struct tdx_hypercall_args args = {
|
||||
.r10 = TDX_HYPERCALL_STANDARD,
|
||||
.r11 = hcall_func(EXIT_REASON_IO_INSTRUCTION),
|
||||
.r12 = size,
|
||||
.r13 = PORT_READ,
|
||||
.r14 = port,
|
||||
};
|
||||
u64 mask = GENMASK(BITS_PER_BYTE * size, 0);
|
||||
bool success;
|
||||
|
||||
/*
|
||||
* Emulate the I/O read via hypercall. More info about ABI can be found
|
||||
* in TDX Guest-Host-Communication Interface (GHCI) section titled
|
||||
* "TDG.VP.VMCALL<Instruction.IO>".
|
||||
*/
|
||||
success = !__tdx_hypercall(&args, TDX_HCALL_HAS_OUTPUT);
|
||||
|
||||
/* Update part of the register affected by the emulated instruction */
|
||||
regs->ax &= ~mask;
|
||||
if (success)
|
||||
regs->ax |= args.r11 & mask;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool handle_out(struct pt_regs *regs, int size, int port)
|
||||
{
|
||||
u64 mask = GENMASK(BITS_PER_BYTE * size, 0);
|
||||
|
||||
/*
|
||||
* Emulate the I/O write via hypercall. More info about ABI can be found
|
||||
* in TDX Guest-Host-Communication Interface (GHCI) section titled
|
||||
* "TDG.VP.VMCALL<Instruction.IO>".
|
||||
*/
|
||||
return !_tdx_hypercall(hcall_func(EXIT_REASON_IO_INSTRUCTION), size,
|
||||
PORT_WRITE, port, regs->ax & mask);
|
||||
}
|
||||
|
||||
/*
|
||||
* Emulate I/O using hypercall.
|
||||
*
|
||||
* Assumes the IO instruction was using ax, which is enforced
|
||||
* by the standard io.h macros.
|
||||
*
|
||||
* Return True on success or False on failure.
|
||||
*/
|
||||
static bool handle_io(struct pt_regs *regs, u32 exit_qual)
|
||||
{
|
||||
int size, port;
|
||||
bool in;
|
||||
|
||||
if (VE_IS_IO_STRING(exit_qual))
|
||||
return false;
|
||||
|
||||
in = VE_IS_IO_IN(exit_qual);
|
||||
size = VE_GET_IO_SIZE(exit_qual);
|
||||
port = VE_GET_PORT_NUM(exit_qual);
|
||||
|
||||
|
||||
if (in)
|
||||
return handle_in(regs, size, port);
|
||||
else
|
||||
return handle_out(regs, size, port);
|
||||
}
|
||||
|
||||
void tdx_get_ve_info(struct ve_info *ve)
|
||||
{
|
||||
struct tdx_module_output out;
|
||||
@ -397,6 +474,8 @@ static bool virt_exception_kernel(struct pt_regs *regs, struct ve_info *ve)
|
||||
return handle_cpuid(regs);
|
||||
case EXIT_REASON_EPT_VIOLATION:
|
||||
return handle_mmio(regs, ve);
|
||||
case EXIT_REASON_IO_INSTRUCTION:
|
||||
return handle_io(regs, ve->exit_qual);
|
||||
default:
|
||||
pr_warn("Unexpected #VE: %lld\n", ve->exit_reason);
|
||||
return false;
|
||||
|
Loading…
Reference in New Issue
Block a user