diff mbox

[Xen-devel,v2,3/4] xen/arm: clean and invalidate all guest caches by VMID after domain build.

Message ID 1391523745-21139-3-git-send-email-ian.campbell@citrix.com
State New
Headers show

Commit Message

Ian Campbell Feb. 4, 2014, 2:22 p.m. UTC
Guests are initially started with caches disabled and so we need to make sure
they see consistent data in RAM (requiring a cache clean) but also that they
do not have old stale data suddenly appear in the caches when they enable
their caches (requiring the invalidate).

This can be split into two halves. First we must flush each page as it is
allocated to the guest. It is not sufficient to do the flush at scrub time
since this will miss pages which are ballooned out by the guest (where the
guest must scrub if it cares about not leaking the pagecontent). We need to
clean as well as invalidate to make sure that any scrubbing which has occured
gets committed to real RAM. To achieve this add a new cacheflush_page function,
which is a stub on x86.

Secondly we need to flush anything which the domain builder touches, which we
do via a new domctl.

Signed-off-by: Ian Campbell <ian.campbell@citrix.com>
Cc: jbeulich@suse.com
Cc: keir@xen.org
Cc: ian.jackson@eu.citrix.com
--
v2:
   Switch to cleaning at page allocation time + explicit flushing of tha eregions
   which the toolstack touches.

   Add XSM for new domctl.

   New domctl restricts the amount of space it is willing to flush, to avoid
   thinking about preemption.
---
 tools/libxc/xc_dom_boot.c           |    4 ++++
 tools/libxc/xc_dom_core.c           |    3 +++
 tools/libxc/xc_domain.c             |   10 ++++++++++
 tools/libxc/xc_private.c            |    2 ++
 tools/libxc/xenctrl.h               |    3 ++-
 xen/arch/arm/domctl.c               |   14 ++++++++++++++
 xen/arch/arm/mm.c                   |    9 +++++++++
 xen/arch/arm/p2m.c                  |   24 ++++++++++++++++++++++++
 xen/common/page_alloc.c             |    5 +++++
 xen/include/asm-arm/p2m.h           |    3 +++
 xen/include/asm-arm/page.h          |    3 +++
 xen/include/asm-x86/page.h          |    3 +++
 xen/include/public/domctl.h         |   13 +++++++++++++
 xen/xsm/flask/hooks.c               |    3 +++
 xen/xsm/flask/policy/access_vectors |    2 ++
 15 files changed, 100 insertions(+), 1 deletion(-)

Comments

Ian Campbell Feb. 4, 2014, 3:37 p.m. UTC | #1
On Tue, 2014-02-04 at 15:32 +0000, Ian Jackson wrote:
> Ian Campbell writes ("[PATCH v2 3/4] xen/arm: clean and invalidate all guest caches by VMID after domain build."):
> > Guests are initially started with caches disabled and so we need to make sure
> > they see consistent data in RAM (requiring a cache clean) but also that they
> > do not have old stale data suddenly appear in the caches when they enable
> > their caches (requiring the invalidate).
> > 
> > This can be split into two halves. First we must flush each page as it is
> > allocated to the guest. It is not sufficient to do the flush at scrub time
> > since this will miss pages which are ballooned out by the guest (where the
> > guest must scrub if it cares about not leaking the pagecontent). We need to
> > clean as well as invalidate to make sure that any scrubbing which has occured
> > gets committed to real RAM. To achieve this add a new cacheflush_page function,
> > which is a stub on x86.
> 
> > diff --git a/tools/libxc/xc_dom_core.c b/tools/libxc/xc_dom_core.c
> > index 77a4e64..306b414 100644
> > --- a/tools/libxc/xc_dom_core.c
> > +++ b/tools/libxc/xc_dom_core.c
> > @@ -603,6 +603,9 @@ void xc_dom_unmap_one(struct xc_dom_image *dom, xen_pfn_t pfn)
> >          prev->next = phys->next;
> >      else
> >          dom->phys_pages = phys->next;
> > +
> > +    xc_domain_cacheflush(dom->xch, dom->guest_domid,
> > +                         phys->first, phys->first + phys->count);
> >  }
> 
> The approach you are taking here is that for pages explicitly mapped
> by some libxc caller, you do the flush on unmap.  But what about
> callers who don't unmap ?  Are there callers which don't unmap and
> which instead are relying on memory coherency assumptions which aren't
> true on arm ?

Callers which don't unmap would be leaking mappings and therefore buggy
in a long running toolstack.

Also after the initial start of day period we require that the guest
enable its caches.

