The time taken to switch a VM to Secure-VM, increases by the size of the VM. A
100GB VM takes about 7minutes. This is unacceptable. This linear increase is
caused by a suboptimal behavior by the Ultravisor and the Hypervisor. The
Ultravisor unnecessarily migrates all the GFN of the VM from normal-memory to
secure-memory. It has to just migrate the necessary and sufficient GFNs.
However when the optimization is incorporated in the Ultravisor, the Hypervisor
starts misbehaving. The Hypervisor has a inbuilt assumption that the Ultravisor
will explicitly request to migrate, each and every GFN of the VM. If only
necessary and sufficient GFNs are requested for migration, the Hypervisor
continues to manage the remaining GFNs as normal GFNs. This leads of memory
corruption, manifested consistently when the SVM reboots.
The same is true, when a memory slot is hotplugged into a SVM. The Hypervisor
expects the ultravisor to request migration of all GFNs to secure-GFN. But the
hypervisor cannot handle any H_SVM_PAGE_IN requests from the Ultravisor, done
in the context of UV_REGISTER_MEM_SLOT ucall. This problem manifests as random
errors in the SVM, when a memory-slot is hotplugged.
This patch series automatically migrates the non-migrated pages of a SVM,
and thus solves the problem.
Testing: Passed rigorous testing using various sized SVMs.
Changelog:
v3: . Optimized the page-migration retry-logic.
. Relax and relinquish the cpu regularly while bulk migrating
the non-migrated pages. This issue was causing soft-lockups.
Fixed it.
. Added a new patch, to retry page-migration a couple of times
before returning H_BUSY in H_SVM_PAGE_IN. This issue was
seen a few times in a 24hour continuous reboot test of the SVMs.
v2: . fixed a bug observed by Laurent. The state of the GFN's associated
with Secure-VMs were not reset during memslot flush.
. Re-organized the code, for easier review.
. Better description of the patch series.
v1: fixed a bug observed by Bharata. Pages that where paged-in and later
paged-out must also be skipped from migration during H_SVM_INIT_DONE.
Laurent Dufour (1):
KVM: PPC: Book3S HV: migrate hot plugged memory
Ram Pai (4):
KVM: PPC: Book3S HV: Disable page merging in H_SVM_INIT_START
KVM: PPC: Book3S HV: track the state GFNs associated with secure VMs
KVM: PPC: Book3S HV: migrate remaining normal-GFNs to secure-GFNs in
H_SVM_INIT_DONE
KVM: PPC: Book3S HV: retry page migration before erroring-out
H_SVM_PAGE_IN
Documentation/powerpc/ultravisor.rst | 3 +
arch/powerpc/include/asm/kvm_book3s_uvmem.h | 2 +
arch/powerpc/kvm/book3s_hv.c | 10 +-
arch/powerpc/kvm/book3s_hv_uvmem.c | 487 ++++++++++++++++++++++++----
4 files changed, 429 insertions(+), 73 deletions(-)
--
1.8.3.1
During the life of SVM, its GFNs transition through normal, secure and
shared states. Since the kernel does not track GFNs that are shared, it
is not possible to disambiguate a shared GFN from a GFN whose PFN has
not yet been migrated to a secure-PFN. Also it is not possible to
disambiguate a secure-GFN from a GFN whose GFN has been pagedout from
the ultravisor.
The ability to identify the state of a GFN is needed to skip migration
of its PFN to secure-PFN during ESM transition.
The code is re-organized to track the states of a GFN as explained
below.
************************************************************************
1. States of a GFN
---------------
The GFN can be in one of the following states.
(a) Secure - The GFN is secure. The GFN is associated with
a Secure VM, the contents of the GFN is not accessible
to the Hypervisor. This GFN can be backed by a secure-PFN,
or can be backed by a normal-PFN with contents encrypted.
The former is true when the GFN is paged-in into the
ultravisor. The latter is true when the GFN is paged-out
of the ultravisor.
(b) Shared - The GFN is shared. The GFN is associated with a
a secure VM. The contents of the GFN is accessible to
Hypervisor. This GFN is backed by a normal-PFN and its
content is un-encrypted.
(c) Normal - The GFN is a normal. The GFN is associated with
a normal VM. The contents of the GFN is accesible to
the Hypervisor. Its content is never encrypted.
2. States of a VM.
---------------
(a) Normal VM: A VM whose contents are always accessible to
the hypervisor. All its GFNs are normal-GFNs.
(b) Secure VM: A VM whose contents are not accessible to the
hypervisor without the VM's consent. Its GFNs are
either Shared-GFN or Secure-GFNs.
(c) Transient VM: A Normal VM that is transitioning to secure VM.
The transition starts on successful return of
H_SVM_INIT_START, and ends on successful return
of H_SVM_INIT_DONE. This transient VM, can have GFNs
in any of the three states; i.e Secure-GFN, Shared-GFN,
and Normal-GFN. The VM never executes in this state
in supervisor-mode.
3. Memory slot State.
------------------
The state of a memory slot mirrors the state of the
VM the memory slot is associated with.
4. VM State transition.
--------------------
A VM always starts in Normal Mode.
H_SVM_INIT_START moves the VM into transient state. During this
time the Ultravisor may request some of its GFNs to be shared or
secured. So its GFNs can be in one of the three GFN states.
H_SVM_INIT_DONE moves the VM entirely from transient state to
secure-state. At this point any left-over normal-GFNs are
transitioned to Secure-GFN.
H_SVM_INIT_ABORT moves the transient VM back to normal VM.
All its GFNs are moved to Normal-GFNs.
UV_TERMINATE transitions the secure-VM back to normal-VM. All
the secure-GFN and shared-GFNs are tranistioned to normal-GFN
Note: The contents of the normal-GFN is undefined at this point.
5. GFN state implementation:
-------------------------
Secure GFN is associated with a secure-PFN; also called uvmem_pfn,
when the GFN is paged-in. Its pfn[] has KVMPPC_GFN_UVMEM_PFN flag
set, and contains the value of the secure-PFN.
It is associated with a normal-PFN; also called mem_pfn, when
the GFN is pagedout. Its pfn[] has KVMPPC_GFN_MEM_PFN flag set.
The value of the normal-PFN is not tracked.
Shared GFN is associated with a normal-PFN. Its pfn[] has
KVMPPC_UVMEM_SHARED_PFN flag set. The value of the normal-PFN
is not tracked.
Normal GFN is associated with normal-PFN. Its pfn[] has
no flag set. The value of the normal-PFN is not tracked.
6. Life cycle of a GFN
--------------------
--------------------------------------------------------------
| | Share | Unshare | SVM |H_SVM_INIT_DONE|
| |operation |operation | abort/ | |
| | | | terminate | |
-------------------------------------------------------------
| | | | | |
| Secure | Shared | Secure |Normal |Secure |
| | | | | |
| Shared | Shared | Secure |Normal |Shared |
| | | | | |
| Normal | Shared | Secure |Normal |Secure |
--------------------------------------------------------------
7. Life cycle of a VM
--------------------
--------------------------------------------------------------------
| | start | H_SVM_ |H_SVM_ |H_SVM_ |UV_SVM_ |
| | VM |INIT_START|INIT_DONE|INIT_ABORT |TERMINATE |
| | | | | | |
--------- ----------------------------------------------------------
| | | | | | |
| Normal | Normal | Transient|Error |Error |Normal |
| | | | | | |
| Secure | Error | Error |Error |Error |Normal |
| | | | | | |
|Transient| N/A | Error |Secure |Normal |Normal |
--------------------------------------------------------------------
************************************************************************
Cc: Paul Mackerras <redacted>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Bharata B Rao <redacted>
Cc: Aneesh Kumar K.V <redacted>
Cc: Sukadev Bhattiprolu <redacted>
Cc: Laurent Dufour <redacted>
Cc: Thiago Jung Bauermann <redacted>
Cc: David Gibson <redacted>
Cc: Claudio Carvalho <redacted>
Cc: kvm-ppc@vger.kernel.org
Cc: linuxppc-dev@lists.ozlabs.org
Reviewed-by: Thiago Jung Bauermann <redacted>
Signed-off-by: Ram Pai <redacted>
---
arch/powerpc/kvm/book3s_hv_uvmem.c | 187 +++++++++++++++++++++++++++++++++----
1 file changed, 168 insertions(+), 19 deletions(-)
@@ -172,24 +292,41 @@ static void kvmppc_uvmem_pfn_insert(unsigned long gfn, unsigned long uvmem_pfn,if(gfn>=p->base_pfn&&gfn<p->base_pfn+p->nr_pfns){unsignedlongindex=gfn-p->base_pfn;-p->pfns[index]=uvmem_pfn|KVMPPC_UVMEM_PFN;+if(flag==KVMPPC_GFN_UVMEM_PFN)+p->pfns[index]=uvmem_pfn|flag;+else+p->pfns[index]=flag;return;}}}-staticvoidkvmppc_uvmem_pfn_remove(unsignedlonggfn,structkvm*kvm)+/* mark the GFN as secure-GFN associated with @uvmem pfn device-PFN. */+staticvoidkvmppc_gfn_secure_uvmem_pfn(unsignedlonggfn,+unsignedlonguvmem_pfn,structkvm*kvm){-structkvmppc_uvmem_slot*p;+kvmppc_mark_gfn(gfn,kvm,KVMPPC_GFN_UVMEM_PFN,uvmem_pfn);+}-list_for_each_entry(p,&kvm->arch.uvmem_pfns,list){-if(gfn>=p->base_pfn&&gfn<p->base_pfn+p->nr_pfns){-p->pfns[gfn-p->base_pfn]=0;-return;-}-}+/* mark the GFN as secure-GFN associated with a memory-PFN. */+staticvoidkvmppc_gfn_secure_mem_pfn(unsignedlonggfn,structkvm*kvm)+{+kvmppc_mark_gfn(gfn,kvm,KVMPPC_GFN_MEM_PFN,0);+}++/* mark the GFN as a shared GFN. */+staticvoidkvmppc_gfn_shared(unsignedlonggfn,structkvm*kvm)+{+kvmppc_mark_gfn(gfn,kvm,KVMPPC_GFN_SHARED,0);+}++/* mark the GFN as a non-existent GFN. */+staticvoidkvmppc_gfn_remove(unsignedlonggfn,structkvm*kvm)+{+kvmppc_mark_gfn(gfn,kvm,0,0);}+/* return true, if the GFN is a secure-GFN backed by a secure-PFN */staticboolkvmppc_gfn_is_uvmem_pfn(unsignedlonggfn,structkvm*kvm,unsignedlong*uvmem_pfn){
Merging of pages associated with each memslot of a SVM is
disabled the page is migrated in H_SVM_PAGE_IN handler.
This operation should have been done much earlier; the moment the VM
is initiated for secure-transition. Delaying this operation, increases
the probability for those pages to acquire new references , making it
impossible to migrate those pages in H_SVM_PAGE_IN handler.
Disable page-migration in H_SVM_INIT_START handling.
Signed-off-by: Ram Pai <redacted>
---
arch/powerpc/kvm/book3s_hv_uvmem.c | 96 +++++++++++++++++++++++++++++---------
1 file changed, 74 insertions(+), 22 deletions(-)
@@ -232,11 +291,18 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm)returnH_AUTHORITY;srcu_idx=srcu_read_lock(&kvm->srcu);++/* disable page-merging for all memslot */+ret=kvmppc_disable_page_merge(kvm);+if(ret)+gotoout;++/* register the memslot */slots=kvm_memslots(kvm);kvm_for_each_memslot(memslot,slots){if(kvmppc_uvmem_slot_init(kvm,memslot)){ret=H_PARAMETER;-gotoout;+break;}ret=uv_register_mem_slot(kvm->arch.lpid,memslot->base_gfn<<PAGE_SHIFT,
@@ -245,9 +311,12 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm)if(ret<0){kvmppc_uvmem_slot_free(kvm,memslot);ret=H_PARAMETER;-gotoout;+break;}}++if(ret)+kvmppc_enable_page_merge(kvm);out:srcu_read_unlock(&kvm->srcu,srcu_idx);returnret;
The Ultravisor is expected to explicitly call H_SVM_PAGE_IN for all the pages
of the SVM before calling H_SVM_INIT_DONE. This causes a huge delay in
tranistioning the VM to SVM. The Ultravisor is interested in the pages that
contain the kernel, initrd and other important data structures. The rest of the
pages contain throw-away content. Hence requesting just the necessary and
sufficient pages from the Hypervisor is sufficient.
However if not all pages are requested by the Ultravisor, the Hypervisor
continues to consider the GFNs corresponding to the non-requested pages as
normal GFNs. This can lead to data-corruption and undefined behavior.
Move all the PFNs associated with the SVM's GFNs to secure-PFNs, in
H_SVM_INIT_DONE. Skip the GFNs that are already Paged-in or Shared or
Paged-in followed by a Paged-out.
Cc: Paul Mackerras <redacted>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Bharata B Rao <redacted>
Cc: Aneesh Kumar K.V <redacted>
Cc: Sukadev Bhattiprolu <redacted>
Cc: Laurent Dufour <redacted>
Cc: Thiago Jung Bauermann <redacted>
Cc: David Gibson <redacted>
Cc: Claudio Carvalho <redacted>
Cc: kvm-ppc@vger.kernel.org
Cc: linuxppc-dev@lists.ozlabs.org
Signed-off-by: Ram Pai <redacted>
---
Documentation/powerpc/ultravisor.rst | 2 +
arch/powerpc/include/asm/kvm_book3s_uvmem.h | 2 +
arch/powerpc/kvm/book3s_hv_uvmem.c | 166 +++++++++++++++++++++++++---
3 files changed, 156 insertions(+), 14 deletions(-)
@@ -933,6 +933,8 @@ Return values* H_UNSUPPORTED if called from the wrong context (e.g. from an SVM or before an H_SVM_INIT_START hypercall).+* H_STATE if the hypervisor could not successfully+ transition the VM to Secure VM. Description ~~~~~~~~~~~
@@ -461,12 +499,31 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm)unsignedlongkvmppc_h_svm_init_done(structkvm*kvm){+structkvm_memslots*slots;+structkvm_memory_slot*memslot;+intsrcu_idx;+longret=H_SUCCESS;+if(!(kvm->arch.secure_guest&KVMPPC_SECURE_INIT_START))returnH_UNSUPPORTED;+/* migrate any unmoved normal pfn to device pfns*/+srcu_idx=srcu_read_lock(&kvm->srcu);+slots=kvm_memslots(kvm);+kvm_for_each_memslot(memslot,slots){+ret=kvmppc_uv_migrate_mem_slot(kvm,memslot);+if(ret){+ret=H_STATE;+gotoout;+}+}+kvm->arch.secure_guest|=KVMPPC_SECURE_INIT_DONE;pr_info("LPID %d went secure\n",kvm->arch.lpid);-returnH_SUCCESS;++out:+srcu_read_unlock(&kvm->srcu,srcu_idx);+returnret;}/*
From: Laurent Dufour <redacted>
When a memory slot is hot plugged to a SVM, PFNs associated with the
GFNs in that slot must be migrated to the secure-PFNs, aka device-PFNs.
kvmppc_uv_migrate_mem_slot() is called to accomplish this. UV_PAGE_IN
ucall is skipped, since the ultravisor does not trust the content of
those pages and hence ignores it.
Signed-off-by: Laurent Dufour <redacted>
Signed-off-by: Ram Pai <redacted>
[resolved conflicts, and modified the commit log]
---
arch/powerpc/kvm/book3s_hv.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
The page requested for page-in; sometimes, can have transient
references, and hence cannot migrate immediately. Retry a few times
before returning error.
H_SVM_PAGE_IN interface is enhanced to return H_BUSY if the page is
not in a migratable state.
Cc: Paul Mackerras <redacted>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Bharata B Rao <redacted>
Cc: Aneesh Kumar K.V <redacted>
Cc: Sukadev Bhattiprolu <redacted>
Cc: Laurent Dufour <redacted>
Cc: Thiago Jung Bauermann <redacted>
Cc: David Gibson <redacted>
Cc: Claudio Carvalho <redacted>
Cc: kvm-ppc@vger.kernel.org
Cc: linuxppc-dev@lists.ozlabs.org
Signed-off-by: Ram Pai <redacted>
---
Documentation/powerpc/ultravisor.rst | 1 +
arch/powerpc/kvm/book3s_hv_uvmem.c | 54 +++++++++++++++++++++---------------
2 files changed, 33 insertions(+), 22 deletions(-)
@@ -1034,6 +1034,7 @@ Return values* H_PARAMETER if ``guest_pa`` is invalid.* H_P2 if ``flags`` is invalid.* H_P3 if ``order`` of page is invalid.+* H_BUSY if ``page`` is not in a state to pagein Description ~~~~~~~~~~~
@@ -843,7 +843,7 @@ unsigned long kvmppc_h_svm_page_in(struct kvm *kvm, unsigned long gpa,structvm_area_struct*vma;intsrcu_idx;unsignedlonggfn=gpa>>page_shift;-intret;+intret,repeat_count=REPEAT_COUNT;if(!(kvm->arch.secure_guest&KVMPPC_SECURE_INIT_START))returnH_UNSUPPORTED;
@@ -857,34 +857,44 @@ unsigned long kvmppc_h_svm_page_in(struct kvm *kvm, unsigned long gpa,if(flags&H_PAGE_IN_SHARED)returnkvmppc_share_page(kvm,gpa,page_shift);-ret=H_PARAMETER;srcu_idx=srcu_read_lock(&kvm->srcu);-down_write(&kvm->mm->mmap_sem);-start=gfn_to_hva(kvm,gfn);-if(kvm_is_error_hva(start))-gotoout;--mutex_lock(&kvm->arch.uvmem_lock);/* Fail the page-in request of an already paged-in page */-if(kvmppc_gfn_is_uvmem_pfn(gfn,kvm,NULL))-gotoout_unlock;+mutex_lock(&kvm->arch.uvmem_lock);+ret=kvmppc_gfn_is_uvmem_pfn(gfn,kvm,NULL);+mutex_unlock(&kvm->arch.uvmem_lock);+if(ret){+srcu_read_unlock(&kvm->srcu,srcu_idx);+returnH_PARAMETER;+}-end=start+(1UL<<page_shift);-vma=find_vma_intersection(kvm->mm,start,end);-if(!vma||vma->vm_start>start||vma->vm_end<end)-gotoout_unlock;+do{+ret=H_PARAMETER;+down_write(&kvm->mm->mmap_sem);-if(kvmppc_svm_migrate_page(vma,start,end,gpa,kvm,page_shift,-true))-gotoout_unlock;+start=gfn_to_hva(kvm,gfn);+if(kvm_is_error_hva(start)){+up_write(&kvm->mm->mmap_sem);+break;+}-ret=H_SUCCESS;+end=start+(1UL<<page_shift);+vma=find_vma_intersection(kvm->mm,start,end);+if(!vma||vma->vm_start>start||vma->vm_end<end){+up_write(&kvm->mm->mmap_sem);+break;+}++mutex_lock(&kvm->arch.uvmem_lock);+ret=kvmppc_svm_migrate_page(vma,start,end,gpa,kvm,page_shift,true);+mutex_unlock(&kvm->arch.uvmem_lock);++up_write(&kvm->mm->mmap_sem);+}while(ret==-2&&repeat_count--);++if(ret==-2)+ret=H_BUSY;-out_unlock:-mutex_unlock(&kvm->arch.uvmem_lock);-out:-up_write(&kvm->mm->mmap_sem);srcu_read_unlock(&kvm->srcu,srcu_idx);returnret;}
From: Bharata B Rao <hidden> Date: 2020-07-13 05:31:57
On Sat, Jul 11, 2020 at 02:13:43AM -0700, Ram Pai wrote:
Merging of pages associated with each memslot of a SVM is
disabled the page is migrated in H_SVM_PAGE_IN handler.
This operation should have been done much earlier; the moment the VM
is initiated for secure-transition. Delaying this operation, increases
the probability for those pages to acquire new references , making it
impossible to migrate those pages in H_SVM_PAGE_IN handler.
Disable page-migration in H_SVM_INIT_START handling.
While it is a good idea to disable KSM merging for all VMAs during
H_SVM_INIT_START, I am curious if you did observe an actual case of
ksm_madvise() failing which resulted in subsequent H_SVM_PAGE_IN
failing to migrate?
When you rebase the patches against latest upstream you may want to
replace the above and other instances by mmap_write/read_lock().
quoted hunk
+ do {
+ vma = find_vma_intersection(kvm->mm, start, end);
+ if (!vma) {
+ ret = H_STATE;
+ break;
+ }
+ ret = ksm_madvise(vma, vma->vm_start, vma->vm_end,
+ merge_flag, &vma->vm_flags);
+ if (ret) {
+ ret = H_STATE;
+ break;
+ }
+ start = vma->vm_end + 1;
+ } while (end > vma->vm_end);
+
+ up_write(&kvm->mm->mmap_sem);
+ return ret;
+}
+
+static int __kvmppc_page_merge(struct kvm *kvm, bool merge)
+{
+ struct kvm_memslots *slots;
+ struct kvm_memory_slot *memslot;
+ int ret = 0;
+
+ slots = kvm_memslots(kvm);
+ kvm_for_each_memslot(memslot, slots) {
+ ret = kvmppc_memslot_page_merge(kvm, memslot, merge);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static inline int kvmppc_disable_page_merge(struct kvm *kvm)
+{
+ return __kvmppc_page_merge(kvm, false);
+}
+
+static inline int kvmppc_enable_page_merge(struct kvm *kvm)
+{
+ return __kvmppc_page_merge(kvm, true);
+}
+
unsigned long kvmppc_h_svm_init_start(struct kvm *kvm)
{
struct kvm_memslots *slots;
@@ -232,11 +291,18 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm) return H_AUTHORITY; srcu_idx = srcu_read_lock(&kvm->srcu);++ /* disable page-merging for all memslot */+ ret = kvmppc_disable_page_merge(kvm);+ if (ret)+ goto out;++ /* register the memslot */ slots = kvm_memslots(kvm); kvm_for_each_memslot(memslot, slots) { if (kvmppc_uvmem_slot_init(kvm, memslot)) { ret = H_PARAMETER;- goto out;+ break; } ret = uv_register_mem_slot(kvm->arch.lpid, memslot->base_gfn << PAGE_SHIFT,
@@ -245,9 +311,12 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm) if (ret < 0) { kvmppc_uvmem_slot_free(kvm, memslot); ret = H_PARAMETER;- goto out;+ break; } }++ if (ret)+ kvmppc_enable_page_merge(kvm);
Is there any use of enabling KSM merging in the failure path here?
Won't UV terminate the VM if H_SVM_INIT_START fails? If there is no need,
you can do away with some extra routines above.
@@ -384,7 +453,7 @@ static struct page *kvmppc_uvmem_get_page(unsigned long gpa, struct kvm *kvm) */ static int kvmppc_svm_page_in(struct vm_area_struct *vma, unsigned long start, unsigned long end, unsigned long gpa, struct kvm *kvm,- unsigned long page_shift, bool *downgrade)+ unsigned long page_shift) { unsigned long src_pfn, dst_pfn = 0; struct migrate_vma mig;
@@ -400,18 +469,6 @@ static int kvmppc_svm_page_in(struct vm_area_struct *vma, unsigned long start, mig.src = &src_pfn; mig.dst = &dst_pfn;- /*- * We come here with mmap_sem write lock held just for- * ksm_madvise(), otherwise we only need read mmap_sem.- * Hence downgrade to read lock once ksm_madvise() is done.- */- ret = ksm_madvise(vma, vma->vm_start, vma->vm_end,- MADV_UNMERGEABLE, &vma->vm_flags);
I haven't seen the subsequent patches yet, but guess you are
taking care of disabling KSM mering for hot-plugged memory too.
Regards,
Bharata.
From: Bharata B Rao <hidden> Date: 2020-07-13 09:47:30
On Sat, Jul 11, 2020 at 02:13:45AM -0700, Ram Pai wrote:
quoted hunk
The Ultravisor is expected to explicitly call H_SVM_PAGE_IN for all the pages
of the SVM before calling H_SVM_INIT_DONE. This causes a huge delay in
tranistioning the VM to SVM. The Ultravisor is interested in the pages that
contain the kernel, initrd and other important data structures. The rest of the
pages contain throw-away content. Hence requesting just the necessary and
sufficient pages from the Hypervisor is sufficient.
However if not all pages are requested by the Ultravisor, the Hypervisor
continues to consider the GFNs corresponding to the non-requested pages as
normal GFNs. This can lead to data-corruption and undefined behavior.
Move all the PFNs associated with the SVM's GFNs to secure-PFNs, in
H_SVM_INIT_DONE. Skip the GFNs that are already Paged-in or Shared or
Paged-in followed by a Paged-out.
Cc: Paul Mackerras <redacted>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Bharata B Rao <redacted>
Cc: Aneesh Kumar K.V <redacted>
Cc: Sukadev Bhattiprolu <redacted>
Cc: Laurent Dufour <redacted>
Cc: Thiago Jung Bauermann <redacted>
Cc: David Gibson <redacted>
Cc: Claudio Carvalho <redacted>
Cc: kvm-ppc@vger.kernel.org
Cc: linuxppc-dev@lists.ozlabs.org
Signed-off-by: Ram Pai <redacted>
---
Documentation/powerpc/ultravisor.rst | 2 +
arch/powerpc/include/asm/kvm_book3s_uvmem.h | 2 +
arch/powerpc/kvm/book3s_hv_uvmem.c | 166 +++++++++++++++++++++++++---
3 files changed, 156 insertions(+), 14 deletions(-)
@@ -933,6 +933,8 @@ Return values* H_UNSUPPORTED if called from the wrong context (e.g. from an SVM or before an H_SVM_INIT_START hypercall).+* H_STATE if the hypervisor could not successfully+ transition the VM to Secure VM. Description ~~~~~~~~~~~
@@ -461,12 +499,31 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm)unsignedlongkvmppc_h_svm_init_done(structkvm*kvm){+structkvm_memslots*slots;+structkvm_memory_slot*memslot;+intsrcu_idx;+longret=H_SUCCESS;+if(!(kvm->arch.secure_guest&KVMPPC_SECURE_INIT_START))returnH_UNSUPPORTED;+/* migrate any unmoved normal pfn to device pfns*/+srcu_idx=srcu_read_lock(&kvm->srcu);+slots=kvm_memslots(kvm);+kvm_for_each_memslot(memslot,slots){+ret=kvmppc_uv_migrate_mem_slot(kvm,memslot);+if(ret){+ret=H_STATE;+gotoout;+}+}+kvm->arch.secure_guest|=KVMPPC_SECURE_INIT_DONE;pr_info("LPID %d went secure\n",kvm->arch.lpid);-returnH_SUCCESS;++out:+srcu_read_unlock(&kvm->srcu,srcu_idx);+returnret;}/*
@@ -613,7 +672,7 @@ static int kvmppc_svm_page_in(struct vm_area_struct *vma, unsigned long start,returnret;if(!(*mig.src&MIGRATE_PFN_MIGRATE)){-ret=-1;+ret=-2;
migrate_vma_setup() has marked that this pfn can't be migrated. What
transient errors are you observing which will disappear within 10
retries?
Also till now when UV used to pull in all the pages, we never seemed to
have hit these transient errors. But now when HV is pushing the same
pages, we see these errors which are disappearing after 10 retries.
Can you explain this more please? What sort of pages are these?
quoted hunk
goto out_finalize;
}
@@ -623,11 +682,16 @@ static int kvmppc_svm_page_in(struct vm_area_struct *vma, unsigned long start, goto out_finalize; }- pfn = *mig.src >> MIGRATE_PFN_SHIFT;- spage = migrate_pfn_to_page(*mig.src);- if (spage)- uv_page_in(kvm->arch.lpid, pfn << page_shift, gpa, 0,- page_shift);+ if (pagein) {+ pfn = *mig.src >> MIGRATE_PFN_SHIFT;+ spage = migrate_pfn_to_page(*mig.src);+ if (spage) {+ ret = uv_page_in(kvm->arch.lpid, pfn << page_shift,+ gpa, 0, page_shift);+ if (ret)+ goto out_finalize;+ }+ } *mig.dst = migrate_pfn(page_to_pfn(dpage)) | MIGRATE_PFN_LOCKED; migrate_vma_pages(&mig);
@@ -637,6 +701,77 @@ static int kvmppc_svm_page_in(struct vm_area_struct *vma, unsigned long start, } /*+ * return 1, if some page migration failed because of transient error,+ * while the remaining pages migrated successfully.+ * The caller can use this as a hint to retry.+ *+ * return 0 otherwise. *ret indicates the success status+ * of this call.+ */+static int __kvmppc_uv_migrate_mem_slot(struct kvm *kvm,+ const struct kvm_memory_slot *memslot, int *ret)+{+ unsigned long gfn = memslot->base_gfn;+ struct vm_area_struct *vma;+ unsigned long start, end;+ bool retry = 0;++ *ret = 0;+ while (kvmppc_next_nontransitioned_gfn(memslot, kvm, &gfn)) {++ down_write(&kvm->mm->mmap_sem);
Acquiring and releasing mmap_sem in a loop? Any reason?
Now that you have moved ksm_madvise() calls to init time, any specific
reason to take write mmap_sem here?
From: Bharata B Rao <hidden> Date: 2020-07-13 09:53:04
On Sat, Jul 11, 2020 at 02:13:46AM -0700, Ram Pai wrote:
The page requested for page-in; sometimes, can have transient
references, and hence cannot migrate immediately. Retry a few times
before returning error.
As I noted in the previous patch, we need to understand what are these
transient errors and they occur on what type of pages?
The previous patch also introduced a bit of retry logic in the
page-in path. Can you consolidate the retry logic into a separate
patch?
quoted hunk
H_SVM_PAGE_IN interface is enhanced to return H_BUSY if the page is
not in a migratable state.
Cc: Paul Mackerras <redacted>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Bharata B Rao <redacted>
Cc: Aneesh Kumar K.V <redacted>
Cc: Sukadev Bhattiprolu <redacted>
Cc: Laurent Dufour <redacted>
Cc: Thiago Jung Bauermann <redacted>
Cc: David Gibson <redacted>
Cc: Claudio Carvalho <redacted>
Cc: kvm-ppc@vger.kernel.org
Cc: linuxppc-dev@lists.ozlabs.org
Signed-off-by: Ram Pai <redacted>
---
Documentation/powerpc/ultravisor.rst | 1 +
arch/powerpc/kvm/book3s_hv_uvmem.c | 54 +++++++++++++++++++++---------------
2 files changed, 33 insertions(+), 22 deletions(-)
@@ -1034,6 +1034,7 @@ Return values* H_PARAMETER if ``guest_pa`` is invalid.* H_P2 if ``flags`` is invalid.* H_P3 if ``order`` of page is invalid.+* H_BUSY if ``page`` is not in a state to pagein Description ~~~~~~~~~~~
@@ -843,7 +843,7 @@ unsigned long kvmppc_h_svm_page_in(struct kvm *kvm, unsigned long gpa,structvm_area_struct*vma;intsrcu_idx;unsignedlonggfn=gpa>>page_shift;-intret;+intret,repeat_count=REPEAT_COUNT;if(!(kvm->arch.secure_guest&KVMPPC_SECURE_INIT_START))returnH_UNSUPPORTED;
@@ -857,34 +857,44 @@ unsigned long kvmppc_h_svm_page_in(struct kvm *kvm, unsigned long gpa,if(flags&H_PAGE_IN_SHARED)returnkvmppc_share_page(kvm,gpa,page_shift);-ret=H_PARAMETER;srcu_idx=srcu_read_lock(&kvm->srcu);-down_write(&kvm->mm->mmap_sem);-start=gfn_to_hva(kvm,gfn);-if(kvm_is_error_hva(start))-gotoout;--mutex_lock(&kvm->arch.uvmem_lock);/* Fail the page-in request of an already paged-in page */-if(kvmppc_gfn_is_uvmem_pfn(gfn,kvm,NULL))-gotoout_unlock;+mutex_lock(&kvm->arch.uvmem_lock);+ret=kvmppc_gfn_is_uvmem_pfn(gfn,kvm,NULL);+mutex_unlock(&kvm->arch.uvmem_lock);+if(ret){+srcu_read_unlock(&kvm->srcu,srcu_idx);+returnH_PARAMETER;+}-end=start+(1UL<<page_shift);-vma=find_vma_intersection(kvm->mm,start,end);-if(!vma||vma->vm_start>start||vma->vm_end<end)-gotoout_unlock;+do{+ret=H_PARAMETER;+down_write(&kvm->mm->mmap_sem);
Again with ksm_madvise() moved to init time, check if you still need
write mmap_sem here.
Regards,
Bharata.
On Mon, Jul 13, 2020 at 03:15:06PM +0530, Bharata B Rao wrote:
On Sat, Jul 11, 2020 at 02:13:45AM -0700, Ram Pai wrote:
quoted
The Ultravisor is expected to explicitly call H_SVM_PAGE_IN for all the pages
if (!(*mig.src & MIGRATE_PFN_MIGRATE)) {
- ret = -1;
+ ret = -2;
migrate_vma_setup() has marked that this pfn can't be migrated. What
transient errors are you observing which will disappear within 10
retries?
Also till now when UV used to pull in all the pages, we never seemed to
have hit these transient errors. But now when HV is pushing the same
pages, we see these errors which are disappearing after 10 retries.
Can you explain this more please? What sort of pages are these?
We did see them even before this patch. The retry alleviates the
problem, but does not entirely eliminate it. If the chance of seeing
the issue without the patch is 1%, the chance of seeing this issue
with this patch becomes 0.25%.
Acquiring and releasing mmap_sem in a loop? Any reason?
Now that you have moved ksm_madvise() calls to init time, any specific
reason to take write mmap_sem here?
The semaphore protects the vma. right?
And its acquired/released in the loop, to provide the ability to relinquish
the cpu without getting into a tight loop and cause softlockups.
On Mon, Jul 13, 2020 at 03:20:43PM +0530, Bharata B Rao wrote:
On Sat, Jul 11, 2020 at 02:13:46AM -0700, Ram Pai wrote:
quoted
The page requested for page-in; sometimes, can have transient
references, and hence cannot migrate immediately. Retry a few times
before returning error.
As I noted in the previous patch, we need to understand what are these
transient errors and they occur on what type of pages?
Its not clear when they occur. But they occur quite irregularly and they
do occur on pages that were shared in the previous boot.
The previous patch also introduced a bit of retry logic in the
page-in path. Can you consolidate the retry logic into a separate
patch?
yes. having the retry logic in a seperate patch gives us the flexibility
to drop it, if needed. The retry patch is not the core element of this
patch series.
RP
On Mon, Jul 13, 2020 at 10:59:41AM +0530, Bharata B Rao wrote:
On Sat, Jul 11, 2020 at 02:13:43AM -0700, Ram Pai wrote:
quoted
Merging of pages associated with each memslot of a SVM is
disabled the page is migrated in H_SVM_PAGE_IN handler.
This operation should have been done much earlier; the moment the VM
is initiated for secure-transition. Delaying this operation, increases
the probability for those pages to acquire new references , making it
impossible to migrate those pages in H_SVM_PAGE_IN handler.
Disable page-migration in H_SVM_INIT_START handling.
While it is a good idea to disable KSM merging for all VMAs during
H_SVM_INIT_START, I am curious if you did observe an actual case of
ksm_madvise() failing which resulted in subsequent H_SVM_PAGE_IN
failing to migrate?
No. I did not find any ksm_madvise() failing. But it did not make sense
to ksm_madvise() everytime a page_in was requested. Hence i proposed
this patch. H_SVM_INIT_START is the right place for ksm_advise().
When you rebase the patches against latest upstream you may want to
replace the above and other instances by mmap_write/read_lock().
ok.
quoted
+ do {
+ vma = find_vma_intersection(kvm->mm, start, end);
+ if (!vma) {
+ ret = H_STATE;
+ break;
+ }
+ ret = ksm_madvise(vma, vma->vm_start, vma->vm_end,
+ merge_flag, &vma->vm_flags);
+ if (ret) {
+ ret = H_STATE;
+ break;
+ }
+ start = vma->vm_end + 1;
+ } while (end > vma->vm_end);
+
+ up_write(&kvm->mm->mmap_sem);
+ return ret;
+}
+
+static int __kvmppc_page_merge(struct kvm *kvm, bool merge)
+{
+ struct kvm_memslots *slots;
+ struct kvm_memory_slot *memslot;
+ int ret = 0;
+
+ slots = kvm_memslots(kvm);
+ kvm_for_each_memslot(memslot, slots) {
+ ret = kvmppc_memslot_page_merge(kvm, memslot, merge);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static inline int kvmppc_disable_page_merge(struct kvm *kvm)
+{
+ return __kvmppc_page_merge(kvm, false);
+}
+
+static inline int kvmppc_enable_page_merge(struct kvm *kvm)
+{
+ return __kvmppc_page_merge(kvm, true);
+}
+
unsigned long kvmppc_h_svm_init_start(struct kvm *kvm)
{
struct kvm_memslots *slots;
@@ -232,11 +291,18 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm) return H_AUTHORITY; srcu_idx = srcu_read_lock(&kvm->srcu);++ /* disable page-merging for all memslot */+ ret = kvmppc_disable_page_merge(kvm);+ if (ret)+ goto out;++ /* register the memslot */ slots = kvm_memslots(kvm); kvm_for_each_memslot(memslot, slots) { if (kvmppc_uvmem_slot_init(kvm, memslot)) { ret = H_PARAMETER;- goto out;+ break; } ret = uv_register_mem_slot(kvm->arch.lpid, memslot->base_gfn << PAGE_SHIFT,
@@ -245,9 +311,12 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm) if (ret < 0) { kvmppc_uvmem_slot_free(kvm, memslot); ret = H_PARAMETER;- goto out;+ break; } }++ if (ret)+ kvmppc_enable_page_merge(kvm);
Is there any use of enabling KSM merging in the failure path here?
Won't UV terminate the VM if H_SVM_INIT_START fails? If there is no need,
you can do away with some extra routines above.
UV will terminate it. But I did not want to tie that assumption into
this function.
@@ -384,7 +453,7 @@ static struct page *kvmppc_uvmem_get_page(unsigned long gpa, struct kvm *kvm) */ static int kvmppc_svm_page_in(struct vm_area_struct *vma, unsigned long start, unsigned long end, unsigned long gpa, struct kvm *kvm,- unsigned long page_shift, bool *downgrade)+ unsigned long page_shift) { unsigned long src_pfn, dst_pfn = 0; struct migrate_vma mig;
@@ -400,18 +469,6 @@ static int kvmppc_svm_page_in(struct vm_area_struct *vma, unsigned long start, mig.src = &src_pfn; mig.dst = &dst_pfn;- /*- * We come here with mmap_sem write lock held just for- * ksm_madvise(), otherwise we only need read mmap_sem.- * Hence downgrade to read lock once ksm_madvise() is done.- */- ret = ksm_madvise(vma, vma->vm_start, vma->vm_end,- MADV_UNMERGEABLE, &vma->vm_flags);
I haven't seen the subsequent patches yet, but guess you are
taking care of disabling KSM mering for hot-plugged memory too.
No. This is a good catch. The hotplugged memory patch needs to disable
KSM aswell.
RP
From: Bharata B Rao <hidden> Date: 2020-07-15 07:38:08
On Tue, Jul 14, 2020 at 10:16:14PM -0700, Ram Pai wrote:
On Mon, Jul 13, 2020 at 10:59:41AM +0530, Bharata B Rao wrote:
quoted
On Sat, Jul 11, 2020 at 02:13:43AM -0700, Ram Pai wrote:
quoted
Merging of pages associated with each memslot of a SVM is
disabled the page is migrated in H_SVM_PAGE_IN handler.
This operation should have been done much earlier; the moment the VM
is initiated for secure-transition. Delaying this operation, increases
the probability for those pages to acquire new references , making it
impossible to migrate those pages in H_SVM_PAGE_IN handler.
Disable page-migration in H_SVM_INIT_START handling.
While it is a good idea to disable KSM merging for all VMAs during
H_SVM_INIT_START, I am curious if you did observe an actual case of
ksm_madvise() failing which resulted in subsequent H_SVM_PAGE_IN
failing to migrate?
No. I did not find any ksm_madvise() failing. But it did not make sense
to ksm_madvise() everytime a page_in was requested. Hence i proposed
this patch. H_SVM_INIT_START is the right place for ksm_advise().
Indeed yes. Then you may want to update the description which currently
seems to imply that this change is being done to avoid issues arising
out of delayed KSM unmerging advice.
When you rebase the patches against latest upstream you may want to
replace the above and other instances by mmap_write/read_lock().
ok.
quoted
quoted
+ do {
+ vma = find_vma_intersection(kvm->mm, start, end);
+ if (!vma) {
+ ret = H_STATE;
+ break;
+ }
+ ret = ksm_madvise(vma, vma->vm_start, vma->vm_end,
+ merge_flag, &vma->vm_flags);
+ if (ret) {
+ ret = H_STATE;
+ break;
+ }
+ start = vma->vm_end + 1;
+ } while (end > vma->vm_end);
+
+ up_write(&kvm->mm->mmap_sem);
+ return ret;
+}
+
+static int __kvmppc_page_merge(struct kvm *kvm, bool merge)
+{
+ struct kvm_memslots *slots;
+ struct kvm_memory_slot *memslot;
+ int ret = 0;
+
+ slots = kvm_memslots(kvm);
+ kvm_for_each_memslot(memslot, slots) {
+ ret = kvmppc_memslot_page_merge(kvm, memslot, merge);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static inline int kvmppc_disable_page_merge(struct kvm *kvm)
+{
+ return __kvmppc_page_merge(kvm, false);
+}
+
+static inline int kvmppc_enable_page_merge(struct kvm *kvm)
+{
+ return __kvmppc_page_merge(kvm, true);
+}
+
unsigned long kvmppc_h_svm_init_start(struct kvm *kvm)
{
struct kvm_memslots *slots;
@@ -232,11 +291,18 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm) return H_AUTHORITY; srcu_idx = srcu_read_lock(&kvm->srcu);++ /* disable page-merging for all memslot */+ ret = kvmppc_disable_page_merge(kvm);+ if (ret)+ goto out;++ /* register the memslot */ slots = kvm_memslots(kvm); kvm_for_each_memslot(memslot, slots) { if (kvmppc_uvmem_slot_init(kvm, memslot)) { ret = H_PARAMETER;- goto out;+ break; } ret = uv_register_mem_slot(kvm->arch.lpid, memslot->base_gfn << PAGE_SHIFT,
@@ -245,9 +311,12 @@ unsigned long kvmppc_h_svm_init_start(struct kvm *kvm) if (ret < 0) { kvmppc_uvmem_slot_free(kvm, memslot); ret = H_PARAMETER;- goto out;+ break; } }++ if (ret)+ kvmppc_enable_page_merge(kvm);
Is there any use of enabling KSM merging in the failure path here?
Won't UV terminate the VM if H_SVM_INIT_START fails? If there is no need,
you can do away with some extra routines above.
UV will terminate it. But I did not want to tie that assumption into
this function.
Hmm ok, but having code around which isn't expected to be executed at all
was my concern.
Regards,
Bharata.
From: Bharata B Rao <hidden> Date: 2020-07-15 08:07:11
On Tue, Jul 14, 2020 at 10:05:41PM -0700, Ram Pai wrote:
On Mon, Jul 13, 2020 at 03:15:06PM +0530, Bharata B Rao wrote:
quoted
On Sat, Jul 11, 2020 at 02:13:45AM -0700, Ram Pai wrote:
quoted
The Ultravisor is expected to explicitly call H_SVM_PAGE_IN for all the pages
if (!(*mig.src & MIGRATE_PFN_MIGRATE)) {
- ret = -1;
+ ret = -2;
migrate_vma_setup() has marked that this pfn can't be migrated. What
transient errors are you observing which will disappear within 10
retries?
Also till now when UV used to pull in all the pages, we never seemed to
have hit these transient errors. But now when HV is pushing the same
pages, we see these errors which are disappearing after 10 retries.
Can you explain this more please? What sort of pages are these?
We did see them even before this patch. The retry alleviates the
problem, but does not entirely eliminate it. If the chance of seeing
the issue without the patch is 1%, the chance of seeing this issue
with this patch becomes 0.25%.
Okay, but may be we should investigate the problem a bit more to
understand why the page migrations are failing before taking this
route?
Acquiring and releasing mmap_sem in a loop? Any reason?
Now that you have moved ksm_madvise() calls to init time, any specific
reason to take write mmap_sem here?
The semaphore protects the vma. right?
We took write lock just for ksm_madvise() and then downgraded to
read. Now that you are moving that to init time, read is sufficient here.
Regards,
Bharata.