Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4ae7392a authored by Sean Christopherson's avatar Sean Christopherson Committed by Greg Kroah-Hartman
Browse files

KVM: MMU: Do not treat ZONE_DEVICE pages as being reserved

commit a78986aae9b2988f8493f9f65a587ee433e83bc3 upstream.

Explicitly exempt ZONE_DEVICE pages from kvm_is_reserved_pfn() and
instead manually handle ZONE_DEVICE on a case-by-case basis.  For things
like page refcounts, KVM needs to treat ZONE_DEVICE pages like normal
pages, e.g. put pages grabbed via gup().  But for flows such as setting
A/D bits or shifting refcounts for transparent huge pages, KVM needs to
to avoid processing ZONE_DEVICE pages as the flows in question lack the
underlying machinery for proper handling of ZONE_DEVICE pages.

This fixes a hang reported by Adam Borowski[*] in dev_pagemap_cleanup()
when running a KVM guest backed with /dev/dax memory, as KVM straight up
doesn't put any references to ZONE_DEVICE pages acquired by gup().

Note, Dan Williams proposed an alternative solution of doing put_page()
on ZONE_DEVICE pages immediately after gup() in order to simplify the
auditing needed to ensure is_zone_device_page() is called if and only if
the backing device is pinned (via gup()).  But that approach would break
kvm_vcpu_{un}map() as KVM requires the page to be pinned from map() 'til
unmap() when accessing guest memory, unlike KVM's secondary MMU, which
coordinates with mmu_notifier invalidations to avoid creating stale
page references, i.e. doesn't rely on pages being pinned.

[*] http://lkml.kernel.org/r/20190919115547.GA17963@angband.pl



