From 1687432df0374d7dfdf95a70e577e63d17caed60 Mon Sep 17 00:00:00 2001
From: George Dunlap <george.dunlap@citrix.com>
Date: Thu, 15 Dec 2016 16:47:55 +0800
Subject: [PATCH 2/2] xen/p2m: Make p2m_change_entry_type generic

At the moment, p2m_change_type works by keeping a description of what
the various ranges in the p2m table "should" be.

Instead, make it generic: Keep a list of what the old type you were
trying to change was, and change all entries of that type to the new
type.

If there is already a type change in progress, refuse to initiate a
new type change unless its effects can "stack" properly.  Otherwise
return -EBUSY.

Signed-off-by: George Dunlap <george.dunlap@citrix.com>

---

TODO:
- Split out vram logdirty into a separate p2m type
- Actually count the number of entries
- Modify ept_change_entry_type_global() to return -EBUSY if necessary
- Implement a "p2m_finish_type_change" function / hypercall to clear
syncronously out the old type change
---
 xen/arch/x86/mm/p2m-ept.c | 92 ++++++++++++++++++++++++++---------------------
 xen/include/asm-x86/p2m.h |  5 +++
 2 files changed, 56 insertions(+), 41 deletions(-)

diff --git a/xen/arch/x86/mm/p2m-ept.c b/xen/arch/x86/mm/p2m-ept.c
index aba80d5..2a503de 100644
--- a/xen/arch/x86/mm/p2m-ept.c
+++ b/xen/arch/x86/mm/p2m-ept.c
@@ -394,7 +394,8 @@ static inline void misconfigure_epte(ept_entry_t *e)
     e->emt = MTRR_NUM_TYPES;
 }
 
-static bool_t ept_misconfigure_table(mfn_t mfn, bool_t recalc, int level)
+static bool_t ept_misconfigure_table(struct p2m_domain *p2m, mfn_t mfn,
+                                     bool_t recalc, int level)
 {
     int rc;
     ept_entry_t *epte = map_domain_page(mfn);
@@ -553,11 +554,10 @@ static int resolve_misconfig(struct p2m_domain *p2m, unsigned long gfn)
                      * Figure out what the type should be by looking at the current
                      * logdirty range
                      */
-                    if ( e.recalc && p2m_is_changeable(e.sa_p2mt) )
+                    if ( e.recalc && e.sa_p2mt == p2m->recalc.ot )
                     {
-                         e.sa_p2mt = p2m_is_logdirty_range(p2m, gfn + i, gfn + i)
-                                     ? p2m_ram_logdirty : p2m_ram_rw;
-                         ept_p2m_type_to_flags(p2m, &e, e.sa_p2mt, e.access);
+                        e.sa_p2mt = p2m->recalc.nt;
+                        ept_p2m_type_to_flags(p2m, &e, e.sa_p2mt, e.access);
                     }
                     e.recalc = 0;
                     wrc = atomic_write_ept_entry(&epte[i], e, level);
@@ -569,38 +569,11 @@ static int resolve_misconfig(struct p2m_domain *p2m, unsigned long gfn)
                 int emt = epte_get_entry_emt(p2m->domain, gfn, _mfn(e.mfn),
                                              level * EPT_TABLE_ORDER, &ipat,
                                              e.sa_p2mt == p2m_mmio_direct);
-                bool_t recalc = e.recalc;
 
-                /* 
-                 * Figure out what the type should be by looking at the current
-                 * logdirty range; if the whole range isn't one or the other, we
-                 * have to split up the superpage and try again
-                 */
-                if ( recalc && p2m_is_changeable(e.sa_p2mt) )
-                {
-                     unsigned long mask = ~0UL << (level * EPT_TABLE_ORDER);
-
-                     switch ( p2m_is_logdirty_range(p2m, gfn & mask,
-                                                    gfn | ~mask) )
-                     {
-                     case 0:
-                          e.sa_p2mt = p2m_ram_rw;
-                          e.recalc = 0;
-                          break;
-                     case 1:
-                          e.sa_p2mt = p2m_ram_logdirty;
-                          e.recalc = 0;
-                          break;
-                     default: /* Force split. */
-                          emt = -1;
-                          break;
-                     }
-                }
 
                 /* 
-                 * This can happen either due to the logdirty range
-                 * above, or because epte_get_entry_empt() returned -1
-                 * due to the MMIO ranges overlap
+                 * This can happen because epte_get_entry_empt()
+                 * returned -1 due to the MMIO ranges overlap
                  */
                 if ( unlikely(emt < 0) )
                 {
@@ -618,9 +591,12 @@ static int resolve_misconfig(struct p2m_domain *p2m, unsigned long gfn)
                 }
                 e.emt = emt;
                 e.ipat = ipat;
-                e.recalc = 0;
-                if ( recalc && p2m_is_changeable(e.sa_p2mt) )
+                if ( e.recalc && e.sa_p2mt == p2m->recalc.ot )
+                {
+                    e.sa_p2mt = p2m->recalc.nt;
                     ept_p2m_type_to_flags(p2m, &e, e.sa_p2mt, e.access);
+                }
+                e.recalc = 0;
                 wrc = atomic_write_ept_entry(&epte[i], e, level);
                 ASSERT(wrc == 0);
             }