> Aside from this question, the code in this patch looks fine to me.

Thanks.
Ian Campbell Feb. 4, 2014, 3:43 p.m. UTC | #2
On Tue, 2014-02-04 at 15:00 +0000, Jan Beulich wrote:
> >>> On 04.02.14 at 15:22, Ian Campbell <ian.campbell@citrix.com> wrote:
> > --- a/tools/libxc/xc_dom_boot.c
> > +++ b/tools/libxc/xc_dom_boot.c
> > @@ -351,6 +351,10 @@ int xc_dom_gnttab_seed(xc_interface *xch, domid_t domid,
> >          return -1;
> >      }
> >  
> > +    /* Guest shouldn't really touch its grant table until it has
> > +     * enabled its caches. But lets be nice. */
> > +    xc_domain_cacheflush(xch, domid, gnttab_gmfn, gnttab_gmfn + 1);
> 
> Looking at this and further similar code I think it would be cleaner
> for the xc interface to take a start MFN and a count, and for the
> hypervisor interface to use an inclusive range (such that overflow
> is not a problem).

I think you might be right, I'll give that a go.

> 
> > --- a/xen/include/asm-x86/page.h
> > +++ b/xen/include/asm-x86/page.h
> > @@ -346,6 +346,9 @@ static inline uint32_t cacheattr_to_pte_flags(uint32_t cacheattr)
> >      return ((cacheattr & 4) << 5) | ((cacheattr & 3) << 3);
> >  }
> >  
> > +/* No cache maintenance required on x86 architecture. */
> > +static inline void cacheflush_page(unsigned long mfn) {}
> 
> The function name is certainly sub-optimal:

Yes, I should have said that I wasn't really happy with it in the
context of x86.

>  If I needed a page-range
> cache flush and found a function named like this, I'd assume it does
> what its name says - flush the page from the cache. sync_page() or
> some such may be better suited to express that this is something
> that may be a no-op on certain architectures.

sync_page is a much better suggestion. Perhaps even
sync_page_to/from_cache, I suppose that might actually be more
confusing.

Ian.
Ian Campbell Feb. 4, 2014, 4:07 p.m. UTC | #3
On Tue, 2014-02-04 at 15:48 +0000, Jan Beulich wrote:
> >>> On 04.02.14 at 16:43, Ian Campbell <Ian.Campbell@citrix.com> wrote:
> > sync_page is a much better suggestion. Perhaps even
> > sync_page_to/from_cache, I suppose that might actually be more
> > confusing.
> 
> Hmm, on one hand it's even more precise, but otoh - how would a
> sync_page_to_cache() look like?

That suggests moving something into the cache, where this function is
actually pushing something out of the cache and into RAM.

sync_page_to_ram then? I think that is better than _from_cache.

Ian.
Ian Campbell Feb. 4, 2014, 4:09 p.m. UTC | #4
On Tue, 2014-02-04 at 15:55 +0000, Ian Jackson wrote:
> Ian Campbell writes ("Re: [PATCH v2 3/4] xen/arm: clean and invalidate all guest caches by VMID after domain build."):
> > On Tue, 2014-02-04 at 15:32 +0000, Ian Jackson wrote:
> > > The approach you are taking here is that for pages explicitly mapped
> > > by some libxc caller, you do the flush on unmap.  But what about
> > > callers who don't unmap ?  Are there callers which don't unmap and
> > > which instead are relying on memory coherency assumptions which aren't
> > > true on arm ?
> > 
> > Callers which don't unmap would be leaking mappings and therefore buggy
> > in a long running toolstack.
> 
> What I mean is that they might map the guest pages, and expect to
> exchange data with the guest through the pages while they were still
> mapped ...
> 
> > Also after the initial start of day period we require that the guest
> > enable its caches.
> 
> ... but before the guest enables caching.

We basically rule that out in the ABI requirements.

The cases where this would happen are things like xenstore and
xenconsole but those are driven from the guest end, which requires the
caches to be on.

Ian.
diff mbox

Patch

diff --git a/tools/libxc/xc_dom_boot.c b/tools/libxc/xc_dom_boot.c
index 5a9cfc6..3b3f2fb 100644
--- a/tools/libxc/xc_dom_boot.c
+++ b/tools/libxc/xc_dom_boot.c
@@ -351,6 +351,10 @@  int xc_dom_gnttab_seed(xc_interface *xch, domid_t domid,
         return -1;
     }
 
