mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
The main use of {LD,ST}64B* is to talk to a device, which is hopefully
directly assigned to the guest and requires no additional handling.
However, this does not preclude a VMM from exposing a virtual device
to the guest, and to allow 64 byte accesses as part of the programming
interface. A direct consequence of this is that we need to be able
to forward such access to userspace.
Given that such a contraption is very unlikely to ever exist, we choose
to offer a limited service: userspace gets (as part of a new exit reason)
the ESR, the IPA, and that's it. It is fully expected to handle the full
semantics of the instructions, deal with ACCDATA, the return values and
increment PC. Much fun.
A canonical implementation can also simply inject an abort and be done
with it. Frankly, don't try to do anything else unless you have time
to waste.
Acked-by: Arnd Bergmann <arnd@arndb.de>
Acked-by: Oliver Upton <oupton@kernel.org>
Signed-off-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Yicong Yang <yangyicong@hisilicon.com>
Signed-off-by: Zhou Wang <wangzhou1@hisilicon.com>
Signed-off-by: Will Deacon <will@kernel.org>
260 lines
5.9 KiB
C
260 lines
5.9 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_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;
|
|
}
|
|
|
|
static bool kvm_pending_external_abort(struct kvm_vcpu *vcpu)
|
|
{
|
|
if (!vcpu_get_flag(vcpu, PENDING_EXCEPTION))
|
|
return false;
|
|
|
|
if (vcpu_el1_is_32bit(vcpu)) {
|
|
switch (vcpu_get_flag(vcpu, EXCEPT_MASK)) {
|
|
case unpack_vcpu_flag(EXCEPT_AA32_UND):
|
|
case unpack_vcpu_flag(EXCEPT_AA32_IABT):
|
|
case unpack_vcpu_flag(EXCEPT_AA32_DABT):
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
} else {
|
|
switch (vcpu_get_flag(vcpu, EXCEPT_MASK)) {
|
|
case unpack_vcpu_flag(EXCEPT_AA64_EL1_SYNC):
|
|
case unpack_vcpu_flag(EXCEPT_AA64_EL2_SYNC):
|
|
case unpack_vcpu_flag(EXCEPT_AA64_EL1_SERR):
|
|
case unpack_vcpu_flag(EXCEPT_AA64_EL2_SERR):
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* kvm_handle_mmio_return -- Handle MMIO loads after user space emulation
|
|
* or in-kernel IO emulation
|
|
*
|
|
* @vcpu: The VCPU pointer
|
|
*/
|
|
int kvm_handle_mmio_return(struct kvm_vcpu *vcpu)
|
|
{
|
|
unsigned long data;
|
|
unsigned int len;
|
|
int mask;
|
|
|
|
/*
|
|
* Detect if the MMIO return was already handled or if userspace aborted
|
|
* the MMIO access.
|
|
*/
|
|
if (unlikely(!vcpu->mmio_needed || kvm_pending_external_abort(vcpu)))
|
|
return 1;
|
|
|
|
vcpu->mmio_needed = 0;
|
|
|
|
if (!kvm_vcpu_dabt_iswrite(vcpu)) {
|
|
struct kvm_run *run = vcpu->run;
|
|
|
|
len = kvm_vcpu_dabt_get_as(vcpu);
|
|
data = kvm_mmio_read_buf(run->mmio.data, len);
|
|
|
|
if (kvm_vcpu_dabt_issext(vcpu) &&
|
|
len < sizeof(unsigned long)) {
|
|
mask = 1U << ((len * 8) - 1);
|
|
data = (data ^ mask) - mask;
|
|
}
|
|
|
|
if (!kvm_vcpu_dabt_issf(vcpu))
|
|
data = data & 0xffffffff;
|
|
|
|
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, kvm_vcpu_dabt_get_rd(vcpu), data);
|
|
}
|
|
|
|
/*
|
|
* The MMIO instruction is emulated and should not be re-executed
|
|
* in the guest.
|
|
*/
|
|
kvm_incr_pc(vcpu);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa)
|
|
{
|
|
struct kvm_run *run = vcpu->run;
|
|
unsigned long data;
|
|
unsigned long rt;
|
|
int ret;
|
|
bool is_write;
|
|
int len;
|
|
u8 data_buf[8];
|
|
u64 esr;
|
|
|
|
esr = kvm_vcpu_get_esr(vcpu);
|
|
|
|
/*
|
|
* No valid syndrome? Ask userspace for help if it has
|
|
* volunteered to do so, and bail out otherwise.
|
|
*
|
|
* In the protected VM case, there isn't much userspace can do
|
|
* though, so directly deliver an exception to the guest.
|
|
*/
|
|
if (!kvm_vcpu_dabt_isvalid(vcpu)) {
|
|
trace_kvm_mmio_nisv(*vcpu_pc(vcpu), esr,
|
|
kvm_vcpu_get_hfar(vcpu), fault_ipa);
|
|
|
|
if (vcpu_is_protected(vcpu))
|
|
return kvm_inject_sea_dabt(vcpu, kvm_vcpu_get_hfar(vcpu));
|
|
|
|
if (test_bit(KVM_ARCH_FLAG_RETURN_NISV_IO_ABORT_TO_USER,
|
|
&vcpu->kvm->arch.flags)) {
|
|
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;
|
|
}
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/*
|
|
* When (DFSC == 0b00xxxx || DFSC == 0b10101x) && DFSC != 0b0000xx
|
|
* ESR_EL2[12:11] describe the Load/Store Type. This allows us to
|
|
* punt the LD64B/ST64B/ST64BV/ST64BV0 instructions to userspace,
|
|
* which will have to provide a full emulation of these 4
|
|
* instructions. No, we don't expect this do be fast.
|
|
*
|
|
* We rely on traps being set if the corresponding features are not
|
|
* enabled, so if we get here, userspace has promised us to handle
|
|
* it already.
|
|
*/
|
|
switch (kvm_vcpu_trap_get_fault(vcpu)) {
|
|
case 0b000100 ... 0b001111:
|
|
case 0b101010 ... 0b101011:
|
|
if (FIELD_GET(GENMASK(12, 11), esr)) {
|
|
run->exit_reason = KVM_EXIT_ARM_LDST64B;
|
|
run->arm_nisv.esr_iss = esr & ~(u64)ESR_ELx_FSC;
|
|
run->arm_nisv.fault_ipa = fault_ipa;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
is_write = kvm_vcpu_dabt_iswrite(vcpu);
|
|
len = kvm_vcpu_dabt_get_as(vcpu);
|
|
rt = kvm_vcpu_dabt_get_rd(vcpu);
|
|
|
|
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);
|
|
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;
|
|
}
|