[PATCH v1 2/6] xen/riscv: implement copy_to_guest_phys()

Oleksii Kurochko posted 6 patches 1 month, 4 weeks ago
There is a newer version of this series
[PATCH v1 2/6] xen/riscv: implement copy_to_guest_phys()
Posted by Oleksii Kurochko 1 month, 4 weeks ago
Introduce copy_to_guest_phys() for RISC-V, based on the Arm implementation.

Add a generic copy_guest() helper for copying to and from guest physical
(and potentially virtual addresses in the future), and implement
translate_get_page() to translate a guest physical address into a struct
page_info via the domain p2m.

Compared to the Arm code:
- Drop COPY_flush_dcache(), as no such use cases exist on RISC-V.
- Do not implement the linear mapping case, which is currently unused.
- Use PAGE_OFFSET() to initialize the local offset variable in copy_guest().

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
 xen/arch/riscv/Makefile                   |   1 +
 xen/arch/riscv/guestcopy.c                | 112 ++++++++++++++++++++++
 xen/arch/riscv/include/asm/guest_access.h |   7 ++
 3 files changed, 120 insertions(+)
 create mode 100644 xen/arch/riscv/guestcopy.c

diff --git a/xen/arch/riscv/Makefile b/xen/arch/riscv/Makefile
index 7439d029cc45..90210799e038 100644
--- a/xen/arch/riscv/Makefile
+++ b/xen/arch/riscv/Makefile
@@ -3,6 +3,7 @@ obj-y += cpufeature.o
 obj-y += domain.o
 obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
 obj-y += entry.o
+obj-y += guestcopy.o
 obj-y += imsic.o
 obj-y += intc.o
 obj-y += irq.o
diff --git a/xen/arch/riscv/guestcopy.c b/xen/arch/riscv/guestcopy.c
new file mode 100644
index 000000000000..19b681c30b1b
--- /dev/null
+++ b/xen/arch/riscv/guestcopy.c
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <xen/domain_page.h>
+#include <xen/page-size.h>
+#include <xen/sched.h>
+#include <xen/string.h>
+
+#include <asm/guest_access.h>
+
+#define COPY_from_guest     (0U << 0)
+#define COPY_to_guest       (1U << 0)
+#define COPY_ipa            (0U << 1)
+#define COPY_linear         (1U << 1)
+
+typedef union
+{
+    struct
+    {
+        struct vcpu *v;
+    } gva;
+
+    struct
+    {
+        struct domain *d;
+    } gpa;
+} copy_info_t;
+
+#define GVA_INFO(vcpu) ((copy_info_t) { .gva = { vcpu } })
+#define GPA_INFO(domain) ((copy_info_t) { .gpa = { domain } })
+
+static struct page_info *translate_get_page(copy_info_t info, uint64_t addr,
+                                            bool linear, bool write)
+{
+    p2m_type_t p2mt;
+    struct page_info *page;
+
+    if ( linear )
+        BUG_ON("unimplemeted\n");
+
+    page = get_page_from_gfn(info.gpa.d, paddr_to_pfn(addr), &p2mt, P2M_ALLOC);
+
+    if ( !page )
+        return NULL;
+
+    if ( !p2m_is_ram(p2mt) )
+    {
+        put_page(page);
+        return NULL;
+    }
+
+    return page;
+}
+
+static unsigned long copy_guest(void *buf, uint64_t addr, unsigned int len,
+                                copy_info_t info, unsigned int flags)
+{
+    unsigned int offset = PAGE_OFFSET(addr);
+
+    BUILD_BUG_ON((sizeof(addr)) < sizeof(vaddr_t));
+    BUILD_BUG_ON((sizeof(addr)) < sizeof(paddr_t));
+
+    while ( len )
+    {
+        void *p;
+        unsigned int size = min(len, (unsigned int)PAGE_SIZE - offset);
+        struct page_info *page;
+
+        page = translate_get_page(info, addr, flags & COPY_linear,
+                                  flags & COPY_to_guest);
+        if ( page == NULL )
+            return len;
+
+        p = __map_domain_page(page);
+        p += offset;
+        if ( flags & COPY_to_guest )
+        {
+            /*
+             * buf will be NULL when the caller request to zero the
+             * guest memory.
+             */
+            if ( buf )
+                memcpy(p, buf, size);
+            else
+                memset(p, 0, size);
+        }
+        else
+            memcpy(buf, p, size);
+
+        unmap_domain_page(p - offset);
+        put_page(page);
+        len -= size;
+        buf += size;
+        addr += size;
+
+        /*
+         * After the first iteration, guest virtual address is correctly
+         * aligned to PAGE_SIZE.
+         */
+        offset = 0;
+    }
+
+    return 0;
+}
+
+unsigned long copy_to_guest_phys(struct domain *d,
+                                 paddr_t gpa,
+                                 void *buf,
+                                 unsigned int len)
+{
+    return copy_guest(buf, gpa, len, GPA_INFO(d),
+                      COPY_to_guest | COPY_ipa);
+}
diff --git a/xen/arch/riscv/include/asm/guest_access.h b/xen/arch/riscv/include/asm/guest_access.h
index 7cd51fbbdead..024e29b4c9f9 100644
--- a/xen/arch/riscv/include/asm/guest_access.h
+++ b/xen/arch/riscv/include/asm/guest_access.h
@@ -2,6 +2,10 @@
 #ifndef ASM__RISCV__GUEST_ACCESS_H
 #define ASM__RISCV__GUEST_ACCESS_H
 
