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

Re: [PATCH v10 7/10] xen: implement new foreign copy hypercall


  • To: Frediano Ziglio <freddy77@xxxxxxxxx>
  • From: Jan Beulich <jbeulich@xxxxxxxx>
  • Date: Thu, 13 Aug 2026 11:41:07 +0200
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=google header.d=suse.com header.i="@suse.com" header.h="Content-Transfer-Encoding:Content-Type:In-Reply-To:Autocrypt:From:Content-Language:References:Cc:To:Subject:User-Agent:MIME-Version:Date:Message-ID"
  • Autocrypt: addr=jbeulich@xxxxxxxx; keydata= xsDiBFk3nEQRBADAEaSw6zC/EJkiwGPXbWtPxl2xCdSoeepS07jW8UgcHNurfHvUzogEq5xk hu507c3BarVjyWCJOylMNR98Yd8VqD9UfmX0Hb8/BrA+Hl6/DB/eqGptrf4BSRwcZQM32aZK 7Pj2XbGWIUrZrd70x1eAP9QE3P79Y2oLrsCgbZJfEwCgvz9JjGmQqQkRiTVzlZVCJYcyGGsD /0tbFCzD2h20ahe8rC1gbb3K3qk+LpBtvjBu1RY9drYk0NymiGbJWZgab6t1jM7sk2vuf0Py O9Hf9XBmK0uE9IgMaiCpc32XV9oASz6UJebwkX+zF2jG5I1BfnO9g7KlotcA/v5ClMjgo6Gl MDY4HxoSRu3i1cqqSDtVlt+AOVBJBACrZcnHAUSuCXBPy0jOlBhxPqRWv6ND4c9PH1xjQ3NP nxJuMBS8rnNg22uyfAgmBKNLpLgAGVRMZGaGoJObGf72s6TeIqKJo/LtggAS9qAUiuKVnygo 3wjfkS9A3DRO+SpU7JqWdsveeIQyeyEJ/8PTowmSQLakF+3fote9ybzd880fSmFuIEJldWxp Y2ggPGpiZXVsaWNoQHN1c2UuY29tPsJgBBMRAgAgBQJZN5xEAhsDBgsJCAcDAgQVAggDBBYC AwECHgECF4AACgkQoDSui/t3IH4J+wCfQ5jHdEjCRHj23O/5ttg9r9OIruwAn3103WUITZee e7Sbg12UgcQ5lv7SzsFNBFk3nEQQCACCuTjCjFOUdi5Nm244F+78kLghRcin/awv+IrTcIWF hUpSs1Y91iQQ7KItirz5uwCPlwejSJDQJLIS+QtJHaXDXeV6NI0Uef1hP20+y8qydDiVkv6l IreXjTb7DvksRgJNvCkWtYnlS3mYvQ9NzS9PhyALWbXnH6sIJd2O9lKS1Mrfq+y0IXCP10eS FFGg+Av3IQeFatkJAyju0PPthyTqxSI4lZYuJVPknzgaeuJv/2NccrPvmeDg6Coe7ZIeQ8Yj t0ARxu2xytAkkLCel1Lz1WLmwLstV30g80nkgZf/wr+/BXJW/oIvRlonUkxv+IbBM3dX2OV8 AmRv1ySWPTP7AAMFB/9PQK/VtlNUJvg8GXj9ootzrteGfVZVVT4XBJkfwBcpC/XcPzldjv+3 HYudvpdNK3lLujXeA5fLOH+Z/G9WBc5pFVSMocI71I8bT8lIAzreg0WvkWg5V2WZsUMlnDL9 mpwIGFhlbM3gfDMs7MPMu8YQRFVdUvtSpaAs8OFfGQ0ia3LGZcjA6Ik2+xcqscEJzNH+qh8V m5jjp28yZgaqTaRbg3M/+MTbMpicpZuqF4rnB0AQD12/3BNWDR6bmh+EkYSMcEIpQmBM51qM EKYTQGybRCjpnKHGOxG0rfFY1085mBDZCH5Kx0cl0HVJuQKC+dV2ZY5AqjcKwAxpE75MLFkr wkkEGBECAAkFAlk3nEQCGwwACgkQoDSui/t3IH7nnwCfcJWUDUFKdCsBH/E5d+0ZnMQi+G0A nAuWpQkjM1ASeQwSHEeAWPgskBQL
  • Cc: Frediano Ziglio <frediano.ziglio@xxxxxxxxxx>, Andrew Cooper <andrew.cooper3@xxxxxxxxxx>, Roger Pau Monné <roger@xxxxxxxxxxxxxx>, Teddy Astie <teddy.astie@xxxxxxxxxx>, Anthony PERARD <anthony.perard@xxxxxxxxxx>, Juergen Gross <jgross@xxxxxxxx>, "Daniel P . Smith" <dpsmith@xxxxxxxxxxxxxxxxxxxx>, xen-devel@xxxxxxxxxxxxxxxxxxxx
  • Delivery-date: Thu, 13 Aug 2026 09:41:30 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>

