blob: 22f5b9fe574c4682aad24e5cd54d60a03b12e4db [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/cpufeature.h>
#include <asm/insn.h>
#include <asm/insn-eval.h>
#include <asm/virt_exception.h>
struct ve_x86_ops ve_x86_ops;
int ve_handle_mmio(struct pt_regs *regs, struct ve_info *ve)
{
unsigned long *reg, val, vaddr;
char buffer[MAX_INSN_SIZE];
enum insn_mmio_type mmio;
struct insn insn = {};
int size, extend_size;
u8 extend_val = 0;
/* Only in-kernel MMIO is supported */
if (WARN_ON_ONCE(user_mode(regs)))
return -EFAULT;
if (copy_from_kernel_nofault(buffer, (void *)regs->ip, MAX_INSN_SIZE))
return -EFAULT;
if (insn_decode(&insn, buffer, MAX_INSN_SIZE, INSN_MODE_64))
return -EINVAL;
mmio = insn_decode_mmio(&insn, &size);
if (WARN_ON_ONCE(mmio == INSN_MMIO_DECODE_FAILED))
return -EINVAL;
if (mmio != INSN_MMIO_WRITE_IMM && mmio != INSN_MMIO_MOVS) {
reg = insn_get_modrm_reg_ptr(&insn, regs);
if (!reg)
return -EINVAL;
}
/*
* Reject EPT violation #VEs that split pages.
*
* MMIO accesses are supposed to be naturally aligned and therefore
* never cross page boundaries. Seeing split page accesses indicates
* a bug or a load_unaligned_zeropad() that stepped into an MMIO page.
*
* load_unaligned_zeropad() will recover using exception fixups.
*/
vaddr = (unsigned long)insn_get_addr_ref(&insn, regs);
if (vaddr / PAGE_SIZE != (vaddr + size - 1) / PAGE_SIZE)
return -EFAULT;
/* Handle writes first */
switch (mmio) {
case INSN_MMIO_WRITE:
memcpy(&val, reg, size);
if (!ve_x86_ops.mmio_write(size, ve->gpa, val))
return -EIO;
return insn.length;
case INSN_MMIO_WRITE_IMM:
val = insn.immediate.value;
if (!ve_x86_ops.mmio_write(size, ve->gpa, val))
return -EIO;
return insn.length;
case INSN_MMIO_READ:
case INSN_MMIO_READ_ZERO_EXTEND:
case INSN_MMIO_READ_SIGN_EXTEND:
/* Reads are handled below */
break;
case INSN_MMIO_MOVS:
case INSN_MMIO_DECODE_FAILED:
/*
* MMIO was accessed with an instruction that could not be
* decoded or handled properly. It was likely not using io.h
* helpers or accessed MMIO accidentally.
*/
return -EINVAL;
default:
WARN_ONCE(1, "Unknown insn_decode_mmio() decode value?");
return -EINVAL;
}
/* Handle reads */
if (!ve_x86_ops.mmio_read(size, ve->gpa, &val))
return -EIO;
switch (mmio) {
case INSN_MMIO_READ:
/* Zero-extend for 32-bit operation */
extend_size = size == 4 ? sizeof(*reg) : 0;
break;
case INSN_MMIO_READ_ZERO_EXTEND:
/* Zero extend based on operand size */
extend_size = insn.opnd_bytes;
break;
case INSN_MMIO_READ_SIGN_EXTEND:
/* Sign extend based on operand size */
extend_size = insn.opnd_bytes;
if (size == 1 && val & BIT(7))
extend_val = 0xFF;
else if (size > 1 && val & BIT(15))
extend_val = 0xFF;
break;
default:
/* All other cases has to be covered with the first switch() */
WARN_ON_ONCE(1);
return -EINVAL;
}
if (extend_size)
memset(reg, extend_val, extend_size);
memcpy(reg, &val, size);
return insn.length;
}
bool handle_virt_exception(struct pt_regs *regs, struct ve_info *ve)
{
if (ve_x86_ops.handle_virt_exception)
return ve_x86_ops.handle_virt_exception(regs, ve);
return false;
}
void get_ve_info(struct ve_info *ve)
{
if (ve_x86_ops.get_ve_info)
ve_x86_ops.get_ve_info(ve);
}