+#include <xen/types.h>
+
+struct domain;
+
 unsigned long raw_copy_to_guest(void *to, const void *from, unsigned len);
 unsigned long raw_copy_from_guest(void *to, const void *from, unsigned len);
 unsigned long raw_clear_guest(void *to, unsigned int len);
@@ -18,6 +22,9 @@ unsigned long raw_clear_guest(void *to, unsigned int len);
 #define guest_handle_okay(hnd, nr) (1)
 #define guest_handle_subrange_okay(hnd, first, last) (1)
 
+unsigned long copy_to_guest_phys(struct domain *d, paddr_t gpa, void *buf,
+                                 unsigned int len);
+
 #endif /* ASM__RISCV__GUEST_ACCESS_H */
 /*
  * Local variables:
-- 
2.52.0
Re: [PATCH v1 2/6] xen/riscv: implement copy_to_guest_phys()
Posted by Jan Beulich 1 month, 3 weeks ago
On 12.02.2026 17:21, Oleksii Kurochko wrote:
> --- /dev/null
> +++ b/xen/arch/riscv/guestcopy.c
> @@ -0,0 +1,112 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +#include <xen/domain_page.h>
> +#include <xen/page-size.h>
> +#include <xen/sched.h>
> +#include <xen/string.h>
> +
> +#include <asm/guest_access.h>
> +
> +#define COPY_from_guest     (0U << 0)
> +#define COPY_to_guest       (1U << 0)
> +#define COPY_ipa            (0U << 1)

Like already asked elsewhere - is "ipa" a term commonly in use on RISC-V?
To me it's Arm terminology, which you don't want to copy as is.

Also, don't you prefer to use BIT() everywhere else?

> +#define COPY_linear         (1U << 1)
> +
> +typedef union
> +{
> +    struct
> +    {
> +        struct vcpu *v;
> +    } gva;
> +
> +    struct
> +    {
> +        struct domain *d;
> +    } gpa;
> +} copy_info_t;
> +
> +#define GVA_INFO(vcpu) ((copy_info_t) { .gva = { vcpu } })
> +#define GPA_INFO(domain) ((copy_info_t) { .gpa = { domain } })
> +
> +static struct page_info *translate_get_page(copy_info_t info, uint64_t addr,

The caller has to pass in a domain here. I therefore recommend against
use of copy_info_t for this function. Or wait, this is misleading, as
the consuming part ...

> +                                            bool linear, bool write)
> +{
> +    p2m_type_t p2mt;
> +    struct page_info *page;
> +
> +    if ( linear )
> +        BUG_ON("unimplemeted\n");

... of "linear" is missing here.

In any event, this one please shorter as:

    BUG_ON(linear);

> +    page = get_page_from_gfn(info.gpa.d, paddr_to_pfn(addr), &p2mt, P2M_ALLOC);
> +
> +    if ( !page )
> +        return NULL;
> +
> +    if ( !p2m_is_ram(p2mt) )
> +    {
> +        put_page(page);
> +        return NULL;
> +    }
> +
> +    return page;
> +}

The "write" function parameter also is unused, but there's no BUG_ON() for
that one? Imo the p2m_is_ram() check isn't thorough enough (on the Arm
original): p2m_ram_ro shouldn't be allowed when "write" is true. As soon
as you gain p2m_ram_ro on RISC-V, things will need updating here as well.
Perhaps best to leave a note.

> +static unsigned long copy_guest(void *buf, uint64_t addr, unsigned int len,
> +                                copy_info_t info, unsigned int flags)

Why an "unsigned long" return value when ...

> +{
> +    unsigned int offset = PAGE_OFFSET(addr);
> +
> +    BUILD_BUG_ON((sizeof(addr)) < sizeof(vaddr_t));
> +    BUILD_BUG_ON((sizeof(addr)) < sizeof(paddr_t));
> +
> +    while ( len )
> +    {
> +        void *p;
> +        unsigned int size = min(len, (unsigned int)PAGE_SIZE - offset);
> +        struct page_info *page;
> +
> +        page = translate_get_page(info, addr, flags & COPY_linear,
> +                                  flags & COPY_to_guest);
> +        if ( page == NULL )
> +            return len;

... only an "unsigned int" (or 0 further down) is returned? Same
question for copy_to_guest_phys() below then.

> +        p = __map_domain_page(page);
> +        p += offset;
> +        if ( flags & COPY_to_guest )
> +        {
> +            /*
> +             * buf will be NULL when the caller request to zero the
> +             * guest memory.
> +             */
> +            if ( buf )
> +                memcpy(p, buf, size);
> +            else
> +                memset(p, 0, size);
> +        }
> +        else
> +            memcpy(buf, p, size);
> +
> +        unmap_domain_page(p - offset);
> +        put_page(page);
> +        len -= size;
> +        buf += size;
> +        addr += size;
> +
> +        /*
> +         * After the first iteration, guest virtual address is correctly
> +         * aligned to PAGE_SIZE.
> +         */
> +        offset = 0;
> +    }
> +
> +    return 0;
> +}
> +
> +unsigned long copy_to_guest_phys(struct domain *d,
> +                                 paddr_t gpa,
> +                                 void *buf,
> +                                 unsigned int len)

