@@ -421,8 +421,12 @@ struct kvm_vcpu_arch {
/* Don't run the guest (internal implementation need) */
bool pause;
- /* Cache some mmu pages needed inside spinlock regions */
- struct kvm_mmu_memory_cache mmu_page_cache;
+ union {
+ /* Cache some mmu pages needed inside spinlock regions */
+ struct kvm_mmu_memory_cache mmu_page_cache;
+ /* Pages to be donated to pkvm/EL2 if it runs out */
+ struct kvm_hyp_memcache pkvm_memcache;
+ };
/* Target CPU and feature flags */
int target;
@@ -57,6 +57,7 @@ extern struct host_kvm host_kvm;
enum pkvm_component_id {
PKVM_ID_HOST,
PKVM_ID_HYP,
+ PKVM_ID_GUEST,
};
extern unsigned long hyp_nr_cpus;
@@ -67,6 +68,7 @@ int __pkvm_host_unshare_hyp(u64 pfn);
int __pkvm_host_reclaim_page(u64 pfn);
int __pkvm_host_donate_hyp(u64 pfn, u64 nr_pages);
int __pkvm_hyp_donate_host(u64 pfn, u64 nr_pages);
+int __pkvm_host_share_guest(u64 pfn, u64 gfn, struct kvm_vcpu *vcpu);
bool addr_is_memory(phys_addr_t phys);
int host_stage2_idmap_locked(phys_addr_t addr, u64 size, enum kvm_pgtable_prot prot);
@@ -579,11 +579,21 @@ struct pkvm_mem_transition {
struct {
u64 completer_addr;
} hyp;
+ struct {
+ struct kvm_vcpu *vcpu;
+ } guest;
};
} initiator;
struct {
enum pkvm_component_id id;
+
+ union {
+ struct {
+ struct kvm_vcpu *vcpu;
+ phys_addr_t phys;
+ } guest;
+ };
} completer;
};
@@ -847,6 +857,52 @@ static int hyp_complete_donation(u64 addr,
return pkvm_create_mappings_locked(start, end, prot);
}
+static enum pkvm_page_state guest_get_page_state(kvm_pte_t pte)
+{
+ if (!kvm_pte_valid(pte))
+ return PKVM_NOPAGE;
+
+ return pkvm_getstate(kvm_pgtable_stage2_pte_prot(pte));
+}
+
+static int __guest_check_page_state_range(struct kvm_vcpu *vcpu, u64 addr,
+ u64 size, enum pkvm_page_state state)
+{
+ struct kvm_shadow_vm *vm = get_shadow_vm(vcpu);
+ struct check_walk_data d = {
+ .desired = state,
+ .get_page_state = guest_get_page_state,
+ };
+
+ hyp_assert_lock_held(&vm->lock);
+ return check_page_state_range(&vm->pgt, addr, size, &d);
+}
+
+static int guest_ack_share(u64 addr, const struct pkvm_mem_transition *tx,
+ enum kvm_pgtable_prot perms)
+{
+ u64 size = tx->nr_pages * PAGE_SIZE;
+
+ if (perms != KVM_PGTABLE_PROT_RWX)
+ return -EPERM;
+
+ return __guest_check_page_state_range(tx->completer.guest.vcpu, addr,
+ size, PKVM_NOPAGE);
+}
+
+static int guest_complete_share(u64 addr, const struct pkvm_mem_transition *tx,
+ enum kvm_pgtable_prot perms)
+{
+ struct kvm_vcpu *vcpu = tx->completer.guest.vcpu;
+ struct kvm_shadow_vm *vm = get_shadow_vm(vcpu);
+ u64 size = tx->nr_pages * PAGE_SIZE;
+ enum kvm_pgtable_prot prot;
+
+ prot = pkvm_mkstate(perms, PKVM_PAGE_SHARED_BORROWED);
+ return kvm_pgtable_stage2_map(&vm->pgt, addr, size, tx->completer.guest.phys,
+ prot, &vcpu->arch.pkvm_memcache);
+}
+
static int check_share(struct pkvm_mem_share *share)
{
const struct pkvm_mem_transition *tx = &share->tx;
@@ -868,6 +924,9 @@ static int check_share(struct pkvm_mem_share *share)
case PKVM_ID_HYP:
ret = hyp_ack_share(completer_addr, tx, share->completer_prot);
break;
+ case PKVM_ID_GUEST:
+ ret = guest_ack_share(completer_addr, tx, share->completer_prot);
+ break;
default:
ret = -EINVAL;
}
@@ -896,6 +955,9 @@ static int __do_share(struct pkvm_mem_share *share)
case PKVM_ID_HYP:
ret = hyp_complete_share(completer_addr, tx, share->completer_prot);
break;
+ case PKVM_ID_GUEST:
+ ret = guest_complete_share(completer_addr, tx, share->completer_prot);
+ break;
default:
ret = -EINVAL;
}
@@ -1262,6 +1324,44 @@ void hyp_unpin_shared_mem(void *from, void *to)
host_unlock_component();
}
+int __pkvm_host_share_guest(u64 pfn, u64 gfn, struct kvm_vcpu *vcpu)
+{
+ int ret;
+ u64 host_addr = hyp_pfn_to_phys(pfn);
+ u64 guest_addr = hyp_pfn_to_phys(gfn);
+ struct kvm_shadow_vm *vm = get_shadow_vm(vcpu);
+ struct pkvm_mem_share share = {
+ .tx = {
+ .nr_pages = 1,
+ .initiator = {
+ .id = PKVM_ID_HOST,
+ .addr = host_addr,
+ .host = {
+ .completer_addr = guest_addr,
+ },
+ },
+ .completer = {
+ .id = PKVM_ID_GUEST,
+ .guest = {
+ .vcpu = vcpu,
+ .phys = host_addr,
+ },
+ },
+ },
+ .completer_prot = KVM_PGTABLE_PROT_RWX,
+ };
+
+ host_lock_component();
+ guest_lock_component(vm);
+
+ ret = do_share(&share);
+
+ guest_unlock_component(vm);
+ host_unlock_component();
+
+ return ret;
+}
+
static int hyp_zero_page(phys_addr_t phys)
{
void *addr;
In preparation for handling guest stage-2 mappings at EL2, extend our memory protection mechanisms to support sharing of pages from the host to a specific guest. Signed-off-by: Will Deacon <will@kernel.org> --- arch/arm64/include/asm/kvm_host.h | 8 +- arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 2 + arch/arm64/kvm/hyp/nvhe/mem_protect.c | 100 ++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-)