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

[Xen-devel] [PATCH v7 04/10] xen: Introduce XEN_DOMCTL_soft_reset



New operation reassigns all memory pages from source domain to the destination
domain mapping them at exactly the same GFNs. Pages mapped more than once (e.g.
grants) are being copied.

Signed-off-by: Vitaly Kuznetsov <vkuznets@xxxxxxxxxx>
---
Changes in v7:
- is_soft_reset flag added to struct domain to preserve destination domain's
  paused state across possible hypercall preemption.
[Jan Beulich]
- XENMEM_soft_reset -> XEN_DOMCTL_soft_reset
- domain_soft_reset() returns int
- no locking for is_dying as it is now proteced by domctl_lock
- print a warning on !mfn_valid case
- check PGC_allocated for pages
- no print on assign_pages failure (it prints error messages on both its 
failure paths)
- don't re-read page->count_info, use copy_page flag
- minor stucturing change
- pause both source and destination domain while processing the hypercall
- remove nr_transferred from public interface
[Tim Deegan]
- use get_gfn_type_access() in PoD case (x86-only)
---
 xen/common/domain.c         | 186 ++++++++++++++++++++++++++++++++++++++++++++
 xen/common/domctl.c         |  72 +++++++++++++++++
 xen/include/public/domctl.h |  28 +++++++
 xen/include/xen/sched.h     |   6 ++
 4 files changed, 292 insertions(+)

diff --git a/xen/common/domain.c b/xen/common/domain.c
index 7825c56..824f325 100644
--- a/xen/common/domain.c
+++ b/xen/common/domain.c
@@ -1006,6 +1006,192 @@ int domain_unpause_by_systemcontroller(struct domain *d)
     return 0;
 }
 