On 10.08.2026 12:30, Frediano Ziglio wrote:
> Add a sub hypercall to __HYPERVISOR_memory_op to allow to read/write
> memory from/to a foreign domain.
> 
> Extending MMUEXT_COPY_PAGE seems better on first sight but considering
> that MMUEXT is meant for PV only and trying to change that sub-op this
> solution is better.
> 
> Signed-off-by: Frediano Ziglio <frediano.ziglio@xxxxxxxxxx>

First: Can you please update your recipient list before sending a new version?
Roger's move pre-dates this submission by quite a few days.

Then: The asymmetry of the new sub-op also continues to have no justification
at all. If you insist on not using two (domid,list-of-gfns) tuples to describe
the buffers, despite multiple maintainers having asked you to do so, please
put down a word explaining that decision.

> ---
>  xen/common/memory.c         | 149 ++++++++++++++++++++++++++++++++++++
>  xen/include/public/memory.h |  45 ++++++++++-
>  xen/include/xsm/dummy.h     |  14 ++++
>  xen/include/xsm/hooks.h     |   2 +
>  xen/xsm/flask/hooks.c       |  10 +++
>  5 files changed, 219 insertions(+), 1 deletion(-)

As before: If you insist on not implementing the compat case, that decision
wants justifying in the description. Without that it'll look like an
oversight.

