mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2025-03-11 23:27:42 +07:00
x86/sev-es: Handle MMIO events
Add a handler for #VC exceptions caused by MMIO intercepts. These intercepts come along as nested page faults on pages with reserved bits set. Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com> [ jroedel@suse.de: Adapt to VC handling framework ] Co-developed-by: Joerg Roedel <jroedel@suse.de> Signed-off-by: Joerg Roedel <jroedel@suse.de> Signed-off-by: Borislav Petkov <bp@suse.de> Link: https://lkml.kernel.org/r/20200907131613.12703-50-joro@8bytes.org
This commit is contained in:
parent
5e3427a7bc
commit
51ee7d6e3d
@ -81,6 +81,11 @@
|
|||||||
#define SVM_EXIT_AVIC_INCOMPLETE_IPI 0x401
|
#define SVM_EXIT_AVIC_INCOMPLETE_IPI 0x401
|
||||||
#define SVM_EXIT_AVIC_UNACCELERATED_ACCESS 0x402
|
#define SVM_EXIT_AVIC_UNACCELERATED_ACCESS 0x402
|
||||||
|
|
||||||
|
/* SEV-ES software-defined VMGEXIT events */
|
||||||
|
#define SVM_VMGEXIT_MMIO_READ 0x80000001
|
||||||
|
#define SVM_VMGEXIT_MMIO_WRITE 0x80000002
|
||||||
|
#define SVM_VMGEXIT_UNSUPPORTED_EVENT 0x8000ffff
|
||||||
|
|
||||||
#define SVM_EXIT_ERR -1
|
#define SVM_EXIT_ERR -1
|
||||||
|
|
||||||
#define SVM_EXIT_REASONS \
|
#define SVM_EXIT_REASONS \
|
||||||
|
@ -359,6 +359,37 @@ static enum es_result vc_read_mem(struct es_em_ctxt *ctxt,
|
|||||||
return ES_EXCEPTION;
|
return ES_EXCEPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool vc_slow_virt_to_phys(struct ghcb *ghcb, struct es_em_ctxt *ctxt,
|
||||||
|
unsigned long vaddr, phys_addr_t *paddr)
|
||||||
|
{
|
||||||
|
unsigned long va = (unsigned long)vaddr;
|
||||||
|
unsigned int level;
|
||||||
|
phys_addr_t pa;
|
||||||
|
pgd_t *pgd;
|
||||||
|
pte_t *pte;
|
||||||
|
|
||||||
|
pgd = __va(read_cr3_pa());
|
||||||
|
pgd = &pgd[pgd_index(va)];
|
||||||
|
pte = lookup_address_in_pgd(pgd, va, &level);
|
||||||
|
if (!pte) {
|
||||||
|
ctxt->fi.vector = X86_TRAP_PF;
|
||||||
|
ctxt->fi.cr2 = vaddr;
|
||||||
|
ctxt->fi.error_code = 0;
|
||||||
|
|
||||||
|
if (user_mode(ctxt->regs))
|
||||||
|
ctxt->fi.error_code |= X86_PF_USER;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pa = (phys_addr_t)pte_pfn(*pte) << PAGE_SHIFT;
|
||||||
|
pa |= va & ~page_level_mask(level);
|
||||||
|
|
||||||
|
*paddr = pa;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* Include code shared with pre-decompression boot stage */
|
/* Include code shared with pre-decompression boot stage */
|
||||||
#include "sev-es-shared.c"
|
#include "sev-es-shared.c"
|
||||||
|
|
||||||
@ -447,6 +478,194 @@ static void __init vc_early_forward_exception(struct es_em_ctxt *ctxt)
|
|||||||
do_early_exception(ctxt->regs, trapnr);
|
do_early_exception(ctxt->regs, trapnr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static long *vc_insn_get_reg(struct es_em_ctxt *ctxt)
|
||||||
|
{
|
||||||
|
long *reg_array;
|
||||||
|
int offset;
|
||||||
|
|
||||||
|
reg_array = (long *)ctxt->regs;
|
||||||
|
offset = insn_get_modrm_reg_off(&ctxt->insn, ctxt->regs);
|
||||||
|
|
||||||
|
if (offset < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
offset /= sizeof(long);
|
||||||
|
|
||||||
|
return reg_array + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum es_result vc_do_mmio(struct ghcb *ghcb, struct es_em_ctxt *ctxt,
|
||||||
|
unsigned int bytes, bool read)
|
||||||
|
{
|
||||||
|
u64 exit_code, exit_info_1, exit_info_2;
|
||||||
|
unsigned long ghcb_pa = __pa(ghcb);
|
||||||
|
phys_addr_t paddr;
|
||||||
|
void __user *ref;
|
||||||
|
|
||||||
|
ref = insn_get_addr_ref(&ctxt->insn, ctxt->regs);
|
||||||
|
if (ref == (void __user *)-1L)
|
||||||
|
return ES_UNSUPPORTED;
|
||||||
|
|
||||||
|
exit_code = read ? SVM_VMGEXIT_MMIO_READ : SVM_VMGEXIT_MMIO_WRITE;
|
||||||
|
|
||||||
|
if (!vc_slow_virt_to_phys(ghcb, ctxt, (unsigned long)ref, &paddr)) {
|
||||||
|
if (!read)
|
||||||
|
ctxt->fi.error_code |= X86_PF_WRITE;
|
||||||
|
|
||||||
|
return ES_EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_info_1 = paddr;
|
||||||
|
/* Can never be greater than 8 */
|
||||||
|
exit_info_2 = bytes;
|
||||||
|
|
||||||
|
ghcb->save.sw_scratch = ghcb_pa + offsetof(struct ghcb, shared_buffer);
|
||||||
|
|
||||||
|
return sev_es_ghcb_hv_call(ghcb, ctxt, exit_code, exit_info_1, exit_info_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum es_result vc_handle_mmio_twobyte_ops(struct ghcb *ghcb,
|
||||||
|
struct es_em_ctxt *ctxt)
|
||||||
|
{
|
||||||
|
struct insn *insn = &ctxt->insn;
|
||||||
|
unsigned int bytes = 0;
|
||||||
|
enum es_result ret;
|
||||||
|
int sign_byte;
|
||||||
|
long *reg_data;
|
||||||
|
|
||||||
|
switch (insn->opcode.bytes[1]) {
|
||||||
|
/* MMIO Read w/ zero-extension */
|
||||||
|
case 0xb6:
|
||||||
|
bytes = 1;
|
||||||
|
fallthrough;
|
||||||
|
case 0xb7:
|
||||||
|
if (!bytes)
|
||||||
|
bytes = 2;
|
||||||
|
|
||||||
|
ret = vc_do_mmio(ghcb, ctxt, bytes, true);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Zero extend based on operand size */
|
||||||
|
reg_data = vc_insn_get_reg(ctxt);
|
||||||
|
if (!reg_data)
|
||||||
|
return ES_DECODE_FAILED;
|
||||||
|
|
||||||
|
memset(reg_data, 0, insn->opnd_bytes);
|
||||||
|
|
||||||
|
memcpy(reg_data, ghcb->shared_buffer, bytes);
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* MMIO Read w/ sign-extension */
|
||||||
|
case 0xbe:
|
||||||
|
bytes = 1;
|
||||||
|
fallthrough;
|
||||||
|
case 0xbf:
|
||||||
|
if (!bytes)
|
||||||
|
bytes = 2;
|
||||||
|
|
||||||
|
ret = vc_do_mmio(ghcb, ctxt, bytes, true);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Sign extend based on operand size */
|
||||||
|
reg_data = vc_insn_get_reg(ctxt);
|
||||||
|
if (!reg_data)
|
||||||
|
return ES_DECODE_FAILED;
|
||||||
|
|
||||||
|
if (bytes == 1) {
|
||||||
|
u8 *val = (u8 *)ghcb->shared_buffer;
|
||||||
|
|
||||||
|
sign_byte = (*val & 0x80) ? 0xff : 0x00;
|
||||||
|
} else {
|
||||||
|
u16 *val = (u16 *)ghcb->shared_buffer;
|
||||||
|
|
||||||
|
sign_byte = (*val & 0x8000) ? 0xff : 0x00;
|
||||||
|
}
|
||||||
|
memset(reg_data, sign_byte, insn->opnd_bytes);
|
||||||
|
|
||||||
|
memcpy(reg_data, ghcb->shared_buffer, bytes);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ret = ES_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum es_result vc_handle_mmio(struct ghcb *ghcb,
|
||||||
|
struct es_em_ctxt *ctxt)
|
||||||
|
{
|
||||||
|
struct insn *insn = &ctxt->insn;
|
||||||
|
unsigned int bytes = 0;
|
||||||
|
enum es_result ret;
|
||||||
|
long *reg_data;
|
||||||
|
|
||||||
|
switch (insn->opcode.bytes[0]) {
|
||||||
|
/* MMIO Write */
|
||||||
|
case 0x88:
|
||||||
|
bytes = 1;
|
||||||
|
fallthrough;
|
||||||
|
case 0x89:
|
||||||
|
if (!bytes)
|
||||||
|
bytes = insn->opnd_bytes;
|
||||||
|
|
||||||
|
reg_data = vc_insn_get_reg(ctxt);
|
||||||
|
if (!reg_data)
|
||||||
|
return ES_DECODE_FAILED;
|
||||||
|
|
||||||
|
memcpy(ghcb->shared_buffer, reg_data, bytes);
|
||||||
|
|
||||||
|
ret = vc_do_mmio(ghcb, ctxt, bytes, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xc6:
|
||||||
|
bytes = 1;
|
||||||
|
fallthrough;
|
||||||
|
case 0xc7:
|
||||||
|
if (!bytes)
|
||||||
|
bytes = insn->opnd_bytes;
|
||||||
|
|
||||||
|
memcpy(ghcb->shared_buffer, insn->immediate1.bytes, bytes);
|
||||||
|
|
||||||
|
ret = vc_do_mmio(ghcb, ctxt, bytes, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* MMIO Read */
|
||||||
|
case 0x8a:
|
||||||
|
bytes = 1;
|
||||||
|
fallthrough;
|
||||||
|
case 0x8b:
|
||||||
|
if (!bytes)
|
||||||
|
bytes = insn->opnd_bytes;
|
||||||
|
|
||||||
|
ret = vc_do_mmio(ghcb, ctxt, bytes, true);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
|
||||||
|
reg_data = vc_insn_get_reg(ctxt);
|
||||||
|
if (!reg_data)
|
||||||
|
return ES_DECODE_FAILED;
|
||||||
|
|
||||||
|
/* Zero-extend for 32-bit operation */
|
||||||
|
if (bytes == 4)
|
||||||
|
*reg_data = 0;
|
||||||
|
|
||||||
|
memcpy(reg_data, ghcb->shared_buffer, bytes);
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Two-Byte Opcodes */
|
||||||
|
case 0x0f:
|
||||||
|
ret = vc_handle_mmio_twobyte_ops(ghcb, ctxt);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = ES_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static enum es_result vc_handle_exitcode(struct es_em_ctxt *ctxt,
|
static enum es_result vc_handle_exitcode(struct es_em_ctxt *ctxt,
|
||||||
struct ghcb *ghcb,
|
struct ghcb *ghcb,
|
||||||
unsigned long exit_code)
|
unsigned long exit_code)
|
||||||
@ -460,6 +679,9 @@ static enum es_result vc_handle_exitcode(struct es_em_ctxt *ctxt,
|
|||||||
case SVM_EXIT_IOIO:
|
case SVM_EXIT_IOIO:
|
||||||
result = vc_handle_ioio(ghcb, ctxt);
|
result = vc_handle_ioio(ghcb, ctxt);
|
||||||
break;
|
break;
|
||||||
|
case SVM_EXIT_NPF:
|
||||||
|
result = vc_handle_mmio(ghcb, ctxt);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
/*
|
/*
|
||||||
* Unexpected #VC exception
|
* Unexpected #VC exception
|
||||||
|
Loading…
Reference in New Issue
Block a user