May I suggest to make good use of line length, just like how copy_guest()
does?

Jan
Re: [PATCH v1 2/6] xen/riscv: implement copy_to_guest_phys()
Posted by Oleksii Kurochko 1 month, 3 weeks ago
On 2/16/26 3:57 PM, Jan Beulich wrote:
> On 12.02.2026 17:21, Oleksii Kurochko wrote:
>> --- /dev/null
>> +++ b/xen/arch/riscv/guestcopy.c
>> @@ -0,0 +1,112 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +
>> +#include <xen/domain_page.h>
>> +#include <xen/page-size.h>
>> +#include <xen/sched.h>
>> +#include <xen/string.h>
>> +
>> +#include <asm/guest_access.h>
>> +
>> +#define COPY_from_guest     (0U << 0)
>> +#define COPY_to_guest       (1U << 0)
>> +#define COPY_ipa            (0U << 1)
> Like already asked elsewhere - is "ipa" a term commonly in use on RISC-V?
> To me it's Arm terminology, which you don't want to copy as is.

As we discussed in another patch thread, IPA isn't really used for RISC-V
and I will rename it to GPA.

>
> Also, don't you prefer to use BIT() everywhere else?

Yes, BIT() would be better for consistency.


>
>> +#define COPY_linear         (1U << 1)
>> +
>> +typedef union
>> +{
>> +    struct
>> +    {
>> +        struct vcpu *v;
>> +    } gva;
>> +
>> +    struct
>> +    {
>> +        struct domain *d;
>> +    } gpa;
>> +} copy_info_t;
>> +
>> +#define GVA_INFO(vcpu) ((copy_info_t) { .gva = { vcpu } })
>> +#define GPA_INFO(domain) ((copy_info_t) { .gpa = { domain } })
>> +
>> +static struct page_info *translate_get_page(copy_info_t info, uint64_t addr,
> The caller has to pass in a domain here. I therefore recommend against
> use of copy_info_t for this function. Or wait, this is misleading, as
> the consuming part ...
>
>> +                                            bool linear, bool write)
>> +{
>> +    p2m_type_t p2mt;
>> +    struct page_info *page;
>> +
>> +    if ( linear )
>> +        BUG_ON("unimplemeted\n");
> ... of "linear" is missing here.

Yes, for this once cases it will be used vcpu as an argument passed by "copy_info_t info".
I will add the comment above suggested below BUG_ON(linear).

Btw, I think it makes sense to change linear to GVA to be more close to RISC-V spec?

>
> In any event, this one please shorter as:
>
>      BUG_ON(linear);
>
>> +    page = get_page_from_gfn(info.gpa.d, paddr_to_pfn(addr), &p2mt, P2M_ALLOC);
>> +
>> +    if ( !page )
>> +        return NULL;
>> +
>> +    if ( !p2m_is_ram(p2mt) )
>> +    {
>> +        put_page(page);
>> +        return NULL;
>> +    }
>> +
>> +    return page;
>> +}
> The "write" function parameter also is unused, but there's no BUG_ON() for
> that one? Imo the p2m_is_ram() check isn't thorough enough (on the Arm
> original): p2m_ram_ro shouldn't be allowed when "write" is true. As soon
> as you gain p2m_ram_ro on RISC-V, things will need updating here as well.
> Perhaps best to leave a note.

I will apply your changes from suggested for Arm patch (Arm: tighten
translate_get_page()) so write will be used and also no extra updates will
be needed here.