> --- a/xen/common/memory.c
> +++ b/xen/common/memory.c
> @@ -1548,6 +1548,141 @@ static int acquire_resource(
>      return rc;
>  }
>  
> +/*
> + * The "noinline" qualifier avoids the compiler to create a large function
> + * consuming quite a lot of stack.
> + */
> +static int noinline mem_foreigncopy(

I'm wondering: Is the "mem" prefix really meaningful for a static function in
a file named memory.c?

> +    XEN_GUEST_HANDLE_PARAM(xen_foreigncopy_t) arg)
> +{
> +    struct domain *d, *const currd = current->domain;

With the comment on the new XSM hooks (below) in mind: currd wants to be
pointer-to-const.

> +    xen_foreigncopy_t copy;
> +    int rc, direction;

Plain int for rc is fine of course, but direction can't go negative, can it?

> +    if ( copy_from_guest(&copy, arg, 1) )
> +        return -EFAULT;
> +
> +    if ( copy.flags & ~XENMEM_foreigncopy_direction )
> +        return -EINVAL;
> +
> +    direction = copy.flags & XENMEM_foreigncopy_direction;
> +
> +    d = rcu_lock_domain_by_any_id(copy.domid);
> +    if ( !d )
> +        return -ESRCH;
> +
> +    /*
> +     * Check we are allowed to map and access these foreign pages.
> +     */

This really means to be a single-line comment.

> +    if ( direction == XENMEM_foreigncopy_from )
> +        rc = xsm_foreigncopy_from(XSM_TARGET, currd, d);
> +    else
> +        rc = xsm_foreigncopy_to(XSM_TARGET, currd, d);
> +    if ( rc )
> +        goto out;
> +
> +    while ( copy.nr_frames )
> +    {
> +        /*
> +         * Arbitrary size.  Not too much stack space, and a reasonable stride
> +         * for continuation checks.
> +         */
> +        xen_pfn_t gfn_list[32];
> +        unsigned int todo = MIN(ARRAY_SIZE(gfn_list), copy.nr_frames);
> +
> +        rc = -EFAULT;
> +        if ( copy_from_guest(gfn_list, copy.frame_list, todo) )
> +            goto out;
> +
> +        for ( unsigned int i = 0; i < todo; i++ )
> +        {
> +            struct page_info *foreign_page;
> +            mfn_t foreign_mfn;
> +            void *foreign;
> +            p2m_type_t p2mt;
> +            p2m_query_t q = (direction == XENMEM_foreigncopy_to) ?
> +                            P2M_ALLOC | P2M_UNSHARE : P2M_ALLOC;
> +
> +            foreign_page = get_page_from_gfn(d, gfn_list[i], &p2mt, q);

Is there a reason check_get_page_from_gfn() can't or shouldn't be used here?
That would then also make this code properly deal with shared pages, and
refuse use against MMIO ones.

> +            if ( unlikely(p2m_is_paged(p2mt)) )
> +            {
> +                if ( foreign_page )
> +                    put_page(foreign_page);
> +                p2m_mem_paging_populate(d, _gfn(gfn_list[i]));
> +                p2mt = p2m_ram_paging_in;
> +                foreign_page = NULL;
> +            }
> +
> +            if ( unlikely(!foreign_page) )
> +            {
> +                rc = -ENOENT;
> +                if ( p2mt != p2m_ram_paging_in )
> +                {
> +                    gdprintk(XENLOG_WARNING,
> +                             "Error accessing foreign gfn %" PRI_gfn "\n",
> +                             gfn_list[i]);
> +                    rc = -EINVAL;
> +                }
> +                copy.nr_frames -= i;
> +                guest_handle_add_offset(copy.frame_list, i);
> +                goto out;
> +            }
> +
> +            foreign_mfn = page_to_mfn(foreign_page);
> +
> +            /* A page is dirtied when it's being copied to. */
> +            if ( direction == XENMEM_foreigncopy_to )
> +                paging_mark_dirty(d, foreign_mfn);

This may better be folded into the "else" below.

> +            foreign = map_domain_page(foreign_mfn);
> +            if ( direction == XENMEM_foreigncopy_from )
> +                rc = copy_to_guest(copy.buffer, foreign, PAGE_SIZE);
> +            else
> +                rc = copy_from_guest(foreign, copy.buffer, PAGE_SIZE);

What I continue to be missing prior to this is the obtaining of a writable
page ref. That's, as previously said, imperative for PV guests and at the
very least advisable for HVM ones. (I really wonder how many more times I
need to comment on this.)

> +            unmap_domain_page(foreign);
> +            put_page(foreign_page);
> +
> +            if ( unlikely(rc) )
> +            {
> +                gdprintk(XENLOG_WARNING,
> +                         "Error %d copying gfn %" PRI_gfn "\n",
> +                         rc, gfn_list[i]);
> +                copy.nr_frames -= i;
> +                guest_handle_add_offset(copy.frame_list, i);
> +                goto out;

rc at this point holds the number of bytes which could not be copied. That's
not suitable as a return value from this function. (Strictly speaking this
is also an abuse of rc, as copy_{to,from}_*() return unsigned quantities.)

> +            }
> +
> +            guest_handle_add_offset(copy.buffer, PAGE_SIZE);
> +        }
> +
> +        copy.nr_frames -= todo;
> +        guest_handle_add_offset(copy.frame_list, todo);
> +
> +        if ( copy.nr_frames && hypercall_preempt_check() )
> +        {
> +            rc = hypercall_create_continuation(
> +                __HYPERVISOR_memory_op, "lh", XENMEM_foreigncopy, arg);

Nit: Indentation (the anchor point is the start of the function name, not
the start of the statement).

> --- a/xen/include/public/memory.h
> +++ b/xen/include/public/memory.h
> @@ -740,7 +740,50 @@ struct xen_vnuma_topology_info {
>  typedef struct xen_vnuma_topology_info xen_vnuma_topology_info_t;
>  DEFINE_XEN_GUEST_HANDLE(xen_vnuma_topology_info_t);
>  
> -/* Next available subop number is 29 */
> +/*
> + * Copy memory from/to a given domain.
> + * This calls is meant to replace expensive operations during migration which

Nit: "This call is ..." However, is ...

> + * are only supported for PV guests.

... this entire sentence really worth to have here (it looks more like
something to have in the description)? For it to be possible to find if
someone considered using those "expensive operations", I think it would need
to be less vague and name those operations. Furthermore, if those other
operations were supported only for PV guests, how would migration work for
non-PV ones?

> + */
> +#define XENMEM_foreigncopy 29
> +struct xen_foreigncopy {
> +    /* IN - The domain whose memory is to be copied. */
> +    domid_t domid;
> +
> +    /* IN - Flags. */
> +#define XENMEM_foreigncopy_from 0
> +#define XENMEM_foreigncopy_to 1
> +#define XENMEM_foreigncopy_direction 1
> +    uint16_t flags;
> +
> +    /*
> +     * IN/OUT
> +     *
> +     * As an IN parameter number of frames of the domain to be copied.

I think there's a comma wanted after "parameter".

> +     * On output updated number of frames left (0 if success).

I further think that adding "to" after "updated" would help here.

> --- a/xen/include/xsm/dummy.h
> +++ b/xen/include/xsm/dummy.h
> @@ -569,6 +569,20 @@ static XSM_INLINE int cf_check xsm_map_gmfn_foreign(
>      return xsm_default_action(action, d, t);
>  }
>  
> +static XSM_INLINE int cf_check xsm_foreigncopy_from(
> +    XSM_DEFAULT_ARG struct domain *d, struct domain *t)

I think these and ...

> +{
> +    XSM_ASSERT_ACTION(XSM_TARGET);
> +    return xsm_default_action(action, d, t);
> +}
> +
> +static XSM_INLINE int cf_check xsm_foreigncopy_to(
> +    XSM_DEFAULT_ARG struct domain *d, struct domain *t)

... want to be pointer-to-const right away, requiring to re-base over "XSM:
make Argo hooks well-formed ones" (or alternatively requiring to split out
the change there to xsm_default_action()). We really should avoid gaining
...

> --- a/xen/include/xsm/hooks.h
> +++ b/xen/include/xsm/hooks.h
> @@ -58,6 +58,8 @@ XSM_HOOK(int, add_to_physmap, struct domain *, struct 
> domain *)
>  XSM_HOOK(int, remove_from_physmap, struct domain *, struct domain *)
>  XSM_HOOK(int, map_gmfn_foreign, struct domain *, struct domain *)
>  XSM_HOOK(int, claim_pages, struct domain *)
> +XSM_HOOK(int, foreigncopy_from, struct domain *, struct domain *);
> +XSM_HOOK(int, foreigncopy_to, struct domain *, struct domain *);

... new hooks with non-const-correct parameters.

Further: Why two new hooks? See e.g. "XSM: fold xsm_{,un}map_domain_pirq()
hooks", "XSM: fold xsm_{,un}map_domain_irq() hooks", or "XSM: fold
xsm_{,un}bind_pt_irq() hooks": We'd like to reduce the number of hooks, to
reduce (when non-dummy XSM is in use) the number of cf_check entry points.

> --- a/xen/xsm/flask/hooks.c
> +++ b/xen/xsm/flask/hooks.c
> @@ -1368,6 +1368,16 @@ static int cf_check flask_map_gmfn_foreign(struct 
> domain *d, struct domain *t)
>      return domain_has_perm(d, t, SECCLASS_MMU, MMU__MAP_READ | 
> MMU__MAP_WRITE);
>  }
>  
> +static int cf_check flask_foreigncopy_from(struct domain *d, struct domain 
> *t)
> +{
> +    return domain_has_perm(d, t, SECCLASS_MMU, MMU__MAP_READ);
> +}
> +
> +static int cf_check flask_foreigncopy_to(struct domain *d, struct domain *t)
> +{
> +    return domain_has_perm(d, t, SECCLASS_MMU, MMU__MAP_READ | 
> MMU__MAP_WRITE);

Why both READ and WRITE?

Jan



 


Rackspace

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