All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 0/3] Support multiple ioreq pages
@ 2026-04-20  9:38 Julian Vetter
  2026-04-20  9:38 ` [PATCH v6 1/3] ioreq: switch ioreq page allocation to vmap Julian Vetter
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Julian Vetter @ 2026-04-20  9:38 UTC (permalink / raw)
  To: xen-devel
  Cc: Jan Beulich, Andrew Cooper, Roger Pau Monné, Anthony PERARD,
	Michal Orzel, Julien Grall, Stefano Stabellini, Julian Vetter

Hello Jan,
thank you again for your thorough feedback. I have addressed your
comments. I just have one remaining issue. For the temporary MFNs array
I now use a static array, i.e., mfns[IOREQ_NR_PAGES_MAX], with a
IOREQ_NR_PAGES_MAX = DIV_ROUND_UP(HVM_MAX_VCPUS, PAGE_SIZE / sizeof(ioreq_t)).
Is this appropriate? Or should I leave the HVM_MAX_VCPUS out of this and
instead use a fixed constant value? Also for now I haven't taken the
path to permanently store the MFNs as you suggested. I'm not sure, but I
don't think it's really necessary. Since this array will rarely exceed
the size of 2 or 3.

Signed-off-by: Julian Vetter <julian.vetter@vates.tech>
---
Julian Vetter (3):
  ioreq: switch ioreq page allocation to vmap
  ioreq: Indent ioreq_server_alloc_mfn() body one level deeper
  x86/ioreq: Extend ioreq server to support multiple ioreq pages

 xen/arch/x86/hvm/ioreq.c |  63 ++++++++++++++++---
 xen/common/ioreq.c       | 127 ++++++++++++++++++++++++++-------------
 xen/include/xen/ioreq.h  |  13 +++-
 3 files changed, 151 insertions(+), 52 deletions(-)

-- 
2.53.0



--
Julian Vetter | Vates Hypervisor & Kernel Developer

XCP-ng & Xen Orchestra - Vates solutions

web: https://vates.tech



^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v6 1/3] ioreq: switch ioreq page allocation to vmap
  2026-04-20  9:38 [PATCH v6 0/3] Support multiple ioreq pages Julian Vetter
@ 2026-04-20  9:38 ` Julian Vetter
  2026-04-20  9:38 ` [PATCH v6 2/3] ioreq: Indent ioreq_server_alloc_mfn() body one level deeper Julian Vetter
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 7+ messages in thread
From: Julian Vetter @ 2026-04-20  9:38 UTC (permalink / raw)
  To: xen-devel
  Cc: Jan Beulich, Andrew Cooper, Roger Pau Monné, Anthony PERARD,
	Michal Orzel, Julien Grall, Stefano Stabellini, Julian Vetter

Switch the Xen-side ioreq page mapping from prepare_ring_for_helper() /
map_domain_page_global() to explicit vmap(), to ensure vmap_to_page()
can recover the struct page_info * uniformly during teardown.

This is a prerequisite for multi-page ioreq support: the non-buf ioreq
region will need to span multiple pages for domains with more vCPUs than
fit in a single page, and vmap() is the natural interface for contiguous
multi-page Xen VA mappings.

In non-debug builds map_domain_page_global() uses the directmap for low
MFNs rather than vmap(), so this change has a small overhead in the
common case. Debug builds already used vmap() indirectly.

With both paths using vmap(), vmap_to_page() can recover the struct
page_info * uniformly, so drop the 'page' field from struct ioreq_page
and update all callers accordingly.

Signed-off-by: Julian Vetter <julian.vetter@vates.tech>
---
Changes in v6:
- Updated commit message to clearly specify why these changes are made
- Added comment to say that this is {prepare,destroy}_ring_for_helper()
  just using vmap_to_page() + v{map,unmap}()
- Kept proper ordering in ioreq_server_free_mfn(), first clearing the va
  pointer before unmapping
---
 xen/arch/x86/hvm/ioreq.c | 55 +++++++++++++++++++++++++++++++++-------
 xen/common/ioreq.c       | 34 +++++++++++++------------
 xen/include/xen/ioreq.h  |  1 -
 3 files changed, 64 insertions(+), 26 deletions(-)

diff --git a/xen/arch/x86/hvm/ioreq.c b/xen/arch/x86/hvm/ioreq.c
index a5fa97e149..3cabec141c 100644
--- a/xen/arch/x86/hvm/ioreq.c
+++ b/xen/arch/x86/hvm/ioreq.c
@@ -15,6 +15,7 @@
 #include <xen/sched.h>
 #include <xen/softirq.h>
 #include <xen/trace.h>
+#include <xen/vmap.h>
 #include <xen/vpci.h>
 
 #include <asm/hvm/emulate.h>
@@ -128,8 +129,13 @@ static void hvm_unmap_ioreq_gfn(struct ioreq_server *s, bool buf)
     if ( gfn_eq(iorp->gfn, INVALID_GFN) )
         return;
 
-    destroy_ring_for_helper(&iorp->va, iorp->page);
-    iorp->page = NULL;
+    /* Equivalent to destroy_ring_for_helper(), using vmap_to_page(). */
+    if ( iorp->va )
+    {
+        put_page_and_type(vmap_to_page(iorp->va));
+        vunmap(iorp->va);
+        iorp->va = NULL;
+    }
 
     hvm_free_ioreq_gfn(s, iorp->gfn);
     iorp->gfn = INVALID_GFN;
@@ -139,9 +145,12 @@ static int hvm_map_ioreq_gfn(struct ioreq_server *s, bool buf)
 {
     struct domain *d = s->target;
     struct ioreq_page *iorp = buf ? &s->bufioreq : &s->ioreq;
+    struct page_info *page;
+    p2m_type_t p2mt;
+    mfn_t mfn;
     int rc;
 
-    if ( iorp->page )
+    if ( iorp->va )
     {
         /*
          * If a page has already been allocated (which will happen on
@@ -162,12 +171,40 @@ static int hvm_map_ioreq_gfn(struct ioreq_server *s, bool buf)
     if ( gfn_eq(iorp->gfn, INVALID_GFN) )
         return -ENOMEM;
 
-    rc = prepare_ring_for_helper(d, gfn_x(iorp->gfn), &iorp->page,
-                                 &iorp->va);
-
+    /*
+     * Equivalent to prepare_ring_for_helper() using vmap(). Using vmap()
+     * rather than map_domain_page_global() ensures vmap_to_page() can
+     * recover the struct page_info * uniformly at teardown, which is
+     * needed to support multi-page ioreq mappings (see nr_ioreq_pages()).
+     */
+    rc = check_get_page_from_gfn(d, iorp->gfn, false, &p2mt, &page);
     if ( rc )
-        hvm_unmap_ioreq_gfn(s, buf);
+    {
+        if ( rc == -EAGAIN )
+            rc = -ENOENT;
+        goto fail;
+    }
+
+    if ( !get_page_type(page, PGT_writable_page) )
+    {
+        put_page(page);
+        rc = -EINVAL;
+        goto fail;
+    }
+
+    mfn = page_to_mfn(page);
+    iorp->va = vmap(&mfn, 1);
+    if ( !iorp->va )
+    {
+        put_page_and_type(page);
+        rc = -ENOMEM;
+        goto fail;
+    }
+
+    return 0;
 
+ fail:
+    hvm_unmap_ioreq_gfn(s, buf);
     return rc;
 }
 
@@ -179,7 +216,7 @@ static void hvm_remove_ioreq_gfn(struct ioreq_server *s, bool buf)
     if ( gfn_eq(iorp->gfn, INVALID_GFN) )
         return;
 
-    if ( p2m_remove_page(d, iorp->gfn, page_to_mfn(iorp->page), 0) )
+    if ( p2m_remove_page(d, iorp->gfn, vmap_to_mfn(iorp->va), 0) )
         domain_crash(d);
     clear_page(iorp->va);
 }
@@ -195,7 +232,7 @@ static int hvm_add_ioreq_gfn(struct ioreq_server *s, bool buf)
 
     clear_page(iorp->va);
 
-    rc = p2m_add_page(d, iorp->gfn, page_to_mfn(iorp->page), 0, p2m_ram_rw);
+    rc = p2m_add_page(d, iorp->gfn, vmap_to_mfn(iorp->va), 0, p2m_ram_rw);
     if ( rc == 0 )
         paging_mark_pfn_dirty(d, _pfn(gfn_x(iorp->gfn)));
 
diff --git a/xen/common/ioreq.c b/xen/common/ioreq.c
index f5fd30ce12..d8d02167b4 100644
--- a/xen/common/ioreq.c
+++ b/xen/common/ioreq.c
@@ -17,11 +17,11 @@
  */
 
 #include <xen/domain.h>
-#include <xen/domain_page.h>
 #include <xen/event.h>
 #include <xen/init.h>
 #include <xen/ioreq.h>
 #include <xen/irq.h>
+#include <xen/vmap.h>
 #include <xen/lib.h>
 #include <xen/paging.h>
 #include <xen/sched.h>
@@ -262,8 +262,9 @@ static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
 {
     struct ioreq_page *iorp = buf ? &s->bufioreq : &s->ioreq;
     struct page_info *page;
+    mfn_t mfn;
 
-    if ( iorp->page )
+    if ( iorp->va )
     {
         /*
          * If a guest frame has already been mapped (which may happen
@@ -291,11 +292,11 @@ static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
         return -ENODATA;
     }
 
-    iorp->va = __map_domain_page_global(page);
+    mfn = page_to_mfn(page);
+    iorp->va = vmap(&mfn, 1);
     if ( !iorp->va )
         goto fail;
 
-    iorp->page = page;
     clear_page(iorp->va);
     return 0;
 
@@ -309,15 +310,16 @@ static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
 static void ioreq_server_free_mfn(struct ioreq_server *s, bool buf)
 {
     struct ioreq_page *iorp = buf ? &s->bufioreq : &s->ioreq;
-    struct page_info *page = iorp->page;
+    struct page_info *page;
+    void *va;
 
-    if ( !page )
+    if ( !iorp->va )
         return;
 
-    iorp->page = NULL;
-
-    unmap_domain_page_global(iorp->va);
+    va = iorp->va;
+    page = vmap_to_page(va);
     iorp->va = NULL;
+    vunmap(va);
 
     put_page_alloc_ref(page);
     put_page_and_type(page);
@@ -333,7 +335,8 @@ bool is_ioreq_server_page(struct domain *d, const struct page_info *page)
 
     FOR_EACH_IOREQ_SERVER(d, id, s)
     {
-        if ( (s->ioreq.page == page) || (s->bufioreq.page == page) )
+        if ( (s->ioreq.va && vmap_to_page(s->ioreq.va) == page) ||
+             (s->bufioreq.va && vmap_to_page(s->bufioreq.va) == page) )
         {
             found = true;
             break;
@@ -627,10 +630,9 @@ static void ioreq_server_deinit(struct ioreq_server *s)
      * NOTE: It is safe to call both arch_ioreq_server_unmap_pages() and
      *       ioreq_server_free_pages() in that order.
      *       This is because the former will do nothing if the pages
-     *       are not mapped, leaving the page to be freed by the latter.
-     *       However if the pages are mapped then the former will set
-     *       the page_info pointer to NULL, meaning the latter will do
-     *       nothing.
+     *       are not mapped, leaving the pages to be freed by the latter.
+     *       However if the pages are mapped then the former will clear
+     *       iorp->va, meaning the latter will do nothing.
      */
     arch_ioreq_server_unmap_pages(s);
     ioreq_server_free_pages(s);
@@ -819,12 +821,12 @@ int ioreq_server_get_frame(struct domain *d, ioservid_t id,
         if ( !HANDLE_BUFIOREQ(s) )
             goto out;
 
-        *mfn = page_to_mfn(s->bufioreq.page);
+        *mfn = vmap_to_mfn(s->bufioreq.va);
         rc = 0;
         break;
 
     case XENMEM_resource_ioreq_server_frame_ioreq(0):
-        *mfn = page_to_mfn(s->ioreq.page);
+        *mfn = vmap_to_mfn(s->ioreq.va);
         rc = 0;
         break;
 
diff --git a/xen/include/xen/ioreq.h b/xen/include/xen/ioreq.h
index e86f0869fa..d63fa4729e 100644
--- a/xen/include/xen/ioreq.h
+++ b/xen/include/xen/ioreq.h
@@ -25,7 +25,6 @@
 
 struct ioreq_page {
     gfn_t gfn;
-    struct page_info *page;
     void *va;
 };
 
-- 
2.53.0



--
Julian Vetter | Vates Hypervisor & Kernel Developer

XCP-ng & Xen Orchestra - Vates solutions

web: https://vates.tech



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v6 2/3] ioreq: Indent ioreq_server_alloc_mfn() body one level deeper
  2026-04-20  9:38 [PATCH v6 0/3] Support multiple ioreq pages Julian Vetter
  2026-04-20  9:38 ` [PATCH v6 1/3] ioreq: switch ioreq page allocation to vmap Julian Vetter