Reported-by: default avatarAdam Borowski <kilobyte@angband.pl>
Analyzed-by: default avatarDavid Hildenbrand <david@redhat.com>
Acked-by: default avatarDan Williams <dan.j.williams@intel.com>
Cc: stable@vger.kernel.org
Fixes: 3565fce3 ("mm, x86: get_user_pages() for dax mappings")
Signed-off-by: default avatarSean Christopherson <sean.j.christopherson@intel.com>
Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
[sean: backport to 4.x; resolve conflict in mmu.c]
Signed-off-by: default avatarSean Christopherson <sean.j.christopherson@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 03bf4876
Loading
Loading
Loading
Loading
+4 −4
Original line number Original line Diff line number Diff line
@@ -3261,7 +3261,7 @@ static void transparent_hugepage_adjust(struct kvm_vcpu *vcpu,
	 * here.
	 * here.
	 */
	 */
	if (!is_error_noslot_pfn(pfn) && !kvm_is_reserved_pfn(pfn) &&
	if (!is_error_noslot_pfn(pfn) && !kvm_is_reserved_pfn(pfn) &&
	    level == PT_PAGE_TABLE_LEVEL &&
	    !kvm_is_zone_device_pfn(pfn) && level == PT_PAGE_TABLE_LEVEL &&
	    PageTransCompoundMap(pfn_to_page(pfn)) &&
	    PageTransCompoundMap(pfn_to_page(pfn)) &&
	    !mmu_gfn_lpage_is_disallowed(vcpu, gfn, PT_DIRECTORY_LEVEL)) {
	    !mmu_gfn_lpage_is_disallowed(vcpu, gfn, PT_DIRECTORY_LEVEL)) {
		unsigned long mask;
		unsigned long mask;
@@ -5709,8 +5709,8 @@ static bool kvm_mmu_zap_collapsible_spte(struct kvm *kvm,
		 * the guest, and the guest page table is using 4K page size
		 * the guest, and the guest page table is using 4K page size
		 * mapping if the indirect sp has level = 1.
		 * mapping if the indirect sp has level = 1.
		 */
		 */
		if (sp->role.direct &&
		if (sp->role.direct && !kvm_is_reserved_pfn(pfn) &&
			!kvm_is_reserved_pfn(pfn) &&
		    !kvm_is_zone_device_pfn(pfn) &&
		    PageTransCompoundMap(pfn_to_page(pfn))) {
		    PageTransCompoundMap(pfn_to_page(pfn))) {
			drop_spte(kvm, sptep);
			drop_spte(kvm, sptep);
			need_tlb_flush = 1;
			need_tlb_flush = 1;
+1 −0
Original line number Original line Diff line number Diff line
@@ -911,6 +911,7 @@ int kvm_cpu_has_pending_timer(struct kvm_vcpu *vcpu);
void kvm_vcpu_kick(struct kvm_vcpu *vcpu);
void kvm_vcpu_kick(struct kvm_vcpu *vcpu);


bool kvm_is_reserved_pfn(kvm_pfn_t pfn);
bool kvm_is_reserved_pfn(kvm_pfn_t pfn);
bool kvm_is_zone_device_pfn(kvm_pfn_t pfn);


struct kvm_irq_ack_notifier {
struct kvm_irq_ack_notifier {
	struct hlist_node link;
	struct hlist_node link;
+23 −3
Original line number Original line Diff line number Diff line
@@ -147,10 +147,30 @@ __weak int kvm_arch_mmu_notifier_invalidate_range(struct kvm *kvm,
	return 0;
	return 0;
}
}


bool kvm_is_zone_device_pfn(kvm_pfn_t pfn)
{
	/*
	 * The metadata used by is_zone_device_page() to determine whether or
	 * not a page is ZONE_DEVICE is guaranteed to be valid if and only if
	 * the device has been pinned, e.g. by get_user_pages().  WARN if the
	 * page_count() is zero to help detect bad usage of this helper.
	 */
	if (!pfn_valid(pfn) || WARN_ON_ONCE(!page_count(pfn_to_page(pfn))))
		return false;

	return is_zone_device_page(pfn_to_page(pfn));
}

bool kvm_is_reserved_pfn(kvm_pfn_t pfn)
bool kvm_is_reserved_pfn(kvm_pfn_t pfn)
{
{
	/*
	 * ZONE_DEVICE pages currently set PG_reserved, but from a refcounting
	 * perspective they are "normal" pages, albeit with slightly different
	 * usage rules.
	 */
	if (pfn_valid(pfn))
	if (pfn_valid(pfn))
		return PageReserved(pfn_to_page(pfn));
		return PageReserved(pfn_to_page(pfn)) &&
		       !kvm_is_zone_device_pfn(pfn);


	return true;
	return true;
}
}
@@ -1727,7 +1747,7 @@ EXPORT_SYMBOL_GPL(kvm_release_pfn_dirty);


void kvm_set_pfn_dirty(kvm_pfn_t pfn)
void kvm_set_pfn_dirty(kvm_pfn_t pfn)
{
{
	if (!kvm_is_reserved_pfn(pfn)) {
	if (!kvm_is_reserved_pfn(pfn) && !kvm_is_zone_device_pfn(pfn)) {
		struct page *page = pfn_to_page(pfn);
		struct page *page = pfn_to_page(pfn);


		if (!PageReserved(page))
		if (!PageReserved(page))
@@ -1738,7 +1758,7 @@ EXPORT_SYMBOL_GPL(kvm_set_pfn_dirty);


void kvm_set_pfn_accessed(kvm_pfn_t pfn)
void kvm_set_pfn_accessed(kvm_pfn_t pfn)
{
{
	if (!kvm_is_reserved_pfn(pfn))
	if (!kvm_is_reserved_pfn(pfn) && !kvm_is_zone_device_pfn(pfn))
		mark_page_accessed(pfn_to_page(pfn));
		mark_page_accessed(pfn_to_page(pfn));
}
}
EXPORT_SYMBOL_GPL(kvm_set_pfn_accessed);
EXPORT_SYMBOL_GPL(kvm_set_pfn_accessed);