ANDROID: KVM: arm64: Introduce kvm_iommu_ops host_stage2_idmap_complete

This callback is called whenever a series of update have been done to
the IOMMU idmap. This is typically useful to invalidate several TLB
entries in one go instead of doing it on a per-idmap operation for the
sglist based host to guest donation.

The driver may not provide this callback.

Bug: 357781595
Bug: 361026567
Change-Id: Ib677d6738bc16eadf33c4df0405b1967a1f1dc45
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/arch/arm64/include/asm/kvm_hypevents.h b/arch/arm64/include/asm/kvm_hypevents.h
index 20aee22..24438e9 100644
--- a/arch/arm64/include/asm/kvm_hypevents.h
+++ b/arch/arm64/include/asm/kvm_hypevents.h
@@ -165,4 +165,15 @@ HYP_EVENT(iommu_idmap,
 	),
 	HE_PRINTK("from=0x%llx to=0x%llx prot=0x%x", __entry->from, __entry->to, __entry->prot)
 );
+
+HYP_EVENT(iommu_idmap_complete,
+	HE_PROTO(bool map),
+	HE_STRUCT(
+		he_field(bool, map)
+	),
+	HE_ASSIGN(
+		__entry->map = map;
+	),
+	HE_PRINTK("map=%d", __entry->map)
+);
 #endif /* __ARM64_KVM_HYPEVENTS_H_ */
diff --git a/arch/arm64/kvm/hyp/include/nvhe/iommu.h b/arch/arm64/kvm/hyp/include/nvhe/iommu.h
index 64a6d1a..b9ed791 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/iommu.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/iommu.h
@@ -34,6 +34,7 @@ struct kvm_iommu_ops {
 	phys_addr_t (*iova_to_phys)(struct kvm_hyp_iommu_domain *domain, unsigned long iova);
 	void (*iotlb_sync)(struct kvm_hyp_iommu_domain *domain,
 			   struct iommu_iotlb_gather *gather);
+	void (*host_stage2_idmap_complete)(bool map);
 };
 
 int kvm_iommu_init(void *pool_base, size_t nr_pages, struct kvm_iommu_ops *ops);
@@ -58,6 +59,7 @@ size_t kvm_iommu_unmap_pages(pkvm_handle_t domain_id, unsigned long iova,
 			     size_t pgsize, size_t pgcount);
 phys_addr_t kvm_iommu_iova_to_phys(pkvm_handle_t domain_id, unsigned long iova);
 bool kvm_iommu_host_dabt_handler(struct user_pt_regs *regs, u64 esr, u64 addr);
+void kvm_iommu_host_stage2_idmap_complete(bool map);
 
 /* Flags for memory allocation for IOMMU drivers */
 #define IOMMU_PAGE_NOCACHE                             BIT(0)
diff --git a/arch/arm64/kvm/hyp/nvhe/iommu/iommu.c b/arch/arm64/kvm/hyp/nvhe/iommu/iommu.c
index 5d4c566..694ac86 100644
--- a/arch/arm64/kvm/hyp/nvhe/iommu/iommu.c
+++ b/arch/arm64/kvm/hyp/nvhe/iommu/iommu.c
@@ -435,3 +435,13 @@ bool kvm_iommu_host_dabt_handler(struct user_pt_regs *regs, u64 esr, u64 addr)
 	}
 	return false;
 }
+
+void kvm_iommu_host_stage2_idmap_complete(bool map)
+{
+	if (!kvm_idmap_initialized ||
+	    !kvm_iommu_ops->host_stage2_idmap_complete)
+		return;
+
+	trace_iommu_idmap_complete(map);
+	kvm_iommu_ops->host_stage2_idmap_complete(map);
+}
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 08f3237..4ae70f4 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -708,6 +708,7 @@ static int __host_stage2_set_owner_locked(phys_addr_t addr, u64 size, u8 owner_i
 	if (update_iommu) {
 		prot = owner_id == PKVM_ID_HOST ? PKVM_HOST_MEM_PROT : 0;
 		kvm_iommu_host_stage2_idmap(addr, addr + size, prot);
+		kvm_iommu_host_stage2_idmap_complete(!!prot);
 	}
 	if (!is_memory)
 		return 0;
@@ -997,6 +998,7 @@ static int __host_set_page_state_range(u64 addr, u64 size,
 		if (ret)
 			return ret;
 		kvm_iommu_host_stage2_idmap(addr, addr + size, PKVM_HOST_MEM_PROT);
+		kvm_iommu_host_stage2_idmap_complete(true);
 	}
 
 	__host_update_page_state(addr, size, state);
@@ -1418,8 +1420,10 @@ int module_change_host_page_prot(u64 pfn, enum kvm_pgtable_prot prot,
 	} else {
 		ret = host_stage2_idmap_locked(
 				addr, size, prot);
-		if (update_iommu)
+		if (update_iommu) {
 			kvm_iommu_host_stage2_idmap(addr, addr + size, prot);
+			kvm_iommu_host_stage2_idmap_complete(!!prot);
+		}
 	}
 
 	if (WARN_ON(ret) || !page || !prot)
@@ -1540,14 +1544,19 @@ int __pkvm_host_split_guest(u64 gfn, u64 size, struct pkvm_hyp_vcpu *vcpu)
 	return ret;
 }
 