@ 2026-04-20  9:38 ` Julian Vetter
  2026-04-20  9:38 ` [PATCH v6 3/3] x86/ioreq: Extend ioreq server to support multiple ioreq pages Julian Vetter
  2026-04-20 10:05 ` [PATCH v6 0/3] Support " Jan Beulich
  3 siblings, 0 replies; 7+ messages in thread
From: Julian Vetter @ 2026-04-20  9:38 UTC (permalink / raw)
  To: xen-devel
  Cc: Jan Beulich, Andrew Cooper, Roger Pau Monné, Anthony PERARD,
	Michal Orzel, Julien Grall, Stefano Stabellini, Julian Vetter

No functional change. It adds a wrapping block to prepare for the loop
that the subsequent patch introduces to handle multiple ioreq pages.

Signed-off-by: Julian Vetter <julian.vetter@vates.tech>
---
Changes in v6:
- Dropped the indentation change for ioreq_server_free_mfn, because the
  modifications in the next patch don't really merit the change anymore
---
 xen/common/ioreq.c | 28 +++++++++++++++-------------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/xen/common/ioreq.c b/xen/common/ioreq.c
index d8d02167b4..bae9b99c99 100644
--- a/xen/common/ioreq.c
+++ b/xen/common/ioreq.c
@@ -277,22 +277,24 @@ static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
         return 0;
     }
 
-    page = alloc_domheap_page(s->target, MEMF_no_refcount);
+    {
+        page = alloc_domheap_page(s->target, MEMF_no_refcount);
 
-    if ( !page )
-        return -ENOMEM;
+        if ( !page )
+            return -ENOMEM;
 
-    if ( !get_page_and_type(page, s->target, PGT_writable_page) )
-    {
-        /*
-         * The domain can't possibly know about this page yet, so failure
-         * here is a clear indication of something fishy going on.
-         */
-        domain_crash(s->emulator);
-        return -ENODATA;
-    }
+        if ( !get_page_and_type(page, s->target, PGT_writable_page) )
+        {
+            /*
+             * The domain can't possibly know about this page yet, so failure
+             * here is a clear indication of something fishy going on.
+             */
+            domain_crash(s->emulator);
+            return -ENODATA;
+        }
 
-    mfn = page_to_mfn(page);
+        mfn = page_to_mfn(page);
+    }
     iorp->va = vmap(&mfn, 1);
     if ( !iorp->va )
         goto fail;
-- 
2.53.0



--
Julian Vetter | Vates Hypervisor & Kernel Developer

XCP-ng & Xen Orchestra - Vates solutions

web: https://vates.tech



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v6 3/3] x86/ioreq: Extend ioreq server to support multiple ioreq pages
  2026-04-20  9:38 [PATCH v6 0/3] Support multiple ioreq pages Julian Vetter
  2026-04-20  9:38 ` [PATCH v6 1/3] ioreq: switch ioreq page allocation to vmap Julian Vetter
  2026-04-20  9:38 ` [PATCH v6 2/3] ioreq: Indent ioreq_server_alloc_mfn() body one level deeper Julian Vetter
@ 2026-04-20  9:38 ` Julian Vetter
  2026-04-20 12:49   ` Teddy Astie
  2026-04-20 10:05 ` [PATCH v6 0/3] Support " Jan Beulich
  3 siblings, 1 reply; 7+ messages in thread
