diff --git a/xen/arch/x86/include/asm/mem_sharing.h b/xen/arch/x86/include/asm/mem_sharing.h index bfd47d18f2..a671d84058 100644 --- a/xen/arch/x86/include/asm/mem_sharing.h +++ b/xen/arch/x86/include/asm/mem_sharing.h @@ -84,7 +84,7 @@ int mem_sharing_fork_reset(struct domain *d, bool reset_state, * link so the parent can run again. Preemptible. See the definition for the * full rationale. */ -int mem_sharing_fork_complete(struct domain *d); +int mem_sharing_fork_complete(struct domain *d, bool lazy); /* * If called by a foreign domain, possible errors are @@ -154,7 +154,7 @@ static inline int mem_sharing_fork_reset(struct domain *d, bool reset_state, return -EOPNOTSUPP; } -static inline int mem_sharing_fork_complete(struct domain *d) +static inline int mem_sharing_fork_complete(struct domain *d, bool lazy) { return -EOPNOTSUPP; } diff --git a/xen/arch/x86/mm/mem_sharing.c b/xen/arch/x86/mm/mem_sharing.c index e61c317745..a43c5047a1 100644 --- a/xen/arch/x86/mm/mem_sharing.c +++ b/xen/arch/x86/mm/mem_sharing.c @@ -2036,8 +2036,12 @@ int mem_sharing_fork_reset(struct domain *d, bool reset_state, * order is the child's and then the parent's, which is the order get_gfn() * already nested them in. Both are dropped before every preemption, because a * continuation returns to the caller. + * + * A lazy completion skips the walk and keeps the parent link and its pause: the + * child is made runnable and keeps faulting pages in from the parent, which + * has to stay paused and alive for as long as the child does. */ -int mem_sharing_fork_complete(struct domain *d) +int mem_sharing_fork_complete(struct domain *d, bool lazy) { struct domain *pd = d->parent; struct p2m_domain *pp2m, *cp2m; @@ -2055,7 +2059,8 @@ int mem_sharing_fork_complete(struct domain *d) p2m_lock(cp2m); p2m_lock(pp2m); - for ( gfn = msd->next_gfn_to_materialize; gfn <= max_pfn; gfn++ ) + for ( gfn = lazy ? max_pfn + 1 : msd->next_gfn_to_materialize; gfn <= max_pfn; + gfn++ ) { p2m_type_t t; p2m_access_t a; @@ -2163,6 +2168,12 @@ int mem_sharing_fork_complete(struct domain *d) unmap_guest_area(v, &v->runstate_guest_area); } + if ( lazy ) + { + msd->block_interrupts = false; + return 0; + } + /* * The child is now self-contained. Drop the parent link and release the * pause + reference the fork took, so the parent can be scheduled again. @@ -2513,14 +2524,14 @@ int mem_sharing_memop(XEN_GUEST_HANDLE_PARAM(xen_mem_sharing_op_t) arg) case XENMEM_sharing_op_fork_complete: rc = -EINVAL; - if ( mso.u.fork.pad || mso.u.fork.flags ) + if ( mso.u.fork.pad || (mso.u.fork.flags & ~XENMEM_FORK_COMPLETE_LAZY) ) goto out; rc = -ENOSYS; if ( !mem_sharing_is_fork(d) ) goto out; - rc = mem_sharing_fork_complete(d); + rc = mem_sharing_fork_complete(d, mso.u.fork.flags & XENMEM_FORK_COMPLETE_LAZY); if ( rc == -ERESTART ) rc = hypercall_create_continuation(__HYPERVISOR_memory_op, "lh", XENMEM_sharing_op, arg); diff --git a/xen/include/public/memory.h b/xen/include/public/memory.h index 0bd1981068..d8bb5b1cec 100644 --- a/xen/include/public/memory.h +++ b/xen/include/public/memory.h @@ -554,6 +554,12 @@ struct xen_mem_sharing_op { #define XENMEM_FORK_BLOCK_INTERRUPTS (1u << 1) #define XENMEM_FORK_RESET_STATE (1u << 2) #define XENMEM_FORK_RESET_MEMORY (1u << 3) +/* + * FORK_COMPLETE only: make the child runnable without materializing its + * physmap or releasing the parent. The child keeps faulting pages in from the + * parent, which therefore has to stay paused for as long as the child lives. + */ +#define XENMEM_FORK_COMPLETE_LAZY (1u << 4) uint16_t flags; /* IN: optional settings */ uint32_t pad; /* Must be set to 0 */ } fork;