[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[xen stable-4.22] x86/PoD: map one page at a time in p2m_pod_zero_check()



commit 47b8f2047c44ce1268af98a944242ab58963322c
Author:     George Dunlap <gwd@xxxxxxxxxxxxxx>
AuthorDate: Thu Oct 29 13:20:04 2026 +0100
Commit:     Jan Beulich <jbeulich@xxxxxxxx>
CommitDate: Thu Oct 29 13:20:04 2026 +0100

    x86/PoD: map one page at a time in p2m_pod_zero_check()
    
    p2m_pod_zero_check() maps every page of its batch that passes its
    checks, up to POD_SWEEP_STRIDE (16) of them, and keeps them mapped
    across the p2m lookups and updates which follow, each of which maps
    page-table pages of its own.  Its callers hold mappings too: the p2m
    lookup which found the entry to populate still has its page-table page
    mapped, and a PV mmu_update() the L1 page it is writing.
    
    map_domain_page() cannot always provide that many at once.  It uses the
    mapcache on behalf of PV vCPUs only.  The mapcache has
    MAPCACHE_VCPU_ENTRIES (16) slots per vCPU of the domain, shared by its
    vCPUs, and a mapping which finds no slot free or reclaimable hits the
    BUG_ON() in map_domain_page(): the host crashes.
    
    The sweep runs in the context of whichever vCPU populates a PoD entry
    while the domain's PoD cache is empty.  The guest's own vCPUs, being
    HVM, map through the directmap; a PV vCPU gets there when its domain
    maps or copies the guest's memory: a device model in dom0 or in a stub
    domain, a backend doing grant copies.  A single-vCPU PV domain, such
    as a device-model stub domain or a dom0 booted with dom0_max_vcpus=1,
    has 16 slots, and a sweep over 16 candidate pages needs more.
    
    The sweep has mapped its whole batch at once since 9ad9a1609fbe ("PoD
    memory 5/9: emergency scan"), when x86-64 reached all memory through the
    directmap.  The per-vCPU budget came with the mapcache's return to
    x86-64 in 4b28bf6ae90b ("x86: re-introduce map_domain_page() et al").
    
    Map one page at a time instead, as p2m_pod_zero_check_superpage()
    already does: map a page for the quick check and unmap it again, and
    map it anew for the full check after the p2m update and the TLB flush.
    The checks and their order are unchanged.  Each page is mapped twice,
    which costs little next to scanning it, and the error paths no longer
    have mappings to undo.
    
    Fixes: 4b28bf6ae90b ("x86: re-introduce map_domain_page() et al")
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: George Dunlap <gwd@xxxxxxxxxxxxxx>
    Acked-by: Roger Pau Monné <roger@xxxxxxxxxxxxxx>
    master commit: 41ddcfe808ec1228289ae60c30768d50a58c98fe
    master date: 2026-10-29 09:13:29 +0100
---
 xen/arch/x86/mm/p2m-pod.c | 72 ++++++++++++++++++++---------------------------
 1 file changed, 31 insertions(+), 41 deletions(-)

diff --git a/xen/arch/x86/mm/p2m-pod.c b/xen/arch/x86/mm/p2m-pod.c
index 4602c32cff..ae1210e279 100644
--- a/xen/arch/x86/mm/p2m-pod.c
+++ b/xen/arch/x86/mm/p2m-pod.c
@@ -901,9 +901,10 @@ p2m_pod_zero_check(struct p2m_domain *p2m, const gfn_t 
*gfns, unsigned int count
 {
     mfn_t mfns[POD_SWEEP_STRIDE];
     p2m_type_t types[POD_SWEEP_STRIDE];
-    unsigned long *map[POD_SWEEP_STRIDE];
+    unsigned int check = 0;
+    const unsigned long *map;
     struct domain *d = p2m->domain;
-    unsigned int i, j, max_ref = 1;
+    unsigned int j, max_ref = 1;
 
     BUG_ON(count > POD_SWEEP_STRIDE);
 
@@ -911,8 +912,11 @@ p2m_pod_zero_check(struct p2m_domain *p2m, const gfn_t 
*gfns, unsigned int count
     if ( paging_mode_shadow(d) )
         max_ref++;
 
-    /* First, get the gfn list, translate to mfns, and map the pages. */
-    for ( i = 0; i < count; i++ )
+    /*
+     * First, get the gfn list, translate to mfns, and check for being a
+     * plausible candidate.
+     */
+    for ( unsigned int i = 0; i < count; i++ )
     {
         p2m_access_t a;
 
@@ -921,9 +925,8 @@ p2m_pod_zero_check(struct p2m_domain *p2m, const gfn_t 
*gfns, unsigned int count
 
         /*
          * If this is ram, and not a pagetable or a special page, and
-         * probably not mapped elsewhere, map it; otherwise, skip.
+         * probably not mapped elsewhere, check it; otherwise, skip.
          */
-        map[i] = NULL;
         if ( p2m_is_ram(types[i]) )
         {
             const struct page_info *pg = mfn_to_page(mfns[i]);
@@ -932,7 +935,7 @@ p2m_pod_zero_check(struct p2m_domain *p2m, const gfn_t 
*gfns, unsigned int count
                  (pg->count_info & PGC_allocated) &&
                  !(pg->count_info & PGC_shadowed_pt) &&
                  ((pg->count_info & PGC_count_mask) <= max_ref) )
-                map[i] = map_domain_page(mfns[i]);
+                __set_bit(i, &check);
         }
     }
 
@@ -940,20 +943,23 @@ p2m_pod_zero_check(struct p2m_domain *p2m, const gfn_t 
*gfns, unsigned int count
      * Then, go through and check for zeroed pages, removing write permission
      * for those with zeroes.
      */
-    for ( i = 0; i < count; i++ )
+    for_each_set_bit ( i, check )
     {
-        if ( !map[i] )
-            continue;
-
         /* Quick zero-check */
+        map = map_domain_page(mfns[i]);
         for ( j = 0; j < 16; j++ )
-            if ( *(map[i] + j) != 0 )
-                goto skip;
+            if ( map[j] != 0 )
+                break;
+        unmap_domain_page(map);
 
         /* Try to remove the page, restoring old mapping if it fails. */
-        if ( p2m_set_entry(p2m, gfns[i], INVALID_MFN, PAGE_ORDER_4K,
+        if ( j < 16 ||
+             p2m_set_entry(p2m, gfns[i], INVALID_MFN, PAGE_ORDER_4K,
                            p2m_populate_on_demand, p2m->default_access) )
-            goto skip;
+        {
+            __clear_bit(i, &check);
+            continue;
+        }
 
         /*
          * See if the page was successfully unmapped.  (Allow one refcount
@@ -961,6 +967,8 @@ p2m_pod_zero_check(struct p2m_domain *p2m, const gfn_t 
*gfns, unsigned int count
          */
         if ( (mfn_to_page(mfns[i])->count_info & PGC_count_mask) > 1 )
         {
+            __clear_bit(i, &check);
+
             /*
              * If the previous p2m_set_entry call succeeded, this one shouldn't
              * be able to fail.  If it does, crashing the domain should be 
safe.
@@ -970,38 +978,27 @@ p2m_pod_zero_check(struct p2m_domain *p2m, const gfn_t 
*gfns, unsigned int count
             {
                 ASSERT_UNREACHABLE();
                 domain_crash(d);
-                goto out_unmap;
+                return;
             }
-
-        skip:
-            unmap_domain_page(map[i]);
-            map[i] = NULL;
-
-            continue;
         }
     }
 
     p2m_tlb_flush_sync(p2m);
 
     /* Now check each page for real */
-    for ( i = 0; i < count; i++ )
+    for_each_set_bit ( i, check )
     {
-        if ( !map[i] )
-            continue;
-
-        for ( j = 0; j < (PAGE_SIZE / sizeof(*map[i])); j++ )
-            if ( *(map[i] + j) != 0 )
+        map = map_domain_page(mfns[i]);
+        for ( j = 0; j < (PAGE_SIZE / sizeof(*map)); j++ )
+            if ( map[j] != 0 )
                 break;
-
-        unmap_domain_page(map[i]);
-
-        map[i] = NULL;
+        unmap_domain_page(map);
 
         /*
          * See comment in p2m_pod_zero_check_superpage() re gnttab
          * check timing.
          */
-        if ( j < (PAGE_SIZE / sizeof(*map[i])) )
+        if ( j < (PAGE_SIZE / sizeof(*map)) )
         {
             /*
              * If the previous p2m_set_entry call succeeded, this one shouldn't
@@ -1012,14 +1009,7 @@ p2m_pod_zero_check(struct p2m_domain *p2m, const gfn_t 
*gfns, unsigned int count
             {
                 ASSERT_UNREACHABLE();
                 domain_crash(d);
- out_unmap:
-                /*
-                 * Something went wrong, probably crashing the domain.  Unmap
-                 * everything and return.
-                 */
-                for ( i = 0; i < count; i++ )
-                    if ( map[i] )
-                        unmap_domain_page(map[i]);
+                return;
             }
         }
         else
--
generated by git-patchbot for /home/xen/git/xen.git#stable-4.22



 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.