From: Julian Vetter @ 2026-04-20  9:38 UTC (permalink / raw)
  To: xen-devel
  Cc: Jan Beulich, Andrew Cooper, Roger Pau Monné, Anthony PERARD,
	Michal Orzel, Julien Grall, Stefano Stabellini, Julian Vetter

As the number of vCPUs grows, a single ioreq page of 128 slots may not
be sufficient. Add support for allocating and mapping multiple ioreq
pages so that the ioreq region can scale with d->max_vcpus.

Introduce nr_ioreq_pages() to compute the number of pages required for
a given domain, and IOREQ_NR_PAGES_MAX as a compile-time upper bound
(based on HVM_MAX_VCPUS).

ioreq_server_alloc_mfn() is updated to allocate nr_ioreq_pages() pages
and map them contiguously via vmap().

is_ioreq_server_page() iterates over all ioreq pages when checking
page ownership. ioreq_server_get_frame() allows callers to retrieve any
ioreq page by index via the XENMEM_acquire_resource interface.

On x86, the legacy GFN mapping path (hvm_map_ioreq_gfn) is limited to
a single ioreq page; device models requiring more ioreq slots must use
the resource mapping interface (XENMEM_acquire_resource).

Signed-off-by: Julian Vetter <julian.vetter@vates.tech>
---
Changes in v6:
- Adapted the comment to not mention the guest, but the device model
- Replaced the dynamic allocation for the mfns array by a static array
- Fixed error handling in ioreq_server_alloc_mfn, using an extra
  nr_alloc variable to track the already allocated pages