-static int __host_donate_guest(struct pkvm_hyp_vcpu *vcpu, u64 phys, u64 ipa, size_t size)
+static int __host_set_owner_guest(struct pkvm_hyp_vcpu *vcpu, u64 phys, u64 ipa,
+				  size_t size, bool is_memory)
 {
 	struct pkvm_hyp_vm *vm = pkvm_hyp_vcpu_to_hyp_vm(vcpu);
 	u64 nr_pages = size >> PAGE_SHIFT;
-	enum kvm_pgtable_prot prot;
 	int ret;
 
-	WARN_ON(host_stage2_set_owner_locked(phys, size, PKVM_ID_GUEST));
+	/*
+	 * update_iommu=false, the caller must do the update _before_ this function is called. This
+	 * intends to protect pvmfw loading.
+	 */
+	WARN_ON(__host_stage2_set_owner_locked(phys, size, PKVM_ID_GUEST,
+					       is_memory, 0, false));
 	psci_mem_protect_inc(nr_pages);
 	if (pkvm_ipa_range_has_pvmfw(vm, ipa, ipa + size)) {
 		ret = pkvm_load_pvmfw_pages(vm, ipa, phys, size);
@@ -1557,10 +1566,6 @@ static int __host_donate_guest(struct pkvm_hyp_vcpu *vcpu, u64 phys, u64 ipa, si
 		}
 	}
 
-	prot = pkvm_mkstate(KVM_PGTABLE_PROT_RWX, PKVM_PAGE_OWNED);
-	WARN_ON(kvm_pgtable_stage2_map(&vm->pgt, ipa, size, phys, prot,
-				       &vcpu->vcpu.arch.stage2_mc, 0));
-
 	return 0;
 }
 
@@ -1569,6 +1574,8 @@ int __pkvm_host_donate_guest(u64 pfn, u64 gfn, struct pkvm_hyp_vcpu *vcpu, u64 n
 	struct pkvm_hyp_vm *vm = pkvm_hyp_vcpu_to_hyp_vm(vcpu);
 	u64 phys = hyp_pfn_to_phys(pfn);
 	u64 ipa = hyp_pfn_to_phys(gfn);
+	enum kvm_pgtable_prot prot;
+	bool is_memory;
 	size_t size;
 	int ret;
 
@@ -1585,7 +1592,16 @@ int __pkvm_host_donate_guest(u64 pfn, u64 gfn, struct pkvm_hyp_vcpu *vcpu, u64 n
 	if (ret)
 		goto unlock;
 
-	WARN_ON(__host_donate_guest(vcpu, phys, ipa, size));
+	is_memory = addr_is_memory(phys);
+	if (is_memory) {
+		kvm_iommu_host_stage2_idmap(phys, phys + size, 0);
+		kvm_iommu_host_stage2_idmap_complete(false);
+	}
+	WARN_ON(__host_set_owner_guest(vcpu, phys, ipa, size, is_memory));
+
+	prot = pkvm_mkstate(KVM_PGTABLE_PROT_RWX, PKVM_PAGE_OWNED);
+	WARN_ON(kvm_pgtable_stage2_map(&vm->pgt, ipa, size, phys, prot,
+				       &vcpu->vcpu.arch.stage2_mc, 0));
 
 unlock:
 	guest_unlock_component(vm);
@@ -1662,12 +1678,29 @@ int __pkvm_host_donate_sglist_guest(struct pkvm_hyp_vcpu *vcpu)
 			goto unlock;
 	}
 
+	if (is_memory) {
+		for_each_hyp_ppage(ppage) {
+			size_t size = PAGE_SIZE << ppage->order;
+			u64 phys = hyp_pfn_to_phys(ppage->pfn);
+
+			kvm_iommu_host_stage2_idmap(phys, phys + size, 0);
+		}
+
+		kvm_iommu_host_stage2_idmap_complete(false);
+	}
+
 	for_each_hyp_ppage(ppage) {
 		size_t size = PAGE_SIZE << ppage->order;
 		u64 phys = hyp_pfn_to_phys(ppage->pfn);
 		u64 ipa = hyp_pfn_to_phys(ppage->gfn);
+		enum kvm_pgtable_prot prot;
 
-		WARN_ON(__host_donate_guest(vcpu, phys, ipa, size));
+		/* Now the sglist is unmapped from the IOMMUs, we can load pvmfw */
+		WARN_ON(__host_set_owner_guest(vcpu, phys, ipa, size, is_memory));
+
+		prot = pkvm_mkstate(KVM_PGTABLE_PROT_RWX, PKVM_PAGE_OWNED);
+		WARN_ON(kvm_pgtable_stage2_map(&vm->pgt, ipa, size, phys, prot,
+				       &vcpu->vcpu.arch.stage2_mc, 0));
 	}
 
 unlock: