x86: fix pinned cache attribute handling
authorJan Beulich <jbeulich@suse.com>
Thu, 10 Apr 2014 14:07:17 +0000 (16:07 +0200)
committerJan Beulich <jbeulich@suse.com>
Thu, 10 Apr 2014 14:07:17 +0000 (16:07 +0200)
- make sure UC- is only used for PAT purposes (MTRRs and hence EPT
  don't have this type)
- add order input to "get", and properly handle conflict case (forcing
  an EPT page split)
- properly detect (and refuse) overlaps during "set"
- properly use RCU constructs
- support deleting ranges through a special type input to "set"
- set ignore-PAT flag in epte_get_entry_emt() when "get" succeeds
- set "get" output to ~0 (invalid) rather than 0 (UC) on error (the
  caller shouldn't be looking at it anyway)
- move struct hvm_mem_pinned_cacheattr_range from header to C file
  (used only there)

Note that the code (before and after this change) implies the GFN
ranges passed to the hypercall to be inclusive, which is in contrast
to the sole current user in qemu (all variants). It is not clear to me
at which layer (qemu, libxc, hypervisor) this would best be fixed.

Signed-off-by: Jan Beulich <jbeulich@suse.com>
Reviewed-by: Tim Deegan <tim@xen.org>
Acked-by: Kevin Tian <kevin.tian@intel.com>
xen/arch/x86/hvm/mtrr.c
xen/arch/x86/mm/shadow/multi.c
xen/include/asm-x86/hvm/cacheattr.h
xen/include/public/domctl.h

index 956272656f53e4d36864a486774641be6896eae5..d494fedbd582519bfa8201c913b54fb7d86e3aa9 100644 (file)
@@ -551,6 +551,15 @@ bool_t mtrr_pat_not_equal(struct vcpu *vd, struct vcpu *vs)
     return 0;
 }
 
+struct hvm_mem_pinned_cacheattr_range {
+    struct list_head list;
+    uint64_t start, end;
+    uint32_t type;
+    struct rcu_head rcu;
+};
+
+static DEFINE_RCU_READ_LOCK(pinned_cacheattr_rcu_lock);
+
 void hvm_init_cacheattr_region_list(
     struct domain *d)
 {
@@ -573,30 +582,47 @@ void hvm_destroy_cacheattr_region_list(
     }
 }
 
-int32_t hvm_get_mem_pinned_cacheattr(
+int hvm_get_mem_pinned_cacheattr(
     struct domain *d,
     uint64_t guest_fn,
+    unsigned int order,
     uint32_t *type)
 {
     struct hvm_mem_pinned_cacheattr_range *range;
+    int rc = 0;
 
-    *type = 0;
+    *type = ~0;
 
     if ( !is_hvm_domain(d) )
         return 0;
 
+    rcu_read_lock(&pinned_cacheattr_rcu_lock);
     list_for_each_entry_rcu ( range,
                               &d->arch.hvm_domain.pinned_cacheattr_ranges,
                               list )
     {
-        if ( (guest_fn >= range->start) && (guest_fn <= range->end) )
+        if ( (guest_fn >= range->start) &&
+             (guest_fn + (1UL << order) - 1 <= range->end) )
         {
             *type = range->type;
-            return 1;
+            rc = 1;
+            break;
+        }
+        if ( (guest_fn <= range->end) &&
+             (range->start <= guest_fn + (1UL << order) - 1) )
+        {
+            rc = -1;
+            break;
         }
     }
+    rcu_read_unlock(&pinned_cacheattr_rcu_lock);
 
-    return 0;
+    return rc;
+}
+
+static void free_pinned_cacheattr_entry(struct rcu_head *rcu)
+{
+    xfree(container_of(rcu, struct hvm_mem_pinned_cacheattr_range, rcu));
 }
 
 int32_t hvm_set_mem_pinned_cacheattr(
@@ -606,6 +632,28 @@ int32_t hvm_set_mem_pinned_cacheattr(
     uint32_t  type)
 {
     struct hvm_mem_pinned_cacheattr_range *range;
+    int rc = 1;
+
+    if ( !is_hvm_domain(d) || gfn_end < gfn_start )
+        return 0;
+
+    if ( type == XEN_DOMCTL_DELETE_MEM_CACHEATTR )
+    {
+        /* Remove the requested range. */
+        rcu_read_lock(&pinned_cacheattr_rcu_lock);
+        list_for_each_entry_rcu ( range,
+                                  &d->arch.hvm_domain.pinned_cacheattr_ranges,
+                                  list )
+            if ( range->start == gfn_start && range->end == gfn_end )
+            {
+                rcu_read_unlock(&pinned_cacheattr_rcu_lock);
+                list_del_rcu(&range->list);
+                call_rcu(&range->rcu, free_pinned_cacheattr_entry);
+                return 0;
+            }
+        rcu_read_unlock(&pinned_cacheattr_rcu_lock);
+        return -ENOENT;
+    }
 
     if ( !((type == PAT_TYPE_UNCACHABLE) ||
            (type == PAT_TYPE_WRCOMB) ||
@@ -616,6 +664,27 @@ int32_t hvm_set_mem_pinned_cacheattr(
          !is_hvm_domain(d) )
         return -EINVAL;
 
+    rcu_read_lock(&pinned_cacheattr_rcu_lock);
+    list_for_each_entry_rcu ( range,
+                              &d->arch.hvm_domain.pinned_cacheattr_ranges,
+                              list )
+    {
+        if ( range->start == gfn_start && range->end == gfn_end )
+        {
+            range->type = type;
+            rc = 0;
+            break;
+        }
+        if ( range->start <= gfn_end && gfn_start <= range->end )
+        {
+            rc = -EBUSY;
+            break;
+        }
+    }
+    rcu_read_unlock(&pinned_cacheattr_rcu_lock);
+    if ( rc <= 0 )
+        return rc;
+
     range = xzalloc(struct hvm_mem_pinned_cacheattr_range);
     if ( range == NULL )
         return -ENOMEM;
@@ -732,8 +801,14 @@ int epte_get_entry_emt(struct domain *d, unsigned long gfn, mfn_t mfn,
     if ( !mfn_valid(mfn_x(mfn)) )
         return MTRR_TYPE_UNCACHABLE;
 
-    if ( hvm_get_mem_pinned_cacheattr(d, gfn, &type) )
-        return type;
+    switch ( hvm_get_mem_pinned_cacheattr(d, gfn, order, &type) )
+    {
+    case 1:
+        *ipat = 1;
+        return type != PAT_TYPE_UC_MINUS ? type : PAT_TYPE_UNCACHABLE;
+    case -1:
+        return -1;
+    }
 
     if ( !iommu_enabled ||
          (rangeset_is_empty(d->iomem_caps) &&
index 9dfa345902aadf0c301b9de052bd23847951cb11..dc906522326fa06cc369cc9d5d9816ae463dbcdb 100644 (file)
@@ -601,7 +601,7 @@ _sh_propagate(struct vcpu *v,
          * 3) if disables snoop control, compute the PAT index with
          *    gMTRR and gPAT.
          */
-        if ( hvm_get_mem_pinned_cacheattr(d, gfn_x(target_gfn), &type) )
+        if ( hvm_get_mem_pinned_cacheattr(d, gfn_x(target_gfn), 0, &type) )
             sflags |= pat_type_2_pte_flags(type);
         else if ( d->arch.hvm_domain.is_in_uc_mode )
             sflags |= pat_type_2_pte_flags(PAT_TYPE_UNCACHABLE);
index 371ce953466cfb88cd03d469801bdbc0ebc2dc2b..ab1ccef07036286301a443c046a9cb2422b35bd0 100644 (file)
@@ -1,12 +1,6 @@
 #ifndef __HVM_CACHEATTR_H__
 #define __HVM_CACHEATTR_H__
 
-struct hvm_mem_pinned_cacheattr_range {
-    struct list_head list;
-    uint64_t start, end;
-    uint32_t type;
-};
-
 void hvm_init_cacheattr_region_list(
     struct domain *d);
 void hvm_destroy_cacheattr_region_list(
@@ -15,11 +9,13 @@ void hvm_destroy_cacheattr_region_list(
 /*
  * To see guest_fn is in the pinned range or not,
  * if yes, return 1, and set type to value in this range
- * if no,  return 0, and set type to 0
+ * if no,  return 0, setting type to ~0
+ * if ambiguous, return -1, setting type to ~0 (possible only for order > 0)
  */
-int32_t hvm_get_mem_pinned_cacheattr(
+int hvm_get_mem_pinned_cacheattr(
     struct domain *d,
     uint64_t guest_fn,
+    unsigned int order,
     uint32_t *type);
 
 
index f22fe2eaa2dc1ed61f1b5f4a685a634b87c6a3b1..869ae30dbeffe319e95407b9b8aa2978b77060f4 100644 (file)
@@ -555,6 +555,7 @@ DEFINE_XEN_GUEST_HANDLE(xen_domctl_ioport_mapping_t);
 #define XEN_DOMCTL_MEM_CACHEATTR_WP  5
 #define XEN_DOMCTL_MEM_CACHEATTR_WB  6
 #define XEN_DOMCTL_MEM_CACHEATTR_UCM 7
+#define XEN_DOMCTL_DELETE_MEM_CACHEATTR (~(uint32_t)0)
 struct xen_domctl_pin_mem_cacheattr {
     uint64_aligned_t start, end;
     uint32_t type; /* XEN_DOMCTL_MEM_CACHEATTR_* */