- Dropped unnecessary void casts
---
 xen/arch/x86/hvm/ioreq.c |  8 ++++
 xen/common/ioreq.c       | 93 ++++++++++++++++++++++++++++------------
 xen/include/xen/ioreq.h  | 12 ++++++
 3 files changed, 86 insertions(+), 27 deletions(-)

diff --git a/xen/arch/x86/hvm/ioreq.c b/xen/arch/x86/hvm/ioreq.c
index 3cabec141c..ee679bdf5a 100644
--- a/xen/arch/x86/hvm/ioreq.c
+++ b/xen/arch/x86/hvm/ioreq.c
@@ -166,6 +166,14 @@ static int hvm_map_ioreq_gfn(struct ioreq_server *s, bool buf)
     if ( d->is_dying )
         return -EINVAL;
 
+    /*
+     * The legacy GFN path supports only a single ioreq page. Device models
+     * requiring more ioreq slots must use the resource mapping interface
+     * (XENMEM_acquire_resource).
+     */
+    if ( !buf && nr_ioreq_pages(d) > 1 )
+        return -EOPNOTSUPP;
+
     iorp->gfn = hvm_alloc_ioreq_gfn(s);
 
     if ( gfn_eq(iorp->gfn, INVALID_GFN) )
diff --git a/xen/common/ioreq.c b/xen/common/ioreq.c
index bae9b99c99..3a08e77597 100644
--- a/xen/common/ioreq.c
+++ b/xen/common/ioreq.c
@@ -261,8 +261,11 @@ bool vcpu_ioreq_handle_completion(struct vcpu *v)
 static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
 {
     struct ioreq_page *iorp = buf ? &s->bufioreq : &s->ioreq;
-    struct page_info *page;
-    mfn_t mfn;
+    unsigned int i, nr_alloc = 0, nr_pages = buf ? 1 : nr_ioreq_pages(s->target);
+    mfn_t mfns[IOREQ_NR_PAGES_MAX] = {};
+    int rc;
+
+    ASSERT(nr_pages <= IOREQ_NR_PAGES_MAX);
 
     if ( iorp->va )
     {
@@ -277,11 +280,16 @@ static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
         return 0;
     }
 
+    for ( i = 0; i < nr_pages; i++ )
     {
-        page = alloc_domheap_page(s->target, MEMF_no_refcount);
+        struct page_info *page = alloc_domheap_page(s->target,
+                                                    MEMF_no_refcount);
 
         if ( !page )
-            return -ENOMEM;
+        {
+            rc = -ENOMEM;
+            goto fail;
+        }
 
         if ( !get_page_and_type(page, s->target, PGT_writable_page) )
         {
@@ -290,41 +298,59 @@ static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
              * here is a clear indication of something fishy going on.
              */
             domain_crash(s->emulator);
-            return -ENODATA;
+            rc = -ENODATA;
+            goto fail;
         }
 
-        mfn = page_to_mfn(page);
+        mfns[nr_alloc++] = page_to_mfn(page);
     }
-    iorp->va = vmap(&mfn, 1);
+
+    iorp->va = vmap(mfns, nr_pages);
     if ( !iorp->va )
+    {
+        rc = -ENOMEM;
         goto fail;
+    }
 
-    clear_page(iorp->va);
+    memset(iorp->va, 0, nr_pages * PAGE_SIZE);
     return 0;
 
  fail:
-    put_page_alloc_ref(page);
-    put_page_and_type(page);
+    while ( nr_alloc-- )
+    {
+        struct page_info *page = mfn_to_page(mfns[nr_alloc]);
+
+        put_page_alloc_ref(page);
+        put_page_and_type(page);
+    }
 
-    return -ENOMEM;
+    return rc;
 }
 
 static void ioreq_server_free_mfn(struct ioreq_server *s, bool buf)
 {
     struct ioreq_page *iorp = buf ? &s->bufioreq : &s->ioreq;
-    struct page_info *page;
+    unsigned int i, nr_pages = buf ? 1 : nr_ioreq_pages(s->target);
+    struct page_info *pages[IOREQ_NR_PAGES_MAX];
     void *va;
 
     if ( !iorp->va )
         return;
 
+    ASSERT(nr_pages <= IOREQ_NR_PAGES_MAX);
+
+    for ( i = 0; i < nr_pages; i++ )
+        pages[i] = vmap_to_page(iorp->va + i * PAGE_SIZE);
+
     va = iorp->va;
-    page = vmap_to_page(va);
     iorp->va = NULL;
     vunmap(va);
 
-    put_page_alloc_ref(page);
-    put_page_and_type(page);
+    for ( i = 0; i < nr_pages; i++ )
+    {
+        put_page_alloc_ref(pages[i]);
+        put_page_and_type(pages[i]);
+    }
 }
 
 bool is_ioreq_server_page(struct domain *d, const struct page_info *page)
