* [PATCH v10 1/10] libs/call: cache up to 4 pages in hypercall bounce buffers
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 2/10] libs/guest: move batch_pfns into a separate structure Frediano Ziglio
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Edwin Török, Jan Beulich, Andrew Cooper,
Roger Pau Monné, Teddy Astie, Anthony PERARD, Juergen Gross,
Frediano Ziglio
From: Edwin Török <edwin.torok@citrix.com>
During migration there are a lot of mmap/munmap calls,
because xc_get_pfn_type_batch() exceeds the default hypercall bounce
buffer cache size, and needs to allocate every time it is called.
munmap() is slow, especially in a PV Dom0 (takes an emulation fault),
so is best avoided.
Eventually it'd be good if the memory pool from xmalloc_tlsf.c
was reused here, but for now make it handle the commonly encountered
sizes (so far up to 4 pages).
Signed-off-by: Edwin Török <edwin.torok@citrix.com>
Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com>
Reviewed-by: Anthony PERARD <anthony.perard@vates.tech>
---
Changes since v2:
- change prefix in subject.
Changes since v4:
- fix off-by-one bug.
Changes since v5:
- avoids potential buffer underflow if nr_pages is 0 calling cache_alloc.
Changes since v6:
- align changes made to cache_alloc to cache_free.
Changes since v7:
- use "unsigned int" instead of "unsigned".
Changes since v8:
- added Reviewed-by.
---
tools/libs/call/buffer.c | 34 +++++++++++++++++++++++-----------
tools/libs/call/core.c | 3 ++-
tools/libs/call/private.h | 8 +++++---
3 files changed, 30 insertions(+), 15 deletions(-)
diff --git a/tools/libs/call/buffer.c b/tools/libs/call/buffer.c
index 155e4f9d43..b7d00185c4 100644
--- a/tools/libs/call/buffer.c
+++ b/tools/libs/call/buffer.c
@@ -49,6 +49,9 @@ static void *cache_alloc(xencall_handle *xcall, size_t nr_pages)
{
void *p = NULL;
+ if ( nr_pages == 0 )
+ return NULL;
+
cache_lock(xcall);
xcall->buffer_total_allocations++;
@@ -56,13 +59,13 @@ static void *cache_alloc(xencall_handle *xcall, size_t nr_pages)
if ( xcall->buffer_current_allocations > xcall->buffer_maximum_allocations )
xcall->buffer_maximum_allocations = xcall->buffer_current_allocations;
- if ( nr_pages > 1 )
+ if ( nr_pages > ARRAY_SIZE(xcall->buffer_cache) )
{
xcall->buffer_cache_toobig++;
}
- else if ( xcall->buffer_cache_nr > 0 )
+ else if ( xcall->buffer_cache_nr[nr_pages-1] > 0 )
{
- p = xcall->buffer_cache[--xcall->buffer_cache_nr];
+ p = xcall->buffer_cache[nr_pages-1][--xcall->buffer_cache_nr[nr_pages-1]];
xcall->buffer_cache_hits++;
}
else
@@ -79,15 +82,18 @@ static int cache_free(xencall_handle *xcall, void *p, size_t nr_pages)
{
int rc = 0;
+ if ( nr_pages == 0 )
+ return 0;
+
cache_lock(xcall);
xcall->buffer_total_releases++;
xcall->buffer_current_allocations--;
- if ( nr_pages == 1 &&
- xcall->buffer_cache_nr < BUFFER_CACHE_SIZE )
+ if ( nr_pages && nr_pages <= ARRAY_SIZE(xcall->buffer_cache) &&
+ xcall->buffer_cache_nr[nr_pages-1] < BUFFER_CACHE_SIZE )
{
- xcall->buffer_cache[xcall->buffer_cache_nr++] = p;
+ xcall->buffer_cache[nr_pages-1][xcall->buffer_cache_nr[nr_pages-1]++] = p;
rc = 1;
}
@@ -108,17 +114,23 @@ void buffer_release_cache(xencall_handle *xcall)
DBGPRINTF("current allocations:%d maximum allocations:%d",
xcall->buffer_current_allocations,
xcall->buffer_maximum_allocations);
- DBGPRINTF("cache current size:%d",
- xcall->buffer_cache_nr);
+ for ( unsigned int i = 0; i < ARRAY_SIZE(xcall->buffer_cache_nr); ++i )
+ {
+ DBGPRINTF("cache current size[%u pages]:%d", i+1,
+ xcall->buffer_cache_nr[i]);
+ }
DBGPRINTF("cache hits:%d misses:%d toobig:%d",
xcall->buffer_cache_hits,
xcall->buffer_cache_misses,
xcall->buffer_cache_toobig);
- while ( xcall->buffer_cache_nr > 0 )
+ for ( unsigned int i = 0; i < ARRAY_SIZE(xcall->buffer_cache_nr); ++i )
{
- p = xcall->buffer_cache[--xcall->buffer_cache_nr];
- osdep_free_pages(xcall, p, 1);
+ while ( xcall->buffer_cache_nr[i] > 0 )
+ {
+ p = xcall->buffer_cache[i][--xcall->buffer_cache_nr[i]];
+ osdep_free_pages(xcall, p, i + 1);
+ }
}
cache_unlock(xcall);
diff --git a/tools/libs/call/core.c b/tools/libs/call/core.c
index 02c4f8e1ae..dd8877c1a0 100644
--- a/tools/libs/call/core.c
+++ b/tools/libs/call/core.c
@@ -14,6 +14,7 @@
*/
#include <stdlib.h>
+#include <string.h>
#include "private.h"
@@ -44,7 +45,7 @@ xencall_handle *xencall_open(xentoollog_logger *logger, unsigned open_flags)
xentoolcore__register_active_handle(&xcall->tc_ah);
xcall->flags = open_flags;
- xcall->buffer_cache_nr = 0;
+ memset(xcall->buffer_cache_nr, 0, sizeof(xcall->buffer_cache_nr));
xcall->buffer_total_allocations = 0;
xcall->buffer_total_releases = 0;
diff --git a/tools/libs/call/private.h b/tools/libs/call/private.h
index 9c3aa432ef..8e6a208975 100644
--- a/tools/libs/call/private.h
+++ b/tools/libs/call/private.h
@@ -31,13 +31,15 @@ struct xencall_handle {
Xentoolcore__Active_Handle tc_ah;
/*
- * A simple cache of unused, single page, hypercall buffers
+ * A simple cache of unused, small, hypercall buffers
+ * buffer_cache[i]'s size is (i+1) pages
*
* Protected by a global lock.
*/
#define BUFFER_CACHE_SIZE 4
- int buffer_cache_nr;
- void *buffer_cache[BUFFER_CACHE_SIZE];
+#define BUFFER_CACHE_NRPAGES 4
+ int buffer_cache_nr[BUFFER_CACHE_NRPAGES];
+ void *buffer_cache[BUFFER_CACHE_NRPAGES][BUFFER_CACHE_SIZE];
/*
* Hypercall buffer statistics. All protected by the global
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v10 2/10] libs/guest: move batch_pfns into a separate structure
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 1/10] libs/call: cache up to 4 pages in hypercall bounce buffers Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 3/10] libs/guest: allocate various migration arrays just once Frediano Ziglio
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Frediano Ziglio, Jan Beulich, Andrew Cooper, Roger Pau Monné,
Teddy Astie, Anthony PERARD, Juergen Gross
Preparation for a followup patch "libs/guest: allocate various migration
arrays just once".
Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com>
Reviewed-by: Anthony PERARD <anthony.perard@vates.tech>
---
Changes since v6:
- split from "libs/guest: allocate various migration arrays just once".
Changes since v7:
- initialize "batch_pfns" on declaration.
Changes since v8:
- remove useless check;
- added Reviewed-by.
---
tools/libs/guest/xg_sr_common.h | 5 ++++-
tools/libs/guest/xg_sr_save.c | 28 ++++++++++++++--------------
2 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h
index f1573aefcb..7574c9f5b6 100644
--- a/tools/libs/guest/xg_sr_common.h
+++ b/tools/libs/guest/xg_sr_common.h
@@ -239,11 +239,14 @@ struct xc_sr_context
struct precopy_stats stats;
- xen_pfn_t *batch_pfns;
unsigned int nr_batch_pfns;
unsigned long *deferred_pages;
unsigned long nr_deferred_pages;
xc_hypercall_buffer_t dirty_bitmap_hbuf;
+ struct xc_sr_context_save_buffers
+ {
+ xen_pfn_t batch_pfns[MAX_BATCH_SIZE];
+ } *buffers;
} save;
struct /* Restore data. */
diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c
index 84fdbe4140..22348db445 100644
--- a/tools/libs/guest/xg_sr_save.c
+++ b/tools/libs/guest/xg_sr_save.c
@@ -75,7 +75,7 @@ static int write_checkpoint_record(struct xc_sr_context *ctx)
/*
* Writes a batch of memory as a PAGE_DATA record into the stream. The batch
- * is constructed in ctx->save.batch_pfns.
+ * is constructed in ctx->save.buffers->batch_pfns.
*
* This function:
* - gets the types for each pfn in the batch.
@@ -95,6 +95,7 @@ static int write_batch(struct xc_sr_context *ctx)
void *page, *orig_page;
uint64_t *rec_pfns = NULL;
struct iovec *iov = NULL; int iovcnt = 0;
+ xen_pfn_t *const batch_pfns = ctx->save.buffers->batch_pfns;
struct {
struct xc_sr_rhdr rec;
struct xc_sr_rec_page_data_header page_data;
@@ -110,6 +111,7 @@ static int write_batch(struct xc_sr_context *ctx)
};
assert(nr_pfns != 0);
+ assert(nr_pfns <= MAX_BATCH_SIZE);
/* Mfns of the batch pfns. */
mfns = malloc(nr_pfns * sizeof(*mfns));
@@ -141,13 +143,12 @@ static int write_batch(struct xc_sr_context *ctx)
for ( i = 0; i < nr_pfns; ++i )
{
- types[i] = mfns[i] = ctx->save.ops.pfn_to_gfn(ctx,
- ctx->save.batch_pfns[i]);
+ types[i] = mfns[i] = ctx->save.ops.pfn_to_gfn(ctx, batch_pfns[i]);
/* Likely a ballooned page. */
if ( mfns[i] == INVALID_MFN )
{
- set_bit(ctx->save.batch_pfns[i], ctx->save.deferred_pages);
+ set_bit(batch_pfns[i], ctx->save.deferred_pages);
++ctx->save.nr_deferred_pages;
}
}
@@ -193,7 +194,7 @@ static int write_batch(struct xc_sr_context *ctx)
if ( errors[p] )
{
ERROR("Mapping of pfn %#"PRIpfn" (mfn %#"PRIpfn") failed %d",
- ctx->save.batch_pfns[i], mfns[p], errors[p]);
+ batch_pfns[i], mfns[p], errors[p]);
goto err;
}
@@ -207,7 +208,7 @@ static int write_batch(struct xc_sr_context *ctx)
{
if ( rc == -1 && errno == EAGAIN )
{
- set_bit(ctx->save.batch_pfns[i], ctx->save.deferred_pages);
+ set_bit(batch_pfns[i], ctx->save.deferred_pages);
++ctx->save.nr_deferred_pages;
types[i] = XEN_DOMCTL_PFINFO_XTAB;
--nr_pages;
@@ -235,7 +236,7 @@ static int write_batch(struct xc_sr_context *ctx)
hdrs.rec.length += nr_pages * PAGE_SIZE;
for ( i = 0; i < nr_pfns; ++i )
- rec_pfns[i] = ((uint64_t)(types[i]) << 32) | ctx->save.batch_pfns[i];
+ rec_pfns[i] = ((uint64_t)(types[i]) << 32) | batch_pfns[i];
if ( writev_exact(ctx->fd, iov, iovcnt) )
{
@@ -274,9 +275,9 @@ static int flush_batch(struct xc_sr_context *ctx)
if ( !rc )
{
- VALGRIND_MAKE_MEM_UNDEFINED(ctx->save.batch_pfns,
+ VALGRIND_MAKE_MEM_UNDEFINED(ctx->save.buffers->batch_pfns,
MAX_BATCH_SIZE *
- sizeof(*ctx->save.batch_pfns));
+ sizeof(*ctx->save.buffers->batch_pfns));
}
return rc;
@@ -293,7 +294,7 @@ static int add_to_batch(struct xc_sr_context *ctx, xen_pfn_t pfn)
rc = flush_batch(ctx);
if ( rc == 0 )
- ctx->save.batch_pfns[ctx->save.nr_batch_pfns++] = pfn;
+ ctx->save.buffers->batch_pfns[ctx->save.nr_batch_pfns++] = pfn;
return rc;
}
@@ -784,11 +785,10 @@ static int setup(struct xc_sr_context *ctx)
dirty_bitmap = xc_hypercall_buffer_alloc_pages(
xch, dirty_bitmap, NRPAGES(bitmap_size(ctx->save.p2m_size)));
- ctx->save.batch_pfns = malloc(MAX_BATCH_SIZE *
- sizeof(*ctx->save.batch_pfns));
ctx->save.deferred_pages = bitmap_alloc(ctx->save.p2m_size);
+ ctx->save.buffers = calloc(1, sizeof(*ctx->save.buffers));
- if ( !ctx->save.batch_pfns || !dirty_bitmap || !ctx->save.deferred_pages )
+ if ( !ctx->save.buffers || !dirty_bitmap || !ctx->save.deferred_pages )
{
ERROR("Unable to allocate memory for dirty bitmaps, batch pfns and"
" deferred pages");
@@ -819,7 +819,7 @@ static void cleanup(struct xc_sr_context *ctx)
xc_hypercall_buffer_free_pages(xch, dirty_bitmap,
NRPAGES(bitmap_size(ctx->save.p2m_size)));
free(ctx->save.deferred_pages);
- free(ctx->save.batch_pfns);
+ free(ctx->save.buffers);
}
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v10 3/10] libs/guest: allocate various migration arrays just once
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 1/10] libs/call: cache up to 4 pages in hypercall bounce buffers Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 2/10] libs/guest: move batch_pfns into a separate structure Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 4/10] libs/guest: use Valgrind or sanitizers to detect various buffer overflows Frediano Ziglio
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Edwin Török, Jan Beulich, Andrew Cooper,
Roger Pau Monné, Teddy Astie, Anthony PERARD, Juergen Gross,
Frediano Ziglio
From: Edwin Török <edwin.torok@citrix.com>
Allocate these array just once at the start of migration,
using the maximum batch size, and free them at the end.
Signed-off-by: Edwin Török <edwin.torok@citrix.com>
Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com>
Reviewed-by: Anthony PERARD <anthony.perard@vates.tech>
---
Changes since v2:
- change prefix in subject.
Changes since v3:
- fix comment style
Changes since v4:
- change order of fields in structure.
Changes since v6:
- split preparation commit.
Changes since v8:
- remove useless memset;
- initialize variables while declaring them.
Changes since v9:
- added Reviewed-by.
---
tools/libs/guest/xg_sr_common.h | 6 +++++
tools/libs/guest/xg_sr_save.c | 45 ++++++++++++---------------------
2 files changed, 22 insertions(+), 29 deletions(-)
diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h
index 7574c9f5b6..c07c6db59e 100644
--- a/tools/libs/guest/xg_sr_common.h
+++ b/tools/libs/guest/xg_sr_common.h
@@ -246,6 +246,12 @@ struct xc_sr_context
struct xc_sr_context_save_buffers
{
xen_pfn_t batch_pfns[MAX_BATCH_SIZE];
+ xen_pfn_t mfns[MAX_BATCH_SIZE];
+ xen_pfn_t types[MAX_BATCH_SIZE];
+ void *local_pages[MAX_BATCH_SIZE];
+ struct iovec iov[MAX_BATCH_SIZE + 2]; /* Headers + data. */
+ uint64_t rec_pfns[MAX_BATCH_SIZE];
+ int errors[MAX_BATCH_SIZE];
} *buffers;
} save;
diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c
index 22348db445..6a77e33a47 100644
--- a/tools/libs/guest/xg_sr_save.c
+++ b/tools/libs/guest/xg_sr_save.c
@@ -86,15 +86,12 @@ static int write_checkpoint_record(struct xc_sr_context *ctx)
static int write_batch(struct xc_sr_context *ctx)
{
xc_interface *xch = ctx->xch;
- xen_pfn_t *mfns = NULL, *types = NULL;
void *guest_mapping = NULL;
- void **local_pages = NULL;
- int *errors = NULL, rc = -1;
+ int rc = -1;
unsigned int i, p, nr_pages = 0, nr_pages_mapped = 0;
unsigned int nr_pfns = ctx->save.nr_batch_pfns;
void *page, *orig_page;
- uint64_t *rec_pfns = NULL;
- struct iovec *iov = NULL; int iovcnt = 0;
+ int iovcnt = 0;
xen_pfn_t *const batch_pfns = ctx->save.buffers->batch_pfns;
struct {
struct xc_sr_rhdr rec;
@@ -110,28 +107,21 @@ static int write_batch(struct xc_sr_context *ctx)
},
};
- assert(nr_pfns != 0);
- assert(nr_pfns <= MAX_BATCH_SIZE);
-
/* Mfns of the batch pfns. */
- mfns = malloc(nr_pfns * sizeof(*mfns));
+ xen_pfn_t *const mfns = ctx->save.buffers->mfns;
/* Types of the batch pfns. */
- types = malloc(nr_pfns * sizeof(*types));
+ xen_pfn_t *const types = ctx->save.buffers->types;
/* Errors from attempting to map the gfns. */
- errors = malloc(nr_pfns * sizeof(*errors));
+ int *const errors = ctx->save.buffers->errors;
/* Pointers to locally allocated pages. Need freeing. */
- local_pages = calloc(nr_pfns, sizeof(*local_pages));
+ void **const local_pages = ctx->save.buffers->local_pages;
/* iovec[] for writev(). */
- iov = malloc((nr_pfns + 2) * sizeof(*iov));
+ struct iovec *const iov = ctx->save.buffers->iov;
/* page_data record PFNs list */
- rec_pfns = malloc(nr_pfns * sizeof(*rec_pfns));
+ uint64_t *const rec_pfns = ctx->save.buffers->rec_pfns;
- if ( !mfns || !types || !errors || !local_pages || !iov || !rec_pfns )
- {
- ERROR("Unable to allocate arrays for a batch of %u pages",
- nr_pfns);
- goto err;
- }
+ assert(nr_pfns != 0);
+ assert(nr_pfns <= MAX_BATCH_SIZE);
iov[0].iov_base = &hdrs;
iov[0].iov_len = sizeof(hdrs);
@@ -249,14 +239,11 @@ static int write_batch(struct xc_sr_context *ctx)
err:
if ( guest_mapping )
xenforeignmemory_unmap(xch->fmem, guest_mapping, nr_pages_mapped);
- for ( i = 0; local_pages && i < nr_pfns; ++i )
+ for ( i = 0; i < nr_pfns; ++i )
+ {
free(local_pages[i]);
- free(rec_pfns);
- free(iov);
- free(local_pages);
- free(errors);
- free(types);
- free(mfns);
+ local_pages[i] = NULL;
+ }
return rc;
}
@@ -790,8 +777,8 @@ static int setup(struct xc_sr_context *ctx)
if ( !ctx->save.buffers || !dirty_bitmap || !ctx->save.deferred_pages )
{
- ERROR("Unable to allocate memory for dirty bitmaps, batch pfns and"
- " deferred pages");
+ ERROR("Unable to allocate memory for dirty bitmaps, deferred pages"
+ " and various batch buffers");
rc = -1;
errno = ENOMEM;
goto err;
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v10 4/10] libs/guest: use Valgrind or sanitizers to detect various buffer overflows
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
` (2 preceding siblings ...)
2026-08-10 10:30 ` [PATCH v10 3/10] libs/guest: allocate various migration arrays just once Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 5/10] libs/guest: add xg_foreignmemory_copy_{from,to} Frediano Ziglio
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Frediano Ziglio, Jan Beulich, Andrew Cooper, Roger Pau Monné,
Teddy Astie, Anthony PERARD, Juergen Gross
Previously this was done as buffers were allocated separately.
Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com>
---
Changes since v9:
- add support for sanitizers also;
- remove some unneeded check buffers.
---
tools/config.h.in | 6 ++++
tools/configure | 12 +++++++
tools/configure.ac | 3 +-
tools/libs/ctrl/xc_private.h | 61 +++++++++++++++++++++++++++++++--
tools/libs/guest/xg_sr_common.h | 6 ++++
tools/libs/guest/xg_sr_save.c | 11 ++++++
6 files changed, 96 insertions(+), 3 deletions(-)
diff --git a/tools/config.h.in b/tools/config.h.in
index ed0042018d..d51816453b 100644
--- a/tools/config.h.in
+++ b/tools/config.h.in
@@ -48,6 +48,12 @@
/* ROMBIOS enabled */
#undef HAVE_ROMBIOS
+/* Define to 1 if you have the <sanitizer/asan_interface.h> header file. */
+#undef HAVE_SANITIZER_ASAN_INTERFACE_H
+
+/* Define to 1 if you have the <sanitizer/msan_interface.h> header file. */
+#undef HAVE_SANITIZER_MSAN_INTERFACE_H
+
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H
diff --git a/tools/configure b/tools/configure
index cd989925ed..94e630665f 100755
--- a/tools/configure
+++ b/tools/configure
@@ -10203,6 +10203,18 @@ then :
printf "%s\n" "#define HAVE_UTMP_H 1" >>confdefs.h
fi
+ac_fn_c_check_header_compile "$LINENO" "sanitizer/asan_interface.h" "ac_cv_header_sanitizer_asan_interface_h" "$ac_includes_default"
+if test "x$ac_cv_header_sanitizer_asan_interface_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_SANITIZER_ASAN_INTERFACE_H 1" >>confdefs.h
+
+fi
+ac_fn_c_check_header_compile "$LINENO" "sanitizer/msan_interface.h" "ac_cv_header_sanitizer_msan_interface_h" "$ac_includes_default"
+if test "x$ac_cv_header_sanitizer_msan_interface_h" = xyes
+then :
+ printf "%s\n" "#define HAVE_SANITIZER_MSAN_INTERFACE_H 1" >>confdefs.h
+
+fi
# Check for libnl3 >=3.2.8. If present enable remus network buffering.
diff --git a/tools/configure.ac b/tools/configure.ac
index 74b9f56025..5346ff6129 100644
--- a/tools/configure.ac
+++ b/tools/configure.ac
@@ -454,7 +454,8 @@ AC_CHECK_DECLS([fdt_property_u32],,,[#include <libfdt.h>])
esac
# Checks for header files.
-AC_CHECK_HEADERS([yajl/yajl_version.h sys/eventfd.h valgrind/memcheck.h utmp.h])
+AC_CHECK_HEADERS([yajl/yajl_version.h sys/eventfd.h valgrind/memcheck.h \
+ utmp.h sanitizer/asan_interface.h sanitizer/msan_interface.h])
# Check for libnl3 >=3.2.8. If present enable remus network buffering.
PKG_CHECK_MODULES(LIBNL3, [libnl-3.0 >= 3.2.8 libnl-route-3.0 >= 3.2.8],
diff --git a/tools/libs/ctrl/xc_private.h b/tools/libs/ctrl/xc_private.h
index 8a325c17b0..7803192599 100644
--- a/tools/libs/ctrl/xc_private.h
+++ b/tools/libs/ctrl/xc_private.h
@@ -42,13 +42,70 @@
#include <xen-tools/common-macros.h>
-#if defined(HAVE_VALGRIND_MEMCHECK_H) && !defined(NDEBUG) && !defined(__MINIOS__)
+#undef XEN_USE_MEM_NOACCESS
+#if !defined(NDEBUG) && !defined(__MINIOS__)
+
+#if !defined(__has_feature)
+#define __has_feature(x) 0
+#endif
+
+#if defined(HAVE_SANITIZER_ASAN_INTERFACE_H) && \
+ (__has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__))
+#include <sanitizer/asan_interface.h>
+#define XEN_USE_MEM_NOACCESS 1
+#elif defined(HAVE_SANITIZER_MSAN_INTERFACE_H) && \
+ __has_feature(memory_sanitizer)
+#include <sanitizer/msan_interface.h>
+#define XEN_USE_MEM_NOACCESS 1
+#endif
+#if defined(HAVE_VALGRIND_MEMCHECK_H)
/* Compile in Valgrind client requests? */
#include <valgrind/memcheck.h>
-#else
+#define XEN_USE_MEM_NOACCESS 1
+#endif
+
+#endif
+
+#if !defined(HAVE_VALGRIND_MEMCHECK_H) || defined(NDEBUG) || defined(__MINIOS__)
#define VALGRIND_MAKE_MEM_UNDEFINED(addr, len) /* addr, len */
#endif
+#if defined(XEN_USE_MEM_NOACCESS)
+#define MEM_NOACCESS_BUFFER(name, size) uint8_t name[size];
+#if defined(HAVE_VALGRIND_MEMCHECK_H)
+#define MEM_NOACCESS_INIT_VALGRIND(field) \
+ VALGRIND_MAKE_MEM_NOACCESS(field, sizeof(field))
+#else
+#define MEM_NOACCESS_INIT_VALGRIND(field)
+#endif
+#if defined(HAVE_SANITIZER_ASAN_INTERFACE_H) && \
+ (__has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__))
+#define MEM_NOACCESS_INIT_SANITIZER(field) \
+ ASAN_POISON_MEMORY_REGION(field, sizeof(field))
+#else
+#define MEM_NOACCESS_INIT_SANITIZER(field)
+#endif
+#if defined(HAVE_SANITIZER_MSAN_INTERFACE_H) && \
+ __has_feature(memory_sanitizer)
+#define MEM_UNDEFINED_INIT_SANITIZER(field) \
+ __msan_poison(field, sizeof(field))
+#else
+#define MEM_UNDEFINED_INIT_SANITIZER(field)
+#endif
+#define MEM_NOACCESS_INIT(field) do { \
+ MEM_NOACCESS_INIT_VALGRIND(field); \
+ MEM_NOACCESS_INIT_SANITIZER(field); \
+} while(0)
+#define MEM_UNDEFINED_INIT(field) do { \
+ VALGRIND_MAKE_MEM_UNDEFINED(field, sizeof(field)); \
+ MEM_UNDEFINED_INIT_SANITIZER(field); \
+} while(0)
+#else
+#define MEM_NOACCESS_BUFFER(name, size)
+#define MEM_NOACCESS_INIT(field) do {} while(0)
+#define MEM_UNDEFINED_INIT(field) do {} while(0)
+#endif
+
#if defined(__MINIOS__)
/*
* MiniOS's libc doesn't know about sys/uio.h or writev().
diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h
index c07c6db59e..020b1a5272 100644
--- a/tools/libs/guest/xg_sr_common.h
+++ b/tools/libs/guest/xg_sr_common.h
@@ -246,11 +246,17 @@ struct xc_sr_context
struct xc_sr_context_save_buffers
{
xen_pfn_t batch_pfns[MAX_BATCH_SIZE];
+ MEM_NOACCESS_BUFFER(na0, 64);
xen_pfn_t mfns[MAX_BATCH_SIZE];
+ MEM_NOACCESS_BUFFER(na1, 64);
xen_pfn_t types[MAX_BATCH_SIZE];
+ MEM_NOACCESS_BUFFER(na2, 64);
void *local_pages[MAX_BATCH_SIZE];
+ MEM_NOACCESS_BUFFER(na3, 64);
struct iovec iov[MAX_BATCH_SIZE + 2]; /* Headers + data. */
+ MEM_NOACCESS_BUFFER(na4, 64);
uint64_t rec_pfns[MAX_BATCH_SIZE];
+ MEM_NOACCESS_BUFFER(na5, 64);
int errors[MAX_BATCH_SIZE];
} *buffers;
} save;
diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c
index 6a77e33a47..96d7e9e2f8 100644
--- a/tools/libs/guest/xg_sr_save.c
+++ b/tools/libs/guest/xg_sr_save.c
@@ -123,6 +123,11 @@ static int write_batch(struct xc_sr_context *ctx)
assert(nr_pfns != 0);
assert(nr_pfns <= MAX_BATCH_SIZE);
+ MEM_UNDEFINED_INIT(ctx->save.buffers->mfns);
+ MEM_UNDEFINED_INIT(ctx->save.buffers->types);
+ MEM_UNDEFINED_INIT(ctx->save.buffers->iov);
+ MEM_UNDEFINED_INIT(ctx->save.buffers->rec_pfns);
+
iov[0].iov_base = &hdrs;
iov[0].iov_len = sizeof(hdrs);
@@ -783,6 +788,12 @@ static int setup(struct xc_sr_context *ctx)
errno = ENOMEM;
goto err;
}
+ MEM_NOACCESS_INIT(ctx->save.buffers->na0);
+ MEM_NOACCESS_INIT(ctx->save.buffers->na1);
+ MEM_NOACCESS_INIT(ctx->save.buffers->na2);
+ MEM_NOACCESS_INIT(ctx->save.buffers->na3);
+ MEM_NOACCESS_INIT(ctx->save.buffers->na4);
+ MEM_NOACCESS_INIT(ctx->save.buffers->na5);
rc = 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v10 5/10] libs/guest: add xg_foreignmemory_copy_{from,to}
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
` (3 preceding siblings ...)
2026-08-10 10:30 ` [PATCH v10 4/10] libs/guest: use Valgrind or sanitizers to detect various buffer overflows Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 6/10] libs/guest: use foreign copy API during migration Frediano Ziglio
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Frediano Ziglio, Jan Beulich, Andrew Cooper, Roger Pau Monné,
Teddy Astie, Anthony PERARD, Juergen Gross
This change prepare code to use a new "foreign copy" hypercall.
The new hypercall will copy memory from/to a foreign domain.
The new hypercall can be emulated with a sequence of:
- map foreign memory;
- copy memory;
- unmap foreign memory.
The reason to introduce the emulation first is that you can refactor on the
emulation without having to introduce the new hypercall. Introducing the
hypercall first would make testing more complicated as bugs on the hypercall
have to be taken into account and considered. Also it is easier that way to
enable or disable new code. For instance you want to test for performance
regression (in this case the code emulated should not perform worse).
Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com>
---
Changes since v5:
- Do not overwrite errno if xenforeignmemory_map fails.
Changes since v6:
- improve commit message, explain order and changes.
---
tools/libs/guest/xg_sr_common.c | 57 +++++++++++++++++++++++++++++++++
tools/libs/guest/xg_sr_common.h | 8 +++++
2 files changed, 65 insertions(+)
diff --git a/tools/libs/guest/xg_sr_common.c b/tools/libs/guest/xg_sr_common.c
index 9b2782b5cf..90da21c35f 100644
--- a/tools/libs/guest/xg_sr_common.c
+++ b/tools/libs/guest/xg_sr_common.c
@@ -156,6 +156,63 @@ static void __attribute__((unused)) build_assertions(void)
BUILD_BUG_ON(sizeof(struct xc_sr_rec_hvm_params) != 8);
}
+enum {
+ foreigncopy_from,
+ foreigncopy_to
+};
+
+static int xg_foreignmemory_copy(xc_interface *xch, domid_t domid,
+ int dir, size_t nr_pages, void *buffer,
+ const xen_pfn_t foreign_pfns[nr_pages])
+{
+ if ( nr_pages == 0 )
+ return 0;
+
+ if ( !buffer || !foreign_pfns )
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ int err[nr_pages];
+ const int prot = (dir == foreigncopy_from) ? PROT_READ : PROT_READ|PROT_WRITE;
+
+ void *p = xenforeignmemory_map(xch->fmem, domid, prot, nr_pages, foreign_pfns, err);
+ if ( !p )
+ return -1;
+
+ for ( size_t n = 0; n < nr_pages; ++n )
+ if ( err[n] )
+ {
+ xenforeignmemory_unmap(xch->fmem, p, nr_pages);
+ errno = -err[n];
+ return -1;
+ }
+
+ if ( dir == foreigncopy_from )
+ memcpy(buffer, p, nr_pages * XC_PAGE_SIZE);
+ else
+ memcpy(p, buffer, nr_pages * XC_PAGE_SIZE);
+
+ return xenforeignmemory_unmap(xch->fmem, p, nr_pages);
+}
+
+int xg_foreignmemory_copy_from(xc_interface *xch, domid_t dom,
+ size_t nr_pages, void *dest,
+ const xen_pfn_t source[nr_pages])
+{
+ return xg_foreignmemory_copy(xch, dom, foreigncopy_from,
+ nr_pages, dest, source);
+}
+
+int xg_foreignmemory_copy_to(xc_interface *xch, domid_t dom,
+ size_t nr_pages, const xen_pfn_t dest[nr_pages],
+ const void *source)
+{
+ return xg_foreignmemory_copy(xch, dom, foreigncopy_to,
+ nr_pages, (void *) source, dest);
+}
+
/*
* Local variables:
* mode: C
diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h
index 020b1a5272..50f235ba87 100644
--- a/tools/libs/guest/xg_sr_common.h
+++ b/tools/libs/guest/xg_sr_common.h
@@ -556,6 +556,14 @@ static inline bool page_type_has_stream_data(uint32_t type)
}
}
+int xg_foreignmemory_copy_from(xc_interface *xch, domid_t dom,
+ size_t nr_pages, void *dest,
+ const xen_pfn_t source[nr_pages]);
+
+int xg_foreignmemory_copy_to(xc_interface *xch, domid_t dom,
+ size_t nr_pages, const xen_pfn_t dest[nr_pages],
+ const void *source);
+
#endif
/*
* Local variables:
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v10 6/10] libs/guest: use foreign copy API during migration
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
` (4 preceding siblings ...)
2026-08-10 10:30 ` [PATCH v10 5/10] libs/guest: add xg_foreignmemory_copy_{from,to} Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 7/10] xen: implement new foreign copy hypercall Frediano Ziglio
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Edwin Török, Jan Beulich, Andrew Cooper,
Roger Pau Monné, Teddy Astie, Anthony PERARD, Juergen Gross,
Frediano Ziglio
From: Edwin Török <edwin.torok@citrix.com>
Use foreign code emulation code provided by previous commit to prepare
to use new hypercall.
This to make sure there are no regression in both functionality and
performance.
In particular tested:
- HVM VM;
- PV VM;
- verification code.
Migration times did not change.
Signed-off-by: Edwin Török <edwin.torok@citrix.com>
Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com>
---
Changes since v6:
- merge with "finalize PoC" to remove the PoC;
- remove statistics, old and not clear at all how they were made;
- describe tests made.
---
tools/libs/guest/xg_sr_common.h | 4 +-
tools/libs/guest/xg_sr_restore.c | 78 +++++++++++++++++---------------
tools/libs/guest/xg_sr_save.c | 62 +++++++++++--------------
3 files changed, 71 insertions(+), 73 deletions(-)
diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h
index 50f235ba87..ec3435790a 100644
--- a/tools/libs/guest/xg_sr_common.h
+++ b/tools/libs/guest/xg_sr_common.h
@@ -243,6 +243,7 @@ struct xc_sr_context
unsigned long *deferred_pages;
unsigned long nr_deferred_pages;
xc_hypercall_buffer_t dirty_bitmap_hbuf;
+ xc_hypercall_buffer_t dest_buf;
struct xc_sr_context_save_buffers
{
xen_pfn_t batch_pfns[MAX_BATCH_SIZE];
@@ -256,8 +257,6 @@ struct xc_sr_context
struct iovec iov[MAX_BATCH_SIZE + 2]; /* Headers + data. */
MEM_NOACCESS_BUFFER(na4, 64);
uint64_t rec_pfns[MAX_BATCH_SIZE];
- MEM_NOACCESS_BUFFER(na5, 64);
- int errors[MAX_BATCH_SIZE];
} *buffers;
} save;
@@ -269,6 +268,7 @@ struct xc_sr_context
int send_back_fd;
unsigned long p2m_size;
xc_hypercall_buffer_t dirty_bitmap_hbuf;
+ xc_hypercall_buffer_t verify_buf;
/* From Image Header. */
uint32_t format_version;
diff --git a/tools/libs/guest/xg_sr_restore.c b/tools/libs/guest/xg_sr_restore.c
index 458eaa5992..af97f3d466 100644
--- a/tools/libs/guest/xg_sr_restore.c
+++ b/tools/libs/guest/xg_sr_restore.c
@@ -257,16 +257,15 @@ static int process_page_data(struct xc_sr_context *ctx, unsigned int count,
{
xc_interface *xch = ctx->xch;
xen_pfn_t *mfns = malloc(count * sizeof(*mfns));
- int *map_errs = malloc(count * sizeof(*map_errs));
int rc;
- void *mapping = NULL, *guest_page = NULL;
unsigned int nr_pages = 0;
+ void *const source = page_data;
- if ( !mfns || !map_errs )
+ if ( !mfns )
{
rc = -1;
ERROR("Failed to allocate %zu bytes to process page data",
- count * (sizeof(*mfns) + sizeof(*map_errs)));
+ count * sizeof(*mfns));
goto err;
}
@@ -294,27 +293,8 @@ static int process_page_data(struct xc_sr_context *ctx, unsigned int count,
if ( nr_pages == 0 )
goto done;
- mapping = guest_page = xenforeignmemory_map(
- xch->fmem, ctx->domid, PROT_READ | PROT_WRITE,
- nr_pages, mfns, map_errs);
- if ( !mapping )
- {
- rc = -1;
- PERROR("Unable to map %u mfns for %u pages of data",
- nr_pages, count);
- goto err;
- }
-
for ( unsigned int i = 0; i < nr_pages; ++i )
{
- if ( map_errs[i] )
- {
- rc = -1;
- ERROR("Mapping pfn %#"PRIpfn" (mfn %#"PRIpfn", type %#"PRIx32") failed with %d",
- pfns[i], mfns[i], types[i], map_errs[i]);
- goto err;
- }
-
/* Undo page normalisation done by the saver. */
rc = ctx->restore.ops.localise_page(ctx, types[i], page_data);
if ( rc )
@@ -324,31 +304,41 @@ static int process_page_data(struct xc_sr_context *ctx, unsigned int count,
goto err;
}
- if ( ctx->restore.verify )
+ page_data += PAGE_SIZE;
+ }
+ if ( !ctx->restore.verify )
+ {
+ rc = xg_foreignmemory_copy_to(xch, ctx->domid, nr_pages, mfns, source);
+ if ( rc < 0 )
+ goto err;
+ }
+ else
+ {
+ DECLARE_HYPERCALL_BUFFER_SHADOW(uint8_t, verify_buf,
+ &ctx->restore.verify_buf);
+ void *guest_page = verify_buf;
+
+ rc = xg_foreignmemory_copy_from(xch, ctx->domid, nr_pages, verify_buf, mfns);
+ if ( rc < 0 )
+ goto err;
+
+ page_data = source;
+ for ( unsigned int i = 0; i < nr_pages; ++i )
{
/* Verify mode - compare incoming data to what we already have. */
if ( memcmp(guest_page, page_data, PAGE_SIZE) )
ERROR("verify pfn %#"PRIpfn" failed (type %#"PRIx32")",
pfns[i], types[i] >> XEN_DOMCTL_PFINFO_LTAB_SHIFT);
- }
- else
- {
- /* Regular mode - copy incoming data into place. */
- memcpy(guest_page, page_data, PAGE_SIZE);
- }
- guest_page += PAGE_SIZE;
- page_data += PAGE_SIZE;
+ guest_page += PAGE_SIZE;
+ page_data += PAGE_SIZE;
+ }
}
done:
rc = 0;
err:
- if ( mapping )
- xenforeignmemory_unmap(xch->fmem, mapping, nr_pages);
-
- free(map_errs);
free(mfns);
return rc;
@@ -738,6 +728,18 @@ static int setup(struct xc_sr_context *ctx)
int rc;
DECLARE_HYPERCALL_BUFFER_SHADOW(unsigned long, dirty_bitmap,
&ctx->restore.dirty_bitmap_hbuf);
+ DECLARE_HYPERCALL_BUFFER_SHADOW(uint8_t, verify_buf,
+ &ctx->restore.verify_buf);
+
+ verify_buf = xc_hypercall_buffer_alloc_pages(
+ xch, verify_buf, MAX_BATCH_SIZE);
+
+ if ( !verify_buf )
+ {
+ ERROR("Unable to allocate memory for test buffer");
+ rc = -1;
+ goto err;
+ }
if ( ctx->stream_type == XC_STREAM_COLO )
{
@@ -786,6 +788,8 @@ static void cleanup(struct xc_sr_context *ctx)
unsigned int i;
DECLARE_HYPERCALL_BUFFER_SHADOW(unsigned long, dirty_bitmap,
&ctx->restore.dirty_bitmap_hbuf);
+ DECLARE_HYPERCALL_BUFFER_SHADOW(uint8_t, verify_buf,
+ &ctx->restore.verify_buf);
for ( i = 0; i < ctx->restore.buffered_rec_num; i++ )
free(ctx->restore.buffered_records[i].data);
@@ -794,6 +798,8 @@ static void cleanup(struct xc_sr_context *ctx)
xc_hypercall_buffer_free_pages(
xch, dirty_bitmap, NRPAGES(bitmap_size(ctx->restore.p2m_size)));
+ xc_hypercall_buffer_free_pages(xch, verify_buf, MAX_BATCH_SIZE);
+
free(ctx->restore.buffered_records);
free(ctx->restore.populated_pfns);
diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c
index 96d7e9e2f8..6b381f0219 100644
--- a/tools/libs/guest/xg_sr_save.c
+++ b/tools/libs/guest/xg_sr_save.c
@@ -86,11 +86,9 @@ static int write_checkpoint_record(struct xc_sr_context *ctx)
static int write_batch(struct xc_sr_context *ctx)
{
xc_interface *xch = ctx->xch;
- void *guest_mapping = NULL;
int rc = -1;
- unsigned int i, p, nr_pages = 0, nr_pages_mapped = 0;
+ unsigned int i, nr_pages = 0;
unsigned int nr_pfns = ctx->save.nr_batch_pfns;
- void *page, *orig_page;
int iovcnt = 0;
xen_pfn_t *const batch_pfns = ctx->save.buffers->batch_pfns;
struct {
@@ -111,8 +109,6 @@ static int write_batch(struct xc_sr_context *ctx)
xen_pfn_t *const mfns = ctx->save.buffers->mfns;
/* Types of the batch pfns. */
xen_pfn_t *const types = ctx->save.buffers->types;
- /* Errors from attempting to map the gfns. */
- int *const errors = ctx->save.buffers->errors;
/* Pointers to locally allocated pages. Need freeing. */
void **const local_pages = ctx->save.buffers->local_pages;
/* iovec[] for writev(). */
@@ -170,30 +166,26 @@ static int write_batch(struct xc_sr_context *ctx)
mfns[nr_pages++] = mfns[i];
}
- if ( nr_pages > 0 )
+ if ( nr_pages )
{
- guest_mapping = xenforeignmemory_map(
- xch->fmem, ctx->domid, PROT_READ, nr_pages, mfns, errors);
- if ( !guest_mapping )
+ DECLARE_HYPERCALL_BUFFER_SHADOW(uint8_t, dest_buf,
+ &ctx->save.dest_buf);
+
+ rc = xg_foreignmemory_copy_from(xch, ctx->domid, nr_pages, dest_buf, mfns);
+ if ( rc < 0 )
{
- PERROR("Failed to map guest pages");
+ ERROR("xg_foreignmemory_copy_from failed");
goto err;
}
- nr_pages_mapped = nr_pages;
- for ( i = 0, p = 0; i < nr_pfns; ++i )
+ for ( unsigned int i = 0, p = 0; i < nr_pfns; ++i )
{
+ void *page, *orig_page;
+
if ( !page_type_has_stream_data(types[i]) )
continue;
- if ( errors[p] )
- {
- ERROR("Mapping of pfn %#"PRIpfn" (mfn %#"PRIpfn") failed %d",
- batch_pfns[i], mfns[p], errors[p]);
- goto err;
- }
-
- orig_page = page = guest_mapping + (p * PAGE_SIZE);
+ orig_page = page = dest_buf + (p * PAGE_SIZE);
rc = ctx->save.ops.normalise_page(ctx, types[i], &page);
if ( orig_page != page )
@@ -201,15 +193,13 @@ static int write_batch(struct xc_sr_context *ctx)
if ( rc )
{
- if ( rc == -1 && errno == EAGAIN )
- {
- set_bit(batch_pfns[i], ctx->save.deferred_pages);
- ++ctx->save.nr_deferred_pages;
- types[i] = XEN_DOMCTL_PFINFO_XTAB;
- --nr_pages;
- }
- else
+ if ( rc != -1 || errno != EAGAIN )
goto err;
+
+ set_bit(batch_pfns[i], ctx->save.deferred_pages);
+ ++ctx->save.nr_deferred_pages;
+ types[i] = XEN_DOMCTL_PFINFO_XTAB;
+ --nr_pages;
}
else if ( iov[iovcnt - 1].iov_base + iov[iovcnt - 1].iov_len !=
page )
@@ -222,8 +212,6 @@ static int write_batch(struct xc_sr_context *ctx)
{
iov[iovcnt - 1].iov_len += PAGE_SIZE;
}
-
- rc = -1;
++p;
}
}
@@ -236,14 +224,13 @@ static int write_batch(struct xc_sr_context *ctx)
if ( writev_exact(ctx->fd, iov, iovcnt) )
{
PERROR("Failed to write page data to stream");
+ rc = -1;
goto err;
}
rc = ctx->save.nr_batch_pfns = 0;
err:
- if ( guest_mapping )
- xenforeignmemory_unmap(xch->fmem, guest_mapping, nr_pages_mapped);
for ( i = 0; i < nr_pfns; ++i )
{
free(local_pages[i]);
@@ -770,17 +757,21 @@ static int setup(struct xc_sr_context *ctx)
int rc;
DECLARE_HYPERCALL_BUFFER_SHADOW(unsigned long, dirty_bitmap,
&ctx->save.dirty_bitmap_hbuf);
+ DECLARE_HYPERCALL_BUFFER_SHADOW(uint8_t, dest_buf,
+ &ctx->save.dest_buf);
rc = ctx->save.ops.setup(ctx);
if ( rc )
goto err;
+ dest_buf = xc_hypercall_buffer_alloc_pages(
+ xch, dest_buf, MAX_BATCH_SIZE);
dirty_bitmap = xc_hypercall_buffer_alloc_pages(
xch, dirty_bitmap, NRPAGES(bitmap_size(ctx->save.p2m_size)));
ctx->save.deferred_pages = bitmap_alloc(ctx->save.p2m_size);
ctx->save.buffers = calloc(1, sizeof(*ctx->save.buffers));
- if ( !ctx->save.buffers || !dirty_bitmap || !ctx->save.deferred_pages )
+ if ( !ctx->save.buffers || !dirty_bitmap || !ctx->save.deferred_pages || !dest_buf )
{
ERROR("Unable to allocate memory for dirty bitmaps, deferred pages"
" and various batch buffers");
@@ -793,7 +784,6 @@ static int setup(struct xc_sr_context *ctx)
MEM_NOACCESS_INIT(ctx->save.buffers->na2);
MEM_NOACCESS_INIT(ctx->save.buffers->na3);
MEM_NOACCESS_INIT(ctx->save.buffers->na4);
- MEM_NOACCESS_INIT(ctx->save.buffers->na5);
rc = 0;
@@ -806,7 +796,8 @@ static void cleanup(struct xc_sr_context *ctx)
xc_interface *xch = ctx->xch;
DECLARE_HYPERCALL_BUFFER_SHADOW(unsigned long, dirty_bitmap,
&ctx->save.dirty_bitmap_hbuf);
-
+ DECLARE_HYPERCALL_BUFFER_SHADOW(uint8_t, dest_buf,
+ &ctx->save.dest_buf);
xc_shadow_control(xch, ctx->domid, XEN_DOMCTL_SHADOW_OP_OFF,
NULL, 0);
@@ -816,6 +807,7 @@ static void cleanup(struct xc_sr_context *ctx)
xc_hypercall_buffer_free_pages(xch, dirty_bitmap,
NRPAGES(bitmap_size(ctx->save.p2m_size)));
+ xc_hypercall_buffer_free_pages(xch, dest_buf, MAX_BATCH_SIZE);
free(ctx->save.deferred_pages);
free(ctx->save.buffers);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v10 7/10] xen: implement new foreign copy hypercall
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
` (5 preceding siblings ...)
2026-08-10 10:30 ` [PATCH v10 6/10] libs/guest: use foreign copy API during migration Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 8/10] privcmd: Add definition for new Linux privcmd to access new Xen hypercall Frediano Ziglio
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Frediano Ziglio, Jan Beulich, Andrew Cooper, Roger Pau Monné,
Teddy Astie, Anthony PERARD, Juergen Gross, Daniel P . Smith
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@citrix.com>
---
Changes since v4:
- Fix typo in comment.
Changes since v5:
- update xen_foreigncopy structure comments;
- move check for no frames after checking the domain;
- use mnemonic instead of 1U;
- fix page type checks;
- do not overwrite error copying back structure;
- latch MFN value;
- improved commit message.
Changes since v6:
- check permissions before nr_frames;
- different flag for read or write;
- print error as negative for coherence;
- update some comments;
- different page types for different architectures.
Changes since v9:
- page permission checks like MMU_UPDATE;
- new XSM settings;
- do not restrict domain;
- different explanation why HVM guests are not supported.
---
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(-)
diff --git a/xen/common/memory.c b/xen/common/memory.c
index 9443e35a7f..29a70d99b1 100644
--- 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(
+ XEN_GUEST_HANDLE_PARAM(xen_foreigncopy_t) arg)
+{
+ struct domain *d, *const currd = current->domain;
+ xen_foreigncopy_t copy;
+ int rc, direction;
+
+ if ( copy_from_guest(©, 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.
+ */
+ 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);
+
+ 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);
+
+ 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);
+ 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;
+ }
+
+ 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);
+ goto out;
+ }
+ }
+
+ rc = 0;
+
+ out:
+ rcu_unlock_domain(d);
+
+ /*
+ * Update in all cases, it allows the caller to know how many
+ * frames were successfully copied and the continuation to
+ * continue correctly.
+ */
+ if ( __copy_to_guest(arg, ©, 1) && rc >= 0 )
+ rc = -EFAULT;
+
+ return rc;
+}
+
long do_memory_op(unsigned long cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
{
struct domain *d, *curr_d = current->domain;
@@ -2027,6 +2162,20 @@ long do_memory_op(unsigned long cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
start_extent);
break;
+ case XENMEM_foreigncopy:
+ /*
+ * Instead of using "start_extent" for the continuation, we update
+ * the xen_foreigncopy structure back, so we are not constrained by
+ * MEMOP_EXTENT_SHIFT.
+ * We copy it back also to tell the caller where the copy stopped
+ * (either for error or because all frames were copied).
+ */
+ if ( unlikely(start_extent) )
+ return -EINVAL;
+
+ rc = mem_foreigncopy(guest_handle_cast(arg, xen_foreigncopy_t));
+ break;
+
default:
rc = arch_memory_op(cmd, arg);
break;
diff --git a/xen/include/public/memory.h b/xen/include/public/memory.h
index bd9fc37b52..66bd2a6c42 100644
--- 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
+ * are only supported for PV guests.
+ */
+#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.
+ * On output updated number of frames left (0 if success).
+ */
+ uint32_t nr_frames;
+
+ /*
+ * IN/OUT
+ *
+ * Frames to be copied.
+ * On output updated to point to the first frame unhandled, if any.
+ */
+ XEN_GUEST_HANDLE(xen_pfn_t) frame_list;
+
+ /*
+ * IN/OUT
+ *
+ * Guest buffer to read/write from.
+ * On output updated to point to the first page pointer unhandled.
+ */
+ XEN_GUEST_HANDLE(uint8) buffer;
+};
+typedef struct xen_foreigncopy xen_foreigncopy_t;
+DEFINE_XEN_GUEST_HANDLE(xen_foreigncopy_t);
+
+/* Next available subop number is 30 */
#endif /* __XEN_PUBLIC_MEMORY_H__ */
diff --git a/xen/include/xsm/dummy.h b/xen/include/xsm/dummy.h
index 131631cb27..dcdb7f5396 100644
--- 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)
+{
+ 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)
+{
+ XSM_ASSERT_ACTION(XSM_TARGET);
+ return xsm_default_action(action, d, t);
+}
+
#ifdef CONFIG_HVM
static XSM_INLINE int cf_check xsm_hvm_param(
diff --git a/xen/include/xsm/hooks.h b/xen/include/xsm/hooks.h
index 5bdb23f26d..63e2831d31 100644
--- 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 *);
XSM_HOOK(int, console_io, struct domain *, int)
diff --git a/xen/xsm/flask/hooks.c b/xen/xsm/flask/hooks.c
index 3cfdf6bf08..281800e176 100644
--- 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);
+}
+
#ifdef CONFIG_HVM
static int cf_check flask_hvm_param(struct domain *d, unsigned long op)
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v10 8/10] privcmd: Add definition for new Linux privcmd to access new Xen hypercall
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
` (6 preceding siblings ...)
2026-08-10 10:30 ` [PATCH v10 7/10] xen: implement new foreign copy hypercall Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
2026-08-10 10:30 ` [PATCH v10 9/10] libs/guest: use new hypercall if available Frediano Ziglio
2026-08-10 10:30 ` [PATCH Linux v6 10/10] xen/privcmd: Add new ABI to allow copying foreign memory Frediano Ziglio
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Frediano Ziglio, Jan Beulich, Andrew Cooper, Roger Pau Monné,
Teddy Astie, Anthony PERARD, Juergen Gross
Userspace should use new ioctl to access new hypercall.
Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com>
---
Changes since v4:
- update comment.
---
tools/include/xen-sys/Linux/privcmd.h | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/tools/include/xen-sys/Linux/privcmd.h b/tools/include/xen-sys/Linux/privcmd.h
index 607dfa2287..7a3c41308b 100644
--- a/tools/include/xen-sys/Linux/privcmd.h
+++ b/tools/include/xen-sys/Linux/privcmd.h
@@ -100,6 +100,14 @@ typedef struct privcmd_pcidev_get_gsi {
__u32 gsi;
} privcmd_pcidev_get_gsi_t;
+typedef struct privcmd_foreigncopy {
+ domid_t dom; /* Foreign domain. */
+ __u16 dir; /* Direction, 0 from, 1 to. */
+ __u32 num; /* Number of pages to copy. */
+ const xen_pfn_t __user *pfns; /* Array of pfns. */
+ void __user *buffer; /* Buffer to copy to/from. */
+} privcmd_foreigncopy_t;
+
/*
* @cmd: IOCTL_PRIVCMD_HYPERCALL
* @arg: &privcmd_hypercall_t
@@ -121,6 +129,8 @@ typedef struct privcmd_pcidev_get_gsi {
_IOC(_IOC_NONE, 'P', 7, sizeof(privcmd_mmap_resource_t))
#define IOCTL_PRIVCMD_PCIDEV_GET_GSI \
_IOC(_IOC_NONE, 'P', 10, sizeof(privcmd_pcidev_get_gsi_t))
+#define IOCTL_PRIVCMD_FOREIGNCOPY \
+ _IOWR('P', 11, privcmd_foreigncopy_t)
#define IOCTL_PRIVCMD_UNIMPLEMENTED \
_IOC(_IOC_NONE, 'P', 0xFF, 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v10 9/10] libs/guest: use new hypercall if available
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
` (7 preceding siblings ...)
2026-08-10 10:30 ` [PATCH v10 8/10] privcmd: Add definition for new Linux privcmd to access new Xen hypercall Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
2026-08-10 10:30 ` [PATCH Linux v6 10/10] xen/privcmd: Add new ABI to allow copying foreign memory Frediano Ziglio
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Frediano Ziglio, Jan Beulich, Andrew Cooper, Roger Pau Monné,
Teddy Astie, Anthony PERARD, Juergen Gross
Use new hypercall if available, otherwise fall back to map+copy+unmap
sequence.
I took some statistics while migrating some machines instrumenting the
code to use new and old code and doing it 5 times in a row for each and
the raw operation takes at least 4 (from) or 5 (to) times less.
Specifically for a test done with a machine with Intel Xeon Sapphire
Rapids CPUs and migrating a Windows 10 machine with 12 GB of RAM
the ratios were:
- 4.9 times faster copying from guest to dom0;
- 5.3 times faster copying to guest from dom0.
The test was repeated multiple times resulting consistent in all rans.
Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com>
---
Changes since v4:
- use int8_t instead of char for signed type.
Changes since v6:
- add some statistics.
Changes since v9:
- fixed a pointer initialization.
---
tools/libs/guest/xg_sr_common.c | 47 ++++++++++++++++++++++++++-------
1 file changed, 38 insertions(+), 9 deletions(-)
diff --git a/tools/libs/guest/xg_sr_common.c b/tools/libs/guest/xg_sr_common.c
index 90da21c35f..ce5026c707 100644
--- a/tools/libs/guest/xg_sr_common.c
+++ b/tools/libs/guest/xg_sr_common.c
@@ -156,11 +156,6 @@ static void __attribute__((unused)) build_assertions(void)
BUILD_BUG_ON(sizeof(struct xc_sr_rec_hvm_params) != 8);
}
-enum {
- foreigncopy_from,
- foreigncopy_to
-};
-
static int xg_foreignmemory_copy(xc_interface *xch, domid_t domid,
int dir, size_t nr_pages, void *buffer,
const xen_pfn_t foreign_pfns[nr_pages])
@@ -174,8 +169,42 @@ static int xg_foreignmemory_copy(xc_interface *xch, domid_t domid,
return -1;
}
+ /*
+ * If foreign copy is supported, -1 not initialized, 0 not supported,
+ * 1 supported.
+ */
+ static int8_t foreign_copy_supported = -1;
+
+ if ( foreign_copy_supported )
+ {
+ int rc;
+ privcmd_foreigncopy_t copy = {
+ .dom = domid,
+ .dir = dir,
+ .num = nr_pages,
+ .buffer = buffer,
+ };
+ DECLARE_HYPERCALL_BOUNCE_IN(foreign_pfns, nr_pages * sizeof(xen_pfn_t));
+
+ if ( xc_hypercall_bounce_pre(xch, foreign_pfns) )
+ return -1;
+
+ copy.pfns = (xen_pfn_t *)HYPERCALL_BUFFER_AS_ARG(foreign_pfns);
+
+ rc = ioctl(xencall_fd(xch->xcall), IOCTL_PRIVCMD_FOREIGNCOPY, ©);
+ if ( foreign_copy_supported < 0 )
+ foreign_copy_supported =
+ (!rc || (errno != ENOTTY && errno != ENOSYS));
+
+ xc_hypercall_bounce_post(xch, foreign_pfns);
+
+ if ( foreign_copy_supported )
+ return rc;
+ }
+
+ /* Fallback, emulate. */
int err[nr_pages];
- const int prot = (dir == foreigncopy_from) ? PROT_READ : PROT_READ|PROT_WRITE;
+ const int prot = (dir == XENMEM_foreigncopy_from) ? PROT_READ : PROT_READ|PROT_WRITE;
void *p = xenforeignmemory_map(xch->fmem, domid, prot, nr_pages, foreign_pfns, err);
if ( !p )
@@ -189,7 +218,7 @@ static int xg_foreignmemory_copy(xc_interface *xch, domid_t domid,
return -1;
}
- if ( dir == foreigncopy_from )
+ if ( dir == XENMEM_foreigncopy_from )
memcpy(buffer, p, nr_pages * XC_PAGE_SIZE);
else
memcpy(p, buffer, nr_pages * XC_PAGE_SIZE);
@@ -201,7 +230,7 @@ int xg_foreignmemory_copy_from(xc_interface *xch, domid_t dom,
size_t nr_pages, void *dest,
const xen_pfn_t source[nr_pages])
{
- return xg_foreignmemory_copy(xch, dom, foreigncopy_from,
+ return xg_foreignmemory_copy(xch, dom, XENMEM_foreigncopy_from,
nr_pages, dest, source);
}
@@ -209,7 +238,7 @@ int xg_foreignmemory_copy_to(xc_interface *xch, domid_t dom,
size_t nr_pages, const xen_pfn_t dest[nr_pages],
const void *source)
{
- return xg_foreignmemory_copy(xch, dom, foreigncopy_to,
+ return xg_foreignmemory_copy(xch, dom, XENMEM_foreigncopy_to,
nr_pages, (void *) source, dest);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH Linux v6 10/10] xen/privcmd: Add new ABI to allow copying foreign memory
2026-08-10 10:30 [PATCH v10 0/10] xenguest optimisations Frediano Ziglio
` (8 preceding siblings ...)
2026-08-10 10:30 ` [PATCH v10 9/10] libs/guest: use new hypercall if available Frediano Ziglio
@ 2026-08-10 10:30 ` Frediano Ziglio
9 siblings, 0 replies; 11+ messages in thread
From: Frediano Ziglio @ 2026-08-10 10:30 UTC (permalink / raw)
To: xen-devel
Cc: Frediano Ziglio, Jan Beulich, Andrew Cooper, Roger Pau Monné,
Teddy Astie, Anthony PERARD, Juergen Gross
This new ABI allows to copy foreign domain memory to/from a buffer.
This avoids having to map/copy/unmap foreign memory which is
expensive.
This operation is done particularly when migrating VMs.
Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com>
--
Changes since v4:
- fix wrong assign;
- use set_xen_guest_handle to set handle;
- wrap slow hypercall with xen_preemptible_hcall_{begin,end};
- use _IOWR for ioctl code to be more specific;
- use __copy_to_user if buffer already checked.
Changes since v6:
- compatibility ARM/x86.
---
arch/x86/include/asm/xen/interface.h | 3 ++
drivers/xen/privcmd.c | 49 ++++++++++++++++++++++++++++
include/uapi/xen/privcmd.h | 10 ++++++
include/xen/arm/interface.h | 2 ++
include/xen/interface/memory.h | 37 +++++++++++++++++++++
5 files changed, 101 insertions(+)
diff --git a/arch/x86/include/asm/xen/interface.h b/arch/x86/include/asm/xen/interface.h
index a078a2b0f032..fc76fac8fb16 100644
--- a/arch/x86/include/asm/xen/interface.h
+++ b/arch/x86/include/asm/xen/interface.h
@@ -59,6 +59,7 @@
#elif defined(__x86_64__)
#define set_xen_guest_handle(hnd, val) do { (hnd).p = val; } while (0)
#endif
+#define get_xen_guest_handle(hnd) ((hnd).p)
#else
#if defined(__i386__)
#define set_xen_guest_handle(hnd, val) \
@@ -70,6 +71,7 @@
#elif defined(__x86_64__)
#define set_xen_guest_handle(hnd, val) do { (hnd) = val; } while (0)
#endif
+#define get_xen_guest_handle(hnd) (hnd)
#endif
#ifndef __ASSEMBLER__
@@ -91,6 +93,7 @@ DEFINE_GUEST_HANDLE(int);
DEFINE_GUEST_HANDLE(void);
DEFINE_GUEST_HANDLE(uint64_t);
DEFINE_GUEST_HANDLE(uint32_t);
+DEFINE_GUEST_HANDLE(uint8_t);
DEFINE_GUEST_HANDLE(xen_pfn_t);
DEFINE_GUEST_HANDLE(xen_ulong_t);
#endif
diff --git a/drivers/xen/privcmd.c b/drivers/xen/privcmd.c
index 725a49a0eee7..55364801ba2e 100644
--- a/drivers/xen/privcmd.c
+++ b/drivers/xen/privcmd.c
@@ -1522,6 +1522,51 @@ static inline void privcmd_ioeventfd_exit(void)
}
#endif /* CONFIG_XEN_PRIVCMD_EVENTFD */
+static long privcmd_ioctl_foreigncopy(
+ struct file *file, void __user *udata)
+{
+ const struct privcmd_data *const data = file->private_data;
+ long ret;
+ struct privcmd_foreigncopy copy;
+ struct xen_foreigncopy xcopy;
+
+ if (copy_from_user(©, udata, sizeof(copy)))
+ return -EFAULT;
+ if (copy.dir & ~1u)
+ return -EINVAL;
+ if (copy.num >= U32_MAX >> PAGE_SHIFT)
+ return -EINVAL;
+ if (!access_ok(copy.pfns, copy.num * sizeof(*copy.pfns)))
+ return -EFAULT;
+ if (!access_ok(copy.buffer, copy.num << PAGE_SHIFT))
+ return -EFAULT;
+
+ /* If restriction is in place, check the domid matches */
+ if (data->domid != DOMID_INVALID && data->domid != copy.dom)
+ return -EPERM;
+
+ xcopy.domid = copy.dom;
+ xcopy.flags = copy.dir;
+ xcopy.nr_frames = copy.num;
+ set_xen_guest_handle(xcopy.frame_list, (__force xen_pfn_t *)copy.pfns);
+ set_xen_guest_handle(xcopy.buffer, (__force uint8_t *)copy.buffer);
+
+ xen_preemptible_hcall_begin();
+ ret = HYPERVISOR_memory_op(XENMEM_foreigncopy, &xcopy);
+ xen_preemptible_hcall_end();
+
+ /* copy values back in case of error */
+ if (ret) {
+ copy.num = xcopy.nr_frames;
+ copy.pfns = get_xen_guest_handle(xcopy.frame_list);
+ copy.buffer = get_xen_guest_handle(xcopy.buffer);
+ if (__copy_to_user(udata, ©, sizeof(copy)))
+ ret = -EFAULT;
+ }
+
+ return ret;
+}
+
static long privcmd_ioctl(struct file *file,
unsigned int cmd, unsigned long data)
{
@@ -1569,6 +1614,10 @@ static long privcmd_ioctl(struct file *file,
ret = privcmd_ioctl_pcidev_get_gsi(file, udata);
break;
+ case IOCTL_PRIVCMD_FOREIGNCOPY:
+ ret = privcmd_ioctl_foreigncopy(file, udata);
+ break;
+
default:
break;
}
diff --git a/include/uapi/xen/privcmd.h b/include/uapi/xen/privcmd.h
index 8e2c8fd44764..993b501e35bf 100644
--- a/include/uapi/xen/privcmd.h
+++ b/include/uapi/xen/privcmd.h
@@ -131,6 +131,14 @@ struct privcmd_pcidev_get_gsi {
__u32 gsi;
};
+struct privcmd_foreigncopy {
+ domid_t dom; /* foreign domain */
+ __u16 dir; /* direction, 0 from, 1 to */
+ __u32 num; /* number of pages to copy */
+ const xen_pfn_t __user *pfns; /* array of pfns */
+ void __user *buffer; /* buffer to copy to/from */
+};
+
/*
* @cmd: IOCTL_PRIVCMD_HYPERCALL
* @arg: &privcmd_hypercall_t
@@ -164,5 +172,7 @@ struct privcmd_pcidev_get_gsi {
_IOW('P', 9, struct privcmd_ioeventfd)
#define IOCTL_PRIVCMD_PCIDEV_GET_GSI \
_IOC(_IOC_NONE, 'P', 10, sizeof(struct privcmd_pcidev_get_gsi))
+#define IOCTL_PRIVCMD_FOREIGNCOPY \
+ _IOWR('P', 11, struct privcmd_foreigncopy)
#endif /* __LINUX_PUBLIC_PRIVCMD_H__ */
diff --git a/include/xen/arm/interface.h b/include/xen/arm/interface.h
index 61360b89da40..20ee1ed44436 100644
--- a/include/xen/arm/interface.h
+++ b/include/xen/arm/interface.h
@@ -27,6 +27,7 @@
*(uint64_t *)&(hnd) = 0; \
(hnd).p = val; \
} while (0)
+#define get_xen_guest_handle(hnd) ((hnd).p)
#define __HYPERVISOR_platform_op_raw __HYPERVISOR_platform_op
@@ -53,6 +54,7 @@ DEFINE_GUEST_HANDLE(int);
DEFINE_GUEST_HANDLE(void);
DEFINE_GUEST_HANDLE(uint64_t);
DEFINE_GUEST_HANDLE(uint32_t);
+DEFINE_GUEST_HANDLE(uint8_t);
DEFINE_GUEST_HANDLE(xen_pfn_t);
DEFINE_GUEST_HANDLE(xen_ulong_t);
diff --git a/include/xen/interface/memory.h b/include/xen/interface/memory.h
index 1a371a825c55..5981402fccde 100644
--- a/include/xen/interface/memory.h
+++ b/include/xen/interface/memory.h
@@ -325,4 +325,41 @@ struct xen_mem_acquire_resource {
};
DEFINE_GUEST_HANDLE_STRUCT(xen_mem_acquire_resource);
+/*
+ * Copy memory from/to a given domain.
+ */
+#define XENMEM_foreigncopy 29
+struct xen_foreigncopy {
+ /* IN - The domain whose resource 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
+ *
+ * As an IN parameter number of frames of the domain to be copied.
+ */
+ uint32_t nr_frames;
+
+ /*
+ * IN
+ *
+ * Frames to be copied.
+ */
+ GUEST_HANDLE(xen_pfn_t) frame_list;
+
+ /*
+ * IN/OUT
+ *
+ * Userspace buffer to read/write from.
+ */
+ GUEST_HANDLE(uint8_t) buffer;
+};
+DEFINE_GUEST_HANDLE_STRUCT(xen_foreigncopy);
+
#endif /* __XEN_PUBLIC_MEMORY_H__ */
--
2.54.0
^ permalink raw reply related [flat|nested] 11+ messages in thread