@@ -640,7 +616,7 @@ static int resolve_misconfig(struct p2m_domain *p2m, unsigned long gfn)
         if ( is_epte_misconfigured(&e) )
         {
             ASSERT(is_epte_present(&e));
-            ept_misconfigure_table(_mfn(e.mfn), e.recalc, level);
+            ept_misconfigure_table(p2m, _mfn(e.mfn), e.recalc, level);
             smp_wmb();
             e.emt = 0;
             e.recalc = 0;
@@ -995,8 +971,7 @@ static mfn_t ept_get_entry(struct p2m_domain *p2m,
     {
         if ( (recalc || ept_entry->recalc) &&
              p2m_is_changeable(ept_entry->sa_p2mt) )
-            *t = p2m_is_logdirty_range(p2m, gfn, gfn) ? p2m_ram_logdirty
-                                                      : p2m_ram_rw;
+            *t = p2m->recalc.nt;
         else
             *t = ept_entry->sa_p2mt;
         *a = ept_entry->access;
@@ -1075,6 +1050,27 @@ out:
     return;
 }
 
+static int check_change_entry(struct p2m_domain *p2m,
+                              p2m_type_t ot, p2m_type_t nt)
+{
+    /* 
+     * Using misconfigure to changentry types is safe under the
+     * following conditions:
+     * 1. There are no outstanding 'recalc' entries, or
+     * 2. We're re-iterating the change done before (i.e., 
+     *    we're in the middle of a->b, and we do a->b again)
+     * 3. We're reversing a change done before (i.e.,
+     *    we did a->b and now we want to do b->a).
+     */
+    if ( p2m->recalc.count == 0 ||
+         (p2m->recalc.ot == ot && p2m->recalc.nt == nt) ||
+         (p2m->recalc.ot == nt && p2m->recalc.nt == ot) )
+        return 0;
+
+    return -EBUSY;
+         
+}
+
 static void ept_change_entry_type_global(struct p2m_domain *p2m,
                                          p2m_type_t ot, p2m_type_t nt)
 {
@@ -1083,7 +1079,13 @@ static void ept_change_entry_type_global(struct p2m_domain *p2m,
     if ( !mfn )
         return;
 
-    if ( ept_misconfigure_table(_mfn(mfn), 1, ept_get_wl(&p2m->ept)) )
+    /* FIXME: Change to return -EBUSY */
+    ASSERT(check_change_entry(p2m, ot, nt) == 0);
+
+    p2m->recalc.nt = nt;
+    p2m->recalc.ot = ot;
+
+    if ( ept_misconfigure_table(p2m, _mfn(mfn), 1, ept_get_wl(&p2m->ept)) )
         ept_sync_domain(p2m);
 }
 
@@ -1099,8 +1101,16 @@ static int ept_change_entry_type_range(struct p2m_domain *p2m,
     if ( !ept_get_asr(&p2m->ept) )
         return -EINVAL;
 
+    rc = check_change_entry(p2m, ot, nt);
+    if ( rc != 0 )
+        return rc;
+    
+    p2m->recalc.nt = nt;
+    p2m->recalc.ot = ot;
+
     for ( i = 0; i <= wl; )
     {
+        // FIXME: Change only the necessary entries
         if ( first_gfn & mask )
         {
             unsigned long end_gfn = min(first_gfn | mask, last_gfn);
@@ -1141,7 +1151,7 @@ static void ept_memory_type_changed(struct p2m_domain *p2m)
     if ( !mfn )
         return;
 
-    if ( ept_misconfigure_table(_mfn(mfn), 0, ept_get_wl(&p2m->ept)) )
+    if ( ept_misconfigure_table(p2m, _mfn(mfn), 0, ept_get_wl(&p2m->ept)) )
         ept_sync_domain(p2m);
 }
 
diff --git a/xen/include/asm-x86/p2m.h b/xen/include/asm-x86/p2m.h
index 7035860..7890263 100644
--- a/xen/include/asm-x86/p2m.h
+++ b/xen/include/asm-x86/p2m.h
@@ -336,6 +336,11 @@ struct p2m_domain {
         struct ept_data ept;
         /* NPT-equivalent structure could be added here. */
     };
+
+    struct {
+        p2m_type_t ot, nt;
+        unsigned long count;
+    } recalc;
 };
 
 /* get host p2m table */
-- 
2.10.0