+int domain_soft_reset(struct domain *source_d, struct domain *dest_d,
+                      xen_pfn_t *gmfn_start)
+{
+    int rc = 0;
+    unsigned long mfn, mfn_new, gmfn, last_gmfn, count;
+    unsigned int order;
+    p2m_type_t p2mt;
+    struct page_info *page, *new_page, *next_page;
+    int drop_dom_ref, copy_page;
+
+    last_gmfn = domain_get_maximum_gpfn(source_d);
+    gmfn = *gmfn_start;
+    while ( gmfn <= last_gmfn )
+    {
+        page = get_page_from_gfn(source_d, gmfn, &p2mt, 0);
+        if ( unlikely(page == NULL) )
+        {
+#ifdef CONFIG_X86
+            struct p2m_domain *p2m = p2m_get_hostp2m(source_d);
+            p2m_access_t p2ma;
+            mfn_t mfn_p2m;
+
+            order = 0;
+            mfn_p2m = get_gfn_type_access(p2m, gmfn,
+                                          &p2mt, &p2ma, 0, &order);
+            if ( p2m_is_pod(p2mt) )
+            {
+                rc = guest_physmap_mark_populate_on_demand(dest_d, gmfn,
+                                                           order);
+                if ( unlikely(rc) )
+                {
+                    printk(XENLOG_G_ERR "Failed to mark Dom%d's GFN %lx"
+                           " (order: %d) as PoD\n", source_d->domain_id,
+                           gmfn, order);
+                    goto fail;
+                }
+            }
+            put_gfn(source_d, gmfn);
+            gmfn += 1ul << order;
+#else
+            gmfn++;
+#endif
+            goto preempt_check;
+        }
+
+        mfn = page_to_mfn(page);
+        if ( unlikely(!mfn_valid(mfn)) )
+        {
+            printk(XENLOG_G_WARNING "Dom%d's GFN %lx points to invalid MFN\n",
+                   source_d->domain_id, gmfn);
+            put_page(page);
+            gmfn++;
+            goto preempt_check;
+        }
+
+        next_page = page;
+        count = 0;
+        copy_page = 0;
+        while ( next_page && !is_xen_heap_page(next_page) &&
+                page_to_mfn(next_page) == mfn + count )
+        {
+            /*
+             * A normal page is supposed to be allocated and have count_info=2
+             * (1 from the domain and 1 from get_page_from_gfn() above).
+             * We should never see an unallocated page here and a page with
+             * count_info > 2 has to be copied.
+             */
+            if ( unlikely(!(next_page->count_info & PGC_allocated)) )
+            {
+                printk(XENLOG_G_ERR "Dom%d's page for GFN %lx is not"
+                       " allocated\n", source_d->domain_id, gmfn);
+                put_page(next_page);
+                rc = -EFAULT;
+                goto fail;
+            }
+            else if ( (next_page->count_info & PGC_count_mask) != 2 )
+            {
+                if ( !count )
+                    copy_page = 1;
+                break;
+            }
+
+            count++;
+            drop_dom_ref = 0;
+            spin_lock(&source_d->page_alloc_lock);
+            page_set_owner(next_page, NULL);
+            page_list_del(next_page, &source_d->page_list);
+            source_d->tot_pages--;
+            if ( unlikely(source_d->tot_pages == 0) )
+                drop_dom_ref = 1;
+            spin_unlock(&source_d->page_alloc_lock);
+            put_page(next_page);
+            if ( unlikely(drop_dom_ref) )
+                put_domain(source_d);
+
+            if ( unlikely(assign_pages(dest_d, next_page, 0, 0)) )
+            {
+                printk(XENLOG_G_ERR "Failed to assign Dom%d's page (GFN %lx)"
+                       " to Dom %d (tot_pages: %u, max_pages: %u)\n",
+                       source_d->domain_id, gmfn, dest_d->domain_id,
+                       dest_d->tot_pages, dest_d->max_pages);
+                rc = -EFAULT;
+                goto fail;
+            }
+
+            if ( unlikely(gmfn + count > last_gmfn) )
+            {
+                next_page = NULL;
+                break;
+            }
+
+            next_page = get_page_from_gfn(source_d, gmfn + count, &p2mt, 0);
+        }
+
+        if ( count )
+        {
+            if ( next_page )
+                put_page(next_page);
+        }
+        else if ( unlikely(copy_page) )
+        {
+            new_page = alloc_domheap_page(dest_d, 0);
+            if ( unlikely(new_page == NULL) )
+            {
+                printk(XENLOG_G_ERR "Failed to alloc a page to replace"
+                       " Dom%d's GFN %lx (MFN %lx) for Dom %d\n",
+                       source_d->domain_id, gmfn, mfn, dest_d->domain_id);
+                rc = -ENOMEM;
+                put_page(page);
+                goto fail;
+            }
+            mfn_new = page_to_mfn(new_page);
+            copy_domain_page(mfn_new, mfn);
+            mfn = mfn_new;
+            put_page(page);
+            if ( unlikely(guest_physmap_add_page(dest_d, gmfn, mfn, 0)) )
+            {
+                printk(XENLOG_G_ERR "Failed to add new GFN %lx"
+                       " (MFN %lx) to Dom%d\n",
+                       gmfn, mfn, dest_d->domain_id);
+                rc = -EFAULT;
+                goto fail;
+            }
+            gmfn++;
+        }
+        else
+        {
+            put_page(page);
+            gmfn++;
+            goto preempt_check;
+        }
+
+        while ( count )
+        {
+            order = get_order_from_pages(count);
+            if ( (1ul << order) > count )
+                order--;
+
+            guest_physmap_remove_page(source_d, gmfn, mfn, order);
+
+            if ( unlikely(guest_physmap_add_page(dest_d, gmfn, mfn, order)) )
+            {
+                printk(XENLOG_G_ERR "Failed to re-add Dom%d's GFN %lx"
+                       " (MFN %lx, order: %u) to Dom%d\n", source_d->domain_id,
+                       gmfn, mfn, order, dest_d->domain_id);
+                rc = -EFAULT;
+                goto fail;
+            }
+
+            count -= 1ul << order;
+            gmfn += 1ul << order;
+            mfn += 1ul << order;
+        }
+
+ preempt_check:
+        if ( hypercall_preempt_check() && gmfn <= last_gmfn )
+        {
+            *gmfn_start = gmfn;
+            return -ERESTART;
+        }
+    }
+
+ fail:
+    return rc;
+}
+
 int vcpu_reset(struct vcpu *v)
 {
     struct domain *d = v->domain;
diff --git a/xen/common/domctl.c b/xen/common/domctl.c
index e571e76..2fd21cb 100644
--- a/xen/common/domctl.c
+++ b/xen/common/domctl.c
@@ -703,6 +703,78 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xen_domctl_t) 
u_domctl)
         break;
     }
 