@@ -337,12 +363,25 @@ bool is_ioreq_server_page(struct domain *d, const struct page_info *page)
 
     FOR_EACH_IOREQ_SERVER(d, id, s)
     {
-        if ( (s->ioreq.va && vmap_to_page(s->ioreq.va) == page) ||
-             (s->bufioreq.va && vmap_to_page(s->bufioreq.va) == page) )
+        unsigned int i;
+
+        if ( s->bufioreq.va && vmap_to_page(s->bufioreq.va) == page )
         {
             found = true;
             break;
         }
+
+        for ( i = 0; i < nr_ioreq_pages(d) && s->ioreq.va; i++ )
+        {
+            if ( vmap_to_page(s->ioreq.va + i * PAGE_SIZE) == page )
+            {
+                found = true;
+                break;
+            }
+        }
+
+        if ( found )
+            break;
     }
 
     rspin_unlock(&d->ioreq_server.lock);
@@ -816,26 +855,26 @@ int ioreq_server_get_frame(struct domain *d, ioservid_t id,
     if ( rc )
         goto out;
 
-    switch ( idx )
+    if ( idx == XENMEM_resource_ioreq_server_frame_bufioreq )
     {
-    case XENMEM_resource_ioreq_server_frame_bufioreq:
         rc = -ENOENT;
         if ( !HANDLE_BUFIOREQ(s) )
             goto out;
 
         *mfn = vmap_to_mfn(s->bufioreq.va);
         rc = 0;
-        break;
+    }
+    else if ( idx >= XENMEM_resource_ioreq_server_frame_ioreq(0) &&
+              idx < XENMEM_resource_ioreq_server_frame_ioreq(nr_ioreq_pages(d)) )
+    {
+        unsigned int page_idx = idx - XENMEM_resource_ioreq_server_frame_ioreq(0);
 
-    case XENMEM_resource_ioreq_server_frame_ioreq(0):
-        *mfn = vmap_to_mfn(s->ioreq.va);
+        ASSERT(page_idx < nr_ioreq_pages(d));
+        *mfn = vmap_to_mfn(s->ioreq.va + page_idx * PAGE_SIZE);
         rc = 0;
-        break;
-
-    default:
-        rc = -EINVAL;
-        break;
     }
+    else
+        rc = -EINVAL;
 
  out:
     rspin_unlock(&d->ioreq_server.lock);
diff --git a/xen/include/xen/ioreq.h b/xen/include/xen/ioreq.h
index d63fa4729e..d2a08c2371 100644
--- a/xen/include/xen/ioreq.h
+++ b/xen/include/xen/ioreq.h
@@ -35,6 +35,18 @@ struct ioreq_vcpu {
     bool             pending;
 };
 
+/*
+ * Maximum number of ioreq pages, based on the maximum number
+ * of vCPUs and the number of ioreq slots per page.
+ */
+#define IOREQ_NR_PAGES_MAX \
+    DIV_ROUND_UP(HVM_MAX_VCPUS, PAGE_SIZE / sizeof(ioreq_t))
+
+static inline unsigned int nr_ioreq_pages(const struct domain *d)
+{
+    return DIV_ROUND_UP(d->max_vcpus, PAGE_SIZE / sizeof(ioreq_t));
+}
+
 #define NR_IO_RANGE_TYPES (XEN_DMOP_IO_RANGE_PCI + 1)
 #define MAX_NR_IO_RANGES  256
 
-- 
2.53.0



--
Julian Vetter | Vates Hypervisor & Kernel Developer

XCP-ng & Xen Orchestra - Vates solutions

web: https://vates.tech



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v6 0/3] Support multiple ioreq pages
  2026-04-20  9:38 [PATCH v6 0/3] Support multiple ioreq pages Julian Vetter
                   ` (2 preceding siblings ...)
  2026-04-20  9:38 ` [PATCH v6 3/3] x86/ioreq: Extend ioreq server to support multiple ioreq pages Julian Vetter
