mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-02 08:34:20 +08:00
c726200dd1
For a long time, if a guest accessed memory outside of a memslot using any of the load/store instructions in the architecture which doesn't supply decoding information in the ESR_EL2 (the ISV bit is not set), the kernel would print the following message and terminate the VM as a result of returning -ENOSYS to userspace: load/store instruction decoding not implemented The reason behind this message is that KVM assumes that all accesses outside a memslot is an MMIO access which should be handled by userspace, and we originally expected to eventually implement some sort of decoding of load/store instructions where the ISV bit was not set. However, it turns out that many of the instructions which don't provide decoding information on abort are not safe to use for MMIO accesses, and the remaining few that would potentially make sense to use on MMIO accesses, such as those with register writeback, are not used in practice. It also turns out that fetching an instruction from guest memory can be a pretty horrible affair, involving stopping all CPUs on SMP systems, handling multiple corner cases of address translation in software, and more. It doesn't appear likely that we'll ever implement this in the kernel. What is much more common is that a user has misconfigured his/her guest and is actually not accessing an MMIO region, but just hitting some random hole in the IPA space. In this scenario, the error message above is almost misleading and has led to a great deal of confusion over the years. It is, nevertheless, ABI to userspace, and we therefore need to introduce a new capability that userspace explicitly enables to change behavior. This patch introduces KVM_CAP_ARM_NISV_TO_USER (NISV meaning Non-ISV) which does exactly that, and introduces a new exit reason to report the event to userspace. User space can then emulate an exception to the guest, restart the guest, suspend the guest, or take any other appropriate action as per the policy of the running system. Reported-by: Heinrich Schuchardt <xypron.glpk@gmx.de> Signed-off-by: Christoffer Dall <christoffer.dall@arm.com> Reviewed-by: Alexander Graf <graf@amazon.com> Signed-off-by: Marc Zyngier <maz@kernel.org>
221 lines
4.7 KiB
C
221 lines
4.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2012 - Virtual Open Systems and Columbia University
|
|
* Author: Christoffer Dall <c.dall@virtualopensystems.com>
|
|
*/
|
|
|
|
#include <linux/kvm_host.h>
|
|
#include <asm/kvm_mmio.h>
|
|
#include <asm/kvm_emulate.h>
|
|
#include <trace/events/kvm.h>
|
|
|
|
#include "trace.h"
|
|
|
|
void kvm_mmio_write_buf(void *buf, unsigned int len, unsigned long data)
|
|
{
|
|
void *datap = NULL;
|
|
union {
|
|
u8 byte;
|
|
u16 hword;
|
|
u32 word;
|
|
u64 dword;
|
|
} tmp;
|
|
|
|
switch (len) {
|
|
case 1:
|
|
tmp.byte = data;
|
|
datap = &tmp.byte;
|
|
break;
|
|
case 2:
|
|
tmp.hword = data;
|
|
datap = &tmp.hword;
|
|
break;
|
|
case 4:
|
|
tmp.word = data;
|
|
datap = &tmp.word;
|
|
break;
|
|
case 8:
|
|
tmp.dword = data;
|
|
datap = &tmp.dword;
|
|
break;
|
|
}
|
|
|
|
memcpy(buf, datap, len);
|
|
}
|
|
|
|
unsigned long kvm_mmio_read_buf(const void *buf, unsigned int len)
|
|
{
|
|
unsigned long data = 0;
|
|
union {
|
|
u16 hword;
|
|
u32 word;
|
|
u64 dword;
|
|
} tmp;
|
|
|
|
switch (len) {
|
|
case 1:
|
|
data = *(u8 *)buf;
|
|
break;
|
|
case 2:
|
|
memcpy(&tmp.hword, buf, len);
|
|
data = tmp.hword;
|
|
break;
|
|
case 4:
|
|
memcpy(&tmp.word, buf, len);
|
|
data = tmp.word;
|
|
break;
|
|
case 8:
|
|
memcpy(&tmp.dword, buf, len);
|
|
data = tmp.dword;
|
|
break;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* kvm_handle_mmio_return -- Handle MMIO loads after user space emulation
|
|
* or in-kernel IO emulation
|
|
*
|
|
* @vcpu: The VCPU pointer
|
|
* @run: The VCPU run struct containing the mmio data
|
|
*/
|
|
int kvm_handle_mmio_return(struct kvm_vcpu *vcpu, struct kvm_run *run)
|
|
{
|
|
unsigned long data;
|
|
unsigned int len;
|
|
int mask;
|
|
|
|
/* Detect an already handled MMIO return */
|
|
if (unlikely(!vcpu->mmio_needed))
|
|
return 0;
|
|
|
|
vcpu->mmio_needed = 0;
|
|
|
|
if (!run->mmio.is_write) {
|
|
len = run->mmio.len;
|
|
if (len > sizeof(unsigned long))
|
|
return -EINVAL;
|
|
|
|
data = kvm_mmio_read_buf(run->mmio.data, len);
|
|
|
|
if (vcpu->arch.mmio_decode.sign_extend &&
|
|
len < sizeof(unsigned long)) {
|
|
mask = 1U << ((len * 8) - 1);
|
|
data = (data ^ mask) - mask;
|
|
}
|
|
|
|
trace_kvm_mmio(KVM_TRACE_MMIO_READ, len, run->mmio.phys_addr,
|
|
&data);
|
|
data = vcpu_data_host_to_guest(vcpu, data, len);
|
|
vcpu_set_reg(vcpu, vcpu->arch.mmio_decode.rt, data);
|
|
}
|
|
|
|
/*
|
|
* The MMIO instruction is emulated and should not be re-executed
|
|
* in the guest.
|
|
*/
|
|
kvm_skip_instr(vcpu, kvm_vcpu_trap_il_is32bit(vcpu));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int decode_hsr(struct kvm_vcpu *vcpu, bool *is_write, int *len)
|
|
{
|
|
unsigned long rt;
|
|
int access_size;
|
|
bool sign_extend;
|
|
|
|
if (kvm_vcpu_dabt_iss1tw(vcpu)) {
|
|
/* page table accesses IO mem: tell guest to fix its TTBR */
|
|
kvm_inject_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
|
|
return 1;
|
|
}
|
|
|
|
access_size = kvm_vcpu_dabt_get_as(vcpu);
|
|
if (unlikely(access_size < 0))
|
|
return access_size;
|
|
|
|
*is_write = kvm_vcpu_dabt_iswrite(vcpu);
|
|
sign_extend = kvm_vcpu_dabt_issext(vcpu);
|
|
rt = kvm_vcpu_dabt_get_rd(vcpu);
|
|
|
|
*len = access_size;
|
|
vcpu->arch.mmio_decode.sign_extend = sign_extend;
|
|
vcpu->arch.mmio_decode.rt = rt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int io_mem_abort(struct kvm_vcpu *vcpu, struct kvm_run *run,
|
|
phys_addr_t fault_ipa)
|
|
{
|
|
unsigned long data;
|
|
unsigned long rt;
|
|
int ret;
|
|
bool is_write;
|
|
int len;
|
|
u8 data_buf[8];
|
|
|
|
/*
|
|
* Prepare MMIO operation. First decode the syndrome data we get
|
|
* from the CPU. Then try if some in-kernel emulation feels
|
|
* responsible, otherwise let user space do its magic.
|
|
*/
|
|
if (kvm_vcpu_dabt_isvalid(vcpu)) {
|
|
ret = decode_hsr(vcpu, &is_write, &len);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
if (vcpu->kvm->arch.return_nisv_io_abort_to_user) {
|
|
run->exit_reason = KVM_EXIT_ARM_NISV;
|
|
run->arm_nisv.esr_iss = kvm_vcpu_dabt_iss_nisv_sanitized(vcpu);
|
|
run->arm_nisv.fault_ipa = fault_ipa;
|
|
return 0;
|
|
}
|
|
|
|
kvm_pr_unimpl("Data abort outside memslots with no valid syndrome info\n");
|
|
return -ENOSYS;
|
|
}
|
|
|
|
rt = vcpu->arch.mmio_decode.rt;
|
|
|
|
if (is_write) {
|
|
data = vcpu_data_guest_to_host(vcpu, vcpu_get_reg(vcpu, rt),
|
|
len);
|
|
|
|
trace_kvm_mmio(KVM_TRACE_MMIO_WRITE, len, fault_ipa, &data);
|
|
kvm_mmio_write_buf(data_buf, len, data);
|
|
|
|
ret = kvm_io_bus_write(vcpu, KVM_MMIO_BUS, fault_ipa, len,
|
|
data_buf);
|
|
} else {
|
|
trace_kvm_mmio(KVM_TRACE_MMIO_READ_UNSATISFIED, len,
|
|
fault_ipa, NULL);
|
|
|
|
ret = kvm_io_bus_read(vcpu, KVM_MMIO_BUS, fault_ipa, len,
|
|
data_buf);
|
|
}
|
|
|
|
/* Now prepare kvm_run for the potential return to userland. */
|
|
run->mmio.is_write = is_write;
|
|
run->mmio.phys_addr = fault_ipa;
|
|
run->mmio.len = len;
|
|
vcpu->mmio_needed = 1;
|
|
|
|
if (!ret) {
|
|
/* We handled the access successfully in the kernel. */
|
|
if (!is_write)
|
|
memcpy(run->mmio.data, data_buf, len);
|
|
vcpu->stat.mmio_exit_kernel++;
|
|
kvm_handle_mmio_return(vcpu, run);
|
|
return 1;
|
|
}
|
|
|
|
if (is_write)
|
|
memcpy(run->mmio.data, data_buf, len);
|
|
vcpu->stat.mmio_exit_user++;
|
|
run->exit_reason = KVM_EXIT_MMIO;
|
|
return 0;
|
|
}
|