>
>> +static unsigned long copy_guest(void *buf, uint64_t addr, unsigned int len,
>> +                                copy_info_t info, unsigned int flags)
> Why an "unsigned long" return value when ...
>
>> +{
>> +    unsigned int offset = PAGE_OFFSET(addr);
>> +
>> +    BUILD_BUG_ON((sizeof(addr)) < sizeof(vaddr_t));
>> +    BUILD_BUG_ON((sizeof(addr)) < sizeof(paddr_t));
>> +
>> +    while ( len )
>> +    {
>> +        void *p;
>> +        unsigned int size = min(len, (unsigned int)PAGE_SIZE - offset);
>> +        struct page_info *page;
>> +
>> +        page = translate_get_page(info, addr, flags & COPY_linear,
>> +                                  flags & COPY_to_guest);
>> +        if ( page == NULL )
>> +            return len;
> ... only an "unsigned int" (or 0 further down) is returned? Same
> question for copy_to_guest_phys() below then.

Agree, unsigned int should be enough.

>
>> +        p = __map_domain_page(page);
>> +        p += offset;
>> +        if ( flags & COPY_to_guest )
>> +        {
>> +            /*
>> +             * buf will be NULL when the caller request to zero the
>> +             * guest memory.
>> +             */
>> +            if ( buf )
>> +                memcpy(p, buf, size);
>> +            else
>> +                memset(p, 0, size);
>> +        }
>> +        else
>> +            memcpy(buf, p, size);
>> +
>> +        unmap_domain_page(p - offset);
>> +        put_page(page);
>> +        len -= size;
>> +        buf += size;
>> +        addr += size;
>> +
>> +        /*
>> +         * After the first iteration, guest virtual address is correctly
>> +         * aligned to PAGE_SIZE.
>> +         */
>> +        offset = 0;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +unsigned long copy_to_guest_phys(struct domain *d,
>> +                                 paddr_t gpa,
>> +                                 void *buf,
>> +                                 unsigned int len)
> May I suggest to make good use of line length, just like how copy_guest()
> does?

Sure, I will do that.

Thanks.

~ Oleksii
Re: [PATCH v1 2/6] xen/riscv: implement copy_to_guest_phys()
Posted by Jan Beulich 1 month, 3 weeks ago
On 17.02.2026 11:25, Oleksii Kurochko wrote:
> On 2/16/26 3:57 PM, Jan Beulich wrote:
>> On 12.02.2026 17:21, Oleksii Kurochko wrote:
>>> --- /dev/null
>>> +++ b/xen/arch/riscv/guestcopy.c
>>> @@ -0,0 +1,112 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-only */
>>> +
>>> +#include <xen/domain_page.h>
>>> +#include <xen/page-size.h>
>>> +#include <xen/sched.h>
>>> +#include <xen/string.h>
>>> +
>>> +#include <asm/guest_access.h>
>>> +
>>> +#define COPY_from_guest     (0U << 0)
>>> +#define COPY_to_guest       (1U << 0)
>>> +#define COPY_ipa            (0U << 1)
>> Like already asked elsewhere - is "ipa" a term commonly in use on RISC-V?
>> To me it's Arm terminology, which you don't want to copy as is.
> 
> As we discussed in another patch thread, IPA isn't really used for RISC-V
> and I will rename it to GPA.
> 
>> Also, don't you prefer to use BIT() everywhere else?
> 
> Yes, BIT() would be better for consistency.
> 
>>> +#define COPY_linear         (1U << 1)
>>> +
>>> +typedef union
>>> +{
>>> +    struct
>>> +    {
>>> +        struct vcpu *v;
>>> +    } gva;
>>> +
>>> +    struct
>>> +    {
>>> +        struct domain *d;
>>> +    } gpa;
>>> +} copy_info_t;
>>> +
>>> +#define GVA_INFO(vcpu) ((copy_info_t) { .gva = { vcpu } })
>>> +#define GPA_INFO(domain) ((copy_info_t) { .gpa = { domain } })
>>> +
>>> +static struct page_info *translate_get_page(copy_info_t info, uint64_t addr,
>> The caller has to pass in a domain here. I therefore recommend against
>> use of copy_info_t for this function. Or wait, this is misleading, as
>> the consuming part ...
>>
>>> +                                            bool linear, bool write)
>>> +{
>>> +    p2m_type_t p2mt;
>>> +    struct page_info *page;
>>> +
>>> +    if ( linear )
>>> +        BUG_ON("unimplemeted\n");
>> ... of "linear" is missing here.
> 
> Yes, for this once cases it will be used vcpu as an argument passed by "copy_info_t info".
> I will add the comment above suggested below BUG_ON(linear).
> 
> Btw, I think it makes sense to change linear to GVA to be more close to RISC-V spec?

And to better match the rename to GPA that you talk about above.

Jan