@ 2026-04-20 10:05 ` Jan Beulich
  3 siblings, 0 replies; 7+ messages in thread
From: Jan Beulich @ 2026-04-20 10:05 UTC (permalink / raw)
  To: Julian Vetter
  Cc: Andrew Cooper, Roger Pau Monné, Anthony PERARD, Michal Orzel,
	Julien Grall, Stefano Stabellini, xen-devel

On 20.04.2026 11:38, Julian Vetter wrote:
> Hello Jan,
> thank you again for your thorough feedback. I have addressed your
> comments. I just have one remaining issue. For the temporary MFNs array
> I now use a static array, i.e., mfns[IOREQ_NR_PAGES_MAX], with a
> IOREQ_NR_PAGES_MAX = DIV_ROUND_UP(HVM_MAX_VCPUS, PAGE_SIZE / sizeof(ioreq_t)).
> Is this appropriate?

Whether using a static array is appropriate I can't tell after merely
having read this cover letter.

> Or should I leave the HVM_MAX_VCPUS out of this and
> instead use a fixed constant value? Also for now I haven't taken the
> path to permanently store the MFNs as you suggested. I'm not sure, but I
> don't think it's really necessary. Since this array will rarely exceed
> the size of 2 or 3.

How do you know? The array will need to be large enough to cope with
anything the hypervisor was configured for.

Until we actually raise the vCPU-s limit, using HVM_MAX_VCPUS for the
size calculation looks okay to me. All uses of that constant will need
looking at / adjusting anyway during that subsequent effort.

Jan


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v6 3/3] x86/ioreq: Extend ioreq server to support multiple ioreq pages
  2026-04-20  9:38 ` [PATCH v6 3/3] x86/ioreq: Extend ioreq server to support multiple ioreq pages Julian Vetter
@ 2026-04-20 12:49   ` Teddy Astie
  2026-04-20 13:38     ` Jan Beulich
  0 siblings, 1 reply; 7+ messages in thread
From: Teddy Astie @ 2026-04-20 12:49 UTC (permalink / raw)
  To: Julian Vetter, xen-devel
  Cc: Jan Beulich, Andrew Cooper, Roger Pau Monné, Anthony PERARD,
	Michal Orzel, Julien Grall, Stefano Stabellini