+    case XEN_DOMCTL_soft_reset:
+    {
+        struct domain *dest_d;
+
+        if ( d == current->domain )
+        {
+            ret = -EINVAL;
+            break;
+        }
+
+        dest_d = rcu_lock_domain_by_any_id(op->u.soft_reset.dest_domain);
+        if ( dest_d == NULL )
+        {
+            ret = -ESRCH;
+            break;
+        }
+
+        if ( d == dest_d || dest_d->is_dying || dest_d == current->domain )
+        {
+            ret = -EINVAL;
+            rcu_unlock_domain(dest_d);
+            break;
+        }
+
+        /*
+         * Mark the source domain as dying to prevent further changes of its
+         * mappings. is_dying flag is protected by domctl_lock.
+         */
+        if ( !d->is_dying )
+        {
+            domain_pause(d);
+            d->is_dying = DOMDYING_locked;
+        }
+        else if ( d->is_dying != DOMDYING_locked )
+        {
+            ret = -EINVAL;
+            rcu_unlock_domain(dest_d);
+            break;
+        }
+
+        /*
+         * Destination domain's paused state is preserved across possible
+         * hypercall preemption.
+         */
+        if ( !dest_d->is_soft_reset )
+        {
+            domain_pause(dest_d);
+            dest_d->is_soft_reset = 1;
+        }
+
+        copyback = 1;
+        ret = domain_soft_reset(d, dest_d, &op->u.soft_reset.gmfn_start);
+
+        if ( ret != -ERESTART )
+        {
+            dest_d->is_soft_reset = 0;
+            domain_unpause(dest_d);
+            rcu_unlock_domain(dest_d);
+        }
+        else
+        {
+            rcu_unlock_domain(dest_d);
+            ret = hypercall_create_continuation(
+                __HYPERVISOR_domctl, "h", u_domctl);
+            break;
+        }
+
+        /* Change cmd to support possible preemption of domain_kill() */
+        op->cmd = XEN_DOMCTL_destroydomain;
+        /* fall through */
+    }
+
     case XEN_DOMCTL_destroydomain:
         ret = domain_kill(d);
         if ( ret == -ERESTART )
diff --git a/xen/include/public/domctl.h b/xen/include/public/domctl.h
index 0c0ea4a..8a5db55 100644
--- a/xen/include/public/domctl.h
+++ b/xen/include/public/domctl.h
@@ -1065,6 +1065,32 @@ struct xen_domctl_monitor_op {
 typedef struct xen_domctl__op xen_domctl_monitor_op_t;
 DEFINE_XEN_GUEST_HANDLE(xen_domctl_monitor_op_t);
 
+/*
+ * Transfer all memory pages from one domain to the other. Pages are unmapped
+ * from the source domain and mapped at the same GFNs to the destination
+ * domain. The source domain is being destroyed.
+ *
+ * If a particular page is mapped more then once in the source domain a new
+ * empty page is being allocated for the destination domain and the content is
+ * being copied.
+ *
+ * The caller has to be priviliged and it is supposed to set gmfn_start to 0,
+ * this field is required for the hypercall continuation.
+ */
+struct xen_domctl_soft_reset {
+    /*
+     * [IN] Destination domain.
+     */
+    domid_t  dest_domain; /* destination domain */
+
+    /*
+     * [IN] Start from GMFN.
+     */
+    xen_pfn_t gmfn_start;
+};
+typedef struct xen_domctl_soft_reset xen_domctl_soft_reset_t;
+DEFINE_XEN_GUEST_HANDLE(xen_domctl_soft_reset_t);
+
 struct xen_domctl {
     uint32_t cmd;
 #define XEN_DOMCTL_createdomain                   1
@@ -1140,6 +1166,7 @@ struct xen_domctl {
 #define XEN_DOMCTL_setvnumainfo                  74
 #define XEN_DOMCTL_psr_cmt_op                    75
 #define XEN_DOMCTL_monitor_op                    77
+#define XEN_DOMCTL_soft_reset                    78
 #define XEN_DOMCTL_gdbsx_guestmemio            1000
 #define XEN_DOMCTL_gdbsx_pausevcpu             1001
 #define XEN_DOMCTL_gdbsx_unpausevcpu           1002
@@ -1203,6 +1230,7 @@ struct xen_domctl {
         struct xen_domctl_vnuma             vnuma;
         struct xen_domctl_psr_cmt_op        psr_cmt_op;
         struct xen_domctl_monitor_op        monitor_op;
+        struct xen_domctl_soft_reset        soft_reset;
         uint8_t                             pad[128];
     } u;
 };
diff --git a/xen/include/xen/sched.h b/xen/include/xen/sched.h
index f53b91d..48b8272 100644
--- a/xen/include/xen/sched.h
+++ b/xen/include/xen/sched.h
@@ -396,6 +396,9 @@ struct domain
     bool_t           is_shut_down;     /* fully shut down? */
     int              shutdown_code;
 
+    /* Destination domain in the middle of soft reset operation */
+    bool_t           is_soft_reset;
+
     /* If this is not 0, send suspend notification here instead of
      * raising DOM_EXC */
     evtchn_port_t    suspend_evtchn;
@@ -605,6 +608,9 @@ void domain_shutdown(struct domain *d, u8 reason);
 void domain_resume(struct domain *d);
 void domain_pause_for_debugger(void);
 
+int domain_soft_reset(struct domain *source_d, struct domain *dest_d,
+                      xen_pfn_t *gmfn_start);
+
 int vcpu_start_shutdown_deferral(struct vcpu *v);
 void vcpu_end_shutdown_deferral(struct vcpu *v);
 
-- 
1.9.3


_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
http://lists.xen.org/xen-devel


 


Rackspace

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