+    /* Guest shouldn't really touch its grant table until it has
+     * enabled its caches. But lets be nice. */
+    xc_domain_cacheflush(xch, domid, gnttab_gmfn, gnttab_gmfn + 1);
+
     return 0;
 }
 
diff --git a/tools/libxc/xc_dom_core.c b/tools/libxc/xc_dom_core.c
index 77a4e64..306b414 100644
--- a/tools/libxc/xc_dom_core.c
+++ b/tools/libxc/xc_dom_core.c
@@ -603,6 +603,9 @@  void xc_dom_unmap_one(struct xc_dom_image *dom, xen_pfn_t pfn)
         prev->next = phys->next;
     else
         dom->phys_pages = phys->next;
+
+    xc_domain_cacheflush(dom->xch, dom->guest_domid,
+                         phys->first, phys->first + phys->count);
 }
 
 void xc_dom_unmap_all(struct xc_dom_image *dom)
diff --git a/tools/libxc/xc_domain.c b/tools/libxc/xc_domain.c
index c2fdd74..092d610 100644
--- a/tools/libxc/xc_domain.c
+++ b/tools/libxc/xc_domain.c
@@ -48,6 +48,16 @@  int xc_domain_create(xc_interface *xch,
     return 0;
 }
 
+int xc_domain_cacheflush(xc_interface *xch, uint32_t domid,
+                         xen_pfn_t start_pfn, xen_pfn_t end_pfn)
+{
+    DECLARE_DOMCTL;
+    domctl.cmd = XEN_DOMCTL_cacheflush;
+    domctl.domain = (domid_t)domid;
+    domctl.u.cacheflush.start_pfn = start_pfn;
+    domctl.u.cacheflush.end_pfn = end_pfn;
+    return do_domctl(xch, &domctl);
+}
 
 int xc_domain_pause(xc_interface *xch,
                     uint32_t domid)
diff --git a/tools/libxc/xc_private.c b/tools/libxc/xc_private.c
index 838fd21..556810f 100644
--- a/tools/libxc/xc_private.c
+++ b/tools/libxc/xc_private.c
@@ -628,6 +628,7 @@  int xc_copy_to_domain_page(xc_interface *xch,
         return -1;
     memcpy(vaddr, src_page, PAGE_SIZE);
     munmap(vaddr, PAGE_SIZE);
+    xc_domain_cacheflush(xch, domid, dst_pfn, dst_pfn+1);
     return 0;
 }
 
@@ -641,6 +642,7 @@  int xc_clear_domain_page(xc_interface *xch,
         return -1;
     memset(vaddr, 0, PAGE_SIZE);
     munmap(vaddr, PAGE_SIZE);
+    xc_domain_cacheflush(xch, domid, dst_pfn, dst_pfn+1);
     return 0;
 }
 
diff --git a/tools/libxc/xenctrl.h b/tools/libxc/xenctrl.h
index 13f816b..80c397e 100644
--- a/tools/libxc/xenctrl.h
+++ b/tools/libxc/xenctrl.h
@@ -453,7 +453,8 @@  int xc_domain_create(xc_interface *xch,
                      xen_domain_handle_t handle,
                      uint32_t flags,
                      uint32_t *pdomid);
-
+int xc_domain_cacheflush(xc_interface *xch, uint32_t domid,
+			 xen_pfn_t start_pfn, xen_pfn_t end_pfn);
 
 /* Functions to produce a dump of a given domain
  *  xc_domain_dumpcore - produces a dump to a specified file
diff --git a/xen/arch/arm/domctl.c b/xen/arch/arm/domctl.c
index 546e86b..8916e49 100644
--- a/xen/arch/arm/domctl.c
+++ b/xen/arch/arm/domctl.c
@@ -17,6 +17,20 @@  long arch_do_domctl(struct xen_domctl *domctl, struct domain *d,
 {
     switch ( domctl->cmd )
     {
+    case XEN_DOMCTL_cacheflush:
+    {
+        unsigned long s = domctl->u.cacheflush.start_pfn;
+        unsigned long e = domctl->u.cacheflush.end_pfn;
+
+        if ( e < s )
+            return -EINVAL;
+
+        if ( get_order_from_pages(e-s) > MAX_ORDER )
+            return -EINVAL;
+
+        return p2m_cache_flush(d, s, e);
+    }
+
     default:
         return subarch_do_domctl(domctl, d, u_domctl);
     }
diff --git a/xen/arch/arm/mm.c b/xen/arch/arm/mm.c
index 2f48347..0c1a7b9 100644
--- a/xen/arch/arm/mm.c
+++ b/xen/arch/arm/mm.c
@@ -338,6 +338,15 @@  unsigned long domain_page_map_to_mfn(const void *va)
 }
 #endif
 
+void cacheflush_page(unsigned long mfn)
+{
+    void *v = map_domain_page(mfn);
+
+    flush_xen_dcache_va_range(v, PAGE_SIZE);
+
+    unmap_domain_page(v);
+}
+
 void __init arch_init_memory(void)
 {
     /*
diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c
index a61edeb..d452814 100644
--- a/xen/arch/arm/p2m.c
+++ b/xen/arch/arm/p2m.c
@@ -8,6 +8,7 @@ 
 #include <asm/gic.h>
 #include <asm/event.h>
 #include <asm/hardirq.h>
+#include <asm/page.h>
 
 /* First level P2M is 2 consecutive pages */
 #define P2M_FIRST_ORDER 1