Le 20/04/2026 à 11:41, Julian Vetter a écrit :
> As the number of vCPUs grows, a single ioreq page of 128 slots may not
> be sufficient. Add support for allocating and mapping multiple ioreq
> pages so that the ioreq region can scale with d->max_vcpus.
>
> Introduce nr_ioreq_pages() to compute the number of pages required for
> a given domain, and IOREQ_NR_PAGES_MAX as a compile-time upper bound
> (based on HVM_MAX_VCPUS).
>
> ioreq_server_alloc_mfn() is updated to allocate nr_ioreq_pages() pages
> and map them contiguously via vmap().
>
> is_ioreq_server_page() iterates over all ioreq pages when checking
> page ownership. ioreq_server_get_frame() allows callers to retrieve any
> ioreq page by index via the XENMEM_acquire_resource interface.
>
> On x86, the legacy GFN mapping path (hvm_map_ioreq_gfn) is limited to
> a single ioreq page; device models requiring more ioreq slots must use
> the resource mapping interface (XENMEM_acquire_resource).
>
> Signed-off-by: Julian Vetter <julian.vetter@vates.tech>
> ---
> Changes in v6:
> - Adapted the comment to not mention the guest, but the device model
> - Replaced the dynamic allocation for the mfns array by a static array
> - Fixed error handling in ioreq_server_alloc_mfn, using an extra
>    nr_alloc variable to track the already allocated pages
> - Dropped unnecessary void casts
> ---
>   xen/arch/x86/hvm/ioreq.c |  8 ++++
>   xen/common/ioreq.c       | 93 ++++++++++++++++++++++++++++------------
>   xen/include/xen/ioreq.h  | 12 ++++++
>   3 files changed, 86 insertions(+), 27 deletions(-)
>
> diff --git a/xen/arch/x86/hvm/ioreq.c b/xen/arch/x86/hvm/ioreq.c
> index 3cabec141c..ee679bdf5a 100644
> --- a/xen/arch/x86/hvm/ioreq.c
> +++ b/xen/arch/x86/hvm/ioreq.c
> @@ -166,6 +166,14 @@ static int hvm_map_ioreq_gfn(struct ioreq_server *s, bool buf)
>       if ( d->is_dying )
>           return -EINVAL;
>
> +    /*
> +     * The legacy GFN path supports only a single ioreq page. Device models
> +     * requiring more ioreq slots must use the resource mapping interface
> +     * (XENMEM_acquire_resource).
> +     */
> +    if ( !buf && nr_ioreq_pages(d) > 1 )
> +        return -EOPNOTSUPP;
> +
>       iorp->gfn = hvm_alloc_ioreq_gfn(s);
>
>       if ( gfn_eq(iorp->gfn, INVALID_GFN) )
> diff --git a/xen/common/ioreq.c b/xen/common/ioreq.c
> index bae9b99c99..3a08e77597 100644
> --- a/xen/common/ioreq.c
> +++ b/xen/common/ioreq.c
> @@ -261,8 +261,11 @@ bool vcpu_ioreq_handle_completion(struct vcpu *v)
>   static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
>   {
>       struct ioreq_page *iorp = buf ? &s->bufioreq : &s->ioreq;
> -    struct page_info *page;
> -    mfn_t mfn;
> +    unsigned int i, nr_alloc = 0, nr_pages = buf ? 1 : nr_ioreq_pages(s->target);
> +    mfn_t mfns[IOREQ_NR_PAGES_MAX] = {};
> +    int rc;
> +
> +    ASSERT(nr_pages <= IOREQ_NR_PAGES_MAX);
>
>       if ( iorp->va )
>       {
> @@ -277,11 +280,16 @@ static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
>           return 0;
>       }
>
> +    for ( i = 0; i < nr_pages; i++ )
>       {
> -        page = alloc_domheap_page(s->target, MEMF_no_refcount);
> +        struct page_info *page = alloc_domheap_page(s->target,
> +                                                    MEMF_no_refcount);
>
>           if ( !page )
> -            return -ENOMEM;
> +        {
> +            rc = -ENOMEM;
> +            goto fail;
> +        }
>
>           if ( !get_page_and_type(page, s->target, PGT_writable_page) )
>           {
> @@ -290,41 +298,59 @@ static int ioreq_server_alloc_mfn(struct ioreq_server *s, bool buf)
>                * here is a clear indication of something fishy going on.
>                */
>               domain_crash(s->emulator);
> -            return -ENODATA;
> +            rc = -ENODATA;
> +            goto fail;
>           }
>
> -        mfn = page_to_mfn(page);
> +        mfns[nr_alloc++] = page_to_mfn(page);
>       }
> -    iorp->va = vmap(&mfn, 1);
> +
> +    iorp->va = vmap(mfns, nr_pages);
>       if ( !iorp->va )
> +    {
> +        rc = -ENOMEM;
>           goto fail;
> +    }
>
> -    clear_page(iorp->va);
> +    memset(iorp->va, 0, nr_pages * PAGE_SIZE);
>       return 0;
>
>    fail:
> -    put_page_alloc_ref(page);
> -    put_page_and_type(page);
> +    while ( nr_alloc-- )
> +    {
> +        struct page_info *page = mfn_to_page(mfns[nr_alloc]);
> +
> +        put_page_alloc_ref(page);
> +        put_page_and_type(page);
> +    }
>
> -    return -ENOMEM;
> +    return rc;
>   }
>
>   static void ioreq_server_free_mfn(struct ioreq_server *s, bool buf)
>   {
>       struct ioreq_page *iorp = buf ? &s->bufioreq : &s->ioreq;
> -    struct page_info *page;
> +    unsigned int i, nr_pages = buf ? 1 : nr_ioreq_pages(s->target);
> +    struct page_info *pages[IOREQ_NR_PAGES_MAX];
>       void *va;
>
>       if ( !iorp->va )
>           return;
>
> +    ASSERT(nr_pages <= IOREQ_NR_PAGES_MAX);
> +
> +    for ( i = 0; i < nr_pages; i++ )
> +        pages[i] = vmap_to_page(iorp->va + i * PAGE_SIZE);
> +
>       va = iorp->va;
> -    page = vmap_to_page(va);
>       iorp->va = NULL;
>       vunmap(va);
>
> -    put_page_alloc_ref(page);
> -    put_page_and_type(page);
> +    for ( i = 0; i < nr_pages; i++ )
> +    {
> +        put_page_alloc_ref(pages[i]);
> +        put_page_and_type(pages[i]);
> +    }
>   }
>
>   bool is_ioreq_server_page(struct domain *d, const struct page_info *page)
> @@ -337,12 +363,25 @@ bool is_ioreq_server_page(struct domain *d, const struct page_info *page)
>
>       FOR_EACH_IOREQ_SERVER(d, id, s)
>       {
> -        if ( (s->ioreq.va && vmap_to_page(s->ioreq.va) == page) ||
> -             (s->bufioreq.va && vmap_to_page(s->bufioreq.va) == page) )
> +        unsigned int i;
> +
> +        if ( s->bufioreq.va && vmap_to_page(s->bufioreq.va) == page )
>           {
>               found = true;
>               break;
>           }
> +
> +        for ( i = 0; i < nr_ioreq_pages(d) && s->ioreq.va; i++ )
> +        {
> +            if ( vmap_to_page(s->ioreq.va + i * PAGE_SIZE) == page )
> +            {
> +                found = true;
> +                break;
> +            }
> +        }
> +
> +        if ( found )
> +            break;
>       }
>
>       rspin_unlock(&d->ioreq_server.lock);
> @@ -816,26 +855,26 @@ int ioreq_server_get_frame(struct domain *d, ioservid_t id,
>       if ( rc )
>           goto out;
>
> -    switch ( idx )
> +    if ( idx == XENMEM_resource_ioreq_server_frame_bufioreq )
>       {
> -    case XENMEM_resource_ioreq_server_frame_bufioreq:
>           rc = -ENOENT;
>           if ( !HANDLE_BUFIOREQ(s) )
>               goto out;
>
>           *mfn = vmap_to_mfn(s->bufioreq.va);
>           rc = 0;
> -        break;
> +    }
> +    else if ( idx >= XENMEM_resource_ioreq_server_frame_ioreq(0) &&
> +              idx < XENMEM_resource_ioreq_server_frame_ioreq(nr_ioreq_pages(d)) )
> +    {
> +        unsigned int page_idx = idx - XENMEM_resource_ioreq_server_frame_ioreq(0);
>
> -    case XENMEM_resource_ioreq_server_frame_ioreq(0):
> -        *mfn = vmap_to_mfn(s->ioreq.va);
> +        ASSERT(page_idx < nr_ioreq_pages(d));
> +        *mfn = vmap_to_mfn(s->ioreq.va + page_idx * PAGE_SIZE);
>           rc = 0;
> -        break;
> -
> -    default:
> -        rc = -EINVAL;
> -        break;
>       }
> +    else
> +        rc = -EINVAL;
>
>    out:
>       rspin_unlock(&d->ioreq_server.lock);
> diff --git a/xen/include/xen/ioreq.h b/xen/include/xen/ioreq.h
> index d63fa4729e..d2a08c2371 100644
> --- a/xen/include/xen/ioreq.h
> +++ b/xen/include/xen/ioreq.h
> @@ -35,6 +35,18 @@ struct ioreq_vcpu {
>       bool             pending;
>   };
>
> +/*
> + * Maximum number of ioreq pages, based on the maximum number
> + * of vCPUs and the number of ioreq slots per page.
> + */
> +#define IOREQ_NR_PAGES_MAX \
> +    DIV_ROUND_UP(HVM_MAX_VCPUS, PAGE_SIZE / sizeof(ioreq_t))
> +
> +static inline unsigned int nr_ioreq_pages(const struct domain *d)
> +{
> +    return DIV_ROUND_UP(d->max_vcpus, PAGE_SIZE / sizeof(ioreq_t));
> +}
> +
>   #define NR_IO_RANGE_TYPES (XEN_DMOP_IO_RANGE_PCI + 1)
>   #define MAX_NR_IO_RANGES  256
>

Is there anything that would prevent the use of alloc_domheap_pages() to
allocate a configuous set of pages at once; and vmap_contig() to map it
in one go.
That also prevents the ioreq pages from being scattered around.

We would lose a few pages by aligning the size into a order, but that
probably better than the alternatives.

That way, we would only have to keep the base gfn (or first page_info)
and size of the allocation, and don't have to use a array of mfn nor
have to reverse the vmap to track it.

Teddy


--
Teddy Astie | Vates XCP-ng Developer

XCP-ng & Xen Orchestra - Vates solutions

web: https://vates.tech




^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v6 3/3] x86/ioreq: Extend ioreq server to support multiple ioreq pages
  2026-04-20 12:49   ` Teddy Astie
@ 2026-04-20 13:38     ` Jan Beulich
  0 siblings, 0 replies; 7+ messages in thread
