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

[Xen-devel] [PATCH] x86/mm: Allocate log-dirty bitmaps from shadow/HAP memory



x86/mm: Allocate log-dirty bitmaps from shadow/HAP memory.

Move the p2m alloc and free functions back into the per-domain paging
assistance structure and allow them to be called from the log-dirty
code.  This makes it less likely that log-dirty code will run out of
memory populating the log-dirty bitmap.

Signed-off-by: Tim Deegan <Tim.Deegan@xxxxxxxxxx>

diff -r b76df6b4375a -r 38afe60221d7 xen/arch/x86/mm/hap/hap.c
--- a/xen/arch/x86/mm/hap/hap.c Wed Nov 17 20:40:30 2010 +0000
+++ b/xen/arch/x86/mm/hap/hap.c Thu Nov 18 12:35:46 2010 +0000
@@ -276,12 +276,16 @@ static void hap_free(struct domain *d, m
     page_list_add_tail(pg, &d->arch.paging.hap.freelist);
 }
 
-static struct page_info *hap_alloc_p2m_page(struct p2m_domain *p2m)
+static struct page_info *hap_alloc_p2m_page(struct domain *d)
 {
-    struct domain *d = p2m->domain;
     struct page_info *pg;
+    int do_locking;
 
-    hap_lock(d);
+    /* This is called both from the p2m code (which never holds the 
+     * hap lock) and the log-dirty code (which sometimes does). */
+    do_locking = !hap_locked_by_me(d);
+    if ( do_locking )
+        hap_lock(d);
     pg = hap_alloc(d);
 
 #if CONFIG_PAGING_LEVELS == 3
@@ -312,14 +316,21 @@ static struct page_info *hap_alloc_p2m_p
         pg->count_info |= 1;
     }
 
-    hap_unlock(d);
+    if ( do_locking )
+        hap_unlock(d);
     return pg;
 }
 
-static void hap_free_p2m_page(struct p2m_domain *p2m, struct page_info *pg)
+static void hap_free_p2m_page(struct domain *d, struct page_info *pg)
 {
-    struct domain *d = p2m->domain;
-    hap_lock(d);
+    int do_locking;
+
+    /* This is called both from the p2m code (which never holds the 
+     * hap lock) and the log-dirty code (which sometimes does). */
+    do_locking = !hap_locked_by_me(d);
+    if ( do_locking )
+        hap_lock(d);
+
     ASSERT(page_get_owner(pg) == d);
     /* Should have just the one ref we gave it in alloc_p2m_page() */
     if ( (pg->count_info & PGC_count_mask) != 1 )
@@ -333,7 +344,9 @@ static void hap_free_p2m_page(struct p2m
     d->arch.paging.hap.total_pages++;
     hap_free(d, page_to_mfn(pg));
     ASSERT(d->arch.paging.hap.p2m_pages >= 0);
-    hap_unlock(d);
+
+    if ( do_locking )
+        hap_unlock(d);
 }
 
 /* Return the size of the pool, rounded up to the nearest MB */
@@ -597,11 +610,14 @@ int hap_enable(struct domain *d, u32 mod
         }
     }
 
+    /* Allow p2m and log-dirty code to borrow our memory */
+    d->arch.paging.alloc_page = hap_alloc_p2m_page;
+    d->arch.paging.free_page = hap_free_p2m_page;
+
     /* allocate P2m table */
     if ( mode & PG_translate )
     {
-        rv = p2m_alloc_table(p2m_get_hostp2m(d),
-            hap_alloc_p2m_page, hap_free_p2m_page);
+        rv = p2m_alloc_table(p2m_get_hostp2m(d));
         if ( rv != 0 )
             goto out;
     }
