| // 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); |
| } |