@@ -228,6 +229,7 @@  enum p2m_operation {
     ALLOCATE,
     REMOVE,
     RELINQUISH,
+    CACHEFLUSH,
 };
 
 static int apply_p2m_changes(struct domain *d,
@@ -381,6 +383,14 @@  static int apply_p2m_changes(struct domain *d,
                     count++;
                 }
                 break;
+
+            case CACHEFLUSH:
+                {
+                    if ( !pte.p2m.valid || !p2m_is_ram(pte.p2m.type) )
+                        break;
+                    cacheflush_page(pte.p2m.base);
+                }
+                break;
         }
 
         /* Preempt every 2MiB (mapped) or 32 MiB (unmapped) - arbitrary */
@@ -624,6 +634,20 @@  int relinquish_p2m_mapping(struct domain *d)
                               MATTR_MEM, p2m_invalid);
 }
 
+int p2m_cache_flush(struct domain *d, xen_pfn_t start_mfn, xen_pfn_t end_mfn)
+{
+    struct p2m_domain *p2m = &d->arch.p2m;
+
+    start_mfn = MAX(start_mfn, p2m->lowest_mapped_gfn);
+    end_mfn = MIN(end_mfn, p2m->max_mapped_gfn);
+
+    return apply_p2m_changes(d, CACHEFLUSH,
+                             pfn_to_paddr(start_mfn),
+                             pfn_to_paddr(end_mfn),
+                             pfn_to_paddr(INVALID_MFN),
+                             MATTR_MEM, p2m_invalid);
+}
+
 unsigned long gmfn_to_mfn(struct domain *d, unsigned long gpfn)
 {
     paddr_t p = p2m_lookup(d, pfn_to_paddr(gpfn), NULL);
diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c
index 5f484a2..16bb739 100644
--- a/xen/common/page_alloc.c
+++ b/xen/common/page_alloc.c
@@ -710,6 +710,11 @@  static struct page_info *alloc_heap_pages(
         /* Initialise fields which have other uses for free pages. */
         pg[i].u.inuse.type_info = 0;
         page_set_owner(&pg[i], NULL);
+
+        /* Ensure cache and RAM are consistent for platforms where the
+         * guest can control its own visibility of/through the cache.
+         */
+        cacheflush_page(page_to_mfn(&pg[i]));
     }
 
     spin_unlock(&heap_lock);
diff --git a/xen/include/asm-arm/p2m.h b/xen/include/asm-arm/p2m.h
index e9c884a..3b39c45 100644
--- a/xen/include/asm-arm/p2m.h
+++ b/xen/include/asm-arm/p2m.h
@@ -78,6 +78,9 @@  void p2m_load_VTTBR(struct domain *d);
 /* Look up the MFN corresponding to a domain's PFN. */
 paddr_t p2m_lookup(struct domain *d, paddr_t gpfn, p2m_type_t *t);
 
+/* Clean & invalidate caches corresponding to a region of guest address space */
+int p2m_cache_flush(struct domain *d, xen_pfn_t start_mfn, xen_pfn_t end_mfn);
+
 /* Setup p2m RAM mapping for domain d from start-end. */
 int p2m_populate_ram(struct domain *d, paddr_t start, paddr_t end);
 /* Map MMIO regions in the p2m: start_gaddr and end_gaddr is the range
diff --git a/xen/include/asm-arm/page.h b/xen/include/asm-arm/page.h
index 670d4e7..342dde8 100644
--- a/xen/include/asm-arm/page.h
+++ b/xen/include/asm-arm/page.h
@@ -253,6 +253,9 @@  static inline void flush_xen_dcache_va_range(void *p, unsigned long size)
             : : "r" (_p), "m" (*_p));                                   \
 } while (0)
 
+/* Flush the dcache for an entire page. */
+void cacheflush_page(unsigned long mfn);
+
 /* Print a walk of an arbitrary page table */
 void dump_pt_walk(lpae_t *table, paddr_t addr);
 
diff --git a/xen/include/asm-x86/page.h b/xen/include/asm-x86/page.h
index 7a46af5..271517f 100644
--- a/xen/include/asm-x86/page.h
+++ b/xen/include/asm-x86/page.h
@@ -346,6 +346,9 @@  static inline uint32_t cacheattr_to_pte_flags(uint32_t cacheattr)
     return ((cacheattr & 4) << 5) | ((cacheattr & 3) << 3);
 }
 