From: Jan Beulich @ 2026-04-20 13:38 UTC (permalink / raw)
  To: Teddy Astie
  Cc: Andrew Cooper, Roger Pau Monné, Anthony PERARD, Michal Orzel,
	Julien Grall, Stefano Stabellini, Julian Vetter, xen-devel

On 20.04.2026 14:49, Teddy Astie wrote:
> Is there anything that would prevent the use of alloc_domheap_pages() to 
> allocate a configuous set of pages at once; and vmap_contig() to map it 
> in one go.
> That also prevents the ioreq pages from being scattered around.
> 
> We would lose a few pages by aligning the size into a order, but that 
> probably better than the alternatives.
> 
> That way, we would only have to keep the base gfn (or first page_info) 
> and size of the allocation, and don't have to use a array of mfn nor 
> have to reverse the vmap to track it.

Well, higher-order allocations can easily fail when there's ample memory
available. Hence why a goal ought to be to avoid such allocations at
runtime. That's why we (now) have vmalloc() and xvmalloc().

Jan


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-04-20 13:38 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-20  9:38 [PATCH v6 0/3] Support multiple ioreq pages Julian Vetter
2026-04-20  9:38 ` [PATCH v6 1/3] ioreq: switch ioreq page allocation to vmap Julian Vetter
2026-04-20  9:38 ` [PATCH v6 2/3] ioreq: Indent ioreq_server_alloc_mfn() body one level deeper Julian Vetter
2026-04-20  9:38 ` [PATCH v6 3/3] x86/ioreq: Extend ioreq server to support multiple ioreq pages Julian Vetter
2026-04-20 12:49   ` Teddy Astie
2026-04-20 13:38     ` Jan Beulich
2026-04-20 10:05 ` [PATCH v6 0/3] Support " Jan Beulich

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.