diff -r b76df6b4375a -r 38afe60221d7 xen/arch/x86/mm/hap/p2m-ept.c
--- a/xen/arch/x86/mm/hap/p2m-ept.c     Wed Nov 17 20:40:30 2010 +0000
+++ b/xen/arch/x86/mm/hap/p2m-ept.c     Thu Nov 18 12:35:46 2010 +0000
@@ -125,6 +125,8 @@ static int ept_set_middle_entry(struct p
 /* free ept sub tree behind an entry */
 void ept_free_entry(struct p2m_domain *p2m, ept_entry_t *ept_entry, int level)
 {
+    struct domain *d = p2m->domain;
+
     /* End if the entry is a leaf entry. */
     if ( level == 0 || !is_epte_present(ept_entry) ||
          is_epte_superpage(ept_entry) )
@@ -138,7 +140,7 @@ void ept_free_entry(struct p2m_domain *p
         unmap_domain_page(epte);
     }
 
-    p2m->free_page(p2m, mfn_to_page(ept_entry->mfn));
+    d->arch.paging.free_page(d, mfn_to_page(ept_entry->mfn));
 }
 
 static int ept_split_super_page(struct p2m_domain *p2m, ept_entry_t *ept_entry,
diff -r b76df6b4375a -r 38afe60221d7 xen/arch/x86/mm/p2m.c
--- a/xen/arch/x86/mm/p2m.c     Wed Nov 17 20:40:30 2010 +0000
+++ b/xen/arch/x86/mm/p2m.c     Thu Nov 18 12:35:46 2010 +0000
@@ -142,8 +142,9 @@ p2m_alloc_ptp(struct p2m_domain *p2m, un
     struct page_info *pg;
 
     ASSERT(p2m);
-    ASSERT(p2m->alloc_page);
-    pg = p2m->alloc_page(p2m);
+    ASSERT(p2m->domain);
+    ASSERT(p2m->domain->arch.paging.alloc_page);
+    pg = p2m->domain->arch.paging.alloc_page(p2m->domain);
     if (pg == NULL)
         return NULL;
 
@@ -167,7 +168,6 @@ p2m_next_level(struct p2m_domain *p2m, m
     l1_pgentry_t new_entry;
     void *next;
     int i;
-    ASSERT(p2m->alloc_page);
 
     if ( !(p2m_entry = p2m_find_entry(*table, gfn_remainder, gfn,
                                       shift, max)) )
@@ -1792,14 +1792,9 @@ int set_p2m_entry(struct p2m_domain *p2m
 // The structure of the p2m table is that of a pagetable for xen (i.e. it is
 // controlled by CONFIG_PAGING_LEVELS).
 //
-// The alloc_page and free_page functions will be used to get memory to
-// build the p2m, and to release it again at the end of day.
-//
 // Returns 0 for success or -errno.
 //
-int p2m_alloc_table(struct p2m_domain *p2m,
-               struct page_info * (*alloc_page)(struct p2m_domain *p2m),
-               void (*free_page)(struct p2m_domain *p2m, struct page_info *pg))
+int p2m_alloc_table(struct p2m_domain *p2m)
 {
     mfn_t mfn = _mfn(INVALID_MFN);
     struct page_info *page, *p2m_top;
@@ -1817,9 +1812,6 @@ int p2m_alloc_table(struct p2m_domain *p
 
     P2M_PRINTK("allocating p2m table\n");
 
-    p2m->alloc_page = alloc_page;
-    p2m->free_page = free_page;
-
     p2m_top = p2m_alloc_ptp(p2m,
 #if CONFIG_PAGING_LEVELS == 4
         PGT_l4_page_table
@@ -1882,6 +1874,7 @@ void p2m_teardown(struct p2m_domain *p2m
  * We know we don't have any extra mappings to these pages */
 {
     struct page_info *pg;
+    struct domain *d = p2m->domain;
 #ifdef __x86_64__
     unsigned long gfn;
     p2m_type_t t;
@@ -1902,7 +1895,7 @@ void p2m_teardown(struct p2m_domain *p2m
     p2m->phys_table = pagetable_null();
 
     while ( (pg = page_list_remove_head(&p2m->pages)) )
-        p2m->free_page(p2m, pg);
+        d->arch.paging.free_page(d, pg);
     p2m_unlock(p2m);
 }
 
diff -r b76df6b4375a -r 38afe60221d7 xen/arch/x86/mm/paging.c
--- a/xen/arch/x86/mm/paging.c  Wed Nov 17 20:40:30 2010 +0000
+++ b/xen/arch/x86/mm/paging.c  Thu Nov 18 12:35:46 2010 +0000
@@ -99,10 +99,11 @@ static mfn_t paging_new_log_dirty_page(s
 {
     struct page_info *page;
 
-    page = alloc_domheap_page(NULL, MEMF_node(domain_to_node(d)));
+    page = d->arch.paging.alloc_page(d);
     if ( unlikely(page == NULL) )
     {
         d->arch.paging.log_dirty.failed_allocs++;
+        *mapping_p = NULL;
         return _mfn(INVALID_MFN);
     }
 
@@ -131,30 +132,23 @@ static mfn_t paging_new_log_dirty_node(s
     return mfn;
 }
 
-int paging_alloc_log_dirty_bitmap(struct domain *d)
+mfn_t *paging_map_log_dirty_bitmap(struct domain *d)
 {
     mfn_t *mapping;
 
-    if ( mfn_valid(d->arch.paging.log_dirty.top) )
-        return 0;
+    if ( likely(mfn_valid(d->arch.paging.log_dirty.top)) )
+        return map_domain_page(mfn_x(d->arch.paging.log_dirty.top));
 
     d->arch.paging.log_dirty.top = paging_new_log_dirty_node(d, &mapping);
-    if ( unlikely(!mfn_valid(d->arch.paging.log_dirty.top)) )
-    {
-        /* Clear error indicator since we're reporting this one */
-        d->arch.paging.log_dirty.failed_allocs = 0;
-        return -ENOMEM;
-    }
-    unmap_domain_page(mapping);
 
-    return 0;
+    return mapping;
 }
 
 static void paging_free_log_dirty_page(struct domain *d, mfn_t mfn)
 {
     d->arch.paging.log_dirty.allocs--;
-    free_domheap_page(mfn_to_page(mfn));
-}    
+    d->arch.paging.free_page(d, mfn_to_page(mfn));
+}
 
 void paging_free_log_dirty_bitmap(struct domain *d)
 {
@@ -204,36 +198,13 @@ int paging_log_dirty_enable(struct domai
 {
     int ret;
 
+    if ( paging_mode_log_dirty(d) )
+        return -EINVAL;
+
     domain_pause(d);
-    log_dirty_lock(d);
+    ret = d->arch.paging.log_dirty.enable_log_dirty(d);
+    domain_unpause(d);
 
-    if ( paging_mode_log_dirty(d) )
-    {
-        ret = -EINVAL;
-        goto out;
-    }
-
-    ret = paging_alloc_log_dirty_bitmap(d);
-    if ( ret != 0 )
-    {
-        paging_free_log_dirty_bitmap(d);
-        goto out;
-    }
-
-    log_dirty_unlock(d);
-
-    /* Safe because the domain is paused. */
-    ret = d->arch.paging.log_dirty.enable_log_dirty(d);
-
-    /* Possibility of leaving the bitmap allocated here but it'll be
-     * tidied on domain teardown. */
-
-    domain_unpause(d);
-    return ret;
-
- out:
-    log_dirty_unlock(d);
-    domain_unpause(d);
     return ret;
 }
 
@@ -271,8 +242,6 @@ void paging_mark_dirty(struct domain *d,
 
     log_dirty_lock(d);
 
-    ASSERT(mfn_valid(d->arch.paging.log_dirty.top));
-
     /* We /really/ mean PFN here, even for non-translated guests. */
     pfn = get_gpfn_from_mfn(mfn_x(gmfn));
     /* Shared MFNs should NEVER be marked dirty */
@@ -291,7 +260,9 @@ void paging_mark_dirty(struct domain *d,
     i3 = L3_LOGDIRTY_IDX(pfn);
     i4 = L4_LOGDIRTY_IDX(pfn);
 
-    l4 = map_domain_page(mfn_x(d->arch.paging.log_dirty.top));
+    l4 = paging_map_log_dirty_bitmap(d);
+    if ( !l4 )
+        goto out;
     mfn = l4[i4];
     if ( !mfn_valid(mfn) )
         mfn = l4[i4] = paging_new_log_dirty_node(d, &l3);
@@ -367,12 +338,6 @@ int paging_log_dirty_op(struct domain *d
         /* caller may have wanted just to clean the state or access stats. */
         peek = 0;
 
-    if ( (peek || clean) && !mfn_valid(d->arch.paging.log_dirty.top) )
-    {
-        rv = -EINVAL; /* perhaps should be ENOMEM? */
-        goto out;
-    }
-
     if ( unlikely(d->arch.paging.log_dirty.failed_allocs) ) {
         printk("%s: %d failed page allocs while logging dirty pages\n",
                __FUNCTION__, d->arch.paging.log_dirty.failed_allocs);
@@ -381,8 +346,7 @@ int paging_log_dirty_op(struct domain *d
     }
 
     pages = 0;
-    l4 = (mfn_valid(d->arch.paging.log_dirty.top) ?
-          map_domain_page(mfn_x(d->arch.paging.log_dirty.top)) : NULL);
+    l4 = paging_map_log_dirty_bitmap(d);
 
     for ( i4 = 0;
           (pages < sc->pages) && (i4 < LOGDIRTY_NODE_ENTRIES);
@@ -469,12 +433,6 @@ int paging_log_dirty_range(struct domain
                  d->arch.paging.log_dirty.fault_count,
                  d->arch.paging.log_dirty.dirty_count);
 
-    if ( !mfn_valid(d->arch.paging.log_dirty.top) )
-    {
-        rv = -EINVAL; /* perhaps should be ENOMEM? */
-        goto out;
-    }
-
     if ( unlikely(d->arch.paging.log_dirty.failed_allocs) ) {
         printk("%s: %d failed page allocs while logging dirty pages\n",
                __FUNCTION__, d->arch.paging.log_dirty.failed_allocs);
@@ -500,13 +458,13 @@ int paging_log_dirty_range(struct domain
     b2 = L2_LOGDIRTY_IDX(begin_pfn);
     b3 = L3_LOGDIRTY_IDX(begin_pfn);
     b4 = L4_LOGDIRTY_IDX(begin_pfn);
-    l4 = map_domain_page(mfn_x(d->arch.paging.log_dirty.top));
+    l4 = paging_map_log_dirty_bitmap(d);
 
     for ( i4 = b4;
           (pages < nr) && (i4 < LOGDIRTY_NODE_ENTRIES);
           i4++ )
     {
-        l3 = mfn_valid(l4[i4]) ? map_domain_page(mfn_x(l4[i4])) : NULL;
+        l3 = (l4 && mfn_valid(l4[i4])) ? map_domain_page(mfn_x(l4[i4])) : NULL;
         for ( i3 = b3;
               (pages < nr) && (i3 < LOGDIRTY_NODE_ENTRIES);
               i3++ )
@@ -590,7 +548,8 @@ int paging_log_dirty_range(struct domain
         if ( l3 )
             unmap_domain_page(l3);
     }
-    unmap_domain_page(l4);
+    if ( l4 )
+        unmap_domain_page(l4);
 
     log_dirty_unlock(d);
 
diff -r b76df6b4375a -r 38afe60221d7 xen/arch/x86/mm/shadow/common.c
--- a/xen/arch/x86/mm/shadow/common.c   Wed Nov 17 20:40:30 2010 +0000
+++ b/xen/arch/x86/mm/shadow/common.c   Thu Nov 18 12:35:46 2010 +0000
@@ -1620,12 +1620,16 @@ void shadow_free(struct domain *d, mfn_t
  * That's OK because the p2m table only exists for translated domains,
  * and those domains can't ever turn off shadow mode. */
 static struct page_info *
-shadow_alloc_p2m_page(struct p2m_domain *p2m)
+shadow_alloc_p2m_page(struct domain *d)
 {
-    struct domain *d = p2m->domain;
     struct page_info *pg;
-    
-    shadow_lock(d);
+    int do_locking;
+
+    /* This is called both from the p2m code (which never holds the 
+     * shadow lock) and the log-dirty code (which sometimes does). */
+    do_locking = !shadow_locked_by_me(d);
+    if ( do_locking )
+        shadow_lock(d);
 
     if ( d->arch.paging.shadow.total_pages 
          < shadow_min_acceptable_pages(d) + 1 )
@@ -1639,7 +1643,8 @@ shadow_alloc_p2m_page(struct p2m_domain 
     d->arch.paging.shadow.p2m_pages++;
     d->arch.paging.shadow.total_pages--;
 
-    shadow_unlock(d);
+    if ( do_locking )
+        shadow_unlock(d);
 
     /* Unlike shadow pages, mark p2m pages as owned by the domain.
      * Marking the domain as the owner would normally allow the guest to
@@ -1652,9 +1657,10 @@ shadow_alloc_p2m_page(struct p2m_domain 
 }
 
 static void
-shadow_free_p2m_page(struct p2m_domain *p2m, struct page_info *pg)
+shadow_free_p2m_page(struct domain *d, struct page_info *pg)
 {
-    struct domain *d = p2m->domain;
+    int do_locking;
+
     ASSERT(page_get_owner(pg) == d);
     /* Should have just the one ref we gave it in alloc_p2m_page() */
     if ( (pg->count_info & PGC_count_mask) != 1 )
@@ -1666,11 +1672,18 @@ shadow_free_p2m_page(struct p2m_domain *
     pg->u.sh.type = SH_type_p2m_table; /* p2m code reuses type-info */
     page_set_owner(pg, NULL); 
 
-    shadow_lock(d);
+    /* This is called both from the p2m code (which never holds the 
+     * shadow lock) and the log-dirty code (which sometimes does). */
+    do_locking = !shadow_locked_by_me(d);
+    if ( do_locking )
+        shadow_lock(d);
+
     shadow_free(d, page_to_mfn(pg));
     d->arch.paging.shadow.p2m_pages--;
     d->arch.paging.shadow.total_pages++;
-    shadow_unlock(d);
+
+    if ( do_locking )
+        shadow_unlock(d);
 }
 
 #if CONFIG_PAGING_LEVELS == 3
@@ -3032,6 +3045,7 @@ static void sh_new_mode(struct domain *d
 
     ASSERT(shadow_locked_by_me(d));
     ASSERT(d != current->domain);
+
     d->arch.paging.mode = new_mode;
     for_each_vcpu(d, v)
         sh_update_paging_modes(v);
@@ -3079,12 +3093,15 @@ int shadow_enable(struct domain *d, u32 
         shadow_unlock(d);
     }
 
+    /* Allow p2m and log-dirty code to borrow shadow memory */
+    d->arch.paging.alloc_page = shadow_alloc_p2m_page;
+    d->arch.paging.free_page = shadow_free_p2m_page;
+
     /* Init the P2M table.  Must be done before we take the shadow lock 
      * to avoid possible deadlock. */
     if ( mode & PG_translate )
     {
-        rv = p2m_alloc_table(p2m,
-            shadow_alloc_p2m_page, shadow_free_p2m_page);
+        rv = p2m_alloc_table(p2m);
         if (rv != 0)
             goto out_unlocked;
     }
@@ -3095,7 +3112,7 @@ int shadow_enable(struct domain *d, u32 
     {
         /* Get a single page from the shadow pool.  Take it via the 
          * P2M interface to make freeing it simpler afterwards. */
-        pg = shadow_alloc_p2m_page(p2m);
+        pg = shadow_alloc_p2m_page(d);
         if ( pg == NULL )
         {
             rv = -ENOMEM;
@@ -3147,7 +3164,7 @@ int shadow_enable(struct domain *d, u32 
     if ( rv != 0 && !pagetable_is_null(p2m_get_pagetable(p2m)) )
         p2m_teardown(p2m);
     if ( rv != 0 && pg != NULL )
-        shadow_free_p2m_page(p2m, pg);
+        shadow_free_p2m_page(d, pg);
     domain_unpause(d);
     return rv;
 }
@@ -3265,7 +3282,7 @@ void shadow_teardown(struct domain *d)
 
     /* Must be called outside the lock */
     if ( unpaged_pagetable ) 
-        shadow_free_p2m_page(p2m_get_hostp2m(d), unpaged_pagetable);
+        shadow_free_p2m_page(d, unpaged_pagetable);
 }
 
 void shadow_final_teardown(struct domain *d)
@@ -3320,6 +3337,10 @@ static int shadow_one_bit_enable(struct 
             sh_set_allocation(d, 0, NULL);
             return -ENOMEM;
         }
+
+        /* Allow p2m and log-dirty code to borrow shadow memory */
+        d->arch.paging.alloc_page = shadow_alloc_p2m_page;
+        d->arch.paging.free_page = shadow_free_p2m_page;
     }
 
     /* Update the bits */
diff -r b76df6b4375a -r 38afe60221d7 xen/arch/x86/mm/shadow/private.h
--- a/xen/arch/x86/mm/shadow/private.h  Wed Nov 17 20:40:30 2010 +0000
+++ b/xen/arch/x86/mm/shadow/private.h  Thu Nov 18 12:35:46 2010 +0000
@@ -584,7 +584,6 @@ sh_mfn_is_dirty(struct domain *d, mfn_t 
     int rv;
 
     ASSERT(shadow_mode_log_dirty(d));
-    ASSERT(mfn_valid(d->arch.paging.log_dirty.top));
 
     /* We /really/ mean PFN here, even for non-translated guests. */
     pfn = get_gpfn_from_mfn(mfn_x(gmfn));
@@ -602,7 +601,10 @@ sh_mfn_is_dirty(struct domain *d, mfn_t 
          */
         return 1;
 
-    l4 = map_domain_page(mfn_x(d->arch.paging.log_dirty.top));
+    l4 = paging_map_log_dirty_bitmap(d);
+    if ( !l4 ) 
+        return 0;
+
     mfn = l4[L4_LOGDIRTY_IDX(pfn)];
     unmap_domain_page(l4);
     if ( !mfn_valid(mfn) )
diff -r b76df6b4375a -r 38afe60221d7 xen/include/asm-x86/domain.h
--- a/xen/include/asm-x86/domain.h      Wed Nov 17 20:40:30 2010 +0000
+++ b/xen/include/asm-x86/domain.h      Thu Nov 18 12:35:46 2010 +0000
@@ -201,6 +201,10 @@ struct paging_domain {
     struct hap_domain       hap;
     /* log dirty support */
     struct log_dirty_domain log_dirty;
+    /* alloc/free pages from the pool for paging-assistance structures
+     * (used by p2m and log-dirty code for their tries) */
+    struct page_info * (*alloc_page)(struct domain *d);
+    void (*free_page)(struct domain *d, struct page_info *pg);
 };
 
 struct paging_vcpu {
diff -r b76df6b4375a -r 38afe60221d7 xen/include/asm-x86/p2m.h
--- a/xen/include/asm-x86/p2m.h Wed Nov 17 20:40:30 2010 +0000
+++ b/xen/include/asm-x86/p2m.h Thu Nov 18 12:35:46 2010 +0000
@@ -179,10 +179,6 @@ struct p2m_domain {
     /* Pages used to construct the p2m */
     struct page_list_head pages;
 
-    /* Functions to call to get or free pages for the p2m */
-    struct page_info * (*alloc_page  )(struct p2m_domain *p2m);
-    void               (*free_page   )(struct p2m_domain *p2m,
-                                       struct page_info *pg);
     int                (*set_entry   )(struct p2m_domain *p2m,
                                        unsigned long gfn,
                                        mfn_t mfn, unsigned int page_order,
@@ -390,13 +386,8 @@ int p2m_init(struct domain *d);
 
 /* Allocate a new p2m table for a domain. 
  *
- * The alloc_page and free_page functions will be used to get memory to
- * build the p2m, and to release it again at the end of day. 
- *
  * Returns 0 for success or -errno. */
-int p2m_alloc_table(struct p2m_domain *p2m,
-               struct page_info * (*alloc_page)(struct p2m_domain *p2m),
-               void (*free_page)(struct p2m_domain *p2m, struct page_info 
*pg));
+int p2m_alloc_table(struct p2m_domain *p2m);
 
 /* Return all the p2m resources to Xen. */
 void p2m_teardown(struct p2m_domain *p2m);
diff -r b76df6b4375a -r 38afe60221d7 xen/include/asm-x86/paging.h
--- a/xen/include/asm-x86/paging.h      Wed Nov 17 20:40:30 2010 +0000
+++ b/xen/include/asm-x86/paging.h      Thu Nov 18 12:35:46 2010 +0000
@@ -134,8 +134,8 @@ struct paging_mode {
 /*****************************************************************************
  * Log dirty code */
 
-/* allocate log dirty bitmap resource for recording dirty pages */
-int paging_alloc_log_dirty_bitmap(struct domain *d);
+/* get the top of the log-dirty bitmap trie, allocating if necessary */
+mfn_t *paging_map_log_dirty_bitmap(struct domain *d);
 
 /* free log dirty bitmap resource */
 void paging_free_log_dirty_bitmap(struct domain *d);

_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/xen-devel


 


Rackspace

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