mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
KVM: arm64: Add selftests for the pKVM heap allocator
Introduce a comprehensive runtime selftest for the pKVM hypervisor heap allocator, executed during init when CONFIG_NVHE_EL2_DEBUG is enabled. The selftest runs entirely at EL2 and exercises allocator's core mechanisms: * over-sized allocations * basic allocation and alignment * chunk recycling, splitting, merging * memory reclaiming * memory topup Signed-off-by: Vincent Donnefort <vdonnefort@google.com> Reviewed-by: Fuad Tabba <fuad.tabba@linux.dev> Tested-by: Fuad Tabba <fuad.tabba@linux.dev> Link: https://patch.msgid.link/20261001142849.3367614-10-vdonnefort@google.com Signed-off-by: Marc Zyngier <maz@kernel.org>
This commit is contained in:
parent
43759e3c96
commit
bd93068340
|
|
@ -62,6 +62,7 @@ enum __kvm_host_smccc_func {
|
||||||
__KVM_HOST_SMCCC_FUNC___kvm_enable_ssbs,
|
__KVM_HOST_SMCCC_FUNC___kvm_enable_ssbs,
|
||||||
__KVM_HOST_SMCCC_FUNC___vgic_v3_init_lrs,
|
__KVM_HOST_SMCCC_FUNC___vgic_v3_init_lrs,
|
||||||
__KVM_HOST_SMCCC_FUNC___vgic_v3_get_gic_config,
|
__KVM_HOST_SMCCC_FUNC___vgic_v3_get_gic_config,
|
||||||
|
__KVM_HOST_SMCCC_FUNC___pkvm_hyp_alloc_selftest,
|
||||||
|
|
||||||
MARKER(__KVM_HOST_SMCCC_FUNC_MIN_PKVM),
|
MARKER(__KVM_HOST_SMCCC_FUNC_MIN_PKVM),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ typedef u16 pkvm_handle_t;
|
||||||
|
|
||||||
enum pkvm_topup_id {
|
enum pkvm_topup_id {
|
||||||
PKVM_TOPUP_HYP_ALLOC,
|
PKVM_TOPUP_HYP_ALLOC,
|
||||||
|
PKVM_TOPUP_HYP_ALLOC_SELFTEST,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct kvm;
|
struct kvm;
|
||||||
|
|
@ -153,6 +154,7 @@ DECLARE_KVM_HOST_HCALL(int, __pkvm_cpu_set_vector,
|
||||||
DECLARE_KVM_HOST_HCALL0(void, __kvm_enable_ssbs)
|
DECLARE_KVM_HOST_HCALL0(void, __kvm_enable_ssbs)
|
||||||
DECLARE_KVM_HOST_HCALL0(void, __vgic_v3_init_lrs)
|
DECLARE_KVM_HOST_HCALL0(void, __vgic_v3_init_lrs)
|
||||||
DECLARE_KVM_HOST_HCALL0(u64, __vgic_v3_get_gic_config)
|
DECLARE_KVM_HOST_HCALL0(u64, __vgic_v3_get_gic_config)
|
||||||
|
DECLARE_KVM_HOST_HCALL0(int, __pkvm_hyp_alloc_selftest)
|
||||||
|
|
||||||
DECLARE_KVM_HOST_HCALL0(int, __pkvm_prot_finalize)
|
DECLARE_KVM_HOST_HCALL0(int, __pkvm_prot_finalize)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#define HYP_MEMBLOCK_REGIONS 128
|
#define HYP_MEMBLOCK_REGIONS 128
|
||||||
|
|
||||||
|
void pkvm_selftests(void);
|
||||||
int pkvm_init_host_vm(struct kvm *kvm, unsigned long type);
|
int pkvm_init_host_vm(struct kvm *kvm, unsigned long type);
|
||||||
int pkvm_create_hyp_vm(struct kvm *kvm);
|
int pkvm_create_hyp_vm(struct kvm *kvm);
|
||||||
bool pkvm_hyp_vm_is_created(struct kvm *kvm);
|
bool pkvm_hyp_vm_is_created(struct kvm *kvm);
|
||||||
|
|
@ -208,6 +209,7 @@ struct pkvm_mapping {
|
||||||
enum pkvm_hyp_req_type {
|
enum pkvm_hyp_req_type {
|
||||||
PKVM_HYP_NO_REQ = 0,
|
PKVM_HYP_NO_REQ = 0,
|
||||||
PKVM_HYP_REQ_HYP_ALLOC,
|
PKVM_HYP_REQ_HYP_ALLOC,
|
||||||
|
PKVM_HYP_REQ_HYP_ALLOC_SELFTEST,
|
||||||
__PKVM_HYP_REQ_TYPE_MAX,
|
__PKVM_HYP_REQ_TYPE_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -235,6 +237,7 @@ static inline size_t pkvm_hyp_req_arg_size(u8 type)
|
||||||
case PKVM_HYP_NO_REQ:
|
case PKVM_HYP_NO_REQ:
|
||||||
return 0;
|
return 0;
|
||||||
case PKVM_HYP_REQ_HYP_ALLOC:
|
case PKVM_HYP_REQ_HYP_ALLOC:
|
||||||
|
case PKVM_HYP_REQ_HYP_ALLOC_SELFTEST:
|
||||||
return sizeof(req->mem);
|
return sizeof(req->mem);
|
||||||
default:
|
default:
|
||||||
WARN_ON(1);
|
WARN_ON(1);
|
||||||
|
|
|
||||||
|
|
@ -2914,6 +2914,8 @@ static int __init init_hyp_mode(void)
|
||||||
kvm_err("Failed to init hyp memory protection\n");
|
kvm_err("Failed to init hyp memory protection\n");
|
||||||
goto out_err;
|
goto out_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pkvm_selftests();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,15 @@ int hyp_alloc_init(size_t size);
|
||||||
int hyp_alloc_topup(struct kvm_hyp_memcache *host_mc);
|
int hyp_alloc_topup(struct kvm_hyp_memcache *host_mc);
|
||||||
unsigned long hyp_alloc_reclaimable(void);
|
unsigned long hyp_alloc_reclaimable(void);
|
||||||
void hyp_alloc_reclaim(struct kvm_hyp_memcache *host_mc, unsigned long target);
|
void hyp_alloc_reclaim(struct kvm_hyp_memcache *host_mc, unsigned long target);
|
||||||
|
|
||||||
|
#ifdef CONFIG_NVHE_EL2_DEBUG
|
||||||
|
int hyp_allocator_selftest(void);
|
||||||
|
u32 hyp_alloc_selftest_topup_needed(void);
|
||||||
|
int hyp_alloc_selftest_topup(struct kvm_hyp_memcache *host_mc);
|
||||||
|
void hyp_alloc_selftest_reclaim(struct kvm_hyp_memcache *host_mc, unsigned long target);
|
||||||
|
#else
|
||||||
|
static inline int hyp_alloc_selftest_topup(struct kvm_hyp_memcache *host_mc) { return 0; };
|
||||||
|
static inline void
|
||||||
|
hyp_alloc_selftest_reclaim(struct kvm_hyp_memcache *host_mc, unsigned long target) { };
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1001,9 +1001,17 @@ int hyp_alloc_errno(void)
|
||||||
return hyp_allocator_errno(&hyp_allocator);
|
return hyp_allocator_errno(&hyp_allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int selftest_init(void);
|
||||||
|
|
||||||
int hyp_alloc_init(size_t size)
|
int hyp_alloc_init(size_t size)
|
||||||
{
|
{
|
||||||
return hyp_allocator_init(&hyp_allocator, size);
|
int ret;
|
||||||
|
|
||||||
|
ret = hyp_allocator_init(&hyp_allocator, size);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return selftest_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void hyp_alloc_reclaim(struct kvm_hyp_memcache *mc, unsigned long target)
|
void hyp_alloc_reclaim(struct kvm_hyp_memcache *mc, unsigned long target)
|
||||||
|
|
@ -1025,3 +1033,179 @@ u32 hyp_alloc_topup_needed(void)
|
||||||
{
|
{
|
||||||
return hyp_allocator_topup_needed(&hyp_allocator);
|
return hyp_allocator_topup_needed(&hyp_allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_NVHE_EL2_DEBUG
|
||||||
|
#define SELFTEST_MAX_PAGES 6
|
||||||
|
#define SELFTEST_MAX_SIZE (PAGE_SIZE * SELFTEST_MAX_PAGES)
|
||||||
|
|
||||||
|
static DEFINE_PER_CPU(int, __selftest_errno);
|
||||||
|
static DEFINE_PER_CPU(u32, __selftest_topup_needed);
|
||||||
|
|
||||||
|
static struct hyp_allocator selftest_allocator = {
|
||||||
|
.errno = &__selftest_errno,
|
||||||
|
.topup_needed = &__selftest_topup_needed,
|
||||||
|
.lock = __HYP_SPIN_LOCK_UNLOCKED,
|
||||||
|
};
|
||||||
|
|
||||||
|
int hyp_alloc_selftest_topup(struct kvm_hyp_memcache *host_mc)
|
||||||
|
{
|
||||||
|
return hyp_allocator_topup(&selftest_allocator, host_mc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hyp_alloc_selftest_reclaim(struct kvm_hyp_memcache *host_mc, unsigned long target)
|
||||||
|
{
|
||||||
|
hyp_allocator_reclaim(&selftest_allocator, host_mc, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 hyp_alloc_selftest_topup_needed(void)
|
||||||
|
{
|
||||||
|
return hyp_allocator_topup_needed(&selftest_allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int selftest_init(void)
|
||||||
|
{
|
||||||
|
return hyp_allocator_init(&selftest_allocator, SELFTEST_MAX_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *selftest_alloc(size_t size)
|
||||||
|
{
|
||||||
|
return hyp_allocator_alloc(&selftest_allocator, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void selftest_free(void *addr)
|
||||||
|
{
|
||||||
|
hyp_allocator_free(&selftest_allocator, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int selftest_errno(void)
|
||||||
|
{
|
||||||
|
return hyp_allocator_errno(&selftest_allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hyp_allocator_selftest(void)
|
||||||
|
{
|
||||||
|
struct hyp_allocator *allocator = &selftest_allocator;
|
||||||
|
static DEFINE_HYP_SPINLOCK(selftest_lock);
|
||||||
|
struct kvm_hyp_memcache host_mc = { };
|
||||||
|
void *addr1, *addr2, *addr3, *addr4;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
guard(hyp_spinlock)(&selftest_lock);
|
||||||
|
|
||||||
|
if (allocator->mc.nr_pages < SELFTEST_MAX_PAGES) {
|
||||||
|
*this_cpu_ptr(allocator->topup_needed) = SELFTEST_MAX_PAGES -
|
||||||
|
allocator->mc.nr_pages;
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
selftest_alloc(SELFTEST_MAX_SIZE);
|
||||||
|
if (selftest_errno() != -E2BIG)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
selftest_alloc(SIZE_MAX);
|
||||||
|
if (selftest_errno() != -E2BIG)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Test first chunk */
|
||||||
|
addr1 = selftest_alloc(0);
|
||||||
|
if (!addr1 || addr1 != (void *)allocator->start + chunk_hdr_size())
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Test second contiguous chunk with unaligned size */
|
||||||
|
addr2 = selftest_alloc(MIN_ALLOC_SIZE + 1);
|
||||||
|
if (!addr2)
|
||||||
|
return -EINVAL;
|
||||||
|
addr3 = selftest_alloc(0);
|
||||||
|
if (!addr3 ||
|
||||||
|
addr3 != addr2 + (2 * MIN_ALLOC_SIZE) + chunk_hdr_size())
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
selftest_free(addr3);
|
||||||
|
|
||||||
|
/* Test chunk recycling */
|
||||||
|
selftest_free(addr1);
|
||||||
|
if (addr1 != selftest_alloc(0))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Test chunk forward merging */
|
||||||
|
addr3 = selftest_alloc(0);
|
||||||
|
selftest_free(addr2);
|
||||||
|
selftest_free(addr1);
|
||||||
|
if (addr1 != selftest_alloc(MIN_ALLOC_SIZE * 2))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
selftest_free(addr1);
|
||||||
|
|
||||||
|
/* Test chunk splitting */
|
||||||
|
if (addr1 != selftest_alloc(0))
|
||||||
|
return -EINVAL;
|
||||||
|
if (addr2 != selftest_alloc(0))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Test chunk backward merging */
|
||||||
|
selftest_free(addr1);
|
||||||
|
selftest_free(addr2);
|
||||||
|
if (addr1 != selftest_alloc(MIN_ALLOC_SIZE * 2))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
selftest_free(addr1);
|
||||||
|
|
||||||
|
/* Test chunk 3-way merging */
|
||||||
|
addr1 = selftest_alloc(0);
|
||||||
|
addr2 = selftest_alloc(0);
|
||||||
|
addr4 = selftest_alloc(0);
|
||||||
|
selftest_free(addr1);
|
||||||
|
selftest_free(addr3);
|
||||||
|
selftest_free(addr2);
|
||||||
|
if (addr1 != selftest_alloc(MIN_ALLOC_SIZE * 3))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
selftest_free(addr4);
|
||||||
|
selftest_free(addr1);
|
||||||
|
|
||||||
|
/* Test reclaiming */
|
||||||
|
if (addr1 != selftest_alloc(0))
|
||||||
|
return -EINVAL;
|
||||||
|
if (addr2 != selftest_alloc(PAGE_SIZE * 2))
|
||||||
|
return -EINVAL;
|
||||||
|
addr3 = selftest_alloc(0);
|
||||||
|
addr4 = selftest_alloc(PAGE_SIZE);
|
||||||
|
|
||||||
|
/* Test reclaiming the last chunk of the list */
|
||||||
|
selftest_free(addr4);
|
||||||
|
hyp_allocator_reclaim(allocator, &host_mc, SELFTEST_MAX_PAGES);
|
||||||
|
if (host_mc.nr_pages != SELFTEST_MAX_PAGES - 3)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Test punching a hole in the middle of a free chunk ... */
|
||||||
|
selftest_free(addr2);
|
||||||
|
hyp_allocator_reclaim(allocator, &host_mc, SELFTEST_MAX_PAGES);
|
||||||
|
if (host_mc.nr_pages != SELFTEST_MAX_PAGES - 2)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (selftest_alloc(PAGE_SIZE))
|
||||||
|
return -EINVAL;
|
||||||
|
if (selftest_errno() != -ENOMEM)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* ... and to refill this hole */
|
||||||
|
ret = hyp_allocator_topup(allocator, &host_mc);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
/* Chunk at addr2 was made smaller by the reclaim */
|
||||||
|
if (addr2 != selftest_alloc(PAGE_SIZE))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Test reclaiming the entire allocator from the host */
|
||||||
|
selftest_free(addr3);
|
||||||
|
selftest_free(addr2);
|
||||||
|
selftest_free(addr1);
|
||||||
|
if (addr1 != selftest_alloc(SELFTEST_MAX_PAGES * PAGE_SIZE - chunk_hdr_size()))
|
||||||
|
return -EINVAL;
|
||||||
|
selftest_free(addr1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static int selftest_init(void) { return 0; }
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -800,6 +800,23 @@ DEFINE_KVM_HOST_HCALL(int, __pkvm_finalize_teardown_vm,
|
||||||
return __pkvm_finalize_teardown_vm(handle);
|
return __pkvm_finalize_teardown_vm(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_KVM_HOST_HCALL0(int, __pkvm_hyp_alloc_selftest)
|
||||||
|
{
|
||||||
|
struct pkvm_hyp_req req = { .type = PKVM_HYP_NO_REQ };
|
||||||
|
int ret = -EPERM;
|
||||||
|
|
||||||
|
#ifdef CONFIG_NVHE_EL2_DEBUG
|
||||||
|
ret = hyp_allocator_selftest();
|
||||||
|
if (ret == -ENOMEM) {
|
||||||
|
req.type = PKVM_HYP_REQ_HYP_ALLOC_SELFTEST;
|
||||||
|
req.mem.nr_pages = hyp_alloc_selftest_topup_needed();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
pkvm_hyp_req_to_smccc(host_data_ptr(host_ctxt), &req);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_KVM_HOST_HCALL(int, __pkvm_hyp_topup,
|
DEFINE_KVM_HOST_HCALL(int, __pkvm_hyp_topup,
|
||||||
enum pkvm_topup_id, id, phys_addr_t, head, unsigned long, nr_pages)
|
enum pkvm_topup_id, id, phys_addr_t, head, unsigned long, nr_pages)
|
||||||
{
|
{
|
||||||
|
|
@ -814,6 +831,9 @@ DEFINE_KVM_HOST_HCALL(int, __pkvm_hyp_topup,
|
||||||
case PKVM_TOPUP_HYP_ALLOC:
|
case PKVM_TOPUP_HYP_ALLOC:
|
||||||
ret = hyp_alloc_topup(&host_mc);
|
ret = hyp_alloc_topup(&host_mc);
|
||||||
break;
|
break;
|
||||||
|
case PKVM_TOPUP_HYP_ALLOC_SELFTEST:
|
||||||
|
ret = hyp_alloc_selftest_topup(&host_mc);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
@ -835,6 +855,9 @@ DEFINE_KVM_HOST_HCALL(int, __pkvm_hyp_reclaim,
|
||||||
case PKVM_TOPUP_HYP_ALLOC:
|
case PKVM_TOPUP_HYP_ALLOC:
|
||||||
hyp_alloc_reclaim(&host_mc, target);
|
hyp_alloc_reclaim(&host_mc, target);
|
||||||
break;
|
break;
|
||||||
|
case PKVM_TOPUP_HYP_ALLOC_SELFTEST:
|
||||||
|
hyp_alloc_selftest_reclaim(&host_mc, target);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
@ -927,6 +950,7 @@ static const hcall_t host_hcall[] = {
|
||||||
HANDLE_FUNC(__kvm_enable_ssbs),
|
HANDLE_FUNC(__kvm_enable_ssbs),
|
||||||
HANDLE_FUNC(__vgic_v3_init_lrs),
|
HANDLE_FUNC(__vgic_v3_init_lrs),
|
||||||
HANDLE_FUNC(__vgic_v3_get_gic_config),
|
HANDLE_FUNC(__vgic_v3_get_gic_config),
|
||||||
|
HANDLE_FUNC(__pkvm_hyp_alloc_selftest),
|
||||||
HANDLE_FUNC(__pkvm_prot_finalize),
|
HANDLE_FUNC(__pkvm_prot_finalize),
|
||||||
|
|
||||||
HANDLE_FUNC(__kvm_adjust_pc),
|
HANDLE_FUNC(__kvm_adjust_pc),
|
||||||
|
|
|
||||||
|
|
@ -344,6 +344,22 @@ static int __init pkvm_drop_host_privileges(void)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void __init pkvm_selftests(void)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_NVHE_EL2_DEBUG
|
||||||
|
int ret = pkvm_call_hyp_req(__pkvm_hyp_alloc_selftest);
|
||||||
|
unsigned long reclaimed;
|
||||||
|
|
||||||
|
reclaimed = pkvm_hyp_reclaim(PKVM_TOPUP_HYP_ALLOC_SELFTEST, ULONG_MAX);
|
||||||
|
|
||||||
|
/* On failure, not all the pages may be reclaimable */
|
||||||
|
if (!ret)
|
||||||
|
WARN_ON(reclaimed != 6 /* SELFTEST_MAX_PAGES */);
|
||||||
|
else
|
||||||
|
kvm_err("pKVM hyp allocator selftest failed (%d)\n", ret);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static int __init finalize_pkvm(void)
|
static int __init finalize_pkvm(void)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
@ -677,6 +693,9 @@ static int pkvm_handle_hyp_req(struct pkvm_hyp_req *req)
|
||||||
case PKVM_HYP_REQ_HYP_ALLOC:
|
case PKVM_HYP_REQ_HYP_ALLOC:
|
||||||
ret = pkvm_hyp_topup(PKVM_TOPUP_HYP_ALLOC, req->mem.nr_pages);
|
ret = pkvm_hyp_topup(PKVM_TOPUP_HYP_ALLOC, req->mem.nr_pages);
|
||||||
break;
|
break;
|
||||||
|
case PKVM_HYP_REQ_HYP_ALLOC_SELFTEST:
|
||||||
|
ret = pkvm_hyp_topup(PKVM_TOPUP_HYP_ALLOC_SELFTEST, req->mem.nr_pages);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace_kvm_handle_pkvm_hyp_req(req, ret);
|
trace_kvm_handle_pkvm_hyp_req(req, ret);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user