mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 01:32:21 +02:00
KVM/arm64 fixes for 7.2, take #2
- Move locking for kvm_io_bus_get_dev() into the caller, ensuring race-free checks that the returned object is of the correct type - Fix initialisation of the page-table walk level when relaxing permissions - Correctly update the XN attribute when relaxing permissions - Fix the sign extension of loads from emulated MMIO regions - Assorted collection of fixes for pKVM's FFA proxy, together with a couple of FFA driver adjustments -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEn9UcU+C1Yxj9lZw9I9DQutE9ekMFAmpOLOIACgkQI9DQutE9 ekPH5Q//VrbqIVfJekxRoI0B8zWpnG52q9ILdZBEhTL+xHEP+Z8WawzbpfHzv9LQ tt7rUk8gmkGBvMXdY3fZaLkaQRDGvg51SKclxIaoNdmrav7Sscz/sar7P8QWUuIb d0EQ3hqV+2hvNFqUVy0cTAFoog0U00KDpdBV136ePTYgT7/N0B8fiFStdHzVP80H 0WbeWnB0LB15mnkCkO0QY4QmEozfnjKYPrhv52fZFSmnc0BLY3muSR0y+lZ1FtN0 c2aUiYH/iyIkmmpEZyMYzzhwnMkPwwTIof1OgQ93//JUPkmEUtun5SNQ9YaQw/cd /IKDo5xhEn/3jH6Zo2th8oKhH4q5mSOxBd8WH/hkwnTpZYbUZ5Q0647QHHEtWWEp mEdQFroaORrPXtHfKSeltkr5JKWK9vweARwbTcLmNwbGfLqZGgJe8/FgOw8Q3lXB wZcmNc5TqGe4QWiatH7qKTdqulWdQMrRE2XRAinfrfOfZMg69cnNizWAdNPVhPfw B5yewkPrPwZNNXGksZOXfbFN7TnIM2M8j7uwXl5D3rGi/ebDp5ByuVKkP5ZNLnz3 D666cORhX1zdCfqbLx4mhPs+vcqmL8HHMASlaJg+hH1se/veLbkEZ9GIrjw8Y0iO SMUJvwJ5Z2SZpvCKGsC85Wdm/pIBLLh5ISIW8wDIIw+7BoMrXFY= =RmEk -----END PGP SIGNATURE----- Merge tag 'kvmarm-fixes-7.2-2' of git://git.kernel.org/pub/scm/linux/kernel/git/kvmarm/kvmarm into HEAD KVM/arm64 fixes for 7.2, take #2 - Move locking for kvm_io_bus_get_dev() into the caller, ensuring race-free checks that the returned object is of the correct type - Fix initialisation of the page-table walk level when relaxing permissions - Correctly update the XN attribute when relaxing permissions - Fix the sign extension of loads from emulated MMIO regions - Assorted collection of fixes for pKVM's FFA proxy, together with a couple of FFA driver adjustments
This commit is contained in:
commit
15f8ba7806
|
|
@ -352,7 +352,7 @@ static u32 __ffa_host_share_ranges(struct ffa_mem_region_addr_range *ranges,
|
|||
u64 sz = (u64)range->pg_cnt * FFA_PAGE_SIZE;
|
||||
u64 pfn = hyp_phys_to_pfn(range->address);
|
||||
|
||||
if (!PAGE_ALIGNED(sz))
|
||||
if (!PAGE_ALIGNED(sz | range->address))
|
||||
break;
|
||||
|
||||
if (__pkvm_host_share_ffa(pfn, sz / PAGE_SIZE))
|
||||
|
|
@ -372,7 +372,7 @@ static u32 __ffa_host_unshare_ranges(struct ffa_mem_region_addr_range *ranges,
|
|||
u64 sz = (u64)range->pg_cnt * FFA_PAGE_SIZE;
|
||||
u64 pfn = hyp_phys_to_pfn(range->address);
|
||||
|
||||
if (!PAGE_ALIGNED(sz))
|
||||
if (!PAGE_ALIGNED(sz | range->address))
|
||||
break;
|
||||
|
||||
if (__pkvm_host_unshare_ffa(pfn, sz / PAGE_SIZE))
|
||||
|
|
@ -476,11 +476,12 @@ static void __do_ffa_mem_xfer(const u64 func_id,
|
|||
DECLARE_REG(u32, fraglen, ctxt, 2);
|
||||
DECLARE_REG(u64, addr_mbz, ctxt, 3);
|
||||
DECLARE_REG(u32, npages_mbz, ctxt, 4);
|
||||
u32 offset, nr_ranges, checked_offset, em_mem_access_off;
|
||||
struct ffa_mem_region_attributes *ep_mem_access;
|
||||
struct ffa_composite_mem_region *reg;
|
||||
struct ffa_mem_region *buf;
|
||||
u32 offset, nr_ranges, checked_offset;
|
||||
int ret = 0;
|
||||
size_t mem_region_len = FFA_MEM_REGION_SZ(hyp_ffa_version);
|
||||
|
||||
if (addr_mbz || npages_mbz || fraglen > len ||
|
||||
fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) {
|
||||
|
|
@ -488,8 +489,7 @@ static void __do_ffa_mem_xfer(const u64 func_id,
|
|||
goto out;
|
||||
}
|
||||
|
||||
if (fraglen < sizeof(struct ffa_mem_region) +
|
||||
sizeof(struct ffa_mem_region_attributes)) {
|
||||
if (fraglen < mem_region_len + ffa_emad_size_get(hyp_ffa_version)) {
|
||||
ret = FFA_RET_INVALID_PARAMETERS;
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -508,8 +508,13 @@ static void __do_ffa_mem_xfer(const u64 func_id,
|
|||
buf = hyp_buffers.tx;
|
||||
memcpy(buf, host_buffers.tx, fraglen);
|
||||
|
||||
ep_mem_access = (void *)buf +
|
||||
ffa_mem_desc_offset(buf, 0, hyp_ffa_version);
|
||||
em_mem_access_off = ffa_mem_desc_offset(buf, 0, hyp_ffa_version);
|
||||
if ((u64)em_mem_access_off + ffa_emad_size_get(hyp_ffa_version) > fraglen) {
|
||||
ret = FFA_RET_INVALID_PARAMETERS;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
ep_mem_access = (void *)buf + em_mem_access_off;
|
||||
offset = ep_mem_access->composite_off;
|
||||
if (!offset || buf->ep_count != 1 || buf->sender_id != HOST_FFA_ID) {
|
||||
ret = FFA_RET_INVALID_PARAMETERS;
|
||||
|
|
@ -574,9 +579,9 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res,
|
|||
DECLARE_REG(u32, handle_lo, ctxt, 1);
|
||||
DECLARE_REG(u32, handle_hi, ctxt, 2);
|
||||
DECLARE_REG(u32, flags, ctxt, 3);
|
||||
u32 offset, len, fraglen, fragoff, em_mem_access_off;
|
||||
struct ffa_mem_region_attributes *ep_mem_access;
|
||||
struct ffa_composite_mem_region *reg;
|
||||
u32 offset, len, fraglen, fragoff;
|
||||
struct ffa_mem_region *buf;
|
||||
int ret = 0;
|
||||
u64 handle;
|
||||
|
|
@ -599,16 +604,22 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res,
|
|||
len = res->a1;
|
||||
fraglen = res->a2;
|
||||
|
||||
ep_mem_access = (void *)buf +
|
||||
ffa_mem_desc_offset(buf, 0, hyp_ffa_version);
|
||||
em_mem_access_off = ffa_mem_desc_offset(buf, 0, hyp_ffa_version);
|
||||
if ((u64)em_mem_access_off + ffa_emad_size_get(hyp_ffa_version) > fraglen) {
|
||||
ret = FFA_RET_INVALID_PARAMETERS;
|
||||
ffa_rx_release(res);
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
ep_mem_access = (void *)buf + em_mem_access_off;
|
||||
offset = ep_mem_access->composite_off;
|
||||
/*
|
||||
* We can trust the SPMD to get this right, but let's at least
|
||||
* check that we end up with something that doesn't look _completely_
|
||||
* bogus.
|
||||
*/
|
||||
if (WARN_ON(offset > len ||
|
||||
fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE)) {
|
||||
if (offset + CONSTITUENTS_OFFSET(0) > len ||
|
||||
fraglen > KVM_FFA_MBOX_NR_PAGES * PAGE_SIZE) {
|
||||
ret = FFA_RET_ABORTED;
|
||||
ffa_rx_release(res);
|
||||
goto out_unlock;
|
||||
|
|
@ -636,11 +647,16 @@ static void do_ffa_mem_reclaim(struct arm_smccc_1_2_regs *res,
|
|||
ffa_rx_release(res);
|
||||
}
|
||||
|
||||
reg = (void *)buf + offset;
|
||||
if (offset + CONSTITUENTS_OFFSET(reg->addr_range_cnt) > len) {
|
||||
ret = FFA_RET_ABORTED;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
ffa_mem_reclaim(res, handle_lo, handle_hi, flags);
|
||||
if (res->a0 != FFA_SUCCESS)
|
||||
goto out_unlock;
|
||||
|
||||
reg = (void *)buf + offset;
|
||||
/* If the SPMD was happy, then we should be too. */
|
||||
WARN_ON(ffa_host_unshare_ranges(reg->constituents,
|
||||
reg->addr_range_cnt));
|
||||
|
|
@ -864,7 +880,7 @@ static void do_ffa_part_get(struct arm_smccc_1_2_regs *res,
|
|||
|
||||
bool kvm_host_ffa_handler(struct kvm_cpu_context *host_ctxt, u32 func_id)
|
||||
{
|
||||
struct arm_smccc_1_2_regs res;
|
||||
struct arm_smccc_1_2_regs res = {0};
|
||||
|
||||
/*
|
||||
* There's no way we can tell what a non-standard SMC call might
|
||||
|
|
|
|||
|
|
@ -1370,16 +1370,19 @@ int kvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr,
|
|||
if (prot & KVM_PGTABLE_PROT_W)
|
||||
set |= KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W;
|
||||
|
||||
ret = stage2_set_xn_attr(prot, &xn);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (prot & KVM_PGTABLE_PROT_X) {
|
||||
ret = stage2_set_xn_attr(prot, &xn);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
set |= xn & KVM_PTE_LEAF_ATTR_HI_S2_XN;
|
||||
clr |= ~xn & KVM_PTE_LEAF_ATTR_HI_S2_XN;
|
||||
set |= xn & KVM_PTE_LEAF_ATTR_HI_S2_XN;
|
||||
clr |= ~xn & KVM_PTE_LEAF_ATTR_HI_S2_XN;
|
||||
}
|
||||
|
||||
ret = stage2_update_leaf_attrs(pgt, addr, 1, set, clr, NULL, &level, flags);
|
||||
if (!ret || ret == -EAGAIN)
|
||||
kvm_call_hyp(__kvm_tlb_flush_vmid_ipa_nsh, pgt->mmu, addr, level);
|
||||
kvm_call_hyp(__kvm_tlb_flush_vmid_ipa_nsh, pgt->mmu, addr,
|
||||
(ret == -EAGAIN) ? TLBI_TTL_UNKNOWN : level);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,10 @@ int kvm_handle_mmio_return(struct kvm_vcpu *vcpu)
|
|||
len = kvm_vcpu_dabt_get_as(vcpu);
|
||||
data = kvm_mmio_read_buf(run->mmio.data, len);
|
||||
|
||||
trace_kvm_mmio(KVM_TRACE_MMIO_READ, len, run->mmio.phys_addr,
|
||||
&data);
|
||||
data = vcpu_data_host_to_guest(vcpu, data, len);
|
||||
|
||||
if (kvm_vcpu_dabt_issext(vcpu) &&
|
||||
len < sizeof(unsigned long)) {
|
||||
mask = 1U << ((len * 8) - 1);
|
||||
|
|
@ -135,9 +139,6 @@ int kvm_handle_mmio_return(struct kvm_vcpu *vcpu)
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -508,6 +508,8 @@ static struct vgic_its *__vgic_doorbell_to_its(struct kvm *kvm, gpa_t db)
|
|||
struct kvm_io_device *kvm_io_dev;
|
||||
struct vgic_io_device *iodev;
|
||||
|
||||
guard(srcu)(&kvm->srcu);
|
||||
|
||||
kvm_io_dev = kvm_io_bus_get_dev(kvm, KVM_MMIO_BUS, db);
|
||||
if (!kvm_io_dev)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
|
|
|||
|
|
@ -713,30 +713,39 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize,
|
|||
struct ffa_composite_mem_region *composite;
|
||||
struct ffa_mem_region_addr_range *constituents;
|
||||
struct ffa_mem_region_attributes *ep_mem_access;
|
||||
u32 idx, frag_len, length, buf_sz = 0, num_entries = sg_nents(args->sg);
|
||||
u32 idx, frag_len, length, buf_sz = 0, num_entries = sg_nents(args->sg), ep_offset;
|
||||
u32 emad_end, emad_size = ffa_emad_size_get(drv_info->version);
|
||||
|
||||
mem_region->tag = args->tag;
|
||||
mem_region->flags = args->flags;
|
||||
mem_region->sender_id = drv_info->vm_id;
|
||||
mem_region->attributes = ffa_memory_attributes_get(func_id);
|
||||
|
||||
ffa_mem_region_additional_setup(drv_info->version, mem_region);
|
||||
composite_offset = ffa_mem_desc_offset(buffer, args->nattrs,
|
||||
drv_info->version);
|
||||
if (composite_offset + sizeof(*composite) > max_fragsize)
|
||||
return -ENXIO;
|
||||
|
||||
for (idx = 0; idx < args->nattrs; idx++) {
|
||||
ep_mem_access = buffer +
|
||||
ffa_mem_desc_offset(buffer, idx, drv_info->version);
|
||||
ep_offset = ffa_mem_desc_offset(buffer, idx, drv_info->version);
|
||||
if (check_add_overflow(ep_offset, emad_size, &emad_end))
|
||||
return -ENXIO;
|
||||
|
||||
if (emad_end > max_fragsize)
|
||||
return -ENXIO;
|
||||
|
||||
ep_mem_access = buffer + ep_offset;
|
||||
memset(ep_mem_access, 0, emad_size);
|
||||
ep_mem_access->receiver = args->attrs[idx].receiver;
|
||||
ep_mem_access->attrs = args->attrs[idx].attrs;
|
||||
ep_mem_access->composite_off = composite_offset;
|
||||
ep_mem_access->flag = 0;
|
||||
ep_mem_access->reserved = 0;
|
||||
ffa_emad_impdef_value_init(drv_info->version,
|
||||
ep_mem_access->impdef_val,
|
||||
args->attrs[idx].impdef_val);
|
||||
}
|
||||
mem_region->handle = 0;
|
||||
mem_region->ep_count = args->nattrs;
|
||||
ffa_mem_region_additional_setup(drv_info->version, mem_region);
|
||||
|
||||
composite = buffer + composite_offset;
|
||||
composite->total_pg_cnt = ffa_get_num_pages_sg(args->sg);
|
||||
|
|
@ -769,7 +778,7 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize,
|
|||
constituents = buffer;
|
||||
}
|
||||
|
||||
if ((void *)constituents - buffer > max_fragsize) {
|
||||
if ((void *)constituents + sizeof(*constituents) - buffer > max_fragsize) {
|
||||
pr_err("Memory Region Fragment > Tx Buffer size\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
|
@ -778,7 +787,7 @@ ffa_setup_and_transmit(u32 func_id, void *buffer, u32 max_fragsize,
|
|||
constituents->pg_cnt = args->sg->length / FFA_PAGE_SIZE;
|
||||
constituents->reserved = 0;
|
||||
constituents++;
|
||||
frag_len += sizeof(struct ffa_mem_region_addr_range);
|
||||
frag_len += sizeof(*constituents);
|
||||
} while ((args->sg = sg_next(args->sg)));
|
||||
|
||||
return ffa_transmit_fragment(func_id, addr, buf_sz, frag_len,
|
||||
|
|
|
|||
|
|
@ -421,6 +421,13 @@ struct ffa_mem_region {
|
|||
#define FFA_EMAD_HAS_IMPDEF_FIELD(version) ((version) >= FFA_VERSION_1_2)
|
||||
#define FFA_MEM_REGION_HAS_EP_MEM_OFFSET(version) ((version) > FFA_VERSION_1_0)
|
||||
|
||||
/* The layout changed from FFA_VERSION_1_0 and the region includes an
|
||||
* ep_mem_offset.
|
||||
*/
|
||||
#define FFA_MEM_REGION_SZ(version) (!FFA_MEM_REGION_HAS_EP_MEM_OFFSET((version)) ?\
|
||||
offsetof(struct ffa_mem_region, ep_mem_offset) :\
|
||||
sizeof(struct ffa_mem_region))
|
||||
|
||||
static inline u32 ffa_emad_size_get(u32 ffa_version)
|
||||
{
|
||||
u32 sz;
|
||||
|
|
@ -445,7 +452,7 @@ ffa_mem_desc_offset(struct ffa_mem_region *buf, int count, u32 ffa_version)
|
|||
if (!FFA_MEM_REGION_HAS_EP_MEM_OFFSET(ffa_version))
|
||||
offset += offsetof(struct ffa_mem_region, ep_mem_offset);
|
||||
else
|
||||
offset += sizeof(struct ffa_mem_region);
|
||||
offset += buf->ep_mem_offset;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ TEST_GEN_PROGS_arm64 += arm64/hello_el2
|
|||
TEST_GEN_PROGS_arm64 += arm64/host_sve
|
||||
TEST_GEN_PROGS_arm64 += arm64/hypercalls
|
||||
TEST_GEN_PROGS_arm64 += arm64/external_aborts
|
||||
TEST_GEN_PROGS_arm64 += arm64/mmio_sign_ext
|
||||
TEST_GEN_PROGS_arm64 += arm64/page_fault_test
|
||||
TEST_GEN_PROGS_arm64 += arm64/psci_test
|
||||
TEST_GEN_PROGS_arm64 += arm64/sea_to_user
|
||||
|
|
|
|||
255
tools/testing/selftests/kvm/arm64/mmio_sign_ext.c
Normal file
255
tools/testing/selftests/kvm/arm64/mmio_sign_ext.c
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* mmio_sign_ext - Test sign-extending MMIO load emulation (LDRSB/LDRSH/LDRSW)
|
||||
*
|
||||
* Copyright (c) 2026 Google LLC
|
||||
* Author: Fuad Tabba <fuad.tabba@linux.dev>
|
||||
*/
|
||||
|
||||
#include <asm/ptrace.h>
|
||||
|
||||
#include "processor.h"
|
||||
#include "test_util.h"
|
||||
|
||||
#define MMIO_ADDR 0x8000000ULL
|
||||
|
||||
/* AP[1]: allow unprivileged (EL0) access to a mapping. */
|
||||
#define PTE_USER BIT(6)
|
||||
|
||||
/* SPSR for ERET to EL0t with DAIF masked. */
|
||||
#define SPSR_EL0 (PSR_MODE_EL0t | PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT)
|
||||
|
||||
struct mmio_test {
|
||||
const char *name;
|
||||
uint64_t data; /* access-width value, host byte order */
|
||||
uint8_t len;
|
||||
uint64_t expected; /* sign-extended result; same for LE and BE */
|
||||
};
|
||||
|
||||
/* Paired 1:1, in order, with the loads in guest_loads_le() and el0_be_loads. */
|
||||
static const struct mmio_test tests[] = {
|
||||
/* LDRSB Xt: byte sign-extended to 64 bits */
|
||||
{ "LDRSB Xt 0xFF", 0xFF, 1, 0xFFFFFFFFFFFFFFFFULL },
|
||||
{ "LDRSB Xt 0x7F", 0x7F, 1, 0x7FULL },
|
||||
|
||||
/* LDRSB Wt: byte sign-extended to 32 bits, upper 32 bits zeroed */
|
||||
{ "LDRSB Wt 0xFF", 0xFF, 1, 0xFFFFFFFFULL },
|
||||
{ "LDRSB Wt 0x7F", 0x7F, 1, 0x7FULL },
|
||||
|
||||
/* LDRSH Xt: halfword sign-extended to 64 bits */
|
||||
{ "LDRSH Xt 0x8001", 0x8001, 2, 0xFFFFFFFFFFFF8001ULL },
|
||||
{ "LDRSH Xt 0x7FFF", 0x7FFF, 2, 0x7FFFULL },
|
||||
|
||||
/* LDRSH Wt: halfword sign-extended to 32 bits, upper 32 bits zeroed */
|
||||
{ "LDRSH Wt 0x8001", 0x8001, 2, 0xFFFF8001ULL },
|
||||
{ "LDRSH Wt 0x7FFF", 0x7FFF, 2, 0x7FFFULL },
|
||||
|
||||
/* LDRSW Xt: word sign-extended to 64 bits (no Wt form) */
|
||||
{ "LDRSW Xt 0x80000001", 0x80000001, 4, 0xFFFFFFFF80000001ULL },
|
||||
{ "LDRSW Xt 0x7FFFFFFF", 0x7FFFFFFF, 4, 0x7FFFFFFFULL },
|
||||
};
|
||||
|
||||
/* Issue one sign-extending load from MMIO and report the result. */
|
||||
#define GUEST_LDRS(load) do { \
|
||||
uint64_t val; \
|
||||
\
|
||||
asm volatile(load : "=r"(val) : "r"(MMIO_ADDR) : "memory"); \
|
||||
GUEST_SYNC(val); \
|
||||
} while (0)
|
||||
|
||||
/* Little-endian pass: loads issued at EL1. */
|
||||
static void guest_loads_le(void)
|
||||
{
|
||||
GUEST_LDRS("ldrsb %0, [%1]");
|
||||
GUEST_LDRS("ldrsb %0, [%1]");
|
||||
GUEST_LDRS("ldrsb %w0, [%1]");
|
||||
GUEST_LDRS("ldrsb %w0, [%1]");
|
||||
GUEST_LDRS("ldrsh %0, [%1]");
|
||||
GUEST_LDRS("ldrsh %0, [%1]");
|
||||
GUEST_LDRS("ldrsh %w0, [%1]");
|
||||
GUEST_LDRS("ldrsh %w0, [%1]");
|
||||
GUEST_LDRS("ldrsw %0, [%1]");
|
||||
GUEST_LDRS("ldrsw %0, [%1]");
|
||||
}
|
||||
|
||||
/*
|
||||
* Run the big-endian loads at EL0, where SCTLR_EL1.E0E flips only the data
|
||||
* endianness; at EL1, SCTLR_EL1.EE would also flip the page-table walk and
|
||||
* fault on the little-endian tables. x0 holds MMIO_ADDR; results return in
|
||||
* x19..x28 (tests[] order) via a single SVC.
|
||||
*/
|
||||
extern char el0_be_loads[];
|
||||
asm(
|
||||
" .pushsection .text, \"ax\"\n"
|
||||
" .global el0_be_loads\n"
|
||||
"el0_be_loads:\n"
|
||||
" ldrsb x19, [x0]\n"
|
||||
" ldrsb x20, [x0]\n"
|
||||
" ldrsb w21, [x0]\n"
|
||||
" ldrsb w22, [x0]\n"
|
||||
" ldrsh x23, [x0]\n"
|
||||
" ldrsh x24, [x0]\n"
|
||||
" ldrsh w25, [x0]\n"
|
||||
" ldrsh w26, [x0]\n"
|
||||
" ldrsw x27, [x0]\n"
|
||||
" ldrsw x28, [x0]\n"
|
||||
" svc #0\n"
|
||||
" .popsection\n"
|
||||
);
|
||||
|
||||
/* EL1 handler for the EL0 SVC: report the results, then finish. */
|
||||
static void el0_svc_handler(struct ex_regs *regs)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++)
|
||||
GUEST_SYNC(regs->regs[19 + i]);
|
||||
|
||||
GUEST_DONE();
|
||||
}
|
||||
|
||||
static bool guest_mixed_endian_el0(void)
|
||||
{
|
||||
uint64_t mmfr0 = read_sysreg(id_aa64mmfr0_el1);
|
||||
|
||||
return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) ||
|
||||
SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0);
|
||||
}
|
||||
|
||||
static void guest_code(void)
|
||||
{
|
||||
guest_loads_le();
|
||||
|
||||
if (guest_mixed_endian_el0()) {
|
||||
write_sysreg(read_sysreg(sctlr_el1) | SCTLR_EL1_E0E, sctlr_el1);
|
||||
isb();
|
||||
|
||||
asm volatile(
|
||||
" msr elr_el1, %[pc]\n"
|
||||
" msr spsr_el1, %[spsr]\n"
|
||||
" mov x0, %[mmio]\n"
|
||||
" isb\n"
|
||||
" eret\n"
|
||||
:
|
||||
: [pc] "r"(el0_be_loads),
|
||||
[spsr] "r"((uint64_t)SPSR_EL0),
|
||||
[mmio] "r"(MMIO_ADDR)
|
||||
: "x0", "memory");
|
||||
__builtin_unreachable(); /* el0_svc_handler ends the test */
|
||||
}
|
||||
|
||||
GUEST_DONE();
|
||||
}
|
||||
|
||||
static void handle_mmio(struct kvm_run *run, const struct mmio_test *t, bool be)
|
||||
{
|
||||
int i;
|
||||
|
||||
TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR);
|
||||
TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read for %s", t->name);
|
||||
TEST_ASSERT_EQ(run->mmio.len, t->len);
|
||||
|
||||
memset(run->mmio.data, 0, sizeof(run->mmio.data));
|
||||
if (be) {
|
||||
/* The guest reads the device bytes most-significant first. */
|
||||
for (i = 0; i < t->len; i++)
|
||||
run->mmio.data[i] = t->data >> (8 * (t->len - 1 - i));
|
||||
} else {
|
||||
/* Works because arm64 KVM hosts are always little-endian. */
|
||||
memcpy(run->mmio.data, &t->data, t->len);
|
||||
}
|
||||
}
|
||||
|
||||
static void expect_sync(struct kvm_vcpu *vcpu, struct ucall *uc,
|
||||
const struct mmio_test *t)
|
||||
{
|
||||
switch (get_ucall(vcpu, uc)) {
|
||||
case UCALL_SYNC:
|
||||
TEST_ASSERT(uc->args[1] == t->expected,
|
||||
"%s: got %#lx, want %#lx", t->name,
|
||||
(unsigned long)uc->args[1], (unsigned long)t->expected);
|
||||
break;
|
||||
case UCALL_ABORT:
|
||||
REPORT_GUEST_ASSERT(*uc);
|
||||
break;
|
||||
default:
|
||||
TEST_FAIL("Unexpected ucall for %s", t->name);
|
||||
}
|
||||
}
|
||||
|
||||
/* OR PTE_USER into the leaf descriptors covering [gva, gva + len). */
|
||||
static void make_el0_accessible(struct kvm_vm *vm, uint64_t gva, uint64_t len)
|
||||
{
|
||||
uint64_t addr;
|
||||
|
||||
for (addr = gva & ~((uint64_t)vm->page_size - 1); addr < gva + len;
|
||||
addr += vm->page_size)
|
||||
*virt_get_pte_hva(vm, addr) |= PTE_USER;
|
||||
}
|
||||
|
||||
static bool vcpu_mixed_endian_el0(struct kvm_vcpu *vcpu)
|
||||
{
|
||||
uint64_t mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1));
|
||||
|
||||
return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) ||
|
||||
SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
struct kvm_vcpu *vcpu;
|
||||
struct kvm_vm *vm;
|
||||
struct ucall uc;
|
||||
unsigned int i;
|
||||
bool be;
|
||||
|
||||
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
|
||||
virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1);
|
||||
|
||||
vm_init_descriptor_tables(vm);
|
||||
vcpu_init_descriptor_tables(vcpu);
|
||||
vm_install_sync_handler(vm, VECTOR_SYNC_LOWER_64, ESR_ELx_EC_SVC64,
|
||||
el0_svc_handler);
|
||||
|
||||
be = vcpu_mixed_endian_el0(vcpu);
|
||||
if (be)
|
||||
make_el0_accessible(vm, MMIO_ADDR, vm->page_size);
|
||||
|
||||
ksft_print_header();
|
||||
ksft_set_plan(ARRAY_SIZE(tests) * (be ? 2 : 1));
|
||||
|
||||
/* Little-endian pass: one load and one result per iteration. */
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
const struct mmio_test *t = &tests[i];
|
||||
|
||||
vcpu_run(vcpu);
|
||||
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
|
||||
handle_mmio(vcpu->run, t, false);
|
||||
|
||||
vcpu_run(vcpu);
|
||||
expect_sync(vcpu, &uc, t);
|
||||
|
||||
ksft_test_result_pass("%s\n", t->name);
|
||||
}
|
||||
|
||||
if (be) {
|
||||
/* The EL0 stub issues all the loads, then reports the results. */
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
vcpu_run(vcpu);
|
||||
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
|
||||
handle_mmio(vcpu->run, &tests[i], true);
|
||||
}
|
||||
for (i = 0; i < ARRAY_SIZE(tests); i++) {
|
||||
vcpu_run(vcpu);
|
||||
expect_sync(vcpu, &uc, &tests[i]);
|
||||
ksft_test_result_pass("BE %s\n", tests[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
vcpu_run(vcpu);
|
||||
TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_DONE, "Expected UCALL_DONE");
|
||||
|
||||
kvm_vm_free(vm);
|
||||
|
||||
ksft_finished();
|
||||
}
|
||||
|
|
@ -6069,25 +6069,19 @@ struct kvm_io_device *kvm_io_bus_get_dev(struct kvm *kvm, enum kvm_bus bus_idx,
|
|||
gpa_t addr)
|
||||
{
|
||||
struct kvm_io_bus *bus;
|
||||
int dev_idx, srcu_idx;
|
||||
struct kvm_io_device *iodev = NULL;
|
||||
int dev_idx;
|
||||
|
||||
srcu_idx = srcu_read_lock(&kvm->srcu);
|
||||
lockdep_assert_held(&kvm->srcu);
|
||||
|
||||
bus = kvm_get_bus_srcu(kvm, bus_idx);
|
||||
if (!bus)
|
||||
goto out_unlock;
|
||||
return NULL;
|
||||
|
||||
dev_idx = kvm_io_bus_get_first_dev(bus, addr, 1);
|
||||
if (dev_idx < 0)
|
||||
goto out_unlock;
|
||||
return NULL;
|
||||
|
||||
iodev = bus->range[dev_idx].dev;
|
||||
|
||||
out_unlock:
|
||||
srcu_read_unlock(&kvm->srcu, srcu_idx);
|
||||
|
||||
return iodev;
|
||||
return bus->range[dev_idx].dev;
|
||||
}
|
||||
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_io_bus_get_dev);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user