+/* No cache maintenance required on x86 architecture. */
+static inline void cacheflush_page(unsigned long mfn) {}
+
 /* return true if permission increased */
 static inline bool_t
 perms_strictly_increased(uint32_t old_flags, uint32_t new_flags)
diff --git a/xen/include/public/domctl.h b/xen/include/public/domctl.h
index 91f01fa..d8d8727 100644
--- a/xen/include/public/domctl.h
+++ b/xen/include/public/domctl.h
@@ -885,6 +885,17 @@  struct xen_domctl_set_max_evtchn {
 typedef struct xen_domctl_set_max_evtchn xen_domctl_set_max_evtchn_t;
 DEFINE_XEN_GUEST_HANDLE(xen_domctl_set_max_evtchn_t);
 
+/*
+ * ARM: Clean and invalidate caches associated with given region of
+ * guest memory.
+ */
+struct xen_domctl_cacheflush {
+    /* IN: page range to flush: [start, end). */
+    xen_pfn_t start_pfn, end_pfn;
+};
+typedef struct xen_domctl_cacheflush xen_domctl_cacheflush_t;
+DEFINE_XEN_GUEST_HANDLE(xen_domctl_cacheflush_t);
+
 struct xen_domctl {
     uint32_t cmd;
 #define XEN_DOMCTL_createdomain                   1
@@ -954,6 +965,7 @@  struct xen_domctl {
 #define XEN_DOMCTL_setnodeaffinity               68
 #define XEN_DOMCTL_getnodeaffinity               69
 #define XEN_DOMCTL_set_max_evtchn                70
+#define XEN_DOMCTL_cacheflush                    71
 #define XEN_DOMCTL_gdbsx_guestmemio            1000
 #define XEN_DOMCTL_gdbsx_pausevcpu             1001
 #define XEN_DOMCTL_gdbsx_unpausevcpu           1002
@@ -1012,6 +1024,7 @@  struct xen_domctl {
         struct xen_domctl_set_max_evtchn    set_max_evtchn;
         struct xen_domctl_gdbsx_memio       gdbsx_guest_memio;
         struct xen_domctl_set_broken_page_p2m set_broken_page_p2m;
+        struct xen_domctl_cacheflush        cacheflush;
         struct xen_domctl_gdbsx_pauseunp_vcpu gdbsx_pauseunp_vcpu;
         struct xen_domctl_gdbsx_domstatus   gdbsx_domstatus;
         uint8_t                             pad[128];
diff --git a/xen/xsm/flask/hooks.c b/xen/xsm/flask/hooks.c
index 50a35fc..1345d7e 100644
--- a/xen/xsm/flask/hooks.c
+++ b/xen/xsm/flask/hooks.c
@@ -737,6 +737,9 @@  static int flask_domctl(struct domain *d, int cmd)
     case XEN_DOMCTL_set_max_evtchn:
         return current_has_perm(d, SECCLASS_DOMAIN2, DOMAIN2__SET_MAX_EVTCHN);
 
+    case XEN_DOMCTL_cacheflush:
+	    return current_has_perm(d, SECCLASS_DOMAIN2, DOMAIN2__CACHEFLUSH);
+
     default:
         printk("flask_domctl: Unknown op %d\n", cmd);
         return -EPERM;
diff --git a/xen/xsm/flask/policy/access_vectors b/xen/xsm/flask/policy/access_vectors
index 1fbe241..a0ed13d 100644
--- a/xen/xsm/flask/policy/access_vectors
+++ b/xen/xsm/flask/policy/access_vectors
@@ -196,6 +196,8 @@  class domain2
     setclaim
 # XEN_DOMCTL_set_max_evtchn
     set_max_evtchn
+# XEN_DOMCTL_cacheflush
+    cacheflush
 }
 
 # Similar to class domain, but primarily contains domctls related to HVM domains