All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 00/19] xen/riscv: introduce p2m functionality
@ 2025-11-24 12:33 Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 01/19] xen/riscv: avoid redundant HGATP*_MODE_SHIFT and HGATP*_VMID_SHIFT Oleksii Kurochko
                   ` (18 more replies)
  0 siblings, 19 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini,
	Bertrand Marquis, Volodymyr Babchuk

In this patch series are introduced necessary functions to build and manage
RISC-V guest page tables and MMIO/RAM mappings.

---
Changes in V5:
 - Addressed comments for v4.
 - The following patches were Acked-by:
   - [v5 16/18] xen/riscv: implement mfn_valid() and page
   - [v5 15/18] xen/riscv: implement put_page()
   - [v5 14/18] xen/riscv: Implement superpage splitting for p2m mappings
   - [v5 09/18] xen/riscv: implement function to map memory in guest p2m
   - [v5 07/18] xen/riscv: add new p2m types and helper macros for type classification
   - [v5 03/18] xen/riscv: introduce things necessary for p2m initialization
---
Changes in V4:
 - Merged to staging:
   - xen/riscv: introduce sbi_remote_hfence_gvma()
   - xen/riscv: introduce sbi_remote_hfence_gvma_vmid()
 - Drop "xen/riscv: introduce page_{get,set}_xenheap_gfn()" as grant tables aren't going to be introduced for the moment. Also, drops other parts connected to grant tables support.
 - All other changes are patch specific.
---
Changes in V3:
 - Introduce metadata table to store P2M types.
 - Use x86's way to allocate VMID.
 - Abstract Arm-specific p2m type name for device MMIO mappings.
 - All other updates please look at specific patch.
---
Changes in V2:
 - Merged to staging:
   - [PATCH v1 1/6] xen/riscv: add inclusion of xen/bitops.h to asm/cmpxchg.h
 - New patches:
   - xen/riscv: implement sbi_remote_hfence_gvma{_vmid}().
 - Split patch "xen/riscv: implement p2m mapping functionality" into smaller
   one patches:
   - xen/riscv: introduce page_set_xenheap_gfn()
   - xen/riscv: implement guest_physmap_add_entry() for mapping GFNs to MFNs
   - xen/riscv: implement p2m_set_entry() and __p2m_set_entry()
   - xen/riscv: Implement p2m_free_entry() and related helpers
   - xen/riscv: Implement superpage splitting for p2m mappings
   - xen/riscv: implement p2m_next_level()
   - xen/riscv: Implement p2m_entry_from_mfn() and support PBMT configuration
 - Move root p2m table allocation to separate patch:
   xen/riscv: add root page table allocation
 - Drop dependency of this patch series from the patch witn an introduction of
   SvPBMT as it was merged.
 - Patch "[PATCH v1 4/6] xen/riscv: define pt_t and pt_walk_t structures" was
   renamed to xen/riscv: introduce pte_{set,get}_mfn() as after dropping of
   bitfields for PTE structure, this patch introduce only pte_{set,get}_mfn().
 - Rename "xen/riscv: define pt_t and pt_walk_t structures" to
   "xen/riscv: introduce pte_{set,get}_mfn()" as pt_t and pt_walk_t were
   dropped.
 - Introduce guest domain's VMID allocation and manegement.
 - Add patches necessary to implement p2m lookup:
   - xen/riscv: implement mfn_valid() and page reference, ownership handling helpers
   - xen/riscv: add support of page lookup by GFN
 - Re-sort patch series.
 - All other changes are patch-specific. Please check them.
---

Oleksii Kurochko (19):
  xen/riscv: avoid redundant HGATP*_MODE_SHIFT and HGATP*_VMID_SHIFT
  xen/riscv: detect and initialize G-stage mode
  xen/riscv: introduce VMID allocation and manegement
  xen/riscv: introduce things necessary for p2m initialization
  xen/riscv: construct the P2M pages pool for guests
  xen/riscv: add root page table allocation
  xen/riscv: introduce pte_{set,get}_mfn()
  xen/riscv: add new p2m types and helper macros for type classification
  xen/dom0less: abstract Arm-specific p2m type name for device MMIO
    mappings
  xen/riscv: implement function to map memory in guest p2m
  xen/riscv: implement p2m_set_range()
  xen/riscv: Implement p2m_free_subtree() and related helpers
  xen/riscv: Implement p2m_pte_from_mfn() and support PBMT configuration
  xen/riscv: implement p2m_next_level()
  xen/riscv: Implement superpage splitting for p2m mappings
  xen/riscv: implement put_page()
  xen/riscv: implement mfn_valid() and page reference, ownership
    handling helpers
  xen/riscv: add support of page lookup by GFN
  xen/riscv: introduce metadata table to store P2M type

 docs/misc/xen-command-line.pandoc           |    9 +
 xen/arch/arm/include/asm/p2m.h              |    5 +
 xen/arch/riscv/Makefile                     |    3 +
 xen/arch/riscv/cpufeature.c                 |    1 +
 xen/arch/riscv/include/asm/Makefile         |    1 -
 xen/arch/riscv/include/asm/cpufeature.h     |    1 +
 xen/arch/riscv/include/asm/domain.h         |   23 +
 xen/arch/riscv/include/asm/flushtlb.h       |   13 +-
 xen/arch/riscv/include/asm/mm.h             |   29 +-
 xen/arch/riscv/include/asm/p2m.h            |  185 ++-
 xen/arch/riscv/include/asm/page.h           |   37 +
 xen/arch/riscv/include/asm/paging.h         |   20 +
 xen/arch/riscv/include/asm/riscv_encoding.h |   23 +-
 xen/arch/riscv/include/asm/vmid.h           |   14 +
 xen/arch/riscv/mm.c                         |   69 +-
 xen/arch/riscv/p2m.c                        | 1443 +++++++++++++++++++
 xen/arch/riscv/paging.c                     |  139 ++
 xen/arch/riscv/setup.c                      |    3 +
 xen/arch/riscv/stubs.c                      |    5 -
 xen/arch/riscv/vmid.c                       |  170 +++
 xen/common/device-tree/dom0less-build.c     |    2 +-
 21 files changed, 2158 insertions(+), 37 deletions(-)
 create mode 100644 xen/arch/riscv/include/asm/paging.h
 create mode 100644 xen/arch/riscv/include/asm/vmid.h
 create mode 100644 xen/arch/riscv/p2m.c
 create mode 100644 xen/arch/riscv/paging.c
 create mode 100644 xen/arch/riscv/vmid.c

-- 
2.51.1



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

* [PATCH v6 01/19] xen/riscv: avoid redundant HGATP*_MODE_SHIFT and HGATP*_VMID_SHIFT
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-12-08 16:13   ` Jan Beulich
  2025-11-24 12:33 ` [PATCH v6 02/19] xen/riscv: detect and initialize G-stage mode Oleksii Kurochko
                   ` (17 subsequent siblings)
  18 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

It is sufficient to use HGATP*_MODE_MASK and HGATP*_VMID_MASK without
the corresponding *_SHIFT definitions.

Rename HGATP{32,64}_PPN to HGATP{32,64}_PPN_MASK to more accurately
describe their purpose. The top-level HGATP_PPN and related aliases are
updated accordingly.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in v6:
 - New patch.
---

 xen/arch/riscv/include/asm/riscv_encoding.h | 16 ++++------------
 1 file changed, 4 insertions(+), 12 deletions(-)

diff --git a/xen/arch/riscv/include/asm/riscv_encoding.h b/xen/arch/riscv/include/asm/riscv_encoding.h
index 6cc8f4eb45..fd27f74cb7 100644
--- a/xen/arch/riscv/include/asm/riscv_encoding.h
+++ b/xen/arch/riscv/include/asm/riscv_encoding.h
@@ -132,15 +132,11 @@
 #define HGATP_MODE_SV39X4		_UL(8)
 #define HGATP_MODE_SV48X4		_UL(9)
 
-#define HGATP32_MODE_SHIFT		31
-#define HGATP32_VMID_SHIFT		22
 #define HGATP32_VMID_MASK		_UL(0x1FC00000)
-#define HGATP32_PPN			_UL(0x003FFFFF)
+#define HGATP32_PPN_MASK		_UL(0x003FFFFF)
 
-#define HGATP64_MODE_SHIFT		60
-#define HGATP64_VMID_SHIFT		44
 #define HGATP64_VMID_MASK		_ULL(0x03FFF00000000000)
-#define HGATP64_PPN			_ULL(0x00000FFFFFFFFFFF)
+#define HGATP64_PPN_MASK		_ULL(0x00000FFFFFFFFFFF)
 
 #define PMP_R				_UL(0x01)
 #define PMP_W				_UL(0x02)
@@ -166,10 +162,8 @@
 #define SATP_MODE_SHIFT			SATP64_MODE_SHIFT
 #define SATP_PPN_MASK			SATP64_PPN
 
-#define HGATP_PPN			HGATP64_PPN
-#define HGATP_VMID_SHIFT		HGATP64_VMID_SHIFT
+#define HGATP_PPN_MASK			HGATP64_PPN_MASK
 #define HGATP_VMID_MASK			HGATP64_VMID_MASK
-#define HGATP_MODE_SHIFT		HGATP64_MODE_SHIFT
 #else
 #define MSTATUS_SD			MSTATUS32_SD
 #define SSTATUS_SD			SSTATUS32_SD
@@ -177,10 +171,8 @@
 #define SATP_MODE_SHIFT			SATP32_MODE_SHIFT
 #define SATP_PPN_MASK			SATP32_PPN
 
-#define HGATP_PPN			HGATP32_PPN
-#define HGATP_VMID_SHIFT		HGATP32_VMID_SHIFT
+#define HGATP_PPN_MASK			HGATP32_PPN_MASK
 #define HGATP_VMID_MASK			HGATP32_VMID_MASK
-#define HGATP_MODE_SHIFT		HGATP32_MODE_SHIFT
 #endif
 
 #define TOPI_IID_SHIFT			16
-- 
2.51.1



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

* [PATCH v6 02/19] xen/riscv: detect and initialize G-stage mode
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 01/19] xen/riscv: avoid redundant HGATP*_MODE_SHIFT and HGATP*_VMID_SHIFT Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-12-08 16:22   ` Jan Beulich
  2025-11-24 12:33 ` [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement Oleksii Kurochko
                   ` (16 subsequent siblings)
  18 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Introduce gstage_mode_detect() and guest_mm_init() to probe supported
G-stage paging modes at boot. The function iterates over possible
HGATP modes (Sv32x4 on RV32, Sv39x4/Sv48x4/Sv57x4 on RV64) and selects
the largest supported MMU translation address mode by programming
CSR_HGATP and reading it back.

The selected mode is stored in max_gstage_mode (marked __ro_after_init)
and reported via printk. If no supported mode is found, Xen panics
since Bare mode is not expected to be used.

Finally, CSR_HGATP is cleared and a local_hfence_gvma_all() is issued
to avoid any potential speculative pollution of the TLB, as required
by the RISC-V spec.

The following issue starts to occur:
 ./<riscv>/asm/flushtlb.h:37:55: error: 'struct page_info'  declared inside
   parameter list will not be visible outside of this definition or
   declaration [-Werror]
 37 | static inline void page_set_tlbflush_timestamp(struct page_info *page)
To resolve it, forward declaration of struct page_info is added to
<asm/flushtlb.h.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in V6:
 - Sort items properly in riscv/Makefile.
 - Move the  call of local_hfence_gvma_all() from gstage_mode_detect()
   to pre_gstage_init() to avoid a redundancy when vmid_init() will be
   introduced.
 - Rename pre_gstage_init() to more generic name guest_mm_init().
 - s/gstage_mode/max_gstage_mode
 - Reverse direction of gstage mode detection to find a maximum supported mode to understand what modes are supported. (lower modes are supported automatically)
 - Introduce struct gstage_mode_desc.
 - Introduce get_max_supported_mode().
---
Changes in V5:
 - Add static and __initconst for local variable modes[] in
   gstage_mode_detect().
 - Change type for gstage_mode from 'unsigned long' to 'unsigned char'.
 - Update the comment inisde defintion if modes[] variable in
   gstage_mode_detect():
   - Add information about Bare mode.
   - Drop "a paged virtual-memory scheme described in Section 10.3" as it isn't
     relevant here.
 - Drop printing of function name when chosen G-stage mode message is printed.
 - Drop the call of gstage_mode_detect() from start_xen(). It will be added into
   p2m_init() when the latter will be introduced.
 - Introduce pre_gstage_init().
 - make gstage_mode_detect() static.
---
Changes in V4:
 - New patch.
---
 xen/arch/riscv/Makefile                     |   1 +
 xen/arch/riscv/include/asm/flushtlb.h       |   7 ++
 xen/arch/riscv/include/asm/p2m.h            |   9 ++
 xen/arch/riscv/include/asm/riscv_encoding.h |   5 +
 xen/arch/riscv/p2m.c                        | 106 ++++++++++++++++++++
 xen/arch/riscv/setup.c                      |   3 +
 6 files changed, 131 insertions(+)
 create mode 100644 xen/arch/riscv/p2m.c

diff --git a/xen/arch/riscv/Makefile b/xen/arch/riscv/Makefile
index e2b8aa42c8..5a6f8c115d 100644
--- a/xen/arch/riscv/Makefile
+++ b/xen/arch/riscv/Makefile
@@ -6,6 +6,7 @@ obj-y += imsic.o
 obj-y += intc.o
 obj-y += irq.o
 obj-y += mm.o
+obj-y += p2m.o
 obj-y += pt.o
 obj-$(CONFIG_RISCV_64) += riscv64/
 obj-y += sbi.o
diff --git a/xen/arch/riscv/include/asm/flushtlb.h b/xen/arch/riscv/include/asm/flushtlb.h
index 51c8f753c5..e70badae0c 100644
--- a/xen/arch/riscv/include/asm/flushtlb.h
+++ b/xen/arch/riscv/include/asm/flushtlb.h
@@ -7,6 +7,13 @@
 
 #include <asm/sbi.h>
 
+struct page_info;
+
+static inline void local_hfence_gvma_all(void)
+{
+    asm volatile ( "hfence.gvma zero, zero" ::: "memory" );
+}
+
 /* Flush TLB of local processor for address va. */
 static inline void flush_tlb_one_local(vaddr_t va)
 {
diff --git a/xen/arch/riscv/include/asm/p2m.h b/xen/arch/riscv/include/asm/p2m.h
index e43c559e0c..3776b98303 100644
--- a/xen/arch/riscv/include/asm/p2m.h
+++ b/xen/arch/riscv/include/asm/p2m.h
@@ -8,6 +8,12 @@
 
 #define paddr_bits PADDR_BITS
 
+struct gstage_mode_desc {
+    unsigned char mode;
+    unsigned int paging_levels;
+    char name[8];
+};
+
 /*
  * List of possible type for each page in the p2m entry.
  * The number of available bit per page in the pte for this purpose is 2 bits.
@@ -88,6 +94,9 @@ static inline bool arch_acquire_resource_check(struct domain *d)
     return false;
 }
 
+void guest_mm_init(void);
+unsigned char get_max_supported_mode(void);
+
 #endif /* ASM__RISCV__P2M_H */
 
 /*
diff --git a/xen/arch/riscv/include/asm/riscv_encoding.h b/xen/arch/riscv/include/asm/riscv_encoding.h
index fd27f74cb7..e0a5e8b58b 100644
--- a/xen/arch/riscv/include/asm/riscv_encoding.h
+++ b/xen/arch/riscv/include/asm/riscv_encoding.h
@@ -131,10 +131,13 @@
 #define HGATP_MODE_SV32X4		_UL(1)
 #define HGATP_MODE_SV39X4		_UL(8)
 #define HGATP_MODE_SV48X4		_UL(9)
+#define HGATP_MODE_SV57X4		_UL(10)
 
+#define HGATP32_MODE_MASK		_UL(0x80000000)
 #define HGATP32_VMID_MASK		_UL(0x1FC00000)
 #define HGATP32_PPN_MASK		_UL(0x003FFFFF)
 
+#define HGATP64_MODE_MASK		_ULL(0xF000000000000000)
 #define HGATP64_VMID_MASK		_ULL(0x03FFF00000000000)
 #define HGATP64_PPN_MASK		_ULL(0x00000FFFFFFFFFFF)
 
@@ -164,6 +167,7 @@
 
 #define HGATP_PPN_MASK			HGATP64_PPN_MASK
 #define HGATP_VMID_MASK			HGATP64_VMID_MASK
+#define HGATP_MODE_MASK			HGATP64_MODE_MASK
 #else
 #define MSTATUS_SD			MSTATUS32_SD
 #define SSTATUS_SD			SSTATUS32_SD
@@ -173,6 +177,7 @@
 
 #define HGATP_PPN_MASK			HGATP32_PPN_MASK
 #define HGATP_VMID_MASK			HGATP32_VMID_MASK
+#define HGATP_MODE_MASK			HGATP32_MODE_MASK
 #endif
 
 #define TOPI_IID_SHIFT			16
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
new file mode 100644
index 0000000000..7bb0fc0ab4
--- /dev/null
+++ b/xen/arch/riscv/p2m.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <xen/init.h>
+#include <xen/lib.h>
+#include <xen/macros.h>
+#include <xen/sections.h>
+
+#include <asm/csr.h>
+#include <asm/flushtlb.h>
+#include <asm/p2m.h>
+#include <asm/riscv_encoding.h>
+
+static struct gstage_mode_desc __ro_after_init max_gstage_mode = {
+    .mode = HGATP_MODE_OFF,
+    .paging_levels = 0,
+    .name = "Bare",
+};
+
+unsigned char get_max_supported_mode(void)
+{
+    return max_gstage_mode.mode;
+}
+
+static void __init gstage_mode_detect(void)
+{
+    static const struct gstage_mode_desc modes[] __initconst = {
+        /*
+         * Based on the RISC-V spec:
+         *   Bare mode is always supported, regardless of SXLEN.
+         *   When SXLEN=32, the only other valid setting for MODE is Sv32.
+         *   When SXLEN=64, three paged virtual-memory schemes are defined:
+         *   Sv39, Sv48, and Sv57.
+         */
+#ifdef CONFIG_RISCV_32
+        { HGATP_MODE_SV32X4, 2, "Sv32x4" }
+#else
+        { HGATP_MODE_SV39X4, 3, "Sv39x4" },
+        { HGATP_MODE_SV48X4, 4, "Sv48x4" },
+        { HGATP_MODE_SV57X4, 5, "Sv57x4" },
+#endif
+    };
+
+    unsigned int mode_idx;
+
+    for ( mode_idx = ARRAY_SIZE(modes); mode_idx-- > 0; )
+    {
+        unsigned long mode = modes[mode_idx].mode;
+
+        csr_write(CSR_HGATP, MASK_INSR(mode, HGATP_MODE_MASK));
+
+        if ( MASK_EXTR(csr_read(CSR_HGATP), HGATP_MODE_MASK) == mode )
+        {
+            max_gstage_mode.mode = modes[mode_idx].mode;
+            max_gstage_mode.paging_levels = modes[mode_idx].paging_levels;
+            safe_strcpy(max_gstage_mode.name, modes[mode_idx].name);
+
+            break;
+        }
+    }
+
+    if ( max_gstage_mode.mode == HGATP_MODE_OFF )
+        panic("Xen expects that G-stage won't be Bare mode\n");
+
+    printk("Max supported G-stage mode is %s\n", max_gstage_mode.name);
+
+    csr_write(CSR_HGATP, 0);
+
+    /* local_hfence_gvma_all() will be called at the end of guest_mm_init. */
+}
+
+void __init guest_mm_init(void)
+{
+    gstage_mode_detect();
+
+    /*
+     * As gstage_mode_detect() is changing CSR_HGATP, it is necessary to flush
+     * guest TLB because:
+     *
+     * From RISC-V spec:
+     *   Speculative executions of the address-translation algorithm behave as
+     *   non-speculative executions of the algorithm do, except that they must
+     *   not set the dirty bit for a PTE, they must not trigger an exception,
+     *   and they must not create address-translation cache entries if those
+     *   entries would have been invalidated by any SFENCE.VMA instruction
+     *   executed by the hart since the speculative execution of the algorithm
+     *   began.
+     *
+     * Also, despite of the fact here it is mentioned that when V=0 two-stage
+     * address translation is inactivated:
+     *   The current virtualization mode, denoted V, indicates whether the hart
+     *   is currently executing in a guest. When V=1, the hart is either in
+     *   virtual S-mode (VS-mode), or in virtual U-mode (VU-mode) atop a guest
+     *   OS running in VS-mode. When V=0, the hart is either in M-mode, in
+     *   HS-mode, or in U-mode atop an OS running in HS-mode. The
+     *   virtualization mode also indicates whether two-stage address
+     *   translation is active (V=1) or inactive (V=0).
+     * But on the same side, writing to hgatp register activates it:
+     *   The hgatp register is considered active for the purposes of
+     *   the address-translation algorithm unless the effective privilege mode
+     *   is U and hstatus.HU=0.
+     *
+     * Thereby it leaves some room for speculation even in this stage of boot,
+     * so it could be that we polluted local TLB so flush all guest TLB.
+     */
+    local_hfence_gvma_all();
+}
diff --git a/xen/arch/riscv/setup.c b/xen/arch/riscv/setup.c
index 483cdd7e17..8f46f1a1de 100644
--- a/xen/arch/riscv/setup.c
+++ b/xen/arch/riscv/setup.c
@@ -22,6 +22,7 @@
 #include <asm/early_printk.h>
 #include <asm/fixmap.h>
 #include <asm/intc.h>
+#include <asm/p2m.h>
 #include <asm/sbi.h>
 #include <asm/setup.h>
 #include <asm/traps.h>
@@ -148,6 +149,8 @@ void __init noreturn start_xen(unsigned long bootcpu_id,
 
     console_init_postirq();
 
+    guest_mm_init();
+
     printk("All set up\n");
 
     machine_halt();
-- 
2.51.1



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

* [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 01/19] xen/riscv: avoid redundant HGATP*_MODE_SHIFT and HGATP*_VMID_SHIFT Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 02/19] xen/riscv: detect and initialize G-stage mode Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-12-08 16:31   ` Jan Beulich
  2025-12-08 17:28   ` Teddy Astie
  2025-11-24 12:33 ` [PATCH v6 04/19] xen/riscv: introduce things necessary for p2m initialization Oleksii Kurochko
                   ` (15 subsequent siblings)
  18 siblings, 2 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Andrew Cooper, Anthony PERARD, Michal Orzel,
	Jan Beulich, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, Alistair Francis, Bob Eshleman, Connor Davis

Current implementation is based on x86's way to allocate VMIDs:
  VMIDs partition the physical TLB. In the current implementation VMIDs are
  introduced to reduce the number of TLB flushes. Each time a guest-physical
  address space changes, instead of flushing the TLB, a new VMID is
  assigned. This reduces the number of TLB flushes to at most 1/#VMIDs.
  The biggest advantage is that hot parts of the hypervisor's code and data
  retain in the TLB.

  VMIDs are a hart-local resource.  As preemption of VMIDs is not possible,
  VMIDs are assigned in a round-robin scheme. To minimize the overhead of
  VMID invalidation, at the time of a TLB flush, VMIDs are tagged with a
  64-bit generation. Only on a generation overflow the code needs to
  invalidate all VMID information stored at the VCPUs with are run on the
  specific physical processor. When this overflow appears VMID usage is
  disabled to retain correctness.

Only minor changes are made compared to the x86 implementation.
These include using RISC-V-specific terminology, adding a check to ensure
the type used for storing the VMID has enough bits to hold VMIDLEN,
and introducing a new function vmidlen_detect() to clarify the VMIDLEN
value, rename stuff connected to VMID enable/disable to "VMID use
enable/disable".

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in V6:
 - Add information about VMID command line option to
   docs/misc/xen-command-line.pandoc.
 - Drop the call of local_hfence_gvma_all() from vmid_init() to avoid
   redundancy as this function is called now in pre_gstage_init().
   Also, update the comment in guest_mm_init() above local_hfence_gvma_all().
 - Add __ro_after_init in declaration of g_disabled.
 - Drop logging of function name in vmid_flush_hart() and drop full stop at
   the end of the message.
 - Add the check that vmid is used at the start of vmid_handle_enter().
 - Drop parentheses around (vmid->vmid == 1) in vmid_handle_enter().
 - Update BUILD_BUG_ON() in vmid_init as HGATP_VMID_SHIFT was dropped.
---
Changes in V5:
 - Rename opt_vmid_use_enabled with opt_vmid to be in sync with command line
   option.
 - Invert the expression for data->used = ... and swap "dis" and "en". Also,
   invert usage of data->used elsewhere.
 - s/vcpu_vmid_flush_vcpu/vmid_flush_vcpu.
 - Add prototypes to asm/vmid.h which could be used outside vmid.c.
 - Update the comment in vmidlen_detect(): instead of Section 3.7 ->
   Section "Physical Memory Protection".
 - Move vmid_init() call to pre_gstage_init().
---
Changes in V4:
 - s/guest's virtual/guest-physical in the comment inside vmid.c
   and in commit message.
 - Drop x86-related numbers in the comment about "Sketch of the Implementation".
 - s/__read_only/__ro_after_init in declaration of opt_vmid_enabled.
 - s/hart_vmid_generation/generation.
 - Update vmidlen_detect() to work with unsigned int type for vmid_bits
   variable.
 - Drop old variable im vmdidlen_detetct, it seems like there is no any reason
   to restore old value of hgatp with no guest running on a hart yet.
 - Update the comment above local_hfence_gvma_all() in vmidlen_detect().
 - s/max_availalbe_bits/max_available_bits.
 - use BITS_PER_BYTE, instead of "<< 3".
 - Add BUILD_BUILD_BUG_ON() instead run-time check that an amount of set bits
   can be held in vmid_data->max_vmid.
 - Apply changes from the patch "x86/HVM: polish hvm_asid_init() a little" here
   (changes connected to g_disabled) with the following minor changes:
   Update the printk() message to "VMIDs use is...".
   Rename g_disabled to g_vmid_used.
 - Rename member 'disabled' of vmid_data structure to used.
 - Use gstage_mode to properly detect VMIDLEN.
---
Changes in V3:
 - Reimplemnt VMID allocation similar to what x86 has implemented.
---
Changes in V2:
 - New patch.
---
 docs/misc/xen-command-line.pandoc   |   9 ++
 xen/arch/riscv/Makefile             |   1 +
 xen/arch/riscv/include/asm/domain.h |   6 +
 xen/arch/riscv/include/asm/vmid.h   |  14 +++
 xen/arch/riscv/p2m.c                |   7 +-
 xen/arch/riscv/vmid.c               | 170 ++++++++++++++++++++++++++++
 6 files changed, 205 insertions(+), 2 deletions(-)
 create mode 100644 xen/arch/riscv/include/asm/vmid.h
 create mode 100644 xen/arch/riscv/vmid.c

diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
index 34004ce282..6c4bfa3603 100644
--- a/docs/misc/xen-command-line.pandoc
+++ b/docs/misc/xen-command-line.pandoc
@@ -3096,3 +3096,12 @@ the hypervisor was compiled with `CONFIG_XSM` enabled.
 * `silo`: this will deny any unmediated communication channels between
   unprivileged VMs.  To choose this, the separated option in kconfig must also
   be enabled.
+
+### vmid (RISC-V)
+> `= <boolean>`
+
+> Default: `true`
+
+Permit Xen to use Virtual Machine Identifiers. This is an optimisation which
+tags the TLB entries with an ID per vcpu. This allows for guest TLB flushes
+to be performed without the overhead of a complete TLB flush.
diff --git a/xen/arch/riscv/Makefile b/xen/arch/riscv/Makefile
index 5a6f8c115d..1098b6d926 100644
--- a/xen/arch/riscv/Makefile
+++ b/xen/arch/riscv/Makefile
@@ -17,6 +17,7 @@ obj-y += smpboot.o
 obj-y += stubs.o
 obj-y += time.o
 obj-y += traps.o
+obj-y += vmid.o
 obj-y += vm_event.o
 
 $(TARGET): $(TARGET)-syms
diff --git a/xen/arch/riscv/include/asm/domain.h b/xen/arch/riscv/include/asm/domain.h
index c3d965a559..aac1040658 100644
--- a/xen/arch/riscv/include/asm/domain.h
+++ b/xen/arch/riscv/include/asm/domain.h
@@ -5,6 +5,11 @@
 #include <xen/xmalloc.h>
 #include <public/hvm/params.h>
 
+struct vcpu_vmid {
+    uint64_t generation;
+    uint16_t vmid;
+};
+
 struct hvm_domain
 {
     uint64_t              params[HVM_NR_PARAMS];
@@ -14,6 +19,7 @@ struct arch_vcpu_io {
 };
 
 struct arch_vcpu {
+    struct vcpu_vmid vmid;
 };
 
 struct arch_domain {
diff --git a/xen/arch/riscv/include/asm/vmid.h b/xen/arch/riscv/include/asm/vmid.h
new file mode 100644
index 0000000000..1c500c4aff
--- /dev/null
+++ b/xen/arch/riscv/include/asm/vmid.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef ASM_RISCV_VMID_H
+#define ASM_RISCV_VMID_H
+
+struct vcpu;
+struct vcpu_vmid;
+
+void vmid_init(void);
+bool vmid_handle_vmenter(struct vcpu_vmid *vmid);
+void vmid_flush_vcpu(struct vcpu *v);
+void vmid_flush_hart(void);
+
+#endif /* ASM_RISCV_VMID_H */
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index 7bb0fc0ab4..a4c5b682ce 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -9,6 +9,7 @@
 #include <asm/flushtlb.h>
 #include <asm/p2m.h>
 #include <asm/riscv_encoding.h>
+#include <asm/vmid.h>
 
 static struct gstage_mode_desc __ro_after_init max_gstage_mode = {
     .mode = HGATP_MODE_OFF,
@@ -72,9 +73,11 @@ void __init guest_mm_init(void)
 {
     gstage_mode_detect();
 
+    vmid_init();
+
     /*
-     * As gstage_mode_detect() is changing CSR_HGATP, it is necessary to flush
-     * guest TLB because:
+     * As gstage_mode_detect() and vmid_init() are changing CSR_HGATP, it is
+     * necessary to flush guest TLB because:
      *
      * From RISC-V spec:
      *   Speculative executions of the address-translation algorithm behave as
diff --git a/xen/arch/riscv/vmid.c b/xen/arch/riscv/vmid.c
new file mode 100644
index 0000000000..dc52d91650
--- /dev/null
+++ b/xen/arch/riscv/vmid.c
@@ -0,0 +1,170 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <xen/domain.h>
+#include <xen/init.h>
+#include <xen/sections.h>
+#include <xen/lib.h>
+#include <xen/param.h>
+#include <xen/percpu.h>
+
+#include <asm/atomic.h>
+#include <asm/csr.h>
+#include <asm/flushtlb.h>
+#include <asm/p2m.h>
+
+/* Xen command-line option to enable VMIDs */
+static bool __ro_after_init opt_vmid = true;
+boolean_param("vmid", opt_vmid);
+
+/*
+ * VMIDs partition the physical TLB. In the current implementation VMIDs are
+ * introduced to reduce the number of TLB flushes. Each time a guest-physical
+ * address space changes, instead of flushing the TLB, a new VMID is
+ * assigned. This reduces the number of TLB flushes to at most 1/#VMIDs.
+ * The biggest advantage is that hot parts of the hypervisor's code and data
+ * retain in the TLB.
+ *
+ * Sketch of the Implementation:
+ *
+ * VMIDs are a hart-local resource.  As preemption of VMIDs is not possible,
+ * VMIDs are assigned in a round-robin scheme. To minimize the overhead of
+ * VMID invalidation, at the time of a TLB flush, VMIDs are tagged with a
+ * 64-bit generation. Only on a generation overflow the code needs to
+ * invalidate all VMID information stored at the VCPUs with are run on the
+ * specific physical processor. When this overflow appears VMID usage is
+ * disabled to retain correctness.
+ */
+
+/* Per-Hart VMID management. */
+struct vmid_data {
+   uint64_t generation;
+   uint16_t next_vmid;
+   uint16_t max_vmid;
+   bool used;
+};
+
+static DEFINE_PER_CPU(struct vmid_data, vmid_data);
+
+static unsigned int vmidlen_detect(void)
+{
+    unsigned int vmid_bits;
+    unsigned char gstage_mode = get_max_supported_mode();
+
+    /*
+     * According to the RISC-V Privileged Architecture Spec:
+     *   When MODE=Bare, guest physical addresses are equal to supervisor
+     *   physical addresses, and there is no further memory protection
+     *   for a guest virtual machine beyond the physical memory protection
+     *   scheme described in Section "Physical Memory Protection".
+     *   In this case, the remaining fields in hgatp must be set to zeros.
+     * Thereby it is necessary to set gstage_mode not equal to Bare.
+     */
+    ASSERT(gstage_mode != HGATP_MODE_OFF);
+    csr_write(CSR_HGATP,
+              MASK_INSR(gstage_mode, HGATP_MODE_MASK) | HGATP_VMID_MASK);
+    vmid_bits = MASK_EXTR(csr_read(CSR_HGATP), HGATP_VMID_MASK);
+    vmid_bits = flsl(vmid_bits);
+    csr_write(CSR_HGATP, _AC(0, UL));
+
+    /* local_hfence_gvma_all() will be called at the end of pre_gstage_init. */
+
+    return vmid_bits;
+}
+
+void vmid_init(void)
+{
+    static int8_t __ro_after_init g_vmid_used = -1;
+
+    unsigned int vmid_len = vmidlen_detect();
+    struct vmid_data *data = &this_cpu(vmid_data);
+
+    BUILD_BUG_ON(MASK_EXTR(HGATP_VMID_MASK, HGATP_VMID_MASK) >
+                 (BIT((sizeof(data->max_vmid) * BITS_PER_BYTE), UL) - 1));
+
+    data->max_vmid = BIT(vmid_len, U) - 1;
+    data->used = opt_vmid && (vmid_len > 1);
+
+    if ( g_vmid_used < 0 )
+    {
+        g_vmid_used = data->used;
+        printk("VMIDs use is %sabled\n", data->used ? "en" : "dis");
+    }
+    else if ( g_vmid_used != data->used )
+        printk("CPU%u: VMIDs use is %sabled\n", smp_processor_id(),
+               data->used ? "en" : "dis");
+
+    /* Zero indicates 'invalid generation', so we start the count at one. */
+    data->generation = 1;
+
+    /* Zero indicates 'VMIDs use disabled', so we start the count at one. */
+    data->next_vmid = 1;
+}
+
+void vmid_flush_vcpu(struct vcpu *v)
+{
+    write_atomic(&v->arch.vmid.generation, 0);
+}
+
+void vmid_flush_hart(void)
+{
+    struct vmid_data *data = &this_cpu(vmid_data);
+
+    if ( !data->used )
+        return;
+
+    if ( likely(++data->generation != 0) )
+        return;
+
+    /*
+     * VMID generations are 64 bit.  Overflow of generations never happens.
+     * For safety, we simply disable ASIDs, so correctness is established; it
+     * only runs a bit slower.
+     */
+    printk("VMID generation overrun. Disabling VMIDs\n");
+    data->used = false;
+}
+
+bool vmid_handle_vmenter(struct vcpu_vmid *vmid)
+{
+    struct vmid_data *data = &this_cpu(vmid_data);
+
+    if ( !data->used )
+        goto disabled;
+
+    /* Test if VCPU has valid VMID. */
+    if ( read_atomic(&vmid->generation) == data->generation )
+        return 0;
+
+    /* If there are no free VMIDs, need to go to a new generation. */
+    if ( unlikely(data->next_vmid > data->max_vmid) )
+    {
+        vmid_flush_hart();
+        data->next_vmid = 1;
+        if ( !data->used )
+            goto disabled;
+    }
+
+    /* Now guaranteed to be a free VMID. */
+    vmid->vmid = data->next_vmid++;
+    write_atomic(&vmid->generation, data->generation);
+
+    /*
+     * When we assign VMID 1, flush all TLB entries as we are starting a new
+     * generation, and all old VMID allocations are now stale.
+     */
+    return vmid->vmid == 1;
+
+ disabled:
+    vmid->vmid = 0;
+    return 0;
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
-- 
2.51.1



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

* [PATCH v6 04/19] xen/riscv: introduce things necessary for p2m initialization
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (2 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 05/19] xen/riscv: construct the P2M pages pool for guests Oleksii Kurochko
                   ` (14 subsequent siblings)
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Introduce the following things:
- Update p2m_domain structure, which describe per p2m-table state, with:
  - lock to protect updates to p2m.
  - pool with pages used to construct p2m.
  - back pointer to domain structure.
- p2m_init() to initalize members introduced in p2m_domain structure.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
Changes in V6:
 - Nothing changed. Only rebase.
---
Changes in V5:
 - Acked-by: Jan Beulich <jbeulich@suse.com>.
---
Changes in V4:
 - Move an introduction of clean_pte member of p2m_domain structure to the
   patch where it is started to be used:
     xen/riscv: add root page table allocation
 - Add prototype of p2m_init() to asm/p2m.h.
---
Changes in V3:
 - s/p2m_type/p2m_types.
 - Drop init. of p2m->clean_pte in p2m_init() as CONFIG_HAS_PASSTHROUGH is
   going to be selected unconditionaly. Plus CONFIG_HAS_PASSTHROUGH isn't
   ready to be used for RISC-V.
   Add compilation error to not forget to init p2m->clean_pte.
 - Move defintion of p2m->domain up in p2m_init().
 - Add iommu_use_hap_pt() when p2m->clean_pte is initialized.
 - Add the comment above p2m_types member of p2m_domain struct.
 - Add need_flush member to p2m_domain structure.
 - Move introduction of p2m_write_(un)lock() and p2m_tlb_flush_sync()
   to the patch where they are really used:
     xen/riscv: implement guest_physmap_add_entry() for mapping GFNs to MFN
 - Add p2m member to arch_domain structure.
 - Drop p2m_types from struct p2m_domain as P2M type for PTE will be stored
   differently.
 - Drop default_access as it isn't going to be used for now.
 - Move defintion of p2m_is_write_locked() to "implement function to map memory
   in guest p2m"  where it is really used.
---
Changes in V2:
 - Use introduced erlier sbi_remote_hfence_gvma_vmid() for proper implementation
   of p2m_force_tlb_flush_sync() as TLB flushing needs to happen for each pCPU
   which potentially has cached a mapping, what is tracked by d->dirty_cpumask.
 - Drop unnecessary blanks.
 - Fix code style for # of pre-processor directive.
 - Drop max_mapped_gfn and lowest_mapped_gfn as they aren't used now.
 - [p2m_init()] Set p2m->clean_pte=false if CONFIG_HAS_PASSTHROUGH=n.
 - [p2m_init()] Update the comment above p2m->domain = d;
 - Drop p2m->need_flush as it seems to be always true for RISC-V and as a
   consequence drop p2m_tlb_flush_sync().
 - Move to separate patch an introduction of root page table allocation.
---
 xen/arch/riscv/include/asm/domain.h |  5 +++++
 xen/arch/riscv/include/asm/p2m.h    | 33 +++++++++++++++++++++++++++++
 xen/arch/riscv/p2m.c                | 20 +++++++++++++++++
 3 files changed, 58 insertions(+)

diff --git a/xen/arch/riscv/include/asm/domain.h b/xen/arch/riscv/include/asm/domain.h
index aac1040658..e688980efa 100644
--- a/xen/arch/riscv/include/asm/domain.h
+++ b/xen/arch/riscv/include/asm/domain.h
@@ -5,6 +5,8 @@
 #include <xen/xmalloc.h>
 #include <public/hvm/params.h>
 
+#include <asm/p2m.h>
+
 struct vcpu_vmid {
     uint64_t generation;
     uint16_t vmid;
@@ -24,6 +26,9 @@ struct arch_vcpu {
 
 struct arch_domain {
     struct hvm_domain hvm;
+
+    /* Virtual MMU */
+    struct p2m_domain p2m;
 };
 
 #include <xen/sched.h>
diff --git a/xen/arch/riscv/include/asm/p2m.h b/xen/arch/riscv/include/asm/p2m.h
index 3776b98303..239f90622e 100644
--- a/xen/arch/riscv/include/asm/p2m.h
+++ b/xen/arch/riscv/include/asm/p2m.h
@@ -3,17 +3,48 @@
 #define ASM__RISCV__P2M_H
 
 #include <xen/errno.h>
+#include <xen/mm.h>
+#include <xen/rwlock.h>
+#include <xen/types.h>
 
 #include <asm/page-bits.h>
 
 #define paddr_bits PADDR_BITS
 
+/* Get host p2m table */
+#define p2m_get_hostp2m(d) (&(d)->arch.p2m)
+
 struct gstage_mode_desc {
     unsigned char mode;
     unsigned int paging_levels;
     char name[8];
 };
 
+/* Per-p2m-table state */
+struct p2m_domain {
+    /*
+     * Lock that protects updates to the p2m.
+     */
+    rwlock_t lock;
+
+    /* Pages used to construct the p2m */
+    struct page_list_head pages;
+
+    /* Back pointer to domain */
+    struct domain *domain;
+
+    /*
+     * P2M updates may required TLBs to be flushed (invalidated).
+     *
+     * Flushes may be deferred by setting 'need_flush' and then flushing
+     * when the p2m write lock is released.
+     *
+     * If an immediate flush is required (e.g, if a super page is
+     * shattered), call p2m_tlb_flush_sync().
+     */
+    bool need_flush;
+};
+
 /*
  * List of possible type for each page in the p2m entry.
  * The number of available bit per page in the pte for this purpose is 2 bits.
@@ -97,6 +128,8 @@ static inline bool arch_acquire_resource_check(struct domain *d)
 void guest_mm_init(void);
 unsigned char get_max_supported_mode(void);
 
+int p2m_init(struct domain *d);
+
 #endif /* ASM__RISCV__P2M_H */
 
 /*
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index a4c5b682ce..3017e9a260 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -3,6 +3,10 @@
 #include <xen/init.h>
 #include <xen/lib.h>
 #include <xen/macros.h>
+#include <xen/mm.h>
+#include <xen/paging.h>
+#include <xen/rwlock.h>
+#include <xen/sched.h>
 #include <xen/sections.h>
 
 #include <asm/csr.h>
@@ -107,3 +111,19 @@ void __init guest_mm_init(void)
      */
     local_hfence_gvma_all();
 }
+
+int p2m_init(struct domain *d)
+{
+    struct p2m_domain *p2m = p2m_get_hostp2m(d);
+
+    /*
+     * "Trivial" initialisation is now complete.  Set the backpointer so the
+     * users of p2m could get an access to domain structure.
+     */
+    p2m->domain = d;
+
+    rwlock_init(&p2m->lock);
+    INIT_PAGE_LIST_HEAD(&p2m->pages);
+
+    return 0;
+}
-- 
2.51.1



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

* [PATCH v6 05/19] xen/riscv: construct the P2M pages pool for guests
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (3 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 04/19] xen/riscv: introduce things necessary for p2m initialization Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 06/19] xen/riscv: add root page table allocation Oleksii Kurochko
                   ` (13 subsequent siblings)
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Implement p2m_set_allocation() to construct p2m pages pool for guests
based on required number of pages.

This is implemented by:
- Adding a `struct paging_domain` which contains a freelist, a
  counter variable and a spinlock to `struct arch_domain` to
  indicate the free p2m pages and the number of p2m total pages in
  the p2m pages pool.
- Adding a helper `p2m_set_allocation` to set the p2m pages pool
  size. This helper should be called before allocating memory for
  a guest and is called from domain_p2m_set_allocation(), the latter
  is a part of common dom0less code.
- Adding implementation of paging_freelist_adjust() and
  paging_domain_init().

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
Changes in V5-6:
 - Nothing changed. Only rebase.
---
Changes in V4:
 - s/paging_freelist_init/paging_freelist_adjust.
 - Add empty line between definiton of paging_freelist_adjust()
   and paging_domain_init().
 - Update commit message.
 - Add Acked-by: Jan Beulich <jbeulich@suse.com>.
---
Changes in v3:
 - Drop usage of p2m_ prefix inside struct paging_domain().
 - Introduce paging_domain_init() to init paging struct.
---
Changes in v2:
 - Drop the comment above inclusion of <xen/event.h> in riscv/p2m.c.
 - Use ACCESS_ONCE() for lhs and rhs for the expressions in
   p2m_set_allocation().
---
 xen/arch/riscv/Makefile             |  1 +
 xen/arch/riscv/include/asm/Makefile |  1 -
 xen/arch/riscv/include/asm/domain.h | 12 ++++++
 xen/arch/riscv/include/asm/paging.h | 13 ++++++
 xen/arch/riscv/p2m.c                | 18 ++++++++
 xen/arch/riscv/paging.c             | 65 +++++++++++++++++++++++++++++
 6 files changed, 109 insertions(+), 1 deletion(-)
 create mode 100644 xen/arch/riscv/include/asm/paging.h
 create mode 100644 xen/arch/riscv/paging.c

diff --git a/xen/arch/riscv/Makefile b/xen/arch/riscv/Makefile
index 1098b6d926..2d1ad7a394 100644
--- a/xen/arch/riscv/Makefile
+++ b/xen/arch/riscv/Makefile
@@ -7,6 +7,7 @@ obj-y += intc.o
 obj-y += irq.o
 obj-y += mm.o
 obj-y += p2m.o
+obj-y += paging.o
 obj-y += pt.o
 obj-$(CONFIG_RISCV_64) += riscv64/
 obj-y += sbi.o
diff --git a/xen/arch/riscv/include/asm/Makefile b/xen/arch/riscv/include/asm/Makefile
index bfdf186c68..3824f31c39 100644
--- a/xen/arch/riscv/include/asm/Makefile
+++ b/xen/arch/riscv/include/asm/Makefile
@@ -6,7 +6,6 @@ generic-y += hardirq.h
 generic-y += hypercall.h
 generic-y += iocap.h
 generic-y += irq-dt.h
-generic-y += paging.h
 generic-y += percpu.h
 generic-y += perfc_defn.h
 generic-y += random.h
diff --git a/xen/arch/riscv/include/asm/domain.h b/xen/arch/riscv/include/asm/domain.h
index e688980efa..316e7c6c84 100644
--- a/xen/arch/riscv/include/asm/domain.h
+++ b/xen/arch/riscv/include/asm/domain.h
@@ -2,6 +2,8 @@
 #ifndef ASM__RISCV__DOMAIN_H
 #define ASM__RISCV__DOMAIN_H
 
+#include <xen/mm.h>
+#include <xen/spinlock.h>
 #include <xen/xmalloc.h>
 #include <public/hvm/params.h>
 
@@ -24,11 +26,21 @@ struct arch_vcpu {
     struct vcpu_vmid vmid;
 };
 
+struct paging_domain {
+    spinlock_t lock;
+    /* Free pages from the pre-allocated pool */
+    struct page_list_head freelist;
+    /* Number of pages from the pre-allocated pool */
+    unsigned long total_pages;
+};
+
 struct arch_domain {
     struct hvm_domain hvm;
 
     /* Virtual MMU */
     struct p2m_domain p2m;
+
+    struct paging_domain paging;
 };
 
 #include <xen/sched.h>
diff --git a/xen/arch/riscv/include/asm/paging.h b/xen/arch/riscv/include/asm/paging.h
new file mode 100644
index 0000000000..98d8b06d45
--- /dev/null
+++ b/xen/arch/riscv/include/asm/paging.h
@@ -0,0 +1,13 @@
+#ifndef ASM_RISCV_PAGING_H
+#define ASM_RISCV_PAGING_H
+
+#include <asm-generic/paging.h>
+
+struct domain;
+
+int paging_domain_init(struct domain *d);
+
+int paging_freelist_adjust(struct domain *d, unsigned long pages,
+                           bool *preempted);
+
+#endif /* ASM_RISCV_PAGING_H */
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index 3017e9a260..857517f8ee 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -12,6 +12,7 @@
 #include <asm/csr.h>
 #include <asm/flushtlb.h>
 #include <asm/p2m.h>
+#include <asm/paging.h>
 #include <asm/riscv_encoding.h>
 #include <asm/vmid.h>
 
@@ -122,8 +123,25 @@ int p2m_init(struct domain *d)
      */
     p2m->domain = d;
 
+    paging_domain_init(d);
+
     rwlock_init(&p2m->lock);
     INIT_PAGE_LIST_HEAD(&p2m->pages);
 
     return 0;
 }
+
+/*
+ * Set the pool of pages to the required number of pages.
+ * Returns 0 for success, non-zero for failure.
+ * Call with d->arch.paging.lock held.
+ */
+int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
+{
+    int rc;
+
+    if ( (rc = paging_freelist_adjust(d, pages, preempted)) )
+        return rc;
+
+    return 0;
+}
diff --git a/xen/arch/riscv/paging.c b/xen/arch/riscv/paging.c
new file mode 100644
index 0000000000..2df8de033b
--- /dev/null
+++ b/xen/arch/riscv/paging.c
@@ -0,0 +1,65 @@
+#include <xen/event.h>
+#include <xen/lib.h>
+#include <xen/mm.h>
+#include <xen/sched.h>
+#include <xen/spinlock.h>
+
+int paging_freelist_adjust(struct domain *d, unsigned long pages,
+                           bool *preempted)
+{
+    struct page_info *pg;
+
+    ASSERT(spin_is_locked(&d->arch.paging.lock));
+
+    for ( ; ; )
+    {
+        if ( d->arch.paging.total_pages < pages )
+        {
+            /* Need to allocate more memory from domheap */
+            pg = alloc_domheap_page(d, MEMF_no_owner);
+            if ( pg == NULL )
+            {
+                printk(XENLOG_ERR "Failed to allocate pages.\n");
+                return -ENOMEM;
+            }
+            ACCESS_ONCE(d->arch.paging.total_pages)++;
+            page_list_add_tail(pg, &d->arch.paging.freelist);
+        }
+        else if ( d->arch.paging.total_pages > pages )
+        {
+            /* Need to return memory to domheap */
+            pg = page_list_remove_head(&d->arch.paging.freelist);
+            if ( pg )
+            {
+                ACCESS_ONCE(d->arch.paging.total_pages)--;
+                free_domheap_page(pg);
+            }
+            else
+            {
+                printk(XENLOG_ERR
+                       "Failed to free pages, freelist is empty.\n");
+                return -ENOMEM;
+            }
+        }
+        else
+            break;
+
+        /* Check to see if we need to yield and try again */
+        if ( preempted && general_preempt_check() )
+        {
+            *preempted = true;
+            return -ERESTART;
+        }
+    }
+
+    return 0;
+}
+
+/* Domain paging struct initialization. */
+int paging_domain_init(struct domain *d)
+{
+    spin_lock_init(&d->arch.paging.lock);
+    INIT_PAGE_LIST_HEAD(&d->arch.paging.freelist);
+
+    return 0;
+}
-- 
2.51.1



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

* [PATCH v6 06/19] xen/riscv: add root page table allocation
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (4 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 05/19] xen/riscv: construct the P2M pages pool for guests Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-12-08 16:40   ` Jan Beulich
  2025-11-24 12:33 ` [PATCH v6 07/19] xen/riscv: introduce pte_{set,get}_mfn() Oleksii Kurochko
                   ` (12 subsequent siblings)
  18 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=all, Size: 16801 bytes --]

Introduce support for allocating and initializing the root page table
required for RISC-V stage-2 address translation.

To implement root page table allocation the following is introduced:
- p2m_get_clean_page() and p2m_alloc_root_table(), p2m_allocate_root()
  helpers to allocate and zero a 16 KiB root page table, as mandated
  by the RISC-V privileged specification for Sv32x4/Sv39x4/Sv48x4/Sv57x4
  modes.
- Update p2m_init() to inititialize p2m_root_order.
- Add maddr_to_page() and page_to_maddr() macros for easier address
  manipulation.
- Introduce paging_ret_to_domheap() to return some pages before
  allocate 16 KiB pages for root page table.
- Allocate root p2m table after p2m pool is initialized.
- Add construct_hgatp() to construct the hgatp register value based on
  p2m->root, p2m->hgatp_mode and VMID.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in V6:
 - Clean the memory page instead of frame_table[] in clear_and_clean_page().
 – Remove full stops from log messages in paging.c and start the messages
   with a lowercase letter.
 - s/paging_ret_page_to_domheap/_paging_ret_to_domheap
 - s/paging_add_page_to_freelist/_paging_add_to_freelist
 - Introduce gstage_mode and initialize it.
---
Changes in V5:
 - Update proto of construct_hgatp(): make first argument pointer-to-const.
 - Code style fixes.
 - s/paging_ret_pages_to_freelist/paging_refill_from_domheap.
 - s/paging_ret_pages_to_domheap/paging_ret_to_domheap.
 - s/paging_ret_page_to_freelist/paging_add_page_to_freelist.
 - Drop ACCESS_ONCE() as all the cases where it is used are used under spinlock() hence ACCESS_ONCE() is redundant.
---
Changes in V4:
 - Drop hgatp_mode from p2m_domain as gstage_mode was introduced and
   initlialized earlier patch. So use gstage_mode instead.
 - s/GUEST_ROOT_PAGE_TABLE_SIZE/GSTAGE_ROOT_PAGE_TABLE_SIZE.
 - Drop p2m_root_order and re-define P2M_ROOT_ORDER:
     #define P2M_ROOT_ORDER  (ilog2(GSTAGE_ROOT_PAGE_TABLE_SIZE) - PAGE_SHIFT)
 - Update implementation of construct_hgatp(): use introduced gstage_mode
   and use MASK_INSRT() to construct ppn value.
 - Drop nr_root_pages variable inside p2m_alloc_root_table().
 - Update the printk's message inside paging_ret_pages_to_domheap().
 - Add an introduction of clean_pte member of p2m_domain structure to this
   patch as it is started to be used here.
   Rename clean_pte to clean_dcache.
 - Drop p2m_allocate_root() function as it is going to be used only in one
   place.
 - Propogate rc from p2m_alloc_root_table() in p2m_set_allocation().
 - Return P2M_ROOT_PAGES to freelist in case of allocation of root page
   table failed.
 - Add allocated root tables pages to p2m->pages pool so a usage of pages
   could be properly taken into account.
---
Changes in v3:
 - Drop insterting of p2m->vmid in hgatp_from_page() as now vmid is allocated
   per-CPU, not per-domain, so it will be inserted later somewhere in
   context_switch or before returning control to a guest.
 - use BIT() to init nr_pages in p2m_allocate_root() instead of open-code
   BIT() macros.
 - Fix order in clear_and_clean_page().
 - s/panic("Specify more xen,domain-p2m-mem-mb\n")/return NULL.
 - Use lock around a procedure of returning back pages necessary for p2m
   root table.
 - Update the comment about allocation of page for root page table.
 - Update an argument of hgatp_from_page() to "struct page_info *p2m_root_page"
   to be consistent with the  function name.
 - Use p2m_get_hostp2m(d) instead of open-coding it.
 - Update the comment above the call of p2m_alloc_root_table().
 - Update the comments in p2m_allocate_root().
 - Move part which returns some page to domheap before root page table allocation
   to paging.c.
 - Pass p2m_domain * instead of struct domain * for p2m_alloc_root_table().
 - Introduce construct_hgatp() instead of hgatp_from_page().
 - Add vmid and hgatp_mode member of struct p2m_domain.
 - Add explanatory comment above clean_dcache_va_range() in
   clear_and_clean_page().
 - Introduce P2M_ROOT_ORDER and P2M_ROOT_PAGES.
 - Drop vmid member from p2m_domain as now we are using per-pCPU
   VMID allocation.
 - Update a declaration of construct_hgatp() to recieve VMID as it
   isn't per-VM anymore.
 - Drop hgatp member of p2m_domain struct as with the new VMID scheme
   allocation construction of hgatp will be needed more often.
 - Drop is_hardware_domain() case in p2m_allocate_root(), just always
   allocate root using p2m pool pages.
 - Refactor p2m_alloc_root_table() and p2m_alloc_table().
---
Changes in v2:
 - This patch was created from "xen/riscv: introduce things necessary for p2m
   initialization" with the following changes:
   - [clear_and_clean_page()] Add missed call of clean_dcache_va_range().
   - Drop p2m_get_clean_page() as it is going to be used only once to allocate
     root page table. Open-code it explicittly in p2m_allocate_root(). Also,
     it will help avoid duplication of the code connected to order and nr_pages
     of p2m root page table.
   - Instead of using order 2 for alloc_domheap_pages(), use
     get_order_from_bytes(KB(16)).
   - Clear and clean a proper amount of allocated pages in p2m_allocate_root().
   - Drop _info from the function name hgatp_from_page_info() and its argument
     page_info.
   - Introduce HGATP_MODE_MASK and use MASK_INSR() instead of shift to calculate
     value of hgatp.
   - Drop unnecessary parentheses in definition of page_to_maddr().
   - Add support of VMID.
   - Drop TLB flushing in p2m_alloc_root_table() and do that once when VMID
     is re-used. [Look at p2m_alloc_vmid()]
   - Allocate p2m root table after p2m pool is fully initialized: first
     return pages to p2m pool them allocate p2m root table.
---
 xen/arch/riscv/include/asm/mm.h             |   4 +
 xen/arch/riscv/include/asm/p2m.h            |  17 +++
 xen/arch/riscv/include/asm/paging.h         |   3 +
 xen/arch/riscv/include/asm/riscv_encoding.h |   2 +
 xen/arch/riscv/p2m.c                        |  99 +++++++++++++++++-
 xen/arch/riscv/paging.c                     | 110 +++++++++++++++-----
 6 files changed, 206 insertions(+), 29 deletions(-)

diff --git a/xen/arch/riscv/include/asm/mm.h b/xen/arch/riscv/include/asm/mm.h
index 9283616c02..dd8cdc9782 100644
--- a/xen/arch/riscv/include/asm/mm.h
+++ b/xen/arch/riscv/include/asm/mm.h
@@ -167,6 +167,10 @@ extern struct page_info *frametable_virt_start;
 #define mfn_to_page(mfn)    (frametable_virt_start + mfn_x(mfn))
 #define page_to_mfn(pg)     _mfn((pg) - frametable_virt_start)
 
+/* Convert between machine addresses and page-info structures. */
+#define maddr_to_page(ma) mfn_to_page(maddr_to_mfn(ma))
+#define page_to_maddr(pg) mfn_to_maddr(page_to_mfn(pg))
+
 static inline void *page_to_virt(const struct page_info *pg)
 {
     return mfn_to_virt(mfn_x(page_to_mfn(pg)));
diff --git a/xen/arch/riscv/include/asm/p2m.h b/xen/arch/riscv/include/asm/p2m.h
index 239f90622e..c9aa19ad43 100644
--- a/xen/arch/riscv/include/asm/p2m.h
+++ b/xen/arch/riscv/include/asm/p2m.h
@@ -2,6 +2,7 @@
 #ifndef ASM__RISCV__P2M_H
 #define ASM__RISCV__P2M_H
 
+#include <xen/bitops.h>
 #include <xen/errno.h>
 #include <xen/mm.h>
 #include <xen/rwlock.h>
@@ -9,6 +10,9 @@
 
 #include <asm/page-bits.h>
 
+#define P2M_ROOT_ORDER  (ilog2(GSTAGE_ROOT_PAGE_TABLE_SIZE) - PAGE_SHIFT)
+#define P2M_ROOT_PAGES  BIT(P2M_ROOT_ORDER, U)
+
 #define paddr_bits PADDR_BITS
 
 /* Get host p2m table */
@@ -30,6 +34,11 @@ struct p2m_domain {
     /* Pages used to construct the p2m */
     struct page_list_head pages;
 
+    /* The root of the p2m tree. May be concatenated */
+    struct page_info *root;
+
+    struct gstage_mode_desc mode;
+
     /* Back pointer to domain */
     struct domain *domain;
 
@@ -43,6 +52,12 @@ struct p2m_domain {
      * shattered), call p2m_tlb_flush_sync().
      */
     bool need_flush;
+
+    /*
+     * Indicate if it is required to clean the cache when writing an entry or
+     * when a page is needed to be fully cleared and cleaned.
+     */
+    bool clean_dcache;
 };
 
 /*
@@ -130,6 +145,8 @@ unsigned char get_max_supported_mode(void);
 
 int p2m_init(struct domain *d);
 
+unsigned long construct_hgatp(const struct p2m_domain *p2m, uint16_t vmid);
+
 #endif /* ASM__RISCV__P2M_H */
 
 /*
diff --git a/xen/arch/riscv/include/asm/paging.h b/xen/arch/riscv/include/asm/paging.h
index 98d8b06d45..01be45528f 100644
--- a/xen/arch/riscv/include/asm/paging.h
+++ b/xen/arch/riscv/include/asm/paging.h
@@ -10,4 +10,7 @@ int paging_domain_init(struct domain *d);
 int paging_freelist_adjust(struct domain *d, unsigned long pages,
                            bool *preempted);
 
+int paging_ret_to_domheap(struct domain *d, unsigned int nr_pages);
+int paging_refill_from_domheap(struct domain *d, unsigned int nr_pages);
+
 #endif /* ASM_RISCV_PAGING_H */
diff --git a/xen/arch/riscv/include/asm/riscv_encoding.h b/xen/arch/riscv/include/asm/riscv_encoding.h
index e0a5e8b58b..1f7e612366 100644
--- a/xen/arch/riscv/include/asm/riscv_encoding.h
+++ b/xen/arch/riscv/include/asm/riscv_encoding.h
@@ -180,6 +180,8 @@
 #define HGATP_MODE_MASK			HGATP32_MODE_MASK
 #endif
 
+#define GSTAGE_ROOT_PAGE_TABLE_SIZE	KB(16)
+
 #define TOPI_IID_SHIFT			16
 #define TOPI_IID_MASK			0xfff
 #define TOPI_IPRIO_MASK		0xff
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index 857517f8ee..7c2ce7ec18 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -3,6 +3,7 @@
 #include <xen/init.h>
 #include <xen/lib.h>
 #include <xen/macros.h>
+#include <xen/domain_page.h>
 #include <xen/mm.h>
 #include <xen/paging.h>
 #include <xen/rwlock.h>
@@ -113,6 +114,74 @@ void __init guest_mm_init(void)
     local_hfence_gvma_all();
 }
 
+static void clear_and_clean_page(struct page_info *page, bool clean_dcache)
+{
+    void *p = __map_domain_page(page);
+
+    clear_page(p);
+
+    /*
+     * If the IOMMU doesn't support coherent walks and the p2m tables are
+     * shared between the CPU and IOMMU, it is necessary to clean the
+     * d-cache.
+     */
+    if ( clean_dcache )
+        clean_dcache_va_range(p, PAGE_SIZE);
+
+    unmap_domain_page(p);
+}
+
+unsigned long construct_hgatp(const struct p2m_domain *p2m, uint16_t vmid)
+{
+    return MASK_INSR(mfn_x(page_to_mfn(p2m->root)), HGATP_PPN_MASK) |
+           MASK_INSR(p2m->mode.mode, HGATP_MODE_MASK) |
+           MASK_INSR(vmid, HGATP_VMID_MASK);
+}
+
+static int p2m_alloc_root_table(struct p2m_domain *p2m)
+{
+    struct domain *d = p2m->domain;
+    struct page_info *page;
+    int rc;
+
+    /*
+     * Return back P2M_ROOT_PAGES to assure the root table memory is also
+     * accounted against the P2M pool of the domain.
+     */
+    if ( (rc = paging_ret_to_domheap(d, P2M_ROOT_PAGES)) )
+        return rc;
+
+    /*
+     * As mentioned in the Priviliged Architecture Spec (version 20240411)
+     * in Section 18.5.1, for the paged virtual-memory schemes  (Sv32x4,
+     * Sv39x4, Sv48x4, and Sv57x4), the root page table is 16 KiB and must
+     * be aligned to a 16-KiB boundary.
+     */
+    page = alloc_domheap_pages(d, P2M_ROOT_ORDER, MEMF_no_owner);
+    if ( !page )
+    {
+        /*
+         * If allocation of root table pages fails, the pages acquired above
+         * must be returned to the freelist to maintain proper freelist
+         * balance.
+         */
+        paging_refill_from_domheap(d, P2M_ROOT_PAGES);
+
+        return -ENOMEM;
+    }
+
+    for ( unsigned int i = 0; i < P2M_ROOT_PAGES; i++ )
+    {
+        clear_and_clean_page(page + i, p2m->clean_dcache);
+
+        page_list_add(page + i, &p2m->pages);
+    }
+
+    p2m->root = page;
+
+    return 0;
+}
+
 int p2m_init(struct domain *d)
 {
     struct p2m_domain *p2m = p2m_get_hostp2m(d);
@@ -128,6 +197,24 @@ int p2m_init(struct domain *d)
     rwlock_init(&p2m->lock);
     INIT_PAGE_LIST_HEAD(&p2m->pages);
 
+    /*
+     * Currently, the infrastructure required to enable CONFIG_HAS_PASSTHROUGH
+     * is not ready for RISC-V support.
+     *
+     * When CONFIG_HAS_PASSTHROUGH=y, p2m->clean_dcache must be properly
+     * initialized.
+     * At the moment, it defaults to false because the p2m structure is
+     * zero-initialized.
+     */
+#ifdef CONFIG_HAS_PASSTHROUGH
+#   error "Add init of p2m->clean_dcache"
+#endif
+
+    /* TODO: don't hardcode used for a domain g-stage mode. */
+    p2m->mode.mode = HGATP_MODE_SV39X4;
+    p2m->mode.paging_levels = 2;
+    safe_strcpy(p2m->mode.name, "Sv39x4");
+
     return 0;
 }
 
@@ -138,10 +225,20 @@ int p2m_init(struct domain *d)
  */
 int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
 {
+    struct p2m_domain *p2m = p2m_get_hostp2m(d);
     int rc;
 
     if ( (rc = paging_freelist_adjust(d, pages, preempted)) )
         return rc;
 
-    return 0;
+    /*
+     * First, initialize p2m pool. Then allocate the root
+     * table so that the necessary pages can be returned from the p2m pool,
+     * since the root table must be allocated using alloc_domheap_pages(...)
+     * to meet its specific requirements.
+     */
+    if ( !p2m->root )
+        rc = p2m_alloc_root_table(p2m);
+
+    return rc;
 }
diff --git a/xen/arch/riscv/paging.c b/xen/arch/riscv/paging.c
index 2df8de033b..d401ddc34e 100644
--- a/xen/arch/riscv/paging.c
+++ b/xen/arch/riscv/paging.c
@@ -4,46 +4,67 @@
 #include <xen/sched.h>
 #include <xen/spinlock.h>
 
+static int _paging_ret_to_domheap(struct domain *d)
+{
+    struct page_info *page;
+
+    ASSERT(spin_is_locked(&d->arch.paging.lock));
+
+    /* Return memory to domheap. */
+    page = page_list_remove_head(&d->arch.paging.freelist);
+    if( page )
+    {
+        d->arch.paging.total_pages--;
+        free_domheap_page(page);
+    }
+    else
+    {
+        printk(XENLOG_ERR
+               "failed to free pages, P2M freelist is empty\n");
+        return -ENOMEM;
+    }
+
+    return 0;
+}
+
+static int _paging_add_to_freelist(struct domain *d)
+{
+    struct page_info *page;
+
+    ASSERT(spin_is_locked(&d->arch.paging.lock));
+
+    /* Need to allocate more memory from domheap */
+    page = alloc_domheap_page(d, MEMF_no_owner);
+    if ( page == NULL )
+    {
+        printk(XENLOG_ERR "failed to allocate pages\n");
+        return -ENOMEM;
+    }
+    d->arch.paging.total_pages++;
+    page_list_add_tail(page, &d->arch.paging.freelist);
+
+    return 0;
+}
+
 int paging_freelist_adjust(struct domain *d, unsigned long pages,
                            bool *preempted)
 {
-    struct page_info *pg;
-
     ASSERT(spin_is_locked(&d->arch.paging.lock));
 
     for ( ; ; )
     {
+        int rc = 0;
+
         if ( d->arch.paging.total_pages < pages )
-        {
-            /* Need to allocate more memory from domheap */
-            pg = alloc_domheap_page(d, MEMF_no_owner);
-            if ( pg == NULL )
-            {
-                printk(XENLOG_ERR "Failed to allocate pages.\n");
-                return -ENOMEM;
-            }
-            ACCESS_ONCE(d->arch.paging.total_pages)++;
-            page_list_add_tail(pg, &d->arch.paging.freelist);
-        }
+            rc = _paging_add_to_freelist(d);
         else if ( d->arch.paging.total_pages > pages )
-        {
-            /* Need to return memory to domheap */
-            pg = page_list_remove_head(&d->arch.paging.freelist);
-            if ( pg )
-            {
-                ACCESS_ONCE(d->arch.paging.total_pages)--;
-                free_domheap_page(pg);
-            }
-            else
-            {
-                printk(XENLOG_ERR
-                       "Failed to free pages, freelist is empty.\n");
-                return -ENOMEM;
-            }
-        }
+            rc = _paging_ret_to_domheap(d);
         else
             break;
 
+        if ( rc )
+            return rc;
+
         /* Check to see if we need to yield and try again */
         if ( preempted && general_preempt_check() )
         {
@@ -55,6 +76,39 @@ int paging_freelist_adjust(struct domain *d, unsigned long pages,
     return 0;
 }
 
+int paging_refill_from_domheap(struct domain *d, unsigned int nr_pages)
+{
+    ASSERT(spin_is_locked(&d->arch.paging.lock));
+
+    for ( unsigned int i = 0; i < nr_pages; i++ )
+    {
+        int rc = _paging_add_to_freelist(d);
+
+        if ( rc )
+            return rc;
+    }
+
+    return 0;
+}
+
+int paging_ret_to_domheap(struct domain *d, unsigned int nr_pages)
+{
+    ASSERT(spin_is_locked(&d->arch.paging.lock));
+
+    if ( d->arch.paging.total_pages < nr_pages )
+        return false;
+
+    for ( unsigned int i = 0; i < nr_pages; i++ )
+    {
+        int rc = _paging_ret_to_domheap(d);
+
+        if ( rc )
+            return rc;
+    }
+
+    return 0;
+}
+
 /* Domain paging struct initialization. */
 int paging_domain_init(struct domain *d)
 {
-- 
2.51.1



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

* [PATCH v6 07/19] xen/riscv: introduce pte_{set,get}_mfn()
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (5 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 06/19] xen/riscv: add root page table allocation Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 08/19] xen/riscv: add new p2m types and helper macros for type classification Oleksii Kurochko
                   ` (11 subsequent siblings)
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Introduce helpers pte_{set,get}_mfn() to simplify setting and getting
of mfn.

Also, introduce PTE_PPN_MASK and add BUILD_BUG_ON() to be sure that
PTE_PPN_MASK remains the same for all MMU modes except Sv32.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
Changes in V4-V6:
 - Nothing changed. Only Rebase.
---
Changes in V3:
 - Add Acked-by: Jan Beulich <jbeulich@suse.com>.
---
Changes in V2:
 - Patch "[PATCH v1 4/6] xen/riscv: define pt_t and pt_walk_t structures" was
   renamed to xen/riscv: introduce pte_{set,get}_mfn() as after dropping of
   bitfields for PTE structure, this patch introduce only pte_{set,get}_mfn().
 - As pt_t and pt_walk_t were dropped, update implementation of
   pte_{set,get}_mfn() to use bit operations and shifts instead of bitfields.
 - Introduce PTE_PPN_MASK to be able to use MASK_INSR for setting/getting PPN.
 - Add BUILD_BUG_ON(RV_STAGE1_MODE > SATP_MODE_SV57) to be sure that when
   new MMU mode will be added, someone checks that PPN is still bits 53:10.
---
 xen/arch/riscv/include/asm/page.h | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/xen/arch/riscv/include/asm/page.h b/xen/arch/riscv/include/asm/page.h
index ddcc4da0a3..66cb192316 100644
--- a/xen/arch/riscv/include/asm/page.h
+++ b/xen/arch/riscv/include/asm/page.h
@@ -112,6 +112,30 @@ typedef struct {
 #endif
 } pte_t;
 
+#if RV_STAGE1_MODE != SATP_MODE_SV32
+#define PTE_PPN_MASK _UL(0x3FFFFFFFFFFC00)
+#else
+#define PTE_PPN_MASK _U(0xFFFFFC00)
+#endif
+
+static inline void pte_set_mfn(pte_t *p, mfn_t mfn)
+{
+    /*
+     * At the moment spec provides Sv32 - Sv57.
+     * If one day new MMU mode will be added it will be needed
+     * to check that PPN mask still continue to cover bits 53:10.
+     */
+    BUILD_BUG_ON(RV_STAGE1_MODE > SATP_MODE_SV57);
+
+    p->pte &= ~PTE_PPN_MASK;
+    p->pte |= MASK_INSR(mfn_x(mfn), PTE_PPN_MASK);
+}
+
+static inline mfn_t pte_get_mfn(pte_t p)
+{
+    return _mfn(MASK_EXTR(p.pte, PTE_PPN_MASK));
+}
+
 static inline bool pte_is_valid(pte_t p)
 {
     return p.pte & PTE_VALID;
-- 
2.51.1



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

* [PATCH v6 08/19] xen/riscv: add new p2m types and helper macros for type classification
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (6 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 07/19] xen/riscv: introduce pte_{set,get}_mfn() Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 09/19] xen/dom0less: abstract Arm-specific p2m type name for device MMIO mappings Oleksii Kurochko
                   ` (10 subsequent siblings)
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=all, Size: 2657 bytes --]

- Extended p2m_type_t with additional types: p2m_mmio_direct,
  p2m_ext_storage.
- Added macros to classify memory types: P2M_RAM_TYPES.
- Introduced helper predicates: p2m_is_ram(), p2m_is_any_ram().
- Introduce arch_dt_passthrough() to tell handle_passthrough_prop()
  from common code how to map device memory.
- Introduce p2m_first_external for detection for relational operations
  with p2m type which is stored outside P2M's PTE bits.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
Changes in V5-V6:
 - Nothing changed. Only Rebase.
---
Changes in V4:
 - Drop underscores for argumets of p2m_is_ram() and p2m_is_any_ram().
 - Add Acked-by: Jan Beulich <jbeulich@suse.com>.
---
Changes in V4:
 - Drop underscode in p2m_to_mask()'s argument and for other similar helpers.
 - Introduce arch_dt_passthrough_p2m_type() instead of p2m_mmio_direct.
 - Drop for the moment grant tables related stuff as it isn't going to be used in the nearest future.
---
Changes in V3:
 - Drop p2m_ram_ro.
 - Rename p2m_mmio_direct_dev to p2m_mmio_direct_io to make it more RISC-V specicific.
 - s/p2m_mmio_direct_dev/p2m_mmio_direct_io.
---
Changes in V2:
 - Drop stuff connected to foreign mapping as it isn't necessary for RISC-V
   right now.
---
 xen/arch/riscv/include/asm/p2m.h | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/xen/arch/riscv/include/asm/p2m.h b/xen/arch/riscv/include/asm/p2m.h
index c9aa19ad43..1c89838408 100644
--- a/xen/arch/riscv/include/asm/p2m.h
+++ b/xen/arch/riscv/include/asm/p2m.h
@@ -70,8 +70,29 @@ struct p2m_domain {
 typedef enum {
     p2m_invalid = 0,    /* Nothing mapped here */
     p2m_ram_rw,         /* Normal read/write domain RAM */
+    p2m_mmio_direct_io, /* Read/write mapping of genuine Device MMIO area,
+                           PTE_PBMT_IO will be used for such mappings */
+    p2m_ext_storage,    /* Following types'll be stored outsude PTE bits: */
+
+    /* Sentinel — not a real type, just a marker for comparison */
+    p2m_first_external = p2m_ext_storage,
 } p2m_type_t;
 
+static inline p2m_type_t arch_dt_passthrough_p2m_type(void)
+{
+    return p2m_mmio_direct_io;
+}
+
+/* We use bitmaps and mask to handle groups of types */
+#define p2m_to_mask(t) BIT(t, UL)
+
+/* RAM types, which map to real machine frames */
+#define P2M_RAM_TYPES (p2m_to_mask(p2m_ram_rw))
+
+/* Useful predicates */
+#define p2m_is_ram(t) (p2m_to_mask(t) & P2M_RAM_TYPES)
+#define p2m_is_any_ram(t) (p2m_to_mask(t) & P2M_RAM_TYPES)
+
 #include <xen/p2m-common.h>
 
 static inline int get_page_and_type(struct page_info *page,
-- 
2.51.1



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

* [PATCH v6 09/19] xen/dom0less: abstract Arm-specific p2m type name for device MMIO mappings
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (7 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 08/19] xen/riscv: add new p2m types and helper macros for type classification Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 10/19] xen/riscv: implement function to map memory in guest p2m Oleksii Kurochko
                   ` (9 subsequent siblings)
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Stefano Stabellini, Julien Grall,
	Bertrand Marquis, Michal Orzel, Volodymyr Babchuk, Jan Beulich

Introduce arch_dt_passthrough_p2m_type() and use it instead of
`p2m_mmio_direct_dev` to avoid leaking Arm-specific naming into
common Xen code, such as dom0less passthrough property handling.

This helps reduce platform-specific terminology in shared logic and
improves clarity for future non-Arm ports (e.g. RISC-V or PowerPC).

No functional changes — the definition is preserved via a static inline
function for Arm.

Suggested-by: Jan Beulich <jbeulich@suse.com>
Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in V5-6:
 - Nothing changed. Only rebase.
---
Changes in V4:
 - Introduce arch_dt_passthrough_p2m_type() instead of re-defining of
   p2m_mmio_direct.
---
Changes in V3:
 - New patch.
---

 xen/arch/arm/include/asm/p2m.h          | 5 +++++
 xen/common/device-tree/dom0less-build.c | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/xen/arch/arm/include/asm/p2m.h b/xen/arch/arm/include/asm/p2m.h
index ef98bc5f4d..010ce8c9eb 100644
--- a/xen/arch/arm/include/asm/p2m.h
+++ b/xen/arch/arm/include/asm/p2m.h
@@ -137,6 +137,11 @@ typedef enum {
     p2m_max_real_type,  /* Types after this won't be store in the p2m */
 } p2m_type_t;
 
+static inline p2m_type_t arch_dt_passthrough_p2m_type(void)
+{
+    return p2m_mmio_direct_dev;
+}
+
 /* We use bitmaps and mask to handle groups of types */
 #define p2m_to_mask(_t) (1UL << (_t))
 
diff --git a/xen/common/device-tree/dom0less-build.c b/xen/common/device-tree/dom0less-build.c
index 2600350a3c..495ef7b16a 100644
--- a/xen/common/device-tree/dom0less-build.c
+++ b/xen/common/device-tree/dom0less-build.c
@@ -185,7 +185,7 @@ static int __init handle_passthrough_prop(struct kernel_info *kinfo,
                                gaddr_to_gfn(gstart),
                                PFN_DOWN(size),
                                maddr_to_mfn(mstart),
-                               p2m_mmio_direct_dev);
+                               arch_dt_passthrough_p2m_type());
         if ( res < 0 )
         {
             printk(XENLOG_ERR
-- 
2.51.1



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

* [PATCH v6 10/19] xen/riscv: implement function to map memory in guest p2m
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (8 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 09/19] xen/dom0less: abstract Arm-specific p2m type name for device MMIO mappings Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 11/19] xen/riscv: implement p2m_set_range() Oleksii Kurochko
                   ` (8 subsequent siblings)
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Implement map_regions_p2mt() to map a region in the guest p2m with
a specific p2m type. The memory attributes will be derived from the
p2m type. This function is used in dom0less common
code.

To implement it, introduce:
- p2m_write_(un)lock() to ensure safe concurrent updates to the P2M.
  As part of this change, introduce p2m_tlb_flush_sync() and
  p2m_force_tlb_flush_sync().
- A stub for p2m_set_range() to map a range of GFNs to MFNs.
- p2m_insert_mapping().
- p2m_is_write_locked().

Drop guest_physmap_add_entry() and call map_regions_p2mt() directly
from guest_physmap_add_page(), making guest_physmap_add_entry()
unnecessary.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
Changes in V6:
 - Nothing changed. Only Rebase.
---
Changes in V5:
 - Put "p2m->need_flush = false;" before TLB flush.
 - Correct the comment above p2m_write_unlock().
 - Add Acked-by: Jan Beulich <jbeulich@suse.com>.
---
Changes in V4:
 - Update the comment above declaration of map_regions_p2mt():
   s/guest p2m/guest's hostp2m.
 - Add const for p2m_force_tlb_flush_sync()'s local variable `d`.
 - Stray 'w' in the comment inside p2m_write_unlock().
 - Drop p2m_insert_mapping() and leave only map_regions_p2mt() as it
   is just re-use insert_mapping().
 - Rename p2m_force_tlb_flush_sync() to p2m_tlb_flush().
 - Update prototype of p2m_is_write_locked() to return bool instead of
   int.
---
Changes in v3:
 - Introudce p2m_write_lock() and p2m_is_write_locked().
 - Introduce p2m_force_tlb_flush_sync() and p2m_flush_tlb() to flush TLBs
   after p2m table update.
 - Change an argument of p2m_insert_mapping() from struct domain *d to
   p2m_domain *p2m.
 - Drop guest_physmap_add_entry() and use map_regions_p2mt() to define
   guest_physmap_add_page().
 - Add declaration of map_regions_p2mt() to asm/p2m.h.
 - Rewrite commit message and subject.
 - Drop p2m_access_t related stuff.
 - Add defintion of  p2m_is_write_locked().
---
Changes in v2:
 - This changes were part of "xen/riscv: implement p2m mapping functionality".
   No additional signigicant changes were done.
---
 xen/arch/riscv/include/asm/p2m.h | 31 ++++++++++++-----
 xen/arch/riscv/p2m.c             | 60 ++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 9 deletions(-)

diff --git a/xen/arch/riscv/include/asm/p2m.h b/xen/arch/riscv/include/asm/p2m.h
index 1c89838408..9acd6a64a8 100644
--- a/xen/arch/riscv/include/asm/p2m.h
+++ b/xen/arch/riscv/include/asm/p2m.h
@@ -128,21 +128,22 @@ static inline int guest_physmap_mark_populate_on_demand(struct domain *d,
     return -EOPNOTSUPP;
 }
 
-static inline int guest_physmap_add_entry(struct domain *d,
-                                          gfn_t gfn, mfn_t mfn,
-                                          unsigned long page_order,
-                                          p2m_type_t t)
-{
-    BUG_ON("unimplemented");
-    return -EINVAL;
-}
+/*
+ * Map a region in the guest's hostp2m p2m with a specific p2m type.
+ * The memory attributes will be derived from the p2m type.
+ */
+int map_regions_p2mt(struct domain *d,
+                     gfn_t gfn,
+                     unsigned long nr,
+                     mfn_t mfn,
+                     p2m_type_t p2mt);
 
 /* Untyped version for RAM only, for compatibility */
 static inline int __must_check
 guest_physmap_add_page(struct domain *d, gfn_t gfn, mfn_t mfn,
                        unsigned int page_order)
 {
-    return guest_physmap_add_entry(d, gfn, mfn, page_order, p2m_ram_rw);
+    return map_regions_p2mt(d, gfn, BIT(page_order, UL), mfn, p2m_ram_rw);
 }
 
 static inline mfn_t gfn_to_mfn(struct domain *d, gfn_t gfn)
@@ -166,6 +167,18 @@ unsigned char get_max_supported_mode(void);
 
 int p2m_init(struct domain *d);
 
+static inline void p2m_write_lock(struct p2m_domain *p2m)
+{
+    write_lock(&p2m->lock);
+}
+
+void p2m_write_unlock(struct p2m_domain *p2m);
+
+static inline bool p2m_is_write_locked(struct p2m_domain *p2m)
+{
+    return rw_is_write_locked(&p2m->lock);
+}
+
 unsigned long construct_hgatp(const struct p2m_domain *p2m, uint16_t vmid);
 
 #endif /* ASM__RISCV__P2M_H */
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index 7c2ce7ec18..c559cde13a 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -114,6 +114,41 @@ void __init guest_mm_init(void)
     local_hfence_gvma_all();
 }
 
+/*
+ * Force a synchronous P2M TLB flush.
+ *
+ * Must be called with the p2m lock held.
+ */
+static void p2m_tlb_flush(struct p2m_domain *p2m)
+{
+    const struct domain *d = p2m->domain;
+
+    ASSERT(p2m_is_write_locked(p2m));
+
+    p2m->need_flush = false;
+
+    sbi_remote_hfence_gvma(d->dirty_cpumask, 0, 0);
+}
+
+void p2m_tlb_flush_sync(struct p2m_domain *p2m)
+{
+    if ( p2m->need_flush )
+        p2m_tlb_flush(p2m);
+}
+
+/* Unlock the P2M and do a P2M TLB flush if necessary */
+void p2m_write_unlock(struct p2m_domain *p2m)
+{
+    /*
+     * The final flush is done with the P2M write lock taken to avoid
+     * someone else modifying the P2M before the TLB invalidation has
+     * completed.
+     */
+    p2m_tlb_flush_sync(p2m);
+
+    write_unlock(&p2m->lock);
+}
+
 static void clear_and_clean_page(struct page_info *page, bool clean_dcache)
 {
     void *p = __map_domain_page(page);
@@ -242,3 +277,28 @@ int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
 
     return rc;
 }
+
+static int p2m_set_range(struct p2m_domain *p2m,
+                         gfn_t sgfn,
+                         unsigned long nr,
+                         mfn_t smfn,
+                         p2m_type_t t)
+{
+    return -EOPNOTSUPP;
+}
+
+int map_regions_p2mt(struct domain *d,
+                     gfn_t gfn,
+                     unsigned long nr,
+                     mfn_t mfn,
+                     p2m_type_t p2mt)
+{
+    struct p2m_domain *p2m = p2m_get_hostp2m(d);
+    int rc;
+
+    p2m_write_lock(p2m);
+    rc = p2m_set_range(p2m, gfn, nr, mfn, p2mt);
+    p2m_write_unlock(p2m);
+
+    return rc;
+}
-- 
2.51.1



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

* [PATCH v6 11/19] xen/riscv: implement p2m_set_range()
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (9 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 10/19] xen/riscv: implement function to map memory in guest p2m Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-12-08 16:52   ` Jan Beulich
  2025-11-24 12:33 ` [PATCH v6 12/19] xen/riscv: Implement p2m_free_subtree() and related helpers Oleksii Kurochko
                   ` (7 subsequent siblings)
  18 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

This patch introduces p2m_set_range() and its core helper p2m_set_entry() for
RISC-V, based loosely on the Arm implementation, with several RISC-V-specific
modifications.

The main changes are:
- Simplification of Break-Before-Make (BBM) approach as according to RISC-V
  spec:
    It is permitted for multiple address-translation cache entries to co-exist
    for the same address. This represents the fact that in a conventional
    TLB hierarchy, it is possible for multiple entries to match a single
    address if, for example, a page is upgraded to a superpage without first
    clearing the original non-leaf PTE’s valid bit and executing an SFENCE.VMA
    with rs1=x0, or if multiple TLBs exist in parallel at a given level of the
    hierarchy. In this case, just as if an SFENCE.VMA is not executed between
    a write to the memory-management tables and subsequent implicit read of the
    same address: it is unpredictable whether the old non-leaf PTE or the new
    leaf PTE is used, but the behavior is otherwise well defined.
  In contrast to the Arm architecture, where BBM is mandatory and failing to
  use it in some cases can lead to CPU instability, RISC-V guarantees
  stability, and the behavior remains safe — though unpredictable in terms of
  which translation will be used.
- Unlike Arm, the valid bit is not repurposed for other uses in this
  implementation. Instead, entry validity is determined based solely on P2M
  PTE's valid bit.

The main functionality is in p2m_set_entry(), which handles mappings aligned
to page table block entries (e.g., 1GB, 2MB, or 4KB with 4KB granularity).

p2m_set_range() breaks a region down into block-aligned mappings and calls
p2m_set_entry() accordingly.

Stub implementations (to be completed later) include:
- p2m_free_subtree()
- p2m_next_level()
- p2m_pte_from_mfn()

Note: Support for shattering block entries is not implemented in this
patch and will be added separately.

Additionally, some straightforward helper functions are now implemented:
- p2m_write_pte()
- p2m_remove_pte()
- p2m_get_root_pointer()

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in V6:
 - Drop the last paragraph in the comment above P2M_LEVEL_ORDER().
 - Update prototype of calc_offset(..., vaddr_t va) to
   calc_offset(..., paddr_t gpa).
 - Rename root_table_indx to idx.
 - s/clean_pte/clean_cache.
 - Fix identations.
 - Introduce P2M_MAX_SUPPORTED_LEVEL_MAPPING to use it instead of magic constant.
 - Rename GFN_MASK to P2M_TABLE_OFFSET
 - Update the implementation of calc_offset() and get_root_pointer().
 - s/P2M_DECLARE_OFFSETS/P2M_BUILD_LEVEL_OFFSETS.
 - Add BUG_ON() inside P2M_BUILD_LEVEL_OFFSETS() to be sure that
   actual root level isn't higer then maximum possible.
 - Move gstage_mode and gstage_level inside p2m_domain structure.
---
Changes in V5:
 - Update the comment above p2m_get_root_pointer().
 - Fix an identation for p2m_set_entry()'s arguments.
 - Update the comment in p2m_set_entry() where lookup is happening.
 - Drop part of the comment above p2m_set_entry() as it is not really
   needed anymore.
 - Introduce P2M_DECLARE_OFFSETS() to use it insetead of
   DECLARE_OFFSETS() as the latter could have an issue with P2M code.
 - Update p2m_get_root_pointer() to work only with P2M root properties.
 - Update the comment inside in p2m_set_entry() for the case when
   p2m_next_level() returns P2M_TABLE_MAP_{NONE,NOMEM}.
 - Simplify a little bit a condition when p2m_free_subtree() by removing
   a case when removing && mfn(0) are checked explicitly.
---
Changes in V4:
 - Introduce gstage_root_level and use it for defintion of P2M_ROOT_LEVEL.
 - Introduce P2M_LEVEL_ORDER() macros and P2M_PAGETABLE_ENTRIES().
 - Add the TODO comment in p2m_write_pte() about possible perfomance
   optimization.
 - Use compound literal for `pte` variable inside p2m_clean_pte().
 - Fix the comment above p2m_next_level().
 - Update ASSERT() inside p2m_set_entry() and leave only a check of a
   target as p2m_mapping_order() that page_order will be correctly
   aligned.
 - Update the comment above declaration of `removing_mapping` in
   p2m_set_entry().
 - Stray blanks.
 - Handle possibly overflow of an amount of unmapped GFNs in case of
   some failute in p2m_set_range().
 - Handle a case when MFN is 0 and removing of such MFN is happening in
   p2m_set_entry.
 - Fix p2m_get_root_pointer() to return correct pointer to root page table.
---
Changes in V3:
 - Drop p2m_access_t connected stuff as it isn't going to be used, at least
   now.
 - Move defintion of P2M_ROOT_ORDER and P2M_ROOT_PAGES to earlier patches.
 - Update the comment above lowest_mapped_gfn declaration.
 - Update the comment above p2m_get_root_pointer(): s/"...ofset of the root
   table"/"...ofset into root table".
 - s/p2m_remove_pte/p2m_clean_pte.
 - Use plain 0 instead of 0x00 in p2m_clean_pte().
 - s/p2m_entry_from_mfn/p2m_pte_from_mfn.
 - s/GUEST_TABLE_*/P2M_TABLE_*.
 - Update the comment above p2m_next_level(): "GFN entry" -> "corresponding
   the entry corresponding to the GFN".
 - s/__p2m_set_entry/_p2m_set_entry.
 - drop "s" for sgfn and smfn prefixes of _p2m_set_entry()'s arguments
   as this function work only with one GFN and one MFN.
 - Return correct return code when p2m_next_level() faild in _p2m_set_entry(),
   also drop "else" and just handle case (rc != P2M_TABLE_NORMAL) separately.
 - Code style fixes.
 - Use unsigned int for "order" in p2m_set_entry().
 - s/p2m_set_entry/p2m_free_subtree.
 - Update ASSERT() in __p2m_set_enty() to check that page_order is propertly
   aligned.
 - Return -EACCES instead of -ENOMEM in the chase when domain is dying and
   someone called p2m_set_entry.
 - s/p2m_set_entry/p2m_set_range.
 - s/__p2m_set_entry/p2m_set_entry
 - s/p2me_is_valid/p2m_is_valid()
 - Return a number of successfully mapped GFNs in case if not all were mapped
   in p2m_set_range().
 - Use BIT(order, UL) instead of 1 << order.
 - Drop IOMMU flushing code from p2m_set_entry().
 - set p2m->need_flush=true when entry in p2m_set_entry() is changed.
 - Introduce p2m_mapping_order() to support superpages.
 - Drop p2m_is_valid() and use pte_is_valid() instead as there is no tricks
   with copying of valid bit anymore.
 - Update p2m_pte_from_mfn() prototype: drop p2m argument.
---
Changes in V2:
 - New patch. It was a part of a big patch "xen/riscv: implement p2m mapping
   functionality" which was splitted to smaller.
 - Update the way when p2m TLB is flushed:
 - RISC-V does't require BBM so there is no need to remove PTE before making
   new so drop 'if /*pte_is_valid(orig_pte) */' and remove PTE only removing
   has been requested.
 - Drop p2m->need_flush |= !!pte_is_valid(orig_pte); for the case when
   PTE's removing is happening as RISC-V could cache invalid PTE and thereby
   it requires to do a flush each time and it doesn't matter if PTE is valid
   or not at the moment when PTE removing is happening.
 - Drop a check if PTE is valid in case of PTE is modified as it was mentioned
   above as BBM isn't required so TLB flushing could be defered and there is
   no need to do it before modifying of PTE.
 - Drop p2m->need_flush as it seems like it will be always true.
 - Drop foreign mapping things as it isn't necessary for RISC-V right now.
 - s/p2m_is_valid/p2me_is_valid.
 - Move definition and initalization of p2m->{max_mapped_gfn,lowest_mapped_gfn}
   to this patch.
---
 xen/arch/riscv/include/asm/p2m.h |  38 ++++
 xen/arch/riscv/p2m.c             | 326 ++++++++++++++++++++++++++++++-
 2 files changed, 363 insertions(+), 1 deletion(-)

diff --git a/xen/arch/riscv/include/asm/p2m.h b/xen/arch/riscv/include/asm/p2m.h
index 9acd6a64a8..fa55d8a3bc 100644
--- a/xen/arch/riscv/include/asm/p2m.h
+++ b/xen/arch/riscv/include/asm/p2m.h
@@ -8,10 +8,38 @@
 #include <xen/rwlock.h>
 #include <xen/types.h>
 
+#include <asm/page.h>
 #include <asm/page-bits.h>
 
 #define P2M_ROOT_ORDER  (ilog2(GSTAGE_ROOT_PAGE_TABLE_SIZE) - PAGE_SHIFT)
 #define P2M_ROOT_PAGES  BIT(P2M_ROOT_ORDER, U)
+#define P2M_ROOT_LEVEL(p2m) ((p2m)->mode.paging_levels)
+
+/*
+ * According to the RISC-V spec:
+ *   When hgatp.MODE specifies a translation scheme of Sv32x4, Sv39x4, Sv48x4,
+ *   or Sv57x4, G-stage address translation is a variation on the usual
+ *   page-based virtual address translation scheme of Sv32, Sv39, Sv48, or
+ *   Sv57, respectively. In each case, the size of the incoming address is
+ *   widened by 2 bits (to 34, 41, 50, or 59 bits).
+ *
+ * P2M_LEVEL_ORDER(lvl) defines the bit position in the GFN from which
+ * the index for this level of the P2M page table starts. The extra 2
+ * bits added by the "x4" schemes only affect the root page table width.
+ *
+ * Therefore, this macro can safely reuse XEN_PT_LEVEL_ORDER() for all
+ * levels: the extra 2 bits do not change the indices of lower levels.
+ */
+#define P2M_LEVEL_ORDER(lvl) XEN_PT_LEVEL_ORDER(lvl)
+
+#define P2M_ROOT_EXTRA_BITS(p2m, lvl) (2 * ((lvl) == P2M_ROOT_LEVEL(p2m)))
+
+#define P2M_PAGETABLE_ENTRIES(p2m, lvl) \
+    (BIT(PAGETABLE_ORDER + P2M_ROOT_EXTRA_BITS(p2m, lvl), UL))
+
+#define P2M_TABLE_OFFSET(p2m, lvl) (P2M_PAGETABLE_ENTRIES(p2m, lvl) - 1UL)
+
+#define P2M_GFN_LEVEL_SHIFT(lvl) (P2M_LEVEL_ORDER(lvl) + PAGE_SHIFT)
 
 #define paddr_bits PADDR_BITS
 
@@ -58,6 +86,16 @@ struct p2m_domain {
      * when a page is needed to be fully cleared and cleaned.
      */
     bool clean_dcache;
+
+    /* Highest guest frame that's ever been mapped in the p2m */
+    gfn_t max_mapped_gfn;
+
+    /*
+     * Lowest mapped gfn in the p2m. When releasing mapped gfn's in a
+     * preemptible manner this is updated to track where to resume
+     * the search. Apart from during teardown this can only decrease.
+     */
+    gfn_t lowest_mapped_gfn;
 };
 
 /*
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index c559cde13a..c21873b2ac 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -9,6 +9,7 @@
 #include <xen/rwlock.h>
 #include <xen/sched.h>
 #include <xen/sections.h>
+#include <xen/xvmalloc.h>
 
 #include <asm/csr.h>
 #include <asm/flushtlb.h>
@@ -17,6 +18,13 @@
 #include <asm/riscv_encoding.h>
 #include <asm/vmid.h>
 
+/*
+ * At the moment, only 4K, 2M, and 1G mappings are supported for G-stage
+ * translation. Therefore, the maximum supported page-table level is 2,
+ * which corresponds to 1G mappings.
+ */
+#define P2M_MAX_SUPPORTED_LEVEL_MAPPING _AC(2, U)
+
 static struct gstage_mode_desc __ro_after_init max_gstage_mode = {
     .mode = HGATP_MODE_OFF,
     .paging_levels = 0,
@@ -28,6 +36,77 @@ unsigned char get_max_supported_mode(void)
     return max_gstage_mode.mode;
 }
 
+static inline unsigned int calc_offset(const struct p2m_domain *p2m,
+                                       const unsigned int lvl,
+                                       const paddr_t gpa)
+{
+    unsigned int off = (gpa >> P2M_GFN_LEVEL_SHIFT(lvl)) &
+                       P2M_TABLE_OFFSET(p2m, lvl);
+
+    /*
+     * For P2M_ROOT_LEVEL, `offset` ranges from 0 to 2047, since the root
+     * page table spans 4 consecutive 4KB pages.
+     * We want to return an index within one of these 4 pages.
+     * The specific page to use is determined by `p2m_get_root_pointer()`.
+     *
+     * Example: if `offset == 512`:
+     *  - A single 4KB page holds 512 entries.
+     *  - Therefore, entry 512 corresponds to index 0 of the second page.
+     *
+     * At all other levels, only one page is allocated, and `offset` is
+     * always in the range 0 to 511, since the VPN is 9 bits long.
+     */
+    return off & (PAGETABLE_ENTRIES - 1);
+}
+
+#define P2M_MAX_ROOT_LEVEL 5
+
+#define P2M_BUILD_LEVEL_OFFSETS(p2m, var, addr) \
+    unsigned int var[P2M_MAX_ROOT_LEVEL] = {-1}; \
+    BUG_ON(P2M_ROOT_LEVEL(p2m) >= P2M_MAX_ROOT_LEVEL); \
+    for ( unsigned int i = 0; i <= P2M_ROOT_LEVEL(p2m); i++ ) \
+        var[i] = calc_offset(p2m, i, addr);
+
+/*
+ * Map one of the four root pages of the P2M root page table.
+ *
+ * The P2M root page table is larger than normal (16KB instead of 4KB),
+ * so it is allocated as four consecutive 4KB pages. This function selects
+ * the appropriate 4KB page based on the given GFN and returns a mapping
+ * to it.
+ *
+ * The caller is responsible for unmapping the page after use.
+ *
+ * Returns NULL if the calculated offset into the root table is invalid.
+ */
+static pte_t *p2m_get_root_pointer(struct p2m_domain *p2m, gfn_t gfn)
+{
+    unsigned long idx;
+    unsigned long root_level = P2M_ROOT_LEVEL(p2m);
+
+    idx = gfn_x(gfn) >> P2M_LEVEL_ORDER(root_level);
+    if ( idx >= P2M_PAGETABLE_ENTRIES(p2m, root_level) )
+        return NULL;
+
+    /*
+     * The P2M root page table is extended by 2 bits, making its size 16KB
+     * (instead of 4KB for non-root page tables). Therefore, p2m->root is
+     * allocated as four consecutive 4KB pages (since alloc_domheap_pages()
+     * only allocates 4KB pages).
+     *
+     * Initially, `idx` is derived directly from `gfn`.
+     * To locate the correct entry within a single 4KB page,
+     * we rescale the offset so it falls within one of the 4 pages.
+     *
+     * Example: if `idx == 512`
+     * - A 4KB page holds 512 entries.
+     * - Thus, entry 512 corresponds to index 0 of the second page.
+     */
+    idx /= PAGETABLE_ENTRIES;
+
+    return __map_domain_page(p2m->root + idx);
+}
+
 static void __init gstage_mode_detect(void)
 {
     static const struct gstage_mode_desc modes[] __initconst = {
@@ -232,6 +311,9 @@ int p2m_init(struct domain *d)
     rwlock_init(&p2m->lock);
     INIT_PAGE_LIST_HEAD(&p2m->pages);
 
+    p2m->max_mapped_gfn = _gfn(0);
+    p2m->lowest_mapped_gfn = _gfn(ULONG_MAX);
+
     /*
      * Currently, the infrastructure required to enable CONFIG_HAS_PASSTHROUGH
      * is not ready for RISC-V support.
@@ -278,13 +360,255 @@ int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
     return rc;
 }
 
+static inline void p2m_write_pte(pte_t *p, pte_t pte, bool clean_cache)
+{
+    write_pte(p, pte);
+
+    /*
+     * TODO: if multiple adjacent PTEs are written without releasing
+     *       the lock, this then redundant cache flushing can be a
+     *       performance issue.
+     */
+    if ( clean_cache )
+        clean_dcache_va_range(p, sizeof(*p));
+}
+
+static inline void p2m_clean_pte(pte_t *p, bool clean_cache)
+{
+    pte_t pte = { .pte = 0 };
+
+    p2m_write_pte(p, pte, clean_cache);
+}
+
+static pte_t p2m_pte_from_mfn(mfn_t mfn, p2m_type_t t)
+{
+    panic("%s: hasn't been implemented yet\n", __func__);
+
+    return (pte_t) { .pte = 0 };
+}
+
+#define P2M_TABLE_MAP_NONE 0
+#define P2M_TABLE_MAP_NOMEM 1
+#define P2M_TABLE_SUPER_PAGE 2
+#define P2M_TABLE_NORMAL 3
+
+/*
+ * Take the currently mapped table, find the entry corresponding to the GFN,
+ * and map the next-level table if available. The previous table will be
+ * unmapped if the next level was mapped (e.g., when P2M_TABLE_NORMAL is
+ * returned).
+ *
+ * `alloc_tbl` parameter indicates whether intermediate tables should
+ * be allocated when not present.
+ *
+ * Return values:
+ *  P2M_TABLE_MAP_NONE: a table allocation isn't permitted.
+ *  P2M_TABLE_MAP_NOMEM: allocating a new page failed.
+ *  P2M_TABLE_SUPER_PAGE: next level or leaf mapped normally.
+ *  P2M_TABLE_NORMAL: The next entry points to a superpage.
+ */
+static int p2m_next_level(struct p2m_domain *p2m, bool alloc_tbl,
+                          unsigned int level, pte_t **table,
+                          unsigned int offset)
+{
+    panic("%s: hasn't been implemented yet\n", __func__);
+
+    return P2M_TABLE_MAP_NONE;
+}
+
+/* Free pte sub-tree behind an entry */
+static void p2m_free_subtree(struct p2m_domain *p2m,
+                             pte_t entry, unsigned int level)
+{
+    panic("%s: hasn't been implemented yet\n", __func__);
+}
+
+/* Insert an entry in the p2m */
+static int p2m_set_entry(struct p2m_domain *p2m,
+                         gfn_t gfn,
+                         unsigned long page_order,
+                         mfn_t mfn,
+                         p2m_type_t t)
+{
+    unsigned int level;
+    unsigned int target = page_order / PAGETABLE_ORDER;
+    pte_t *entry, *table, orig_pte;
+    int rc;
+    /*
+     * A mapping is removed only if the MFN is explicitly set to INVALID_MFN.
+     * Other MFNs that are considered invalid by mfn_valid() (e.g., MMIO)
+     * are still allowed.
+     */
+    bool removing_mapping = mfn_eq(mfn, INVALID_MFN);
+    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
+
+    ASSERT(p2m_is_write_locked(p2m));
+
+    /*
+     * Check if the level target is valid: we only support
+     * 4K - 2M - 1G mapping.
+     */
+    ASSERT(target <= P2M_MAX_SUPPORTED_LEVEL_MAPPING);
+
+    table = p2m_get_root_pointer(p2m, gfn);
+    if ( !table )
+        return -EINVAL;
+
+    for ( level = P2M_ROOT_LEVEL(p2m); level > target; level-- )
+    {
+        /*
+         * Don't try to allocate intermediate page table if the mapping
+         * is about to be removed.
+         */
+        rc = p2m_next_level(p2m, !removing_mapping,
+                            level, &table, offsets[level]);
+        if ( (rc == P2M_TABLE_MAP_NONE) || (rc == P2M_TABLE_MAP_NOMEM) )
+        {
+            rc = (rc == P2M_TABLE_MAP_NONE) ? -ENOENT : -ENOMEM;
+            /*
+             * We are here because p2m_next_level has failed to map
+             * the intermediate page table (e.g the table does not exist
+             * and none should be allocated). It is a valid case
+             * when removing a mapping as it may not exist in the
+             * page table. In this case, just ignore lookup failure.
+             */
+            rc = removing_mapping ? 0 : rc;
+            goto out;
+        }
+
+        if ( rc != P2M_TABLE_NORMAL )
+            break;
+    }
+
+    entry = table + offsets[level];
+
+    /*
+     * If we are here with level > target, we must be at a leaf node,
+     * and we need to break up the superpage.
+     */
+    if ( level > target )
+    {
+        panic("Shattering isn't implemented\n");
+    }
+
+    /*
+     * We should always be there with the correct level because all the
+     * intermediate tables have been installed if necessary.
+     */
+    ASSERT(level == target);
+
+    orig_pte = *entry;
+
+    if ( removing_mapping )
+        p2m_clean_pte(entry, p2m->clean_dcache);
+    else
+    {
+        pte_t pte = p2m_pte_from_mfn(mfn, t);
+
+        p2m_write_pte(entry, pte, p2m->clean_dcache);
+
+        p2m->max_mapped_gfn = gfn_max(p2m->max_mapped_gfn,
+                                      gfn_add(gfn, BIT(page_order, UL) - 1));
+        p2m->lowest_mapped_gfn = gfn_min(p2m->lowest_mapped_gfn, gfn);
+    }
+
+    p2m->need_flush = true;
+
+    /*
+     * Currently, the infrastructure required to enable CONFIG_HAS_PASSTHROUGH
+     * is not ready for RISC-V support.
+     *
+     * When CONFIG_HAS_PASSTHROUGH=y, iommu_iotlb_flush() should be done
+     * here.
+     */
+#ifdef CONFIG_HAS_PASSTHROUGH
+#   error "add code to flush IOMMU TLB"
+#endif
+
+    rc = 0;
+
+    /*
+     * In case of a VALID -> INVALID transition, the original PTE should
+     * always be freed.
+     *
+     * In case of a VALID -> VALID transition, the original PTE should be
+     * freed only if the MFNs are different. If the MFNs are the same
+     * (i.e., only permissions differ), there is no need to free the
+     * original PTE.
+     */
+    if ( pte_is_valid(orig_pte) &&
+         (!pte_is_valid(*entry) ||
+          !mfn_eq(pte_get_mfn(*entry), pte_get_mfn(orig_pte))) )
+        p2m_free_subtree(p2m, orig_pte, level);
+
+ out:
+    unmap_domain_page(table);
+
+    return rc;
+}
+
+/* Return mapping order for given gfn, mfn and nr */
+static unsigned long p2m_mapping_order(const struct p2m_domain *p2m, gfn_t gfn,
+                                       mfn_t mfn, unsigned long nr)
+{
+    unsigned long mask;
+    /* 1gb, 2mb, 4k mappings are supported */
+    unsigned int level = min(P2M_ROOT_LEVEL(p2m), P2M_MAX_SUPPORTED_LEVEL_MAPPING);
+    unsigned long order = 0;
+
+    mask = !mfn_eq(mfn, INVALID_MFN) ? mfn_x(mfn) : 0;
+    mask |= gfn_x(gfn);
+
+    for ( ; level != 0; level-- )
+    {
+        if ( !(mask & (BIT(P2M_LEVEL_ORDER(level), UL) - 1)) &&
+             (nr >= BIT(P2M_LEVEL_ORDER(level), UL)) )
+        {
+            order = P2M_LEVEL_ORDER(level);
+            break;
+        }
+    }
+
+    return order;
+}
+
 static int p2m_set_range(struct p2m_domain *p2m,
                          gfn_t sgfn,
                          unsigned long nr,
                          mfn_t smfn,
                          p2m_type_t t)
 {
-    return -EOPNOTSUPP;
+    int rc = 0;
+    unsigned long left = nr;
+
+    /*
+     * Any reference taken by the P2M mappings (e.g. foreign mapping) will
+     * be dropped in relinquish_p2m_mapping(). As the P2M will still
+     * be accessible after, we need to prevent mapping to be added when the
+     * domain is dying.
+     */
+    if ( unlikely(p2m->domain->is_dying) )
+        return -EACCES;
+
+    while ( left )
+    {
+        unsigned long order = p2m_mapping_order(p2m, sgfn, smfn, left);
+
+        rc = p2m_set_entry(p2m, sgfn, order, smfn, t);
+        if ( rc )
+            break;
+
+        sgfn = gfn_add(sgfn, BIT(order, UL));
+        if ( !mfn_eq(smfn, INVALID_MFN) )
+            smfn = mfn_add(smfn, BIT(order, UL));
+
+        left -= BIT(order, UL);
+    }
+
+    if ( left > INT_MAX )
+        rc = -EOVERFLOW;
+
+    return !left ? rc : left;
 }
 
 int map_regions_p2mt(struct domain *d,
-- 
2.51.1



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

* [PATCH v6 12/19] xen/riscv: Implement p2m_free_subtree() and related helpers
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (10 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 11/19] xen/riscv: implement p2m_set_range() Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-12-08 16:56   ` Jan Beulich
  2025-11-24 12:33 ` [PATCH v6 13/19] xen/riscv: Implement p2m_pte_from_mfn() and support PBMT configuration Oleksii Kurochko
                   ` (6 subsequent siblings)
  18 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=all, Size: 13342 bytes --]

This patch introduces a working implementation of p2m_free_subtree() for RISC-V
based on ARM's implementation of p2m_free_entry(), enabling proper cleanup
of page table entries in the P2M (physical-to-machine) mapping.

Only few things are changed:
- Introduce and use p2m_get_type() to get a type of p2m entry as
  RISC-V's PTE doesn't have enough space to store all necessary types so
  a type is stored outside PTE. But, at the moment, handle only types
  which fit into PTE's bits.

Key additions include:
- p2m_free_subtree(): Recursively frees page table entries at all levels. It
  handles both regular and superpage mappings and ensures that TLB entries
  are flushed before freeing intermediate tables.
- p2m_put_page() and helpers:
  - p2m_put_4k_page(): Clears GFN from xenheap pages if applicable.
  - p2m_put_2m_superpage(): Releases foreign page references in a 2MB
    superpage.
  - p2m_get_type(): Extracts the stored p2m_type from the PTE bits.
- p2m_free_page(): Returns a page to a domain's freelist.
- Introduce p2m_is_foreign() and connected to it things.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in V6:
 - Use PTE_RSW instead of 0x300 in the definition of P2M_TYPE_PTE_BITS_MASK.
 - Optimize a if-condtion of p2m_free_subtree() at the start
   of the function to avoid unnessary checks.
 - Change P2M_PAGETABLE_ENTRIES(1) to PAGETABLE_ENTRIES in p2m_put_2m_superpage() as it is expected be the same and in the 
 cae of PAGETABLE_ENTRIES it won't need to update prototypes because
 of one macro.
 - Drop the comment above p2m_put_4k_page() and p2m_put_2m_superpage().
 - Move introduction of pte_is_superpage() to "xen/riscv: Implement superpage splitting for p2m mappings" as it is starting to be used there.
---
Changes in V5:
 - Rewrite the comment inside p2m_put_foreign_page().
 - s/assert_failed/ASSERT_UNREACHABLE.
 - Use p2mt variable when p2m_free_subtree() is called intead of
   p2m_get_type(entry).
 - Update the commit message: drop info about defintion of XEN_PT_ENTRIES
 - Drop also defintion of XEN_PT_ENTRIES as the macro isn't used anymore.
 - Drop ACCESS_ONCE() for paging_free_page() as it is redundant in the
   case when a code is wrapped by a spinlock.
---
Changes in V4:
 - Stray blanks.
 - Implement arch_flush_tlb_mask() to make the comment in p2m_put_foreign()
   clear and explicit.
 - Update the comment above p2m_is_ram() in p2m_put_4k_page() with an explanation
   why p2m_is_ram() is used.
 - Add a type check inside p2m_put_2m_superpage().
 - Swap two conditions around in p2m_free_subtree():
     if ( (level == 0) || pte_is_superpage(entry, level) )
 - Add ASSERT() inside p2m_free_subtree() to check that level is <= 2; otherwise,
   it could consume a lot of time and big memory usage because of recursion.
 - Drop page_list_del() before p2m_free_page() as page_list_del() is called
   inside p2m_free_page().
 - Update p2m_freelist's total_pages when a page is added to p2m_freelist in
   paging_free_page().
 - Introduce P2M_SUPPORTED_LEVEL_MAPPING and use it in ASSERTs() which check
   supported level.
 - Use P2M_PAGETABLE_ENTRIES as XEN_PT_ENTRIES
   doesn't takeinto  into acount that G stage root page table is
   extended by 2 bits.
 - Update prototype of p2m_put_page() to not have unnecessary changes later.
---
Changes in V3:
 - Use p2m_tlb_flush_sync(p2m) instead of p2m_force_tlb_flush_sync() in
   p2m_free_subtree().
 - Drop p2m_is_valid() implementation as pte_is_valid() is going to be used
   instead.
 - Drop p2m_is_superpage() and introduce pte_is_superpage() instead.
 - s/p2m_free_entry/p2m_free_subtree.
 - s/p2m_type_radix_get/p2m_get_type.
 - Update implementation of p2m_get_type() to get type both from PTE bits,
   other cases will be covered in a separate patch. This requires an
   introduction of new P2M_TYPE_PTE_BITS_MASK macros.
 - Drop p2m argument of p2m_get_type() as it isn't needed anymore.
 - Put cheapest checks first in p2m_is_superpage().
 - Use switch() in p2m_put_page().
 - Update the comment in p2m_put_foreign_page().
 - Code style fixes.
 - Move p2m_foreign stuff to this commit.
 - Drop p2m argument of p2m_put_page() as itsn't used anymore.
---
Changes in V2:
 - New patch. It was a part of 2ma big patch "xen/riscv: implement p2m mapping
   functionality" which was splitted to smaller.
 - s/p2m_is_superpage/p2me_is_superpage.
---
 xen/arch/riscv/include/asm/flushtlb.h |   6 +-
 xen/arch/riscv/include/asm/p2m.h      |  15 +++
 xen/arch/riscv/include/asm/paging.h   |   2 +
 xen/arch/riscv/p2m.c                  | 148 +++++++++++++++++++++++++-
 xen/arch/riscv/paging.c               |   8 ++
 xen/arch/riscv/stubs.c                |   5 -
 6 files changed, 176 insertions(+), 8 deletions(-)

diff --git a/xen/arch/riscv/include/asm/flushtlb.h b/xen/arch/riscv/include/asm/flushtlb.h
index e70badae0c..ab32311568 100644
--- a/xen/arch/riscv/include/asm/flushtlb.h
+++ b/xen/arch/riscv/include/asm/flushtlb.h
@@ -41,8 +41,10 @@ static inline void page_set_tlbflush_timestamp(struct page_info *page)
     BUG_ON("unimplemented");
 }
 
-/* Flush specified CPUs' TLBs */
-void arch_flush_tlb_mask(const cpumask_t *mask);
+static inline void arch_flush_tlb_mask(const cpumask_t *mask)
+{
+    sbi_remote_hfence_gvma(mask, 0, 0);
+}
 
 #endif /* ASM__RISCV__FLUSHTLB_H */
 
diff --git a/xen/arch/riscv/include/asm/p2m.h b/xen/arch/riscv/include/asm/p2m.h
index fa55d8a3bc..b48693a2b4 100644
--- a/xen/arch/riscv/include/asm/p2m.h
+++ b/xen/arch/riscv/include/asm/p2m.h
@@ -111,6 +111,8 @@ typedef enum {
     p2m_mmio_direct_io, /* Read/write mapping of genuine Device MMIO area,
                            PTE_PBMT_IO will be used for such mappings */
     p2m_ext_storage,    /* Following types'll be stored outsude PTE bits: */
+    p2m_map_foreign_rw, /* Read/write RAM pages from foreign domain */
+    p2m_map_foreign_ro, /* Read-only RAM pages from foreign domain */
 
     /* Sentinel — not a real type, just a marker for comparison */
     p2m_first_external = p2m_ext_storage,
@@ -121,15 +123,28 @@ static inline p2m_type_t arch_dt_passthrough_p2m_type(void)
     return p2m_mmio_direct_io;
 }
 
+/*
+ * Bits 8 and 9 are reserved for use by supervisor software;
+ * the implementation shall ignore this field.
+ * We are going to use to save in these bits frequently used types to avoid
+ * get/set of a type from radix tree.
+ */
+#define P2M_TYPE_PTE_BITS_MASK PTE_RSW
+
 /* We use bitmaps and mask to handle groups of types */
 #define p2m_to_mask(t) BIT(t, UL)
 
 /* RAM types, which map to real machine frames */
 #define P2M_RAM_TYPES (p2m_to_mask(p2m_ram_rw))
 
+/* Foreign mappings types */
+#define P2M_FOREIGN_TYPES (p2m_to_mask(p2m_map_foreign_rw) | \
+                           p2m_to_mask(p2m_map_foreign_ro))
+
 /* Useful predicates */
 #define p2m_is_ram(t) (p2m_to_mask(t) & P2M_RAM_TYPES)
 #define p2m_is_any_ram(t) (p2m_to_mask(t) & P2M_RAM_TYPES)
+#define p2m_is_foreign(t) (p2m_to_mask(t) & P2M_FOREIGN_TYPES)
 
 #include <xen/p2m-common.h>
 
diff --git a/xen/arch/riscv/include/asm/paging.h b/xen/arch/riscv/include/asm/paging.h
index 01be45528f..fe462be223 100644
--- a/xen/arch/riscv/include/asm/paging.h
+++ b/xen/arch/riscv/include/asm/paging.h
@@ -13,4 +13,6 @@ int paging_freelist_adjust(struct domain *d, unsigned long pages,
 int paging_ret_to_domheap(struct domain *d, unsigned int nr_pages);
 int paging_refill_from_domheap(struct domain *d, unsigned int nr_pages);
 
+void paging_free_page(struct domain *d, struct page_info *pg);
+
 #endif /* ASM_RISCV_PAGING_H */
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index c21873b2ac..f8af73c9d8 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -360,6 +360,16 @@ int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
     return rc;
 }
 
+static p2m_type_t p2m_get_type(const pte_t pte)
+{
+    p2m_type_t type = MASK_EXTR(pte.pte, P2M_TYPE_PTE_BITS_MASK);
+
+    if ( type == p2m_ext_storage )
+        panic("unimplemented\n");
+
+    return type;
+}
+
 static inline void p2m_write_pte(pte_t *p, pte_t pte, bool clean_cache)
 {
     write_pte(p, pte);
@@ -416,11 +426,147 @@ static int p2m_next_level(struct p2m_domain *p2m, bool alloc_tbl,
     return P2M_TABLE_MAP_NONE;
 }
 
+static void p2m_put_foreign_page(struct page_info *pg)
+{
+    /*
+     * It’s safe to call put_page() here because arch_flush_tlb_mask()
+     * will be invoked if the page is reallocated, which will trigger a
+     * flush of the guest TLBs.
+     */
+    put_page(pg);
+}
+
+static void p2m_put_4k_page(mfn_t mfn, p2m_type_t type)
+{
+    /* TODO: Handle other p2m types */
+
+    if ( p2m_is_foreign(type) )
+    {
+        ASSERT(mfn_valid(mfn));
+        p2m_put_foreign_page(mfn_to_page(mfn));
+    }
+}
+
+static void p2m_put_2m_superpage(mfn_t mfn, p2m_type_t type)
+{
+    struct page_info *pg;
+    unsigned int i;
+
+    /* TODO: Handle other p2m types */
+    if ( !p2m_is_foreign(type) )
+        return;
+
+    ASSERT(mfn_valid(mfn));
+
+    pg = mfn_to_page(mfn);
+
+    /*
+     * PAGETABLE_ENTRIES is used instead of P2M_PAGETABLE_ENTRIES(1) because
+     * they are expected to be identical (this is verified in calc_offset()).
+     * This avoids having to pass p2m_domain here and throughout the call stack
+     * above solely for the sake of one macro.
+     */
+    for ( i = 0; i < PAGETABLE_ENTRIES; i++, pg++ )
+        p2m_put_foreign_page(pg);
+}
+
+static void p2m_put_page(const pte_t pte, unsigned int level, p2m_type_t p2mt)
+{
+    mfn_t mfn = pte_get_mfn(pte);
+
+    ASSERT(pte_is_valid(pte));
+
+    /*
+     * TODO: Currently we don't handle level 2 super-page, Xen is not
+     * preemptible and therefore some work is needed to handle such
+     * superpages, for which at some point Xen might end up freeing memory
+     * and therefore for such a big mapping it could end up in a very long
+     * operation.
+     */
+    switch ( level )
+    {
+    case 1:
+        return p2m_put_2m_superpage(mfn, p2mt);
+
+    case 0:
+        return p2m_put_4k_page(mfn, p2mt);
+
+    default:
+        ASSERT_UNREACHABLE();
+        break;
+    }
+}
+
+static void p2m_free_page(struct p2m_domain *p2m, struct page_info *pg)
+{
+    page_list_del(pg, &p2m->pages);
+
+    paging_free_page(p2m->domain, pg);
+}
+
 /* Free pte sub-tree behind an entry */
 static void p2m_free_subtree(struct p2m_domain *p2m,
                              pte_t entry, unsigned int level)
 {
-    panic("%s: hasn't been implemented yet\n", __func__);
+    unsigned int i;
+    pte_t *table;
+    mfn_t mfn;
+    struct page_info *pg;
+
+    /*
+     * Check if the level is valid: only 4K - 2M - 1G mappings are supported.
+     * To support levels > 2, the implementation of p2m_free_subtree() would
+     * need to be updated, as the current recursive approach could consume
+     * excessive time and memory.
+     */
+    ASSERT(level <= P2M_MAX_SUPPORTED_LEVEL_MAPPING);
+
+    /* Nothing to do if the entry is invalid. */
+    if ( !pte_is_valid(entry) )
+        return;
+
+    if ( pte_is_mapping(entry) )
+    {
+        p2m_type_t p2mt = p2m_get_type(entry);
+
+#ifdef CONFIG_IOREQ_SERVER
+        /*
+         * If this gets called then either the entry was replaced by an entry
+         * with a different base (valid case) or the shattering of a superpage
+         * has failed (error case).
+         * So, at worst, the spurious mapcache invalidation might be sent.
+         */
+        if ( p2m_is_ram(p2mt) &&
+             domain_has_ioreq_server(p2m->domain) )
+            ioreq_request_mapcache_invalidate(p2m->domain);
+#endif
+
+        p2m_put_page(entry, level, p2mt);
+
+        return;
+    }
+
+    table = map_domain_page(pte_get_mfn(entry));
+
+    for ( i = 0; i < P2M_PAGETABLE_ENTRIES(p2m, level); i++ )
+        p2m_free_subtree(p2m, table[i], level - 1);
+
+    unmap_domain_page(table);
+
+    /*
+     * Make sure all the references in the TLB have been removed before
+     * freing the intermediate page table.
+     * XXX: Should we defer the free of the page table to avoid the
+     * flush?
+     */
+    p2m_tlb_flush_sync(p2m);
+
+    mfn = pte_get_mfn(entry);
+    ASSERT(mfn_valid(mfn));
+
+    pg = mfn_to_page(mfn);
+
+    p2m_free_page(p2m, pg);
 }
 
 /* Insert an entry in the p2m */
diff --git a/xen/arch/riscv/paging.c b/xen/arch/riscv/paging.c
index d401ddc34e..09631c9894 100644
--- a/xen/arch/riscv/paging.c
+++ b/xen/arch/riscv/paging.c
@@ -109,6 +109,14 @@ int paging_ret_to_domheap(struct domain *d, unsigned int nr_pages)
     return 0;
 }
 
+void paging_free_page(struct domain *d, struct page_info *pg)
+{
+    spin_lock(&d->arch.paging.lock);
+    page_list_add_tail(pg, &d->arch.paging.freelist);
+    d->arch.paging.total_pages++;
+    spin_unlock(&d->arch.paging.lock);
+}
+
 /* Domain paging struct initialization. */
 int paging_domain_init(struct domain *d)
 {
diff --git a/xen/arch/riscv/stubs.c b/xen/arch/riscv/stubs.c
index 340ed3cd6c..115dc1a9ae 100644
--- a/xen/arch/riscv/stubs.c
+++ b/xen/arch/riscv/stubs.c
@@ -65,11 +65,6 @@ int arch_monitor_domctl_event(struct domain *d,
 
 /* smp.c */
 
-void arch_flush_tlb_mask(const cpumask_t *mask)
-{
-    BUG_ON("unimplemented");
-}
-
 void smp_send_event_check_mask(const cpumask_t *mask)
 {
     BUG_ON("unimplemented");
-- 
2.51.1



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

* [PATCH v6 13/19] xen/riscv: Implement p2m_pte_from_mfn() and support PBMT configuration
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (11 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 12/19] xen/riscv: Implement p2m_free_subtree() and related helpers Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-12-09 11:22   ` Jan Beulich
  2025-11-24 12:33 ` [PATCH v6 14/19] xen/riscv: implement p2m_next_level() Oleksii Kurochko
                   ` (5 subsequent siblings)
  18 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=all, Size: 9502 bytes --]

This patch adds the initial logic for constructing PTEs from MFNs in the RISC-V
p2m subsystem. It includes:
- Implementation of p2m_pte_from_mfn(): Generates a valid PTE using the
  given MFN, p2m_type_t, including permission encoding and PBMT attribute
  setup.
- New helper p2m_set_permission(): Encodes access rights (r, w, x) into the
  PTE based on both p2m type and access permissions.
- p2m_set_type(): Stores the p2m type in PTE's bits. The storage of types,
  which don't fit PTE bits, will be implemented separately later.
- Add detection of Svade extension to properly handle a possible page-fault
  if A and D bits aren't set.

PBMT type encoding support:
- Introduces an enum pbmt_type_t to represent the PBMT field values.
- Maps types like p2m_mmio_direct_dev to p2m_mmio_direct_io, others default
  to pbmt_pma.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in V6:
 - Drop 's' in word "nesssary" in the comment.
---
Changes in V5:
 - Moved setting of p2m_mmio_direct_io inside (!is_table) case in p2m_pte_from_mfn().
 - Extend comment about the place of setting A/D bits with explanation
   why it is done in this way for now.
---
Changes in V4:
 - p2m_set_permission() updates:
   - Update permissions for p2m_ram_rw case, make it also executable.
   - Add pernissions setting for p2m_map_foreign_* types.
   - Drop setting peromissions for p2m_ext_storage.
   - Only turn off PTE_VALID bit for p2m_invalid, don't touch other bits.
 - p2m_pte_from_mfn() updates:
   - Update ASSERT(), add a check that mfn isn't INVALID_MFN (1)
     explicitly to avoid the case when PADDR_MASK isn't narrow enough to
     catch the case (1).
   - Drop unnessary check around call of p2m_set_type() as this check
     is already included inside p2m_set_type().
 - Introduce new p2m type p2m_first_external to detect that passed type
   is stored in external storage.
 - Add handling of PTE's A and D bits in pm2_set_permission. Also, set
   PTE_USER bit. For this cpufeatures.{h and c} were updated to be able
   to detect availability of Svade extension.
 - Drop grant table related code as it isn't going to be used at the moment.
---
Changes in V3:
 - s/p2m_entry_from_mfn/p2m_pte_from_mfn.
 - s/pbmt_type_t/pbmt_type.
 - s/pbmt_max/pbmt_count.
 - s/p2m_type_radix_set/p2m_set_type.
 - Rework p2m_set_type() to handle only types which are fited into PTEs bits.
   Other types will be covered separately.
   Update arguments of p2m_set_type(): there is no any reason for p2m anymore.
 - p2m_set_permissions() updates:
   - Update the code in p2m_set_permission() for cases p2m_raw_rw and
     p2m_mmio_direct_io to set proper type permissions.
   - Add cases for p2m_grant_map_rw and p2m_grant_map_ro.
   - Use ASSERT_UNEACHABLE() instead of BUG() in switch cases of
     p2m_set_permissions.
   - Add blank lines non-fall-through case blocks in switch cases.
 - Set MFN before permissions are set in p2m_pte_from_mfn().
 - Update prototype of p2m_entry_from_mfn().
---
Changes in V2:
 - New patch. It was a part of a big patch "xen/riscv: implement p2m mapping
   functionality" which was splitted to smaller.
---
 xen/arch/riscv/cpufeature.c             |   1 +
 xen/arch/riscv/include/asm/cpufeature.h |   1 +
 xen/arch/riscv/include/asm/page.h       |   8 ++
 xen/arch/riscv/p2m.c                    | 112 +++++++++++++++++++++++-
 4 files changed, 118 insertions(+), 4 deletions(-)

diff --git a/xen/arch/riscv/cpufeature.c b/xen/arch/riscv/cpufeature.c
index b846a106a3..02b68aeaa4 100644
--- a/xen/arch/riscv/cpufeature.c
+++ b/xen/arch/riscv/cpufeature.c
@@ -138,6 +138,7 @@ const struct riscv_isa_ext_data __initconst riscv_isa_ext[] = {
     RISCV_ISA_EXT_DATA(zbs),
     RISCV_ISA_EXT_DATA(smaia),
     RISCV_ISA_EXT_DATA(ssaia),
+    RISCV_ISA_EXT_DATA(svade),
     RISCV_ISA_EXT_DATA(svpbmt),
 };
 
diff --git a/xen/arch/riscv/include/asm/cpufeature.h b/xen/arch/riscv/include/asm/cpufeature.h
index 768b84b769..5f756c76db 100644
--- a/xen/arch/riscv/include/asm/cpufeature.h
+++ b/xen/arch/riscv/include/asm/cpufeature.h
@@ -37,6 +37,7 @@ enum riscv_isa_ext_id {
     RISCV_ISA_EXT_zbs,
     RISCV_ISA_EXT_smaia,
     RISCV_ISA_EXT_ssaia,
+    RISCV_ISA_EXT_svade,
     RISCV_ISA_EXT_svpbmt,
     RISCV_ISA_EXT_MAX
 };
diff --git a/xen/arch/riscv/include/asm/page.h b/xen/arch/riscv/include/asm/page.h
index 66cb192316..c6b7acf1b7 100644
--- a/xen/arch/riscv/include/asm/page.h
+++ b/xen/arch/riscv/include/asm/page.h
@@ -73,6 +73,14 @@
 #define PTE_SMALL       BIT(10, UL)
 #define PTE_POPULATE    BIT(11, UL)
 
+enum pbmt_type {
+    pbmt_pma,
+    pbmt_nc,
+    pbmt_io,
+    pbmt_rsvd,
+    pbmt_count,
+};
+
 #define PTE_ACCESS_MASK (PTE_READABLE | PTE_WRITABLE | PTE_EXECUTABLE)
 
 #define PTE_PBMT_MASK   (PTE_PBMT_NOCACHE | PTE_PBMT_IO)
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index f8af73c9d8..8761204720 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -11,6 +11,7 @@
 #include <xen/sections.h>
 #include <xen/xvmalloc.h>
 
+#include <asm/cpufeature.h>
 #include <asm/csr.h>
 #include <asm/flushtlb.h>
 #include <asm/p2m.h>
@@ -360,6 +361,18 @@ int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
     return rc;
 }
 
+static int p2m_set_type(pte_t *pte, p2m_type_t t)
+{
+    int rc = 0;
+
+    if ( t > p2m_first_external )
+        panic("unimplemeted\n");
+    else
+        pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
+
+    return rc;
+}
+
 static p2m_type_t p2m_get_type(const pte_t pte)
 {
     p2m_type_t type = MASK_EXTR(pte.pte, P2M_TYPE_PTE_BITS_MASK);
@@ -390,11 +403,102 @@ static inline void p2m_clean_pte(pte_t *p, bool clean_cache)
     p2m_write_pte(p, pte, clean_cache);
 }
 
-static pte_t p2m_pte_from_mfn(mfn_t mfn, p2m_type_t t)
+static void p2m_set_permission(pte_t *e, p2m_type_t t)
 {
-    panic("%s: hasn't been implemented yet\n", __func__);
+    e->pte &= ~PTE_ACCESS_MASK;
+
+    e->pte |= PTE_USER;
+
+    /*
+     * Two schemes to manage the A and D bits are defined:
+     *   • The Svade extension: when a virtual page is accessed and the A bit
+     *     is clear, or is written and the D bit is clear, a page-fault
+     *     exception is raised.
+     *   • When the Svade extension is not implemented, the following scheme
+     *     applies.
+     *     When a virtual page is accessed and the A bit is clear, the PTE is
+     *     updated to set the A bit. When the virtual page is written and the
+     *     D bit is clear, the PTE is updated to set the D bit. When G-stage
+     *     address translation is in use and is not Bare, the G-stage virtual
+     *     pages may be accessed or written by implicit accesses to VS-level
+     *     memory management data structures, such as page tables.
+     * Thereby to avoid a page-fault in case of Svade is available, it is
+     * necessary to set A and D bits.
+     *
+     * TODO: For now, it’s fine to simply set the A/D bits, since OpenSBI
+     *       delegates page faults to a lower privilege mode and so OpenSBI
+     *       isn't expect to handle page-faults occured in lower modes.
+     *       By setting the A/D bits here, page faults that would otherwise
+     *       be generated due to unset A/D bits will not occur in Xen.
+     *
+     *       Currently, Xen on RISC-V does not make use of the information
+     *       that could be obtained from handling such page faults, which
+     *       could otherwise be useful for several use cases such as demand
+     *       paging, cache-flushing optimizations, memory access tracking,etc.
+     *
+     *       To support the more general case and the optimizations mentioned
+     *       above, it would be better to stop setting the A/D bits here and
+     *       instead handle page faults that occur due to unset A/D bits.
+     */
+    if ( riscv_isa_extension_available(NULL, RISCV_ISA_EXT_svade) )
+        e->pte |= PTE_ACCESSED | PTE_DIRTY;
+
+    switch ( t )
+    {
+    case p2m_map_foreign_rw:
+    case p2m_mmio_direct_io:
+        e->pte |= PTE_READABLE | PTE_WRITABLE;
+        break;
+
+    case p2m_ram_rw:
+        e->pte |= PTE_ACCESS_MASK;
+        break;
+
+    case p2m_invalid:
+        e->pte &= ~PTE_VALID;
+        break;
+
+    case p2m_map_foreign_ro:
+        e->pte |= PTE_READABLE;
+        break;
+
+    default:
+        ASSERT_UNREACHABLE();
+        break;
+    }
+}
+
+static pte_t p2m_pte_from_mfn(mfn_t mfn, p2m_type_t t, bool is_table)
+{
+    pte_t e = (pte_t) { PTE_VALID };
+
+    pte_set_mfn(&e, mfn);
+
+    ASSERT(!(mfn_to_maddr(mfn) & ~PADDR_MASK) || mfn_eq(mfn, INVALID_MFN));
+
+    if ( !is_table )
+    {
+        switch ( t )
+        {
+        case p2m_mmio_direct_io:
+            e.pte |= PTE_PBMT_IO;
+            break;
+
+        default:
+            break;
+        }
+
+        p2m_set_permission(&e, t);
+        p2m_set_type(&e, t);
+    }
+    else
+        /*
+         * According to the spec and table "Encoding of PTE R/W/X fields":
+         *   X=W=R=0 -> Pointer to next level of page table.
+         */
+        e.pte &= ~PTE_ACCESS_MASK;
 
-    return (pte_t) { .pte = 0 };
+    return e;
 }
 
 #define P2M_TABLE_MAP_NONE 0
@@ -649,7 +753,7 @@ static int p2m_set_entry(struct p2m_domain *p2m,
         p2m_clean_pte(entry, p2m->clean_dcache);
     else
     {
-        pte_t pte = p2m_pte_from_mfn(mfn, t);
+        pte_t pte = p2m_pte_from_mfn(mfn, t, false);
 
         p2m_write_pte(entry, pte, p2m->clean_dcache);
 
-- 
2.51.1



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

* [PATCH v6 14/19] xen/riscv: implement p2m_next_level()
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (12 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 13/19] xen/riscv: Implement p2m_pte_from_mfn() and support PBMT configuration Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 15/19] xen/riscv: Implement superpage splitting for p2m mappings Oleksii Kurochko
                   ` (4 subsequent siblings)
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Implement the p2m_next_level() function, which enables traversal and dynamic
allocation of intermediate levels (if necessary) in the RISC-V
p2m (physical-to-machine) page table hierarchy.

To support this, the following helpers are introduced:
- page_to_p2m_table(): Constructs non-leaf PTEs pointing to next-level page
  tables with correct attributes.
- p2m_alloc_page(): Allocates page table pages, supporting both hardware and
  guest domains.
- p2m_create_table(): Allocates and initializes a new page table page and
  installs it into the hierarchy.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
Changes in V6:
 - s/intermidiate/intermediate.
 - Add Acked-by: Jan Beulich <jbeulich@suse.com>.
---
Changes in V5:
 - Stray more blanks after * in declaration of functions.
 - Correct the comment above p2m_create_table() as metadata pages isn't
   allocated anymore in this function.
 - Move call of     clear_and_clean_page(page, p2m->clean_dcache); from
   p2m_create_table() to p2m_alloc_page().
 - Drop ACCESS_ONCE() in paging_alloc_page().
---
Changes in V4:
 - make `page` argument of page_to_p2m_table pointer-to-const.
 - Move p2m_next_level()'s local variable `ret` to the more narrow space where
   it is really used.
 - Drop stale ASSERT() in p2m_next_level().
 - Stray blank after * in declaration of paging_alloc_page().
 - Decrease p2m_freelist.total_pages when a page is taken from the p2m freelist.
---
Changes in V3:
 - s/p2me_is_mapping/p2m_is_mapping to be in syc with other p2m_is_*() functions.
 - clear_and_clean_page() in p2m_create_table() instead of clear_page() to be
   sure that page is cleared and d-cache is flushed for it.
 - Move ASSERT(level != 0) in p2m_next_level() ahead of trying to allocate a
   page table.
 - Update p2m_create_table() to allocate metadata page to store p2m type in it
   for each entry of page table.
 - Introduce paging_alloc_page() and use it inside p2m_alloc_page().
 - Add allocated page to p2m->pages list in p2m_alloc_page() to simplify
   a caller code a little bit.
 - Drop p2m_is_mapping() and use pte_is_mapping() instead as P2M PTE's valid
   bit doesn't have another purpose anymore.
 - Update an implementation and prototype of page_to_p2m_table(), it is enough
   to pass only a page as an argument.
---
Changes in V2:
 - New patch. It was a part of a big patch "xen/riscv: implement p2m mapping
   functionality" which was splitted to smaller.
 - s/p2m_is_mapping/p2m_is_mapping.
---

 xen/arch/riscv/include/asm/paging.h |  2 +
 xen/arch/riscv/p2m.c                | 77 ++++++++++++++++++++++++++++-
 xen/arch/riscv/paging.c             | 12 +++++
 3 files changed, 89 insertions(+), 2 deletions(-)

diff --git a/xen/arch/riscv/include/asm/paging.h b/xen/arch/riscv/include/asm/paging.h
index fe462be223..c1d225d02b 100644
--- a/xen/arch/riscv/include/asm/paging.h
+++ b/xen/arch/riscv/include/asm/paging.h
@@ -15,4 +15,6 @@ int paging_refill_from_domheap(struct domain *d, unsigned int nr_pages);
 
 void paging_free_page(struct domain *d, struct page_info *pg);
 
+struct page_info *paging_alloc_page(struct domain *d);
+
 #endif /* ASM_RISCV_PAGING_H */
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index 8761204720..16663650ee 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -361,6 +361,19 @@ int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
     return rc;
 }
 
+static struct page_info *p2m_alloc_page(struct p2m_domain *p2m)
+{
+    struct page_info *pg = paging_alloc_page(p2m->domain);
+
+    if ( pg )
+    {
+        page_list_add(pg, &p2m->pages);
+        clear_and_clean_page(pg, p2m->clean_dcache);
+    }
+
+    return pg;
+}
+
 static int p2m_set_type(pte_t *pte, p2m_type_t t)
 {
     int rc = 0;
@@ -501,6 +514,33 @@ static pte_t p2m_pte_from_mfn(mfn_t mfn, p2m_type_t t, bool is_table)
     return e;
 }
 
+/* Generate table entry with correct attributes. */
+static pte_t page_to_p2m_table(const struct page_info *page)
+{
+    /*
+     * p2m_invalid will be ignored inside p2m_pte_from_mfn() as is_table is
+     * set to true and p2m_type_t shouldn't be applied for PTEs which
+     * describe an intermediate table.
+     */
+    return p2m_pte_from_mfn(page_to_mfn(page), p2m_invalid, true);
+}
+
+/* Allocate a new page table page and hook it in via the given entry. */
+static int p2m_create_table(struct p2m_domain *p2m, pte_t *entry)
+{
+    struct page_info *page;
+
+    ASSERT(!pte_is_valid(*entry));
+
+    page = p2m_alloc_page(p2m);
+    if ( page == NULL )
+        return -ENOMEM;
+
+    p2m_write_pte(entry, page_to_p2m_table(page), p2m->clean_dcache);
+
+    return 0;
+}
+
 #define P2M_TABLE_MAP_NONE 0
 #define P2M_TABLE_MAP_NOMEM 1
 #define P2M_TABLE_SUPER_PAGE 2
@@ -525,9 +565,42 @@ static int p2m_next_level(struct p2m_domain *p2m, bool alloc_tbl,
                           unsigned int level, pte_t **table,
                           unsigned int offset)
 {
-    panic("%s: hasn't been implemented yet\n", __func__);
+    pte_t *entry;
+    mfn_t mfn;
+
+    /* The function p2m_next_level() is never called at the last level */
+    ASSERT(level != 0);
+
+    entry = *table + offset;
+
+    if ( !pte_is_valid(*entry) )
+    {
+        int ret;
+
+        if ( !alloc_tbl )
+            return P2M_TABLE_MAP_NONE;
+
+        ret = p2m_create_table(p2m, entry);
+        if ( ret )
+            return P2M_TABLE_MAP_NOMEM;
+    }
+
+    if ( pte_is_mapping(*entry) )
+        return P2M_TABLE_SUPER_PAGE;
+
+    mfn = mfn_from_pte(*entry);
+
+    unmap_domain_page(*table);
+
+    /*
+     * TODO: There's an inefficiency here:
+     *       In p2m_create_table(), the page is mapped to clear it.
+     *       Then that mapping is torn down in p2m_create_table(),
+     *       only to be re-established here.
+     */
+    *table = map_domain_page(mfn);
 
-    return P2M_TABLE_MAP_NONE;
+    return P2M_TABLE_NORMAL;
 }
 
 static void p2m_put_foreign_page(struct page_info *pg)
diff --git a/xen/arch/riscv/paging.c b/xen/arch/riscv/paging.c
index 09631c9894..76a203edbb 100644
--- a/xen/arch/riscv/paging.c
+++ b/xen/arch/riscv/paging.c
@@ -117,6 +117,18 @@ void paging_free_page(struct domain *d, struct page_info *pg)
     spin_unlock(&d->arch.paging.lock);
 }
 
+struct page_info *paging_alloc_page(struct domain *d)
+{
+    struct page_info *pg;
+
+    spin_lock(&d->arch.paging.lock);
+    pg = page_list_remove_head(&d->arch.paging.freelist);
+    d->arch.paging.total_pages--;
+    spin_unlock(&d->arch.paging.lock);
+
+    return pg;
+}
+
 /* Domain paging struct initialization. */
 int paging_domain_init(struct domain *d)
 {
-- 
2.51.1



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

* [PATCH v6 15/19] xen/riscv: Implement superpage splitting for p2m mappings
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (13 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 14/19] xen/riscv: implement p2m_next_level() Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 16/19] xen/riscv: implement put_page() Oleksii Kurochko
                   ` (3 subsequent siblings)
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Add support for down large memory mappings ("superpages") in the RISC-V
p2m mapping so that smaller, more precise mappings ("finer-grained entries")
can be inserted into lower levels of the page table hierarchy.

To implement that the following is done:
- Introduce p2m_split_superpage(): Recursively shatters a superpage into
  smaller page table entries down to the target level, preserving original
  permissions and attributes.
- p2m_set_entry() updated to invoke superpage splitting when inserting
  entries at lower levels within a superpage-mapped region.

This implementation is based on the ARM code, with modifications to the part
that follows the BBM (break-before-make) approach, some parts are simplified
as according to RISC-V spec:
  It is permitted for multiple address-translation cache entries to co-exist
  for the same address. This represents the fact that in a conventional
  TLB hierarchy, it is possible for multiple entries to match a single
  address if, for example, a page is upgraded to a superpage without first
  clearing the original non-leaf PTE’s valid bit and executing an SFENCE.VMA
  with rs1=x0, or if multiple TLBs exist in parallel at a given level of the
  hierarchy. In this case, just as if an SFENCE.VMA is not executed between
  a write to the memory-management tables and subsequent implicit read of the
  same address: it is unpredictable whether the old non-leaf PTE or the new
  leaf PTE is used, but the behavior is otherwise well defined.
In contrast to the Arm architecture, where BBM is mandatory and failing to
use it in some cases can lead to CPU instability, RISC-V guarantees
stability, and the behavior remains safe — though unpredictable in terms of
which translation will be used.

Additionally, the page table walk logic has been adjusted, as ARM uses the
opposite level numbering compared to RISC-V.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
Changes in V6:
 - Move introduction of pte_is_superpage() here as it is starting to be
   used in this patch now.
---
Changes in V5:
 - Add Acked-by: Jan Beulich <jbeulich@suse.com>.
 - use next_level when p2m_split_superpage() is recursively called
   instead of using "level-1".
---
Changes in V4:
 - s/number of levels/level numbering in the commit message.
 - s/permissions/attributes.
 - Remove redundant comment in p2m_split_superpage() about page
   splitting.
 - Use P2M_PAGETABLE_ENTRIES as XEN_PT_ENTRIES
   doesn't takeinto  into acount that G stage root page table is
   extended by 2 bits.
 - Use earlier introduced P2M_LEVEL_ORDER().
---
Changes in V3:
 - Move     page_list_add(page, &p2m->pages) inside p2m_alloc_page().
 - Use 'unsigned long' for local vairiable 'i' in p2m_split_superpage().
 - Update the comment above if ( next_level != target ) in p2m_split_superpage().
 - Reverse cycle to iterate through page table levels in p2m_set_entry().
 - Update p2m_split_superpage() with the same changes which are done in the
   patch "P2M: Don't try to free the existing PTE if we can't allocate a new table".
---
Changes in V2:
 - New patch. It was a part of a big patch "xen/riscv: implement p2m mapping
   functionality" which was splitted to smaller.
 - Update the commit above the cycle which creates new page table as
   RISC-V travserse page tables in an opposite to ARM order.
 - RISC-V doesn't require BBM so there is no needed for invalidating
   and TLB flushing before updating PTE.
---
 xen/arch/riscv/include/asm/page.h |   5 ++
 xen/arch/riscv/p2m.c              | 116 +++++++++++++++++++++++++++++-
 2 files changed, 119 insertions(+), 2 deletions(-)

diff --git a/xen/arch/riscv/include/asm/page.h b/xen/arch/riscv/include/asm/page.h
index c6b7acf1b7..4b6baeaaf2 100644
--- a/xen/arch/riscv/include/asm/page.h
+++ b/xen/arch/riscv/include/asm/page.h
@@ -190,6 +190,11 @@ static inline bool pte_is_mapping(pte_t p)
     return (p.pte & PTE_VALID) && (p.pte & PTE_ACCESS_MASK);
 }
 
+static inline bool pte_is_superpage(pte_t p, unsigned int level)
+{
+    return (level > 0) && pte_is_mapping(p);
+}
+
 static inline int clean_and_invalidate_dcache_va_range(const void *p,
                                                        unsigned long size)
 {
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index 16663650ee..b29c0a220a 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -746,7 +746,88 @@ static void p2m_free_subtree(struct p2m_domain *p2m,
     p2m_free_page(p2m, pg);
 }
 
-/* Insert an entry in the p2m */
+static bool p2m_split_superpage(struct p2m_domain *p2m, pte_t *entry,
+                                unsigned int level, unsigned int target,
+                                const unsigned int *offsets)
+{
+    struct page_info *page;
+    unsigned long i;
+    pte_t pte, *table;
+    bool rv = true;
+
+    /* Convenience aliases */
+    mfn_t mfn = pte_get_mfn(*entry);
+    unsigned int next_level = level - 1;
+    unsigned int level_order = P2M_LEVEL_ORDER(next_level);
+
+    /*
+     * This should only be called with target != level and the entry is
+     * a superpage.
+     */
+    ASSERT(level > target);
+    ASSERT(pte_is_superpage(*entry, level));
+
+    page = p2m_alloc_page(p2m);
+    if ( !page )
+    {
+        /*
+         * The caller is in charge to free the sub-tree.
+         * As we didn't manage to allocate anything, just tell the
+         * caller there is nothing to free by invalidating the PTE.
+         */
+        memset(entry, 0, sizeof(*entry));
+        return false;
+    }
+
+    table = __map_domain_page(page);
+
+    for ( i = 0; i < P2M_PAGETABLE_ENTRIES(p2m, next_level); i++ )
+    {
+        pte_t *new_entry = table + i;
+
+        /*
+         * Use the content of the superpage entry and override
+         * the necessary fields. So the correct attributes are kept.
+         */
+        pte = *entry;
+        pte_set_mfn(&pte, mfn_add(mfn, i << level_order));
+
+        write_pte(new_entry, pte);
+    }
+
+    /*
+     * Shatter superpage in the page to the level we want to make the
+     * changes.
+     * This is done outside the loop to avoid checking the offset
+     * for every entry to know whether the entry should be shattered.
+     */
+    if ( next_level != target )
+        rv = p2m_split_superpage(p2m, table + offsets[next_level],
+                                 next_level, target, offsets);
+
+    if ( p2m->clean_dcache )
+        clean_dcache_va_range(table, PAGE_SIZE);
+
+    /*
+     * TODO: an inefficiency here: the caller almost certainly wants to map
+     *       the same page again, to update the one entry that caused the
+     *       request to shatter the page.
+     */
+    unmap_domain_page(table);
+
+    /*
+     * Even if we failed, we should (according to the current implemetation
+     * of a way how sub-tree is freed if p2m_split_superpage hasn't been
+     * finished fully) install the newly allocated PTE
+     * entry.
+     * The caller will be in charge to free the sub-tree.
+     */
+    p2m_write_pte(entry, page_to_p2m_table(page), p2m->clean_dcache);
+
+    return rv;
+}
+
+/* Insert an entry in the p2m. */
 static int p2m_set_entry(struct p2m_domain *p2m,
                          gfn_t gfn,
                          unsigned long page_order,
@@ -811,7 +892,38 @@ static int p2m_set_entry(struct p2m_domain *p2m,
      */
     if ( level > target )
     {
-        panic("Shattering isn't implemented\n");
+        /* We need to split the original page. */
+        pte_t split_pte = *entry;
+
+        ASSERT(pte_is_superpage(*entry, level));
+
+        if ( !p2m_split_superpage(p2m, &split_pte, level, target, offsets) )
+        {
+            /* Free the allocated sub-tree */
+            p2m_free_subtree(p2m, split_pte, level);
+
+            rc = -ENOMEM;
+            goto out;
+        }
+
+        p2m_write_pte(entry, split_pte, p2m->clean_dcache);
+
+        p2m->need_flush = true;
+
+        /* Then move to the level we want to make real changes */
+        for ( ; level > target; level-- )
+        {
+            rc = p2m_next_level(p2m, true, level, &table, offsets[level]);
+
+            /*
+             * The entry should be found and either be a table
+             * or a superpage if level 0 is not targeted
+             */
+            ASSERT(rc == P2M_TABLE_NORMAL ||
+                   (rc == P2M_TABLE_SUPER_PAGE && target > 0));
+        }
+
+        entry = table + offsets[level];
     }
 
     /*
-- 
2.51.1



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

* [PATCH v6 16/19] xen/riscv: implement put_page()
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (14 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 15/19] xen/riscv: Implement superpage splitting for p2m mappings Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 17/19] xen/riscv: implement mfn_valid() and page reference, ownership handling helpers Oleksii Kurochko
                   ` (2 subsequent siblings)
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Implement put_page(), as it will be used by  p2m_put_*-related code.

Although CONFIG_STATIC_MEMORY has not yet been introduced for RISC-V,
a stub for PGC_static is added to avoid cluttering the code of
put_page() with #ifdefs.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
Changes in V6:
 - Nothing changed. Only rebase.
---
Changes in V5:
 - Correct code style of do-while loop in put_page().
 - Add Acked-by: Jan Beulich <jbeulich@suse.com>.
---
Changes in V4:
 - Update the comment message:
   s/p2m_put_code/p2m_put_*-related code.
   s/put_page_nr/put_page.
---
 xen/arch/riscv/include/asm/mm.h |  7 +++++++
 xen/arch/riscv/mm.c             | 24 +++++++++++++++++++-----
 2 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/xen/arch/riscv/include/asm/mm.h b/xen/arch/riscv/include/asm/mm.h
index dd8cdc9782..0503c92e6c 100644
--- a/xen/arch/riscv/include/asm/mm.h
+++ b/xen/arch/riscv/include/asm/mm.h
@@ -264,6 +264,13 @@ static inline bool arch_mfns_in_directmap(unsigned long mfn, unsigned long nr)
 /* Page is Xen heap? */
 #define _PGC_xen_heap     PG_shift(2)
 #define PGC_xen_heap      PG_mask(1, 2)
+#ifdef CONFIG_STATIC_MEMORY
+/* Page is static memory */
+#define _PGC_static       PG_shift(3)
+#define PGC_static        PG_mask(1, 3)
+#else
+#define PGC_static     0
+#endif
 /* Page is broken? */
 #define _PGC_broken       PG_shift(7)
 #define PGC_broken        PG_mask(1, 7)
diff --git a/xen/arch/riscv/mm.c b/xen/arch/riscv/mm.c
index 1ef015f179..2e42293986 100644
--- a/xen/arch/riscv/mm.c
+++ b/xen/arch/riscv/mm.c
@@ -362,11 +362,6 @@ unsigned long __init calc_phys_offset(void)
     return phys_offset;
 }
 
-void put_page(struct page_info *page)
-{
-    BUG_ON("unimplemented");
-}
-
 void arch_dump_shared_mem_info(void)
 {
     BUG_ON("unimplemented");
@@ -627,3 +622,22 @@ void flush_page_to_ram(unsigned long mfn, bool sync_icache)
     if ( sync_icache )
         invalidate_icache();
 }
+
+void put_page(struct page_info *page)
+{
+    unsigned long nx, x, y = page->count_info;
+
+    do {
+        ASSERT((y & PGC_count_mask) >= 1);
+        x  = y;
+        nx = x - 1;
+    } while ( unlikely((y = cmpxchg(&page->count_info, x, nx)) != x) );
+
+    if ( unlikely((nx & PGC_count_mask) == 0) )
+    {
+        if ( unlikely(nx & PGC_static) )
+            free_domstatic_page(page);
+        else
+            free_domheap_page(page);
+    }
+}
-- 
2.51.1



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

* [PATCH v6 17/19] xen/riscv: implement mfn_valid() and page reference, ownership handling helpers
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (15 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 16/19] xen/riscv: implement put_page() Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN Oleksii Kurochko
  2025-11-24 12:33 ` [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type Oleksii Kurochko
  18 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Implement the mfn_valid() macro to verify whether a given MFN is valid by
checking that it falls within the range [start_page, max_page).
These bounds are initialized based on the start and end addresses of RAM.

As part of this patch, start_page is introduced and initialized with the
PFN of the first RAM page.
Also, initialize pdx_group_valid() by calling set_pdx_range() when
memory banks are being mapped.

Also, after providing a non-stub implementation of the mfn_valid() macro,
the following compilation errors started to occur:
  riscv64-linux-gnu-ld: prelink.o: in function `alloc_heap_pages':
  /build/xen/common/page_alloc.c:1054: undefined reference to `page_is_offlinable'
  riscv64-linux-gnu-ld: /build/xen/common/page_alloc.c:1035: undefined reference to `page_is_offlinable'
  riscv64-linux-gnu-ld: prelink.o: in function `reserve_offlined_page':
  /build/xen/common/page_alloc.c:1151: undefined reference to `page_is_offlinable'
  riscv64-linux-gnu-ld: ./.xen-syms.0: hidden symbol `page_is_offlinable' isn't defined
  riscv64-linux-gnu-ld: final link failed: bad value
  make[2]: *** [arch/riscv/Makefile:28: xen-syms] Error 1

To resolve these errors, the following functions have also been introduced,
based on their Arm counterparts:
- page_get_owner_and_reference() and its variant to safely acquire a
  reference to a page and retrieve its owner.
- Implement page_is_offlinable() to return false for RISC-V.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
---
Changes in V6:
 - Nothing changed. Only rebase.
---
Changes in V5:
- Move declaration/defintion of page_is_offlinale() before put_page() to have
  get_ and put_ functions together.
- Correct code style of do-while loop.
- Add Acked-by: Jan Beulich <jbeulich@suse.com>.
---
Changes in V4:
 - Rebase the patch on top of patch series "[PATCH v2 0/2] constrain page_is_ram_type() to x86".
 - Add implementation of page_is_offlinable() instead of page_is_ram().
 - Update the commit message.
---
Changes in V3:
 - Update defintion of mfn_valid().
 - Use __ro_after_init for variable start_page.
 - Drop ASSERT_UNREACHABLE() in page_get_owner_and_nr_reference().
 - Update the comment inside do/while in page_get_owner_and_nr_reference().
 - Define _PGC_static and drop "#ifdef CONFIG_STATIC_MEMORY" in put_page_nr().
 - Initialize pdx_group_valid() by calling set_pdx_range() when memory banks are mapped.
 - Drop page_get_owner_and_nr_reference() and implement page_get_owner_and_reference()
   without reusing of a page_get_owner_and_nr_reference() to avoid potential dead code.
 - Move defintion of get_page() to "xen/riscv: add support of page lookup by GFN", where
   it is really used.
---
Changes in V2:
 - New patch.
---

 xen/arch/riscv/include/asm/mm.h |  9 +++++++--
 xen/arch/riscv/mm.c             | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/xen/arch/riscv/include/asm/mm.h b/xen/arch/riscv/include/asm/mm.h
index 0503c92e6c..1b16809749 100644
--- a/xen/arch/riscv/include/asm/mm.h
+++ b/xen/arch/riscv/include/asm/mm.h
@@ -5,6 +5,7 @@
 
 #include <public/xen.h>
 #include <xen/bug.h>
+#include <xen/compiler.h>
 #include <xen/const.h>
 #include <xen/mm-frame.h>
 #include <xen/pdx.h>
@@ -300,8 +301,12 @@ static inline bool arch_mfns_in_directmap(unsigned long mfn, unsigned long nr)
 #define page_get_owner(p)    (p)->v.inuse.domain
 #define page_set_owner(p, d) ((p)->v.inuse.domain = (d))
 
-/* TODO: implement */
-#define mfn_valid(mfn) ({ (void)(mfn); 0; })
+extern unsigned long start_page;
+
+#define mfn_valid(mfn) ({                                               \
+    unsigned long tmp_mfn = mfn_x(mfn);                                 \
+    likely((tmp_mfn >= start_page)) && likely(__mfn_valid(tmp_mfn));    \
+})
 
 #define domain_set_alloc_bitsize(d) ((void)(d))
 #define domain_clamp_alloc_bitsize(d, b) ((void)(d), (b))
diff --git a/xen/arch/riscv/mm.c b/xen/arch/riscv/mm.c
index 2e42293986..e25f995b72 100644
--- a/xen/arch/riscv/mm.c
+++ b/xen/arch/riscv/mm.c
@@ -521,6 +521,8 @@ static void __init setup_directmap_mappings(unsigned long base_mfn,
 #error setup_{directmap,frametable}_mapping() should be implemented for RV_32
 #endif
 
+unsigned long __ro_after_init start_page;
+
 /*
  * Setup memory management
  *
@@ -570,9 +572,13 @@ void __init setup_mm(void)
         ram_end = max(ram_end, bank_end);
 
         setup_directmap_mappings(PFN_DOWN(bank_start), PFN_DOWN(bank_size));
+
+        set_pdx_range(paddr_to_pfn(bank_start), paddr_to_pfn(bank_end));
     }
 
     setup_frametable_mappings(ram_start, ram_end);
+
+    start_page = PFN_DOWN(ram_start);
     max_page = PFN_DOWN(ram_end);
 }
 
@@ -623,6 +629,11 @@ void flush_page_to_ram(unsigned long mfn, bool sync_icache)
         invalidate_icache();
 }
 
+bool page_is_offlinable(mfn_t mfn)
+{
+    return false;
+}
+
 void put_page(struct page_info *page)
 {
     unsigned long nx, x, y = page->count_info;
@@ -641,3 +652,24 @@ void put_page(struct page_info *page)
             free_domheap_page(page);
     }
 }
+
+struct domain *page_get_owner_and_reference(struct page_info *page)
+{
+    unsigned long x, y = page->count_info;
+    struct domain *owner;
+
+    do {
+        x = y;
+        /*
+         * Count ==  0: Page is not allocated, so we cannot take a reference.
+         * Count == -1: Reference count would wrap, which is invalid.
+         */
+        if ( unlikely(((x + 1) & PGC_count_mask) <= 1) )
+            return NULL;
+    } while ( (y = cmpxchg(&page->count_info, x, x + 1)) != x );
+
+    owner = page_get_owner(page);
+    ASSERT(owner);
+
+    return owner;
+}
-- 
2.51.1



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

* [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (16 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 17/19] xen/riscv: implement mfn_valid() and page reference, ownership handling helpers Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-12-09 11:38   ` Jan Beulich
  2025-11-24 12:33 ` [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type Oleksii Kurochko
  18 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

Introduce helper functions for safely querying the P2M (physical-to-machine)
mapping:
 - add p2m_read_lock(), p2m_read_unlock(), and p2m_is_locked() for managing
   P2M lock state.
 - Implement p2m_get_entry() to retrieve mapping details for a given GFN,
   including MFN, page order, and validity.
 - Introduce p2m_get_page_from_gfn() to convert a GFN into a page_info
   pointer, acquiring a reference to the page if valid.
 - Introduce get_page().

Implementations are based on Arm's functions with some minor modifications:
- p2m_get_entry():
  - Reverse traversal of page tables, as RISC-V uses the opposite level
    numbering compared to Arm.
  - Removed the return of p2m_access_t from p2m_get_entry() since
    mem_access_settings is not introduced for RISC-V.
  - Updated BUILD_BUG_ON() to check using the level 0 mask, which corresponds
    to Arm's THIRD_MASK.
  - Replaced open-coded bit shifts with the BIT() macro.

Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in V6:
 - Move if-condition with initialization up in p2m_get_page_from_gfn().
 - Pass p2mt to the call of p2m_get_entry() inside p2m_get_page_from_gfn()
   to avoid an issue when 't' is passed NULL. With p2mt passed to p2m_get_entry()
   we will recieve a proper type and so the rest of the function will able to
   continue use a proper type.
- In check_outside_boundary() in the case when is_lower == true fill the bottom
  bits of masked_gfn with all 1s.
- Update code of check_outside_boundary() to return proper level in the case when
  `level` is equal to 0.
- Add ASSERT(p2m) in check_outside_boundary() to be sure that p2m isn't NULL as
  P2M_LEVEL_MASK() depends on p2m value.
---
Changes in V5:
 - Use introduced in earlier patches P2M_DECLARE_OFFSETS() instead of
   DECLARE_OFFSETS().
 - Drop blank line before check_outside_boundary().
 - Use more readable version of if statements inside check_outside_boundary().
 - Accumulate mask in check_outside_boundary() instead of re-writing it for
   each page table level to have correct gfns for comparison.
 - Set argument `t` of p2m_get_entry() to p2m_invalid by default.
 - Drop checking of (rc == P2M_TABLE_MAP_NOMEM ) when p2m_next_level(...,false,...)
   is called.
 - Add ASSERT(mfn & (BIT(P2M_LEVEL_ORDER(level), UL) - 1)); in p2m_get_entry()
   to be sure that recieved `mfn` has cleared lowest bits.
 - Drop `valid` argument from p2m_get_entry(), it is not needed anymore.
 - Drop p2m_lookup(), use p2m_get_entry() explicitly inside p2m_get_page_from_gfn().
 - Update the commit message.
---
Changes in V4:
 - Update prototype of p2m_is_locked() to return bool and accept pointer-to-const.
 - Correct the comment above p2m_get_entry().
 - Drop the check "BUILD_BUG_ON(XEN_PT_LEVEL_MAP_MASK(0) != PAGE_MASK);" inside
   p2m_get_entry() as it is stale and it was needed to sure that 4k page(s) are
   used on L3 (in Arm terms) what is true for RISC-V. (if not special extension
   are used). It was another reason for Arm to have it (and I copied it to RISC-V),
   but it isn't true for RISC-V. (some details could be found in response to the
   patch).
 - Style fixes.
 - Add explanatory comment what the loop inside "gfn is higher then the highest
   p2m mapping" does. Move this loop to separate function check_outside_boundary()
   to cover both boundaries (lower_mapped_gfn and max_mapped_gfn).
 - There is not need to allocate a page table as it is expected that
   p2m_get_entry() normally would be called after a corresponding p2m_set_entry()
   was called. So change 'true' to 'false' in a page table walking loop inside
   p2m_get_entry().
 - Correct handling of p2m_is_foreign case inside p2m_get_page_from_gfn().
 - Introduce and use P2M_LEVEL_MASK instead of XEN_PT_LEVEL_MASK as it isn't take
   into account two extra bits for root table in case of P2M.
 - Drop stale item from "change in v3" - Add is_p2m_foreign() macro and connected stuff.
 - Add p2m_read_(un)lock().
---
Changes in V3:
 - Change struct domain *d argument of p2m_get_page_from_gfn() to
   struct p2m_domain.
 - Update the comment above p2m_get_entry().
 - s/_t/p2mt for local variable in p2m_get_entry().
 - Drop local variable addr in p2m_get_entry() and use gfn_to_gaddr(gfn)
   to define offsets array.
 - Code style fixes.
 - Update a check of rc code from p2m_next_level() in p2m_get_entry()
   and drop "else" case.
 - Do not call p2m_get_type() if p2m_get_entry()'s t argument is NULL.
 - Use struct p2m_domain instead of struct domain for p2m_lookup() and
   p2m_get_page_from_gfn().
 - Move defintion of get_page() from "xen/riscv: implement mfn_valid() and page reference, ownership handling helpers"
---
Changes in V2:
 - New patch.
---

 xen/arch/riscv/include/asm/p2m.h |  21 ++++
 xen/arch/riscv/mm.c              |  13 +++
 xen/arch/riscv/p2m.c             | 183 +++++++++++++++++++++++++++++++
 3 files changed, 217 insertions(+)

diff --git a/xen/arch/riscv/include/asm/p2m.h b/xen/arch/riscv/include/asm/p2m.h
index b48693a2b4..f63b5dec99 100644
--- a/xen/arch/riscv/include/asm/p2m.h
+++ b/xen/arch/riscv/include/asm/p2m.h
@@ -41,6 +41,9 @@
 
 #define P2M_GFN_LEVEL_SHIFT(lvl) (P2M_LEVEL_ORDER(lvl) + PAGE_SHIFT)
 
+#define P2M_LEVEL_MASK(p2m, lvl) \
+    (P2M_TABLE_OFFSET(p2m, lvl) << P2M_GFN_LEVEL_SHIFT(lvl))
+
 #define paddr_bits PADDR_BITS
 
 /* Get host p2m table */
@@ -234,6 +237,24 @@ static inline bool p2m_is_write_locked(struct p2m_domain *p2m)
 
 unsigned long construct_hgatp(const struct p2m_domain *p2m, uint16_t vmid);
 
+static inline void p2m_read_lock(struct p2m_domain *p2m)
+{
+    read_lock(&p2m->lock);
+}
+
+static inline void p2m_read_unlock(struct p2m_domain *p2m)
+{
+    read_unlock(&p2m->lock);
+}
+
+static inline bool p2m_is_locked(const struct p2m_domain *p2m)
+{
+    return rw_is_locked(&p2m->lock);
+}
+
+struct page_info *p2m_get_page_from_gfn(struct p2m_domain *p2m, gfn_t gfn,
+                                        p2m_type_t *t);
+
 #endif /* ASM__RISCV__P2M_H */
 
 /*
diff --git a/xen/arch/riscv/mm.c b/xen/arch/riscv/mm.c
index e25f995b72..e9ce182d06 100644
--- a/xen/arch/riscv/mm.c
+++ b/xen/arch/riscv/mm.c
@@ -673,3 +673,16 @@ struct domain *page_get_owner_and_reference(struct page_info *page)
 
     return owner;
 }
+
+bool get_page(struct page_info *page, const struct domain *domain)
+{
+    const struct domain *owner = page_get_owner_and_reference(page);
+
+    if ( likely(owner == domain) )
+        return true;
+
+    if ( owner != NULL )
+        put_page(page);
+
+    return false;
+}
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index b29c0a220a..c356200fc6 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -1061,3 +1061,186 @@ int map_regions_p2mt(struct domain *d,
 
     return rc;
 }
+
+/*
+ * p2m_get_entry() should always return the correct order value, even if an
+ * entry is not present (i.e. the GFN is outside the range):
+ *   [p2m->lowest_mapped_gfn, p2m->max_mapped_gfn]    (1)
+ *
+ * This ensures that callers of p2m_get_entry() can determine what range of
+ * address space would be altered by a corresponding p2m_set_entry().
+ * Also, it would help to avoid costly page walks for GFNs outside range (1).
+ *
+ * Therefore, this function returns true for GFNs outside range (1), and in
+ * that case the corresponding level is returned via the level_out argument.
+ * Otherwise, it returns false and p2m_get_entry() performs a page walk to
+ * find the proper entry.
+ */
+static bool check_outside_boundary(const struct p2m_domain *p2m, gfn_t gfn,
+                                   gfn_t boundary, bool is_lower,
+                                   unsigned int *level_out)
+{
+    unsigned int level = P2M_ROOT_LEVEL(p2m);
+    bool ret = false;
+
+    ASSERT(p2m);
+
+    if ( is_lower ? gfn_x(gfn) < gfn_x(boundary)
+                  : gfn_x(gfn) > gfn_x(boundary) )
+    {
+        unsigned long mask = 0;
+
+        for ( ; level; level-- )
+        {
+            unsigned long masked_gfn;
+
+            mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
+            masked_gfn = gfn_x(gfn) & mask;
+            masked_gfn |= (is_lower * (BIT(P2M_LEVEL_ORDER(level), UL) - 1));
+
+            if ( is_lower ? masked_gfn < gfn_x(boundary)
+                          : masked_gfn > gfn_x(boundary) )
+                break;
+        }
+
+        ret = true;
+    }
+
+    if ( level_out )
+        *level_out = level;
+
+    return ret;
+}
+
+/*
+ * Get the details of a given gfn.
+ *
+ * If the entry is present, the associated MFN will be returned and the
+ * p2m type of the mapping.
+ * The page_order will correspond to the order of the mapping in the page
+ * table (i.e it could be a superpage).
+ *
+ * If the entry is not present, INVALID_MFN will be returned and the
+ * page_order will be set according to the order of the invalid range.
+ */
+static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
+                           p2m_type_t *t,
+                           unsigned int *page_order)
+{
+    unsigned int level = 0;
+    pte_t entry, *table;
+    int rc;
+    mfn_t mfn = INVALID_MFN;
+    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
+
+    ASSERT(p2m_is_locked(p2m));
+
+    if ( t )
+        *t = p2m_invalid;
+
+    if ( check_outside_boundary(p2m, gfn, p2m->lowest_mapped_gfn, true,
+                                &level) )
+        goto out;
+
+    if ( check_outside_boundary(p2m, gfn, p2m->max_mapped_gfn, false, &level) )
+        goto out;
+
+    table = p2m_get_root_pointer(p2m, gfn);
+
+    /*
+     * The table should always be non-NULL because the gfn is below
+     * p2m->max_mapped_gfn and the root table pages are always present.
+     */
+    if ( !table )
+    {
+        ASSERT_UNREACHABLE();
+        level = P2M_ROOT_LEVEL(p2m);
+        goto out;
+    }
+
+    for ( level = P2M_ROOT_LEVEL(p2m); level; level-- )
+    {
+        rc = p2m_next_level(p2m, false, level, &table, offsets[level]);
+        if ( rc == P2M_TABLE_MAP_NONE )
+            goto out_unmap;
+
+        if ( rc != P2M_TABLE_NORMAL )
+            break;
+    }
+
+    entry = table[offsets[level]];
+
+    if ( pte_is_valid(entry) )
+    {
+        if ( t )
+            *t = p2m_get_type(entry);
+
+        mfn = pte_get_mfn(entry);
+
+        ASSERT(!(mfn_x(mfn) & (BIT(P2M_LEVEL_ORDER(level), UL) - 1)));
+
+        /*
+         * The entry may point to a superpage. Find the MFN associated
+         * to the GFN.
+         */
+        mfn = mfn_add(mfn,
+                      gfn_x(gfn) & (BIT(P2M_LEVEL_ORDER(level), UL) - 1));
+    }
+
+ out_unmap:
+    unmap_domain_page(table);
+
+ out:
+    if ( page_order )
+        *page_order = P2M_LEVEL_ORDER(level);
+
+    return mfn;
+}
+
+struct page_info *p2m_get_page_from_gfn(struct p2m_domain *p2m, gfn_t gfn,
+                                        p2m_type_t *t)
+{
+    struct page_info *page;
+    p2m_type_t p2mt;
+    mfn_t mfn;
+
+    p2m_read_lock(p2m);
+    mfn = p2m_get_entry(p2m, gfn, &p2mt, NULL);
+
+    if ( t )
+        *t = p2mt;
+
+    if ( !mfn_valid(mfn) )
+    {
+        p2m_read_unlock(p2m);
+        return NULL;
+    }
+
+    page = mfn_to_page(mfn);
+
+    /*
+     * get_page won't work on foreign mapping because the page doesn't
+     * belong to the current domain.
+     */
+    if ( unlikely(p2m_is_foreign(p2mt)) )
+    {
+        const struct domain *fdom = page_get_owner_and_reference(page);
+
+        p2m_read_unlock(p2m);
+
+        if ( fdom )
+        {
+            if ( likely(fdom != p2m->domain) )
+                return page;
+
+            ASSERT_UNREACHABLE();
+            put_page(page);
+        }
+
+        return NULL;
+    }
+
+    p2m_read_unlock(p2m);
+
+    return get_page(page, p2m->domain) ? page : NULL;
+}
-- 
2.51.1



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

* [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type
  2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
                   ` (17 preceding siblings ...)
  2025-11-24 12:33 ` [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN Oleksii Kurochko
@ 2025-11-24 12:33 ` Oleksii Kurochko
  2025-12-09 13:47   ` Jan Beulich
  18 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-11-24 12:33 UTC (permalink / raw)
  To: xen-devel
  Cc: Oleksii Kurochko, Alistair Francis, Bob Eshleman, Connor Davis,
	Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini

RISC-V's PTE has only two available bits that can be used to store the P2M
type. This is insufficient to represent all the current RISC-V P2M types.
Therefore, some P2M types must be stored outside the PTE bits.

To address this, a metadata table is introduced to store P2M types that
cannot fit in the PTE itself. Not all P2M types are stored in the
metadata table—only those that require it.

The metadata table is linked to the intermediate page table via the
`struct page_info`'s v.md.metadata field of the corresponding intermediate
page.
Such pages are allocated with MEMF_no_owner, which allows us to use
the v field for the purpose of storing the metadata table.

To simplify the allocation and linking of intermediate and metadata page
tables, `p2m_{alloc,free}_table()` functions are implemented.

These changes impact `p2m_split_superpage()`, since when a superpage is
split, it is necessary to update the metadata table of the new
intermediate page table — if the entry being split has its P2M type set
to `p2m_ext_storage` in its `P2M_TYPES` bits. In addition to updating
the metadata of the new intermediate page table, the corresponding entry
in the metadata for the original superpage is invalidated.

Also, update p2m_{get,set}_type to work with P2M types which don't fit
into PTE bits.

Suggested-by: Jan Beulich <jbeulich@suse.com>
Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in V6:
 - Introduce new type md_t to use it instead of pte_t to store metadata
   types outside PTE bits.
 - Integrate introduced struct md_t.
 - Drop local variable "struct domain *d" inside p2m_set_type().
 - Drop __func__ printting and use %pv.
 - Code style fixes
 - Drop unnessarry check inside if-condition in p2m_pte_from_mfn()
   as we have ASSERT(p2m) inside p2m_set_type() anyway.
 - Return back the commnent inside page_to_p2m_table() as it was deleted
   accidently.
 - move the initialization of
p2m_pte_ctx.pt_page and p2m_pte_ctx.level ahead of the loop
 - Add BUILD_BUG_ON(p2m_invalid) before the call of p2m_alloc_page() in p2m_set_type() and in p2m_get_type() before " if ( type == p2m_ext_storage )".
 - Set to NULL tbl_pg->v.md.pg in p2m_free_table().
 - Make argument 't' of p2m_set_type() non-const as we are going to change it.
 - Add some explanatory comments.
 - Update ASSERT at the start of p2m_set_type() to verify that
   passed ctx->index is lesser then 512 and drop calculation of
   an index of root page as it is guaranteed by calc_offset()
   and get_root_pointer() that we will aready get proper page and
   proper index inside this page.
---
Changes in V5:
 - Rename metadata member of stuct md inside struct page_info to pg.
 - Stray blank in the declaration of p2m_alloc_table().
 - Use "<" instead of "<=" in ASSERT() in p2m_set_type().
 - Move the check that ctx is provided to an earlier point in
   p2m_set_type().
 - Set `md_pg` after ASSERT() in p2m_set_type().
 - Add BUG_ON() insetead of ASSERT_UNREACHABLE() in p2m_set_type().
 - Drop a check that metadata isn't NULL before unmap_domain_page() is
   being called.
 - Make const `md` variable in p2m_get_type().
 - unmap correct domain's page in p2m_get_type: use `md` instead of
   ctx->pt_page->v.md.pg.
 - Add description of how p2m and p2m_pte_ctx is expected to be used
   in p2m_pte_from_mfn() and drop a comment from page_to_p2m_table().
 - Drop the stale part of the comment above p2m_alloc_table().
 - Drop ASSERT(tbl_pg->v.md.pg) from p2m_free_table() as tbl_pg->v.md.pg
   is created conditionally now.
 - Drop an introduction of p2m_alloc_table(), update p2m_alloc_page()
   correspondengly and use it instead.
 - Add missing blank in definition of level member for tmp_ctx variable
   in p2m_free_subtree(). Also, add the comma at the end.
 - Initialize old_type once before for-loop in p2m_split_superpage() as
   old type will be used for all newly created PTEs.
 - Properly initialize p2m_pte_ctx.level with next_level instead of
   level when p2m_set_type() is going to be called for new PTEs.
 - Fix identations.
 - Move ASSERT(p2m) on top of p2m_set_type() to be sure that NULL isn't
   passed for p2m argument of p2m_set_type().
 - s/virt_to_page(table)/mfn_to_page(domain_page_map_to_mfn(table))
   to recieve correct page for a table which is mapped by domain_page_map().
 - Add "return;" after domain_crash() in p2m_set_type() to avoid potential
   NULL pointer dereference of md_pg.
---
Changes in V4:
 - Add Suggested-by: Jan Beulich <jbeulich@suse.com>.
 - Update the comment above declation of md structure inside struct page_info to:
   "Page is used as an intermediate P2M page table".
 - Allocate metadata table on demand to save some memory. (1)
 - Rework p2m_set_type():
   - Add allocatation of metadata page only if needed.
   - Move a check what kind of type we are handling inside p2m_set_type().
 - Move mapping of metadata page inside p2m_get_type() as it is needed only
   in case if PTE's type is equal to p2m_ext_storage.
 - Add some description to p2m_get_type() function.
 - Drop blank after return type of p2m_alloc_table().
 - Drop allocation of metadata page inside p2m_alloc_table becaues of (1).
 - Fix p2m_free_table() to free metadata page only if it was allocated.
---
Changes in V3:
 - Add is_p2m_foreign() macro and connected stuff.
 - Change struct domain *d argument of p2m_get_page_from_gfn() to
   struct p2m_domain.
 - Update the comment above p2m_get_entry().
 - s/_t/p2mt for local variable in p2m_get_entry().
 - Drop local variable addr in p2m_get_entry() and use gfn_to_gaddr(gfn)
   to define offsets array.
 - Code style fixes.
 - Update a check of rc code from p2m_next_level() in p2m_get_entry()
   and drop "else" case.
 - Do not call p2m_get_type() if p2m_get_entry()'s t argument is NULL.
 - Use struct p2m_domain instead of struct domain for p2m_lookup() and
   p2m_get_page_from_gfn().
 - Move defintion of get_page() from "xen/riscv: implement mfn_valid() and page reference, ownership handling helpers"
---
Changes in V2:
 - New patch.
---
 xen/arch/riscv/include/asm/mm.h |   9 ++
 xen/arch/riscv/p2m.c            | 257 ++++++++++++++++++++++++++++----
 2 files changed, 236 insertions(+), 30 deletions(-)

diff --git a/xen/arch/riscv/include/asm/mm.h b/xen/arch/riscv/include/asm/mm.h
index 1b16809749..b18892e4fc 100644
--- a/xen/arch/riscv/include/asm/mm.h
+++ b/xen/arch/riscv/include/asm/mm.h
@@ -149,6 +149,15 @@ struct page_info
             /* Order-size of the free chunk this page is the head of. */
             unsigned int order;
         } free;
+
+        /* Page is used as an intermediate P2M page table */
+        struct {
+            /*
+             * Pointer to a page which store metadata for an intermediate page
+             * table.
+             */
+            struct page_info *pg;
+        } md;
     } v;
 
     union {
diff --git a/xen/arch/riscv/p2m.c b/xen/arch/riscv/p2m.c
index c356200fc6..23f44f547d 100644
--- a/xen/arch/riscv/p2m.c
+++ b/xen/arch/riscv/p2m.c
@@ -26,6 +26,24 @@
  */
 #define P2M_MAX_SUPPORTED_LEVEL_MAPPING _AC(2, U)
 
+struct md_t {
+    /*
+     * Describes a type stored outside PTE bits.
+     * Look at the comment above definition of enum p2m_type_t.
+     */
+    p2m_type_t type : 4;
+};
+
+/*
+ * P2M PTE context is used only when a PTE's P2M type is p2m_ext_storage.
+ * In this case, the P2M type is stored separately in the metadata page.
+ */
+struct p2m_pte_ctx {
+    struct page_info *pt_page;   /* Page table page containing the PTE. */
+    unsigned int index;          /* Index of the PTE within that page. */
+    unsigned int level;          /* Paging level at which the PTE resides. */
+};
+
 static struct gstage_mode_desc __ro_after_init max_gstage_mode = {
     .mode = HGATP_MODE_OFF,
     .paging_levels = 0,
@@ -37,6 +55,10 @@ unsigned char get_max_supported_mode(void)
     return max_gstage_mode.mode;
 }
 
+/*
+ * If anything is changed here, it may also require updates to
+ * p2m_{get,set}_type().
+ */
 static inline unsigned int calc_offset(const struct p2m_domain *p2m,
                                        const unsigned int lvl,
                                        const paddr_t gpa)
@@ -79,6 +101,9 @@ static inline unsigned int calc_offset(const struct p2m_domain *p2m,
  * The caller is responsible for unmapping the page after use.
  *
  * Returns NULL if the calculated offset into the root table is invalid.
+ *
+ * If anything is changed here, it may also require updates to
+ * p2m_{get,set}_type().
  */
 static pte_t *p2m_get_root_pointer(struct p2m_domain *p2m, gfn_t gfn)
 {
@@ -374,24 +399,107 @@ static struct page_info *p2m_alloc_page(struct p2m_domain *p2m)
     return pg;
 }
 
-static int p2m_set_type(pte_t *pte, p2m_type_t t)
+/*
+ * `pte` – PTE entry for which the type `t` will be stored.
+ *
+ * If `t` is `p2m_ext_storage`, both `ctx` and `p2m` must be provided;
+ * otherwise, only p2m may be NULL.
+ */
+static void p2m_set_type(pte_t *pte, p2m_type_t t,
+                         struct p2m_pte_ctx *ctx,
+                         struct p2m_domain *p2m)
 {
-    int rc = 0;
+    struct page_info **md_pg;
+    struct md_t *metadata = NULL;
 
-    if ( t > p2m_first_external )
-        panic("unimplemeted\n");
-    else
-        pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
+    ASSERT(p2m);
 
-    return rc;
+    /*
+     * It is sufficient to compare ctx->index with PAGETABLE_ENTRIES because,
+     * even for the p2m root page table (which is a 16 KB page allocated as
+     * four 4 KB pages), calc_offset() guarantees that the page-table index
+     * will always fall within the range [0, 511].
+     */
+    ASSERT(ctx && ctx->index < PAGETABLE_ENTRIES);
+
+    /*
+     * At the moment, p2m_get_root_pointer() returns one of four possible p2m
+     * root pages, so there is no need to search for the correct ->pt_page
+     * here.
+     * Non-root page tables are 4 KB pages, so simply using ->pt_page is
+     * sufficient.
+     */
+    md_pg = &ctx->pt_page->v.md.pg;
+
+    if ( !*md_pg && (t >= p2m_first_external) )
+    {
+        BUG_ON(ctx->level > P2M_MAX_SUPPORTED_LEVEL_MAPPING);
+
+        if ( ctx->level <= P2M_MAX_SUPPORTED_LEVEL_MAPPING )
+        {
+            /*
+             * Since p2m_alloc_page() initializes an allocated page with zeros, p2m_invalid
+             * is expected to have the value 0 as well. This ensures that if a metadata
+             * page is accessed before being properly initialized, the correct type
+             * (p2m_invalid in this case) will be returned.
+             */
+            BUILD_BUG_ON(p2m_invalid);
+
+            *md_pg = p2m_alloc_page(p2m);
+            if ( !*md_pg )
+            {
+                printk("%pd: can't allocate metadata page\n", p2m->domain);
+                domain_crash(p2m->domain);
+
+                return;
+            }
+        }
+    }
+
+    if ( *md_pg )
+        metadata = __map_domain_page(*md_pg);
+
+    if ( t >= p2m_first_external )
+    {
+        metadata[ctx->index].type = t;
+
+        t = p2m_ext_storage;
+    }
+    else if ( metadata )
+        metadata[ctx->index].type = p2m_invalid;
+
+    pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
+
+    unmap_domain_page(metadata);
 }
 
-static p2m_type_t p2m_get_type(const pte_t pte)
+/*
+ * `pte` -> PTE entry that stores the PTE's type.
+ *
+ * If the PTE's type is `p2m_ext_storage`, `ctx` should be provided;
+ * otherwise it could be NULL.
+ */
+static p2m_type_t p2m_get_type(const pte_t pte, const struct p2m_pte_ctx *ctx)
 {
     p2m_type_t type = MASK_EXTR(pte.pte, P2M_TYPE_PTE_BITS_MASK);
 
+    /*
+     * Since the PTE is initialized with all zeros by default, p2m_invalid must
+     * have the value 0. This ensures that if p2m_get_type() is called for a GFN
+     * that hasn't been initialized, the correct type (p2m_invalid in this case)
+     * will be returned. It also guarantees that metadata won't be touched when
+     * the GFN hasn't been initialized.
+     */
+    BUILD_BUG_ON(p2m_invalid);
+
     if ( type == p2m_ext_storage )
-        panic("unimplemented\n");
+    {
+        const struct md_t *md = __map_domain_page(ctx->pt_page->v.md.pg);
+
+        type = md[ctx->index].type;
+
+        unmap_domain_page(md);
+    }
 
     return type;
 }
@@ -481,7 +589,15 @@ static void p2m_set_permission(pte_t *e, p2m_type_t t)
     }
 }
 
-static pte_t p2m_pte_from_mfn(mfn_t mfn, p2m_type_t t, bool is_table)
+/*
+ * If p2m_pte_from_mfn() is called with p2m_pte_ctx = NULL and p2m = NULL,
+ * it means the function is working with a page table for which the `t`
+ * should not be applicable. Otherwise, the function is handling a leaf PTE
+ * for which `t` is applicable.
+ */
+static pte_t p2m_pte_from_mfn(mfn_t mfn, p2m_type_t t,
+                              struct p2m_pte_ctx *p2m_pte_ctx,
+                              struct p2m_domain *p2m)
 {
     pte_t e = (pte_t) { PTE_VALID };
 
@@ -489,7 +605,7 @@ static pte_t p2m_pte_from_mfn(mfn_t mfn, p2m_type_t t, bool is_table)
 
     ASSERT(!(mfn_to_maddr(mfn) & ~PADDR_MASK) || mfn_eq(mfn, INVALID_MFN));
 
-    if ( !is_table )
+    if ( p2m_pte_ctx )
     {
         switch ( t )
         {
@@ -502,7 +618,7 @@ static pte_t p2m_pte_from_mfn(mfn_t mfn, p2m_type_t t, bool is_table)
         }
 
         p2m_set_permission(&e, t);
-        p2m_set_type(&e, t);
+        p2m_set_type(&e, t, p2m_pte_ctx, p2m);
     }
     else
         /*
@@ -522,7 +638,22 @@ static pte_t page_to_p2m_table(const struct page_info *page)
      * set to true and p2m_type_t shouldn't be applied for PTEs which
      * describe an intermediate table.
      */
-    return p2m_pte_from_mfn(page_to_mfn(page), p2m_invalid, true);
+    return p2m_pte_from_mfn(page_to_mfn(page), p2m_invalid, NULL, NULL);
+}
+
+static void p2m_free_page(struct p2m_domain *p2m, struct page_info *pg);
+
+/*
+ * Free page table's page and metadata page linked to page table's page.
+ */
+static void p2m_free_table(struct p2m_domain *p2m, struct page_info *tbl_pg)
+{
+    if ( tbl_pg->v.md.pg )
+    {
+        p2m_free_page(p2m, tbl_pg->v.md.pg);
+        tbl_pg->v.md.pg = NULL;
+    }
+    p2m_free_page(p2m, tbl_pg);
 }
 
 /* Allocate a new page table page and hook it in via the given entry. */
@@ -683,12 +814,14 @@ static void p2m_free_page(struct p2m_domain *p2m, struct page_info *pg)
 
 /* Free pte sub-tree behind an entry */
 static void p2m_free_subtree(struct p2m_domain *p2m,
-                             pte_t entry, unsigned int level)
+                             pte_t entry,
+                             const struct p2m_pte_ctx *p2m_pte_ctx)
 {
     unsigned int i;
     pte_t *table;
     mfn_t mfn;
     struct page_info *pg;
+    unsigned int level = p2m_pte_ctx->level;
 
     /*
      * Check if the level is valid: only 4K - 2M - 1G mappings are supported.
@@ -704,7 +837,7 @@ static void p2m_free_subtree(struct p2m_domain *p2m,
 
     if ( pte_is_mapping(entry) )
     {
-        p2m_type_t p2mt = p2m_get_type(entry);
+        p2m_type_t p2mt = p2m_get_type(entry, p2m_pte_ctx);
 
 #ifdef CONFIG_IOREQ_SERVER
         /*
@@ -723,10 +856,21 @@ static void p2m_free_subtree(struct p2m_domain *p2m,
         return;
     }
 
-    table = map_domain_page(pte_get_mfn(entry));
+    mfn = pte_get_mfn(entry);
+    ASSERT(mfn_valid(mfn));
+    table = map_domain_page(mfn);
+    pg = mfn_to_page(mfn);
 
     for ( i = 0; i < P2M_PAGETABLE_ENTRIES(p2m, level); i++ )
-        p2m_free_subtree(p2m, table[i], level - 1);
+    {
+        struct p2m_pte_ctx tmp_ctx = {
+            .pt_page = pg,
+            .index = i,
+            .level = level - 1,
+        };
+
+        p2m_free_subtree(p2m, table[i], &tmp_ctx);
+    }
 
     unmap_domain_page(table);
 
@@ -738,17 +882,13 @@ static void p2m_free_subtree(struct p2m_domain *p2m,
      */
     p2m_tlb_flush_sync(p2m);
 
-    mfn = pte_get_mfn(entry);
-    ASSERT(mfn_valid(mfn));
-
-    pg = mfn_to_page(mfn);
-
-    p2m_free_page(p2m, pg);
+    p2m_free_table(p2m, pg);
 }
 
 static bool p2m_split_superpage(struct p2m_domain *p2m, pte_t *entry,
                                 unsigned int level, unsigned int target,
-                                const unsigned int *offsets)
+                                const unsigned int *offsets,
+                                struct page_info *tbl_pg)
 {
     struct page_info *page;
     unsigned long i;
@@ -760,6 +900,10 @@ static bool p2m_split_superpage(struct p2m_domain *p2m, pte_t *entry,
     unsigned int next_level = level - 1;
     unsigned int level_order = P2M_LEVEL_ORDER(next_level);
 
+    struct p2m_pte_ctx p2m_pte_ctx;
+    /* Init with p2m_invalid just to make compiler happy. */
+    p2m_type_t old_type = p2m_invalid;
+
     /*
      * This should only be called with target != level and the entry is
      * a superpage.
@@ -781,6 +925,22 @@ static bool p2m_split_superpage(struct p2m_domain *p2m, pte_t *entry,
 
     table = __map_domain_page(page);
 
+    if ( MASK_EXTR(entry->pte, P2M_TYPE_PTE_BITS_MASK) == p2m_ext_storage )
+    {
+        p2m_pte_ctx.pt_page = tbl_pg;
+        p2m_pte_ctx.index = offsets[level];
+        /*
+         * It doesn't really matter what is a value for a level as
+         * p2m_get_type() doesn't need it, so it is initialized just in case.
+         */
+        p2m_pte_ctx.level = level;
+
+        old_type = p2m_get_type(*entry, &p2m_pte_ctx);
+    }
+
+    p2m_pte_ctx.pt_page = page;
+    p2m_pte_ctx.level = next_level;
+
     for ( i = 0; i < P2M_PAGETABLE_ENTRIES(p2m, next_level); i++ )
     {
         pte_t *new_entry = table + i;
@@ -792,6 +952,13 @@ static bool p2m_split_superpage(struct p2m_domain *p2m, pte_t *entry,
         pte = *entry;
         pte_set_mfn(&pte, mfn_add(mfn, i << level_order));
 
+        if ( MASK_EXTR(pte.pte, P2M_TYPE_PTE_BITS_MASK) == p2m_ext_storage )
+        {
+            p2m_pte_ctx.index = i;
+
+            p2m_set_type(&pte, old_type, &p2m_pte_ctx, p2m);
+        }
+
         write_pte(new_entry, pte);
     }
 
@@ -803,7 +970,7 @@ static bool p2m_split_superpage(struct p2m_domain *p2m, pte_t *entry,
      */
     if ( next_level != target )
         rv = p2m_split_superpage(p2m, table + offsets[next_level],
-                                 next_level, target, offsets);
+                                 next_level, target, offsets, page);
 
     if ( p2m->clean_dcache )
         clean_dcache_va_range(table, PAGE_SIZE);
@@ -894,13 +1061,21 @@ static int p2m_set_entry(struct p2m_domain *p2m,
     {
         /* We need to split the original page. */
         pte_t split_pte = *entry;
+        struct page_info *tbl_pg = mfn_to_page(domain_page_map_to_mfn(table));
 
         ASSERT(pte_is_superpage(*entry, level));
 
-        if ( !p2m_split_superpage(p2m, &split_pte, level, target, offsets) )
+        if ( !p2m_split_superpage(p2m, &split_pte, level, target, offsets,
+                                  tbl_pg) )
         {
+            struct p2m_pte_ctx tmp_ctx = {
+                .pt_page = tbl_pg,
+                .index = offsets[level],
+                .level = level,
+            };
+
             /* Free the allocated sub-tree */
-            p2m_free_subtree(p2m, split_pte, level);
+            p2m_free_subtree(p2m, split_pte, &tmp_ctx);
 
             rc = -ENOMEM;
             goto out;
@@ -938,7 +1113,13 @@ static int p2m_set_entry(struct p2m_domain *p2m,
         p2m_clean_pte(entry, p2m->clean_dcache);
     else
     {
-        pte_t pte = p2m_pte_from_mfn(mfn, t, false);
+        struct p2m_pte_ctx tmp_ctx = {
+            .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
+            .index = offsets[level],
+            .level = level,
+        };
+
+        pte_t pte = p2m_pte_from_mfn(mfn, t, &tmp_ctx, p2m);
 
         p2m_write_pte(entry, pte, p2m->clean_dcache);
 
@@ -974,7 +1155,15 @@ static int p2m_set_entry(struct p2m_domain *p2m,
     if ( pte_is_valid(orig_pte) &&
          (!pte_is_valid(*entry) ||
           !mfn_eq(pte_get_mfn(*entry), pte_get_mfn(orig_pte))) )
-        p2m_free_subtree(p2m, orig_pte, level);
+    {
+        struct p2m_pte_ctx tmp_ctx = {
+            .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
+            .index = offsets[level],
+            .level = level,
+        };
+
+        p2m_free_subtree(p2m, orig_pte, &tmp_ctx);
+    }
 
  out:
     unmap_domain_page(table);
@@ -1173,7 +1362,15 @@ static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
     if ( pte_is_valid(entry) )
     {
         if ( t )
-            *t = p2m_get_type(entry);
+        {
+            struct p2m_pte_ctx p2m_pte_ctx = {
+                .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
+                .index = offsets[level],
+                .level = level,
+            };
+
+            *t = p2m_get_type(entry, &p2m_pte_ctx);
+        }
 
         mfn = pte_get_mfn(entry);
 
-- 
2.51.1



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

* Re: [PATCH v6 01/19] xen/riscv: avoid redundant HGATP*_MODE_SHIFT and HGATP*_VMID_SHIFT
  2025-11-24 12:33 ` [PATCH v6 01/19] xen/riscv: avoid redundant HGATP*_MODE_SHIFT and HGATP*_VMID_SHIFT Oleksii Kurochko
@ 2025-12-08 16:13   ` Jan Beulich
  0 siblings, 0 replies; 52+ messages in thread
From: Jan Beulich @ 2025-12-08 16:13 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 24.11.2025 13:33, Oleksii Kurochko wrote:
> It is sufficient to use HGATP*_MODE_MASK and HGATP*_VMID_MASK without
> the corresponding *_SHIFT definitions.
> 
> Rename HGATP{32,64}_PPN to HGATP{32,64}_PPN_MASK to more accurately
> describe their purpose. The top-level HGATP_PPN and related aliases are
> updated accordingly.
> 
> Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>

Acked-by: Jan Beulich <jbeulich@suse.com>



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

* Re: [PATCH v6 02/19] xen/riscv: detect and initialize G-stage mode
  2025-11-24 12:33 ` [PATCH v6 02/19] xen/riscv: detect and initialize G-stage mode Oleksii Kurochko
@ 2025-12-08 16:22   ` Jan Beulich
  2025-12-09  9:54     ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-08 16:22 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 24.11.2025 13:33, Oleksii Kurochko wrote:
> +static void __init gstage_mode_detect(void)
> +{
> +    static const struct gstage_mode_desc modes[] __initconst = {
> +        /*
> +         * Based on the RISC-V spec:
> +         *   Bare mode is always supported, regardless of SXLEN.
> +         *   When SXLEN=32, the only other valid setting for MODE is Sv32.
> +         *   When SXLEN=64, three paged virtual-memory schemes are defined:
> +         *   Sv39, Sv48, and Sv57.
> +         */
> +#ifdef CONFIG_RISCV_32
> +        { HGATP_MODE_SV32X4, 2, "Sv32x4" }
> +#else
> +        { HGATP_MODE_SV39X4, 3, "Sv39x4" },
> +        { HGATP_MODE_SV48X4, 4, "Sv48x4" },
> +        { HGATP_MODE_SV57X4, 5, "Sv57x4" },
> +#endif
> +    };
> +
> +    unsigned int mode_idx;

Can't this move ...

> +    for ( mode_idx = ARRAY_SIZE(modes); mode_idx-- > 0; )

... into here? You don't use the variable outside of the loop.

> +    {
> +        unsigned long mode = modes[mode_idx].mode;
> +
> +        csr_write(CSR_HGATP, MASK_INSR(mode, HGATP_MODE_MASK));
> +
> +        if ( MASK_EXTR(csr_read(CSR_HGATP), HGATP_MODE_MASK) == mode )
> +        {
> +            max_gstage_mode.mode = modes[mode_idx].mode;
> +            max_gstage_mode.paging_levels = modes[mode_idx].paging_levels;
> +            safe_strcpy(max_gstage_mode.name, modes[mode_idx].name);

This looks as if you were overwriting .rodata here (the string literal
"Bare"). You aren't, but why can't the whole copying be a single struct
assignment?

Preferably with the adjustments:
Acked-by: Jan Beulich <jbeulich@suse.com>

Jan


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

* Re: [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement
  2025-11-24 12:33 ` [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement Oleksii Kurochko
@ 2025-12-08 16:31   ` Jan Beulich
  2025-12-09 10:35     ` Oleksii Kurochko
  2025-12-08 17:28   ` Teddy Astie
  1 sibling, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-08 16:31 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Andrew Cooper, Anthony PERARD, Michal Orzel, Julien Grall,
	Roger Pau Monné, Stefano Stabellini, Alistair Francis,
	Bob Eshleman, Connor Davis, xen-devel

On 24.11.2025 13:33, Oleksii Kurochko wrote:
> --- a/docs/misc/xen-command-line.pandoc
> +++ b/docs/misc/xen-command-line.pandoc
> @@ -3096,3 +3096,12 @@ the hypervisor was compiled with `CONFIG_XSM` enabled.
>  * `silo`: this will deny any unmediated communication channels between
>    unprivileged VMs.  To choose this, the separated option in kconfig must also
>    be enabled.
> +
> +### vmid (RISC-V)
> +> `= <boolean>`
> +
> +> Default: `true`
> +
> +Permit Xen to use Virtual Machine Identifiers. This is an optimisation which
> +tags the TLB entries with an ID per vcpu. This allows for guest TLB flushes
> +to be performed without the overhead of a complete TLB flush.

Please obey to the alphabetic sorting within this file.

> --- /dev/null
> +++ b/xen/arch/riscv/vmid.c
> @@ -0,0 +1,170 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +#include <xen/domain.h>
> +#include <xen/init.h>
> +#include <xen/sections.h>
> +#include <xen/lib.h>
> +#include <xen/param.h>
> +#include <xen/percpu.h>
> +
> +#include <asm/atomic.h>
> +#include <asm/csr.h>
> +#include <asm/flushtlb.h>
> +#include <asm/p2m.h>
> +
> +/* Xen command-line option to enable VMIDs */
> +static bool __ro_after_init opt_vmid = true;
> +boolean_param("vmid", opt_vmid);
> +
> +/*
> + * VMIDs partition the physical TLB. In the current implementation VMIDs are
> + * introduced to reduce the number of TLB flushes. Each time a guest-physical
> + * address space changes, instead of flushing the TLB, a new VMID is
> + * assigned. This reduces the number of TLB flushes to at most 1/#VMIDs.
> + * The biggest advantage is that hot parts of the hypervisor's code and data
> + * retain in the TLB.
> + *
> + * Sketch of the Implementation:
> + *
> + * VMIDs are a hart-local resource.  As preemption of VMIDs is not possible,
> + * VMIDs are assigned in a round-robin scheme. To minimize the overhead of
> + * VMID invalidation, at the time of a TLB flush, VMIDs are tagged with a
> + * 64-bit generation. Only on a generation overflow the code needs to
> + * invalidate all VMID information stored at the VCPUs with are run on the
> + * specific physical processor. When this overflow appears VMID usage is
> + * disabled to retain correctness.
> + */
> +
> +/* Per-Hart VMID management. */
> +struct vmid_data {
> +   uint64_t generation;
> +   uint16_t next_vmid;
> +   uint16_t max_vmid;
> +   bool used;
> +};
> +
> +static DEFINE_PER_CPU(struct vmid_data, vmid_data);
> +
> +static unsigned int vmidlen_detect(void)
> +{
> +    unsigned int vmid_bits;
> +    unsigned char gstage_mode = get_max_supported_mode();
> +
> +    /*
> +     * According to the RISC-V Privileged Architecture Spec:
> +     *   When MODE=Bare, guest physical addresses are equal to supervisor
> +     *   physical addresses, and there is no further memory protection
> +     *   for a guest virtual machine beyond the physical memory protection
> +     *   scheme described in Section "Physical Memory Protection".
> +     *   In this case, the remaining fields in hgatp must be set to zeros.
> +     * Thereby it is necessary to set gstage_mode not equal to Bare.
> +     */
> +    ASSERT(gstage_mode != HGATP_MODE_OFF);
> +    csr_write(CSR_HGATP,
> +              MASK_INSR(gstage_mode, HGATP_MODE_MASK) | HGATP_VMID_MASK);
> +    vmid_bits = MASK_EXTR(csr_read(CSR_HGATP), HGATP_VMID_MASK);
> +    vmid_bits = flsl(vmid_bits);
> +    csr_write(CSR_HGATP, _AC(0, UL));
> +
> +    /* local_hfence_gvma_all() will be called at the end of pre_gstage_init. */
> +
> +    return vmid_bits;
> +}
> +
> +void vmid_init(void)

With the presently sole caller being __init, this (and likely the helper above)
could be __init. Iirc you intend to also call this as secondary harts come up,
so this may be okay. But then it wants justifing in the description.

Jan


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

* Re: [PATCH v6 06/19] xen/riscv: add root page table allocation
  2025-11-24 12:33 ` [PATCH v6 06/19] xen/riscv: add root page table allocation Oleksii Kurochko
@ 2025-12-08 16:40   ` Jan Beulich
  0 siblings, 0 replies; 52+ messages in thread
From: Jan Beulich @ 2025-12-08 16:40 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 24.11.2025 13:33, Oleksii Kurochko wrote:
> Introduce support for allocating and initializing the root page table
> required for RISC-V stage-2 address translation.
> 
> To implement root page table allocation the following is introduced:
> - p2m_get_clean_page() and p2m_alloc_root_table(), p2m_allocate_root()
>   helpers to allocate and zero a 16 KiB root page table, as mandated
>   by the RISC-V privileged specification for Sv32x4/Sv39x4/Sv48x4/Sv57x4
>   modes.
> - Update p2m_init() to inititialize p2m_root_order.
> - Add maddr_to_page() and page_to_maddr() macros for easier address
>   manipulation.
> - Introduce paging_ret_to_domheap() to return some pages before
>   allocate 16 KiB pages for root page table.
> - Allocate root p2m table after p2m pool is initialized.
> - Add construct_hgatp() to construct the hgatp register value based on
>   p2m->root, p2m->hgatp_mode and VMID.
> 
> Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>

Acked-by: Jan Beulich <jbeulich@suse.com>



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

* Re: [PATCH v6 11/19] xen/riscv: implement p2m_set_range()
  2025-11-24 12:33 ` [PATCH v6 11/19] xen/riscv: implement p2m_set_range() Oleksii Kurochko
@ 2025-12-08 16:52   ` Jan Beulich
  2025-12-09 11:47     ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-08 16:52 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 24.11.2025 13:33, Oleksii Kurochko wrote:
> @@ -28,6 +36,77 @@ unsigned char get_max_supported_mode(void)
>      return max_gstage_mode.mode;
>  }
>  
> +static inline unsigned int calc_offset(const struct p2m_domain *p2m,
> +                                       const unsigned int lvl,
> +                                       const paddr_t gpa)
> +{
> +    unsigned int off = (gpa >> P2M_GFN_LEVEL_SHIFT(lvl)) &
> +                       P2M_TABLE_OFFSET(p2m, lvl);
> +
> +    /*
> +     * For P2M_ROOT_LEVEL, `offset` ranges from 0 to 2047, since the root
> +     * page table spans 4 consecutive 4KB pages.
> +     * We want to return an index within one of these 4 pages.
> +     * The specific page to use is determined by `p2m_get_root_pointer()`.
> +     *
> +     * Example: if `offset == 512`:
> +     *  - A single 4KB page holds 512 entries.
> +     *  - Therefore, entry 512 corresponds to index 0 of the second page.
> +     *
> +     * At all other levels, only one page is allocated, and `offset` is
> +     * always in the range 0 to 511, since the VPN is 9 bits long.
> +     */
> +    return off & (PAGETABLE_ENTRIES - 1);
> +}
> +
> +#define P2M_MAX_ROOT_LEVEL 5
> +
> +#define P2M_BUILD_LEVEL_OFFSETS(p2m, var, addr) \
> +    unsigned int var[P2M_MAX_ROOT_LEVEL] = {-1}; \

What use is this initializer? Slot 0 ...

> +    BUG_ON(P2M_ROOT_LEVEL(p2m) >= P2M_MAX_ROOT_LEVEL); \
> +    for ( unsigned int i = 0; i <= P2M_ROOT_LEVEL(p2m); i++ ) \
> +        var[i] = calc_offset(p2m, i, addr);

... is guaranteed to be written to, afaics. With this simplified (or an
explanation given for why it is needed)
Acked-by: Jan Beulich <jbeulich@suse.com>

Jan


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

* Re: [PATCH v6 12/19] xen/riscv: Implement p2m_free_subtree() and related helpers
  2025-11-24 12:33 ` [PATCH v6 12/19] xen/riscv: Implement p2m_free_subtree() and related helpers Oleksii Kurochko
@ 2025-12-08 16:56   ` Jan Beulich
  0 siblings, 0 replies; 52+ messages in thread
From: Jan Beulich @ 2025-12-08 16:56 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 24.11.2025 13:33, Oleksii Kurochko wrote:
> This patch introduces a working implementation of p2m_free_subtree() for RISC-V
> based on ARM's implementation of p2m_free_entry(), enabling proper cleanup
> of page table entries in the P2M (physical-to-machine) mapping.
> 
> Only few things are changed:
> - Introduce and use p2m_get_type() to get a type of p2m entry as
>   RISC-V's PTE doesn't have enough space to store all necessary types so
>   a type is stored outside PTE. But, at the moment, handle only types
>   which fit into PTE's bits.
> 
> Key additions include:
> - p2m_free_subtree(): Recursively frees page table entries at all levels. It
>   handles both regular and superpage mappings and ensures that TLB entries
>   are flushed before freeing intermediate tables.
> - p2m_put_page() and helpers:
>   - p2m_put_4k_page(): Clears GFN from xenheap pages if applicable.
>   - p2m_put_2m_superpage(): Releases foreign page references in a 2MB
>     superpage.
>   - p2m_get_type(): Extracts the stored p2m_type from the PTE bits.
> - p2m_free_page(): Returns a page to a domain's freelist.
> - Introduce p2m_is_foreign() and connected to it things.
> 
> Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>

Acked-by: Jan Beulich <jbeulich@suse.com>



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

* Re: [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement
  2025-11-24 12:33 ` [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement Oleksii Kurochko
  2025-12-08 16:31   ` Jan Beulich
@ 2025-12-08 17:28   ` Teddy Astie
  2025-12-09  7:51     ` Jan Beulich
  1 sibling, 1 reply; 52+ messages in thread
From: Teddy Astie @ 2025-12-08 17:28 UTC (permalink / raw)
  To: Oleksii Kurochko, xen-devel
  Cc: Andrew Cooper, Anthony PERARD, Michal Orzel, Jan Beulich,
	Julien Grall, Roger Pau Monné, Stefano Stabellini,
	Alistair Francis, Bob Eshleman, Connor Davis

Le 24/11/2025 à 13:36, Oleksii Kurochko a écrit :
> diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
> index 34004ce282..6c4bfa3603 100644
> --- a/docs/misc/xen-command-line.pandoc
> +++ b/docs/misc/xen-command-line.pandoc
> @@ -3096,3 +3096,12 @@ the hypervisor was compiled with `CONFIG_XSM` enabled.
>   * `silo`: this will deny any unmediated communication channels between
>     unprivileged VMs.  To choose this, the separated option in kconfig must also
>     be enabled.
> +
> +### vmid (RISC-V)
> +> `= <boolean>`
> +
> +> Default: `true`
> +
> +Permit Xen to use Virtual Machine Identifiers. This is an optimisation which
> +tags the TLB entries with an ID per vcpu. This allows for guest TLB flushes
> +to be performed without the overhead of a complete TLB flush.

Should we regroup all asid/vpid/vmid (which are pretty much the same
thing with different names and for different arch) under a single
command-line option ?

Teddy


--
Teddy Astie | Vates XCP-ng Developer

XCP-ng & Xen Orchestra - Vates solutions

web: https://vates.tech




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

* Re: [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement
  2025-12-08 17:28   ` Teddy Astie
@ 2025-12-09  7:51     ` Jan Beulich
  2025-12-09 10:40       ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-09  7:51 UTC (permalink / raw)
  To: Teddy Astie
  Cc: Andrew Cooper, Anthony PERARD, Michal Orzel, Julien Grall,
	Roger Pau Monné, Stefano Stabellini, Alistair Francis,
	Bob Eshleman, Connor Davis, Oleksii Kurochko, xen-devel

On 08.12.2025 18:28, Teddy Astie wrote:
> Le 24/11/2025 à 13:36, Oleksii Kurochko a écrit :
>> diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
>> index 34004ce282..6c4bfa3603 100644
>> --- a/docs/misc/xen-command-line.pandoc
>> +++ b/docs/misc/xen-command-line.pandoc
>> @@ -3096,3 +3096,12 @@ the hypervisor was compiled with `CONFIG_XSM` enabled.
>>   * `silo`: this will deny any unmediated communication channels between
>>     unprivileged VMs.  To choose this, the separated option in kconfig must also
>>     be enabled.
>> +
>> +### vmid (RISC-V)
>> +> `= <boolean>`
>> +
>> +> Default: `true`
>> +
>> +Permit Xen to use Virtual Machine Identifiers. This is an optimisation which
>> +tags the TLB entries with an ID per vcpu. This allows for guest TLB flushes
>> +to be performed without the overhead of a complete TLB flush.
> 
> Should we regroup all asid/vpid/vmid (which are pretty much the same 
> thing with different names and for different arch) under a single 
> command-line option ?

How would you name such an option, without losing it being recognized by people
knowing the respective arch?

Jan


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

* Re: [PATCH v6 02/19] xen/riscv: detect and initialize G-stage mode
  2025-12-08 16:22   ` Jan Beulich
@ 2025-12-09  9:54     ` Oleksii Kurochko
  0 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-09  9:54 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/8/25 5:22 PM, Jan Beulich wrote:
> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>> +static void __init gstage_mode_detect(void)
>> +{
>> +    static const struct gstage_mode_desc modes[] __initconst = {
>> +        /*
>> +         * Based on the RISC-V spec:
>> +         *   Bare mode is always supported, regardless of SXLEN.
>> +         *   When SXLEN=32, the only other valid setting for MODE is Sv32.
>> +         *   When SXLEN=64, three paged virtual-memory schemes are defined:
>> +         *   Sv39, Sv48, and Sv57.
>> +         */
>> +#ifdef CONFIG_RISCV_32
>> +        { HGATP_MODE_SV32X4, 2, "Sv32x4" }
>> +#else
>> +        { HGATP_MODE_SV39X4, 3, "Sv39x4" },
>> +        { HGATP_MODE_SV48X4, 4, "Sv48x4" },
>> +        { HGATP_MODE_SV57X4, 5, "Sv57x4" },
>> +#endif
>> +    };
>> +
>> +    unsigned int mode_idx;
> Can't this move ...
>
>> +    for ( mode_idx = ARRAY_SIZE(modes); mode_idx-- > 0; )
> ... into here? You don't use the variable outside of the loop.

Agree, mode_idx isn't used outside of the loop anymore, so I will move
declaration of it into for header.

>
>> +    {
>> +        unsigned long mode = modes[mode_idx].mode;
>> +
>> +        csr_write(CSR_HGATP, MASK_INSR(mode, HGATP_MODE_MASK));
>> +
>> +        if ( MASK_EXTR(csr_read(CSR_HGATP), HGATP_MODE_MASK) == mode )
>> +        {
>> +            max_gstage_mode.mode = modes[mode_idx].mode;
>> +            max_gstage_mode.paging_levels = modes[mode_idx].paging_levels;
>> +            safe_strcpy(max_gstage_mode.name, modes[mode_idx].name);
> This looks as if you were overwriting .rodata here (the string literal
> "Bare"). You aren't, but why can't the whole copying be a single struct
> assignment?

Agree, it could be just:
  max_gstage_mode = modes[mode_idx];

>
> Preferably with the adjustments:
> Acked-by: Jan Beulich <jbeulich@suse.com>

Thanks.

~ Oleksii



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

* Re: [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement
  2025-12-08 16:31   ` Jan Beulich
@ 2025-12-09 10:35     ` Oleksii Kurochko
  2025-12-09 10:52       ` Jan Beulich
  0 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-09 10:35 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Andrew Cooper, Anthony PERARD, Michal Orzel, Julien Grall,
	Roger Pau Monné, Stefano Stabellini, Alistair Francis,
	Bob Eshleman, Connor Davis, xen-devel


On 12/8/25 5:31 PM, Jan Beulich wrote:
> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>> --- a/docs/misc/xen-command-line.pandoc
>> +++ b/docs/misc/xen-command-line.pandoc
>> @@ -3096,3 +3096,12 @@ the hypervisor was compiled with `CONFIG_XSM` enabled.
>>   * `silo`: this will deny any unmediated communication channels between
>>     unprivileged VMs.  To choose this, the separated option in kconfig must also
>>     be enabled.
>> +
>> +### vmid (RISC-V)
>> +> `= <boolean>`
>> +
>> +> Default: `true`
>> +
>> +Permit Xen to use Virtual Machine Identifiers. This is an optimisation which
>> +tags the TLB entries with an ID per vcpu. This allows for guest TLB flushes
>> +to be performed without the overhead of a complete TLB flush.
> Please obey to the alphabetic sorting within this file.

Do we have a definition of alphabetical order? In xen-command-line.pandoc there is
`### vm-notify-window (Intel)`, and I would expect `### vmid` to appear before it.
Am I right? So the ordering should be: letters first, then numbers, then special
characters?

>
>> --- /dev/null
>> +++ b/xen/arch/riscv/vmid.c
>> @@ -0,0 +1,170 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +
>> +#include <xen/domain.h>
>> +#include <xen/init.h>
>> +#include <xen/sections.h>
>> +#include <xen/lib.h>
>> +#include <xen/param.h>
>> +#include <xen/percpu.h>
>> +
>> +#include <asm/atomic.h>
>> +#include <asm/csr.h>
>> +#include <asm/flushtlb.h>
>> +#include <asm/p2m.h>
>> +
>> +/* Xen command-line option to enable VMIDs */
>> +static bool __ro_after_init opt_vmid = true;
>> +boolean_param("vmid", opt_vmid);
>> +
>> +/*
>> + * VMIDs partition the physical TLB. In the current implementation VMIDs are
>> + * introduced to reduce the number of TLB flushes. Each time a guest-physical
>> + * address space changes, instead of flushing the TLB, a new VMID is
>> + * assigned. This reduces the number of TLB flushes to at most 1/#VMIDs.
>> + * The biggest advantage is that hot parts of the hypervisor's code and data
>> + * retain in the TLB.
>> + *
>> + * Sketch of the Implementation:
>> + *
>> + * VMIDs are a hart-local resource.  As preemption of VMIDs is not possible,
>> + * VMIDs are assigned in a round-robin scheme. To minimize the overhead of
>> + * VMID invalidation, at the time of a TLB flush, VMIDs are tagged with a
>> + * 64-bit generation. Only on a generation overflow the code needs to
>> + * invalidate all VMID information stored at the VCPUs with are run on the
>> + * specific physical processor. When this overflow appears VMID usage is
>> + * disabled to retain correctness.
>> + */
>> +
>> +/* Per-Hart VMID management. */
>> +struct vmid_data {
>> +   uint64_t generation;
>> +   uint16_t next_vmid;
>> +   uint16_t max_vmid;
>> +   bool used;
>> +};
>> +
>> +static DEFINE_PER_CPU(struct vmid_data, vmid_data);
>> +
>> +static unsigned int vmidlen_detect(void)
>> +{
>> +    unsigned int vmid_bits;
>> +    unsigned char gstage_mode = get_max_supported_mode();
>> +
>> +    /*
>> +     * According to the RISC-V Privileged Architecture Spec:
>> +     *   When MODE=Bare, guest physical addresses are equal to supervisor
>> +     *   physical addresses, and there is no further memory protection
>> +     *   for a guest virtual machine beyond the physical memory protection
>> +     *   scheme described in Section "Physical Memory Protection".
>> +     *   In this case, the remaining fields in hgatp must be set to zeros.
>> +     * Thereby it is necessary to set gstage_mode not equal to Bare.
>> +     */
>> +    ASSERT(gstage_mode != HGATP_MODE_OFF);
>> +    csr_write(CSR_HGATP,
>> +              MASK_INSR(gstage_mode, HGATP_MODE_MASK) | HGATP_VMID_MASK);
>> +    vmid_bits = MASK_EXTR(csr_read(CSR_HGATP), HGATP_VMID_MASK);
>> +    vmid_bits = flsl(vmid_bits);
>> +    csr_write(CSR_HGATP, _AC(0, UL));
>> +
>> +    /* local_hfence_gvma_all() will be called at the end of pre_gstage_init. */
>> +
>> +    return vmid_bits;
>> +}
>> +
>> +void vmid_init(void)
> With the presently sole caller being __init, this (and likely the helper above)
> could be __init. Iirc you intend to also call this as secondary harts come up,
> so this may be okay. But then it wants justifing in the description.

I will add the comment aobve vmidlen_detect():
/*
  * vmidlen_detect() is expected to be called during secondary hart bring-up,
  * so it should not be marked as __init.
  */

And the similar comment for vmid_init().

Thanks.

~ Oleksii



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

* Re: [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement
  2025-12-09  7:51     ` Jan Beulich
@ 2025-12-09 10:40       ` Oleksii Kurochko
  0 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-09 10:40 UTC (permalink / raw)
  To: Teddy Astie
  Cc: Andrew Cooper, Anthony PERARD, Michal Orzel, Julien Grall,
	Roger Pau Monné, Stefano Stabellini, Alistair Francis,
	Bob Eshleman, Connor Davis, xen-devel, Jan Beulich


On 12/9/25 8:51 AM, Jan Beulich wrote:
> On 08.12.2025 18:28, Teddy Astie wrote:
>> Le 24/11/2025 à 13:36, Oleksii Kurochko a écrit :
>>> diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
>>> index 34004ce282..6c4bfa3603 100644
>>> --- a/docs/misc/xen-command-line.pandoc
>>> +++ b/docs/misc/xen-command-line.pandoc
>>> @@ -3096,3 +3096,12 @@ the hypervisor was compiled with `CONFIG_XSM` enabled.
>>>    * `silo`: this will deny any unmediated communication channels between
>>>      unprivileged VMs.  To choose this, the separated option in kconfig must also
>>>      be enabled.
>>> +
>>> +### vmid (RISC-V)
>>> +> `= <boolean>`
>>> +
>>> +> Default: `true`
>>> +
>>> +Permit Xen to use Virtual Machine Identifiers. This is an optimisation which
>>> +tags the TLB entries with an ID per vcpu. This allows for guest TLB flushes
>>> +to be performed without the overhead of a complete TLB flush.
>> Should we regroup all asid/vpid/vmid (which are pretty much the same
>> thing with different names and for different arch) under a single
>> command-line option ?
> How would you name such an option, without losing it being recognized by people
> knowing the respective arch?

Teddy,

I would also like to note that, for RISC-V, both the terms ASID and VMID are used.
Therefore, I prefer to have separate command-line options and not overcomplicate
things. Furthermore, these options are used only in architecture-specific code,
so it would be best to follow the relevant architecture-specific documentation
and specifications.

~ Oleksii



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

* Re: [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement
  2025-12-09 10:35     ` Oleksii Kurochko
@ 2025-12-09 10:52       ` Jan Beulich
  0 siblings, 0 replies; 52+ messages in thread
From: Jan Beulich @ 2025-12-09 10:52 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Andrew Cooper, Anthony PERARD, Michal Orzel, Julien Grall,
	Roger Pau Monné, Stefano Stabellini, Alistair Francis,
	Bob Eshleman, Connor Davis, xen-devel

On 09.12.2025 11:35, Oleksii Kurochko wrote:
> On 12/8/25 5:31 PM, Jan Beulich wrote:
>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>> --- a/docs/misc/xen-command-line.pandoc
>>> +++ b/docs/misc/xen-command-line.pandoc
>>> @@ -3096,3 +3096,12 @@ the hypervisor was compiled with `CONFIG_XSM` enabled.
>>>   * `silo`: this will deny any unmediated communication channels between
>>>     unprivileged VMs.  To choose this, the separated option in kconfig must also
>>>     be enabled.
>>> +
>>> +### vmid (RISC-V)
>>> +> `= <boolean>`
>>> +
>>> +> Default: `true`
>>> +
>>> +Permit Xen to use Virtual Machine Identifiers. This is an optimisation which
>>> +tags the TLB entries with an ID per vcpu. This allows for guest TLB flushes
>>> +to be performed without the overhead of a complete TLB flush.
>> Please obey to the alphabetic sorting within this file.
> 
> Do we have a definition of alphabetical order? In xen-command-line.pandoc there is
> `### vm-notify-window (Intel)`, and I would expect `### vmid` to appear before it.
> Am I right? So the ordering should be: letters first, then numbers, then special
> characters?
This particular detail of sorting isn't well-defined, I don't think. If in doubt,
I'd use C locale sorting rules.

Jan


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

* Re: [PATCH v6 13/19] xen/riscv: Implement p2m_pte_from_mfn() and support PBMT configuration
  2025-11-24 12:33 ` [PATCH v6 13/19] xen/riscv: Implement p2m_pte_from_mfn() and support PBMT configuration Oleksii Kurochko
@ 2025-12-09 11:22   ` Jan Beulich
  0 siblings, 0 replies; 52+ messages in thread
From: Jan Beulich @ 2025-12-09 11:22 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 24.11.2025 13:33, Oleksii Kurochko wrote:
> This patch adds the initial logic for constructing PTEs from MFNs in the RISC-V
> p2m subsystem. It includes:
> - Implementation of p2m_pte_from_mfn(): Generates a valid PTE using the
>   given MFN, p2m_type_t, including permission encoding and PBMT attribute
>   setup.
> - New helper p2m_set_permission(): Encodes access rights (r, w, x) into the
>   PTE based on both p2m type and access permissions.
> - p2m_set_type(): Stores the p2m type in PTE's bits. The storage of types,
>   which don't fit PTE bits, will be implemented separately later.
> - Add detection of Svade extension to properly handle a possible page-fault
>   if A and D bits aren't set.
> 
> PBMT type encoding support:
> - Introduces an enum pbmt_type_t to represent the PBMT field values.
> - Maps types like p2m_mmio_direct_dev to p2m_mmio_direct_io, others default
>   to pbmt_pma.
> 
> Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>

Acked-by: Jan Beulich <jbeulich@suse.com>



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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-11-24 12:33 ` [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN Oleksii Kurochko
@ 2025-12-09 11:38   ` Jan Beulich
  2025-12-09 15:41     ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-09 11:38 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 24.11.2025 13:33, Oleksii Kurochko wrote:
> --- a/xen/arch/riscv/p2m.c
> +++ b/xen/arch/riscv/p2m.c
> @@ -1061,3 +1061,186 @@ int map_regions_p2mt(struct domain *d,
>  
>      return rc;
>  }
> +
> +/*
> + * p2m_get_entry() should always return the correct order value, even if an
> + * entry is not present (i.e. the GFN is outside the range):
> + *   [p2m->lowest_mapped_gfn, p2m->max_mapped_gfn]    (1)
> + *
> + * This ensures that callers of p2m_get_entry() can determine what range of
> + * address space would be altered by a corresponding p2m_set_entry().
> + * Also, it would help to avoid costly page walks for GFNs outside range (1).
> + *
> + * Therefore, this function returns true for GFNs outside range (1), and in
> + * that case the corresponding level is returned via the level_out argument.
> + * Otherwise, it returns false and p2m_get_entry() performs a page walk to
> + * find the proper entry.
> + */
> +static bool check_outside_boundary(const struct p2m_domain *p2m, gfn_t gfn,
> +                                   gfn_t boundary, bool is_lower,
> +                                   unsigned int *level_out)
> +{
> +    unsigned int level = P2M_ROOT_LEVEL(p2m);
> +    bool ret = false;
> +
> +    ASSERT(p2m);
> +
> +    if ( is_lower ? gfn_x(gfn) < gfn_x(boundary)
> +                  : gfn_x(gfn) > gfn_x(boundary) )
> +    {
> +        unsigned long mask = 0;
> +
> +        for ( ; level; level-- )
> +        {
> +            unsigned long masked_gfn;
> +
> +            mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
> +            masked_gfn = gfn_x(gfn) & mask;
> +            masked_gfn |= (is_lower * (BIT(P2M_LEVEL_ORDER(level), UL) - 1));

I fear I still don't fully understand this. I would have expected the same mask to
be used for setting / clearing bits (once inverted, obviously). Why would you clear
only some of the lower bits in one case but set all of them in the other?

Overall, this alternative of clearing / setting of bits may also better (more
clearly / readably) be expressed using if/else or a conditional operator.

> +            if ( is_lower ? masked_gfn < gfn_x(boundary)
> +                          : masked_gfn > gfn_x(boundary) )
> +                break;
> +        }
> +
> +        ret = true;
> +    }
> +
> +    if ( level_out )
> +        *level_out = level;
> +
> +    return ret;
> +}
> +
> +/*
> + * Get the details of a given gfn.
> + *
> + * If the entry is present, the associated MFN will be returned and the
> + * p2m type of the mapping.

There may be a word order issue in this sentence, or there are words missing
at the end. It more likely being the former, isn't the order being returned
also worth mentioning, ...

> + * The page_order will correspond to the order of the mapping in the page
> + * table (i.e it could be a superpage).

... since this really is a separate piece of commentary?

> + * If the entry is not present, INVALID_MFN will be returned and the
> + * page_order will be set according to the order of the invalid range.

... and type will be "invalid".

> + */
> +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
> +                           p2m_type_t *t,
> +                           unsigned int *page_order)
> +{
> +    unsigned int level = 0;
> +    pte_t entry, *table;
> +    int rc;
> +    mfn_t mfn = INVALID_MFN;
> +    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
> +
> +    ASSERT(p2m_is_locked(p2m));
> +
> +    if ( t )
> +        *t = p2m_invalid;

The sole caller passes non-NULL right now. Are you having patches pending
where NULL would be passed? Else, this being a static helper, I'd suggest
to drop the check here (and the other one further down).

Jan


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

* Re: [PATCH v6 11/19] xen/riscv: implement p2m_set_range()
  2025-12-08 16:52   ` Jan Beulich
@ 2025-12-09 11:47     ` Oleksii Kurochko
  0 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-09 11:47 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/8/25 5:52 PM, Jan Beulich wrote:
> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>> @@ -28,6 +36,77 @@ unsigned char get_max_supported_mode(void)
>>       return max_gstage_mode.mode;
>>   }
>>   
>> +static inline unsigned int calc_offset(const struct p2m_domain *p2m,
>> +                                       const unsigned int lvl,
>> +                                       const paddr_t gpa)
>> +{
>> +    unsigned int off = (gpa >> P2M_GFN_LEVEL_SHIFT(lvl)) &
>> +                       P2M_TABLE_OFFSET(p2m, lvl);
>> +
>> +    /*
>> +     * For P2M_ROOT_LEVEL, `offset` ranges from 0 to 2047, since the root
>> +     * page table spans 4 consecutive 4KB pages.
>> +     * We want to return an index within one of these 4 pages.
>> +     * The specific page to use is determined by `p2m_get_root_pointer()`.
>> +     *
>> +     * Example: if `offset == 512`:
>> +     *  - A single 4KB page holds 512 entries.
>> +     *  - Therefore, entry 512 corresponds to index 0 of the second page.
>> +     *
>> +     * At all other levels, only one page is allocated, and `offset` is
>> +     * always in the range 0 to 511, since the VPN is 9 bits long.
>> +     */
>> +    return off & (PAGETABLE_ENTRIES - 1);
>> +}
>> +
>> +#define P2M_MAX_ROOT_LEVEL 5
>> +
>> +#define P2M_BUILD_LEVEL_OFFSETS(p2m, var, addr) \
>> +    unsigned int var[P2M_MAX_ROOT_LEVEL] = {-1}; \
> What use is this initializer? Slot 0 ...

I wanted a way to detect if something mistakenly tries to access an uninitialized
array element, and using -1 would help distinguish whether an element was truly
uninitialized or if calc_offset() simply returned 0. However, you’re right, it
doesn’t work because only Slot 0 is initialized with -1. So...

>
>> +    BUG_ON(P2M_ROOT_LEVEL(p2m) >= P2M_MAX_ROOT_LEVEL); \
>> +    for ( unsigned int i = 0; i <= P2M_ROOT_LEVEL(p2m); i++ ) \
>> +        var[i] = calc_offset(p2m, i, addr);
> ... is guaranteed to be written to, afaics. With this simplified (or an
> explanation given for why it is needed)

... I will just drop an initializer and let var[] to be initialized with 0s.

> Acked-by: Jan Beulich <jbeulich@suse.com>

Thanks.

~ Oleksii



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

* Re: [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type
  2025-11-24 12:33 ` [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type Oleksii Kurochko
@ 2025-12-09 13:47   ` Jan Beulich
  2025-12-09 17:09     ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-09 13:47 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 24.11.2025 13:33, Oleksii Kurochko wrote:
> @@ -374,24 +399,107 @@ static struct page_info *p2m_alloc_page(struct p2m_domain *p2m)
>      return pg;
>  }
>  
> -static int p2m_set_type(pte_t *pte, p2m_type_t t)
> +/*
> + * `pte` – PTE entry for which the type `t` will be stored.
> + *
> + * If `t` is `p2m_ext_storage`, both `ctx` and `p2m` must be provided;
> + * otherwise, only p2m may be NULL.
> + */
> +static void p2m_set_type(pte_t *pte, p2m_type_t t,
> +                         struct p2m_pte_ctx *ctx,
> +                         struct p2m_domain *p2m)

I assume you having struct p2m_domain * separate from ctx here is because the
"get" counterpart wouldn't need it. The same is true for the level member,
though. I wonder therefore whether putting p2m in pte_ctx as well wouldn't
make for an overall cleaner interface. (But this is truly just a thought; I
don#t mean to insist here.)

>  {
> -    int rc = 0;
> +    struct page_info **md_pg;
> +    struct md_t *metadata = NULL;
>  
> -    if ( t > p2m_first_external )
> -        panic("unimplemeted\n");
> -    else
> -        pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
> +    ASSERT(p2m);
>  
> -    return rc;
> +    /*
> +     * It is sufficient to compare ctx->index with PAGETABLE_ENTRIES because,
> +     * even for the p2m root page table (which is a 16 KB page allocated as
> +     * four 4 KB pages), calc_offset() guarantees that the page-table index
> +     * will always fall within the range [0, 511].
> +     */
> +    ASSERT(ctx && ctx->index < PAGETABLE_ENTRIES);
> +
> +    /*
> +     * At the moment, p2m_get_root_pointer() returns one of four possible p2m
> +     * root pages, so there is no need to search for the correct ->pt_page
> +     * here.
> +     * Non-root page tables are 4 KB pages, so simply using ->pt_page is
> +     * sufficient.
> +     */
> +    md_pg = &ctx->pt_page->v.md.pg;
> +
> +    if ( !*md_pg && (t >= p2m_first_external) )
> +    {
> +        BUG_ON(ctx->level > P2M_MAX_SUPPORTED_LEVEL_MAPPING);
> +
> +        if ( ctx->level <= P2M_MAX_SUPPORTED_LEVEL_MAPPING )
> +        {
> +            /*
> +             * Since p2m_alloc_page() initializes an allocated page with zeros, p2m_invalid
> +             * is expected to have the value 0 as well. This ensures that if a metadata
> +             * page is accessed before being properly initialized, the correct type
> +             * (p2m_invalid in this case) will be returned.
> +             */

Nit: Line length.

Also imo "properly initialized" is ambiguous. The clearing of the page can already
count as such. No access to the page may happen ahead of this clearing.

> +            BUILD_BUG_ON(p2m_invalid);
> +
> +            *md_pg = p2m_alloc_page(p2m);
> +            if ( !*md_pg )
> +            {
> +                printk("%pd: can't allocate metadata page\n", p2m->domain);
> +                domain_crash(p2m->domain);
> +
> +                return;
> +            }
> +        }
> +    }
> +
> +    if ( *md_pg )
> +        metadata = __map_domain_page(*md_pg);
> +
> +    if ( t >= p2m_first_external )
> +    {
> +        metadata[ctx->index].type = t;
> +
> +        t = p2m_ext_storage;
> +    }
> +    else if ( metadata )
> +        metadata[ctx->index].type = p2m_invalid;
> +
> +    pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
> +
> +    unmap_domain_page(metadata);
>  }

Just to mention (towards future work): Once a metadata page goes back to be
entirely zero-filled, it could as well be hooked off and returned to the pool.
Not doing so may mean detaining an unused page indefinitely.

> -static p2m_type_t p2m_get_type(const pte_t pte)
> +/*
> + * `pte` -> PTE entry that stores the PTE's type.
> + *
> + * If the PTE's type is `p2m_ext_storage`, `ctx` should be provided;
> + * otherwise it could be NULL.
> + */
> +static p2m_type_t p2m_get_type(const pte_t pte, const struct p2m_pte_ctx *ctx)
>  {
>      p2m_type_t type = MASK_EXTR(pte.pte, P2M_TYPE_PTE_BITS_MASK);
>  
> +    /*
> +     * Since the PTE is initialized with all zeros by default, p2m_invalid must
> +     * have the value 0. This ensures that if p2m_get_type() is called for a GFN
> +     * that hasn't been initialized, the correct type (p2m_invalid in this case)
> +     * will be returned. It also guarantees that metadata won't be touched when
> +     * the GFN hasn't been initialized.
> +     */
> +    BUILD_BUG_ON(p2m_invalid);

I don't think comment and BUILD_BUG_ON() need repeating here. That's relevant
only when (zero-)initializing the page.

>      if ( type == p2m_ext_storage )
> -        panic("unimplemented\n");
> +    {
> +        const struct md_t *md = __map_domain_page(ctx->pt_page->v.md.pg);
> +
> +        type = md[ctx->index].type;

In exchange you may want to assert here that the type found is
>= p2m_first_external (as - supposedly - guaranteed by p2m_set_type()).

> @@ -792,6 +952,13 @@ static bool p2m_split_superpage(struct p2m_domain *p2m, pte_t *entry,
>          pte = *entry;
>          pte_set_mfn(&pte, mfn_add(mfn, i << level_order));
>  
> +        if ( MASK_EXTR(pte.pte, P2M_TYPE_PTE_BITS_MASK) == p2m_ext_storage )
> +        {
> +            p2m_pte_ctx.index = i;
> +
> +            p2m_set_type(&pte, old_type, &p2m_pte_ctx, p2m);

In order to re-use p2m_pte_ctx across multiple iterations without fully re-
initializing, you want the respective parameter of p2m_set_type() be pointer-
to-const.

> @@ -894,13 +1061,21 @@ static int p2m_set_entry(struct p2m_domain *p2m,
>      {
>          /* We need to split the original page. */
>          pte_t split_pte = *entry;
> +        struct page_info *tbl_pg = mfn_to_page(domain_page_map_to_mfn(table));
>  
>          ASSERT(pte_is_superpage(*entry, level));
>  
> -        if ( !p2m_split_superpage(p2m, &split_pte, level, target, offsets) )
> +        if ( !p2m_split_superpage(p2m, &split_pte, level, target, offsets,
> +                                  tbl_pg) )
>          {
> +            struct p2m_pte_ctx tmp_ctx = {
> +                .pt_page = tbl_pg,
> +                .index = offsets[level],
> +                .level = level,
> +            };

This, ...

> @@ -938,7 +1113,13 @@ static int p2m_set_entry(struct p2m_domain *p2m,
>          p2m_clean_pte(entry, p2m->clean_dcache);
>      else
>      {
> -        pte_t pte = p2m_pte_from_mfn(mfn, t, false);
> +        struct p2m_pte_ctx tmp_ctx = {
> +            .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
> +            .index = offsets[level],
> +            .level = level,
> +        };

... this, and ...

> @@ -974,7 +1155,15 @@ static int p2m_set_entry(struct p2m_domain *p2m,
>      if ( pte_is_valid(orig_pte) &&
>           (!pte_is_valid(*entry) ||
>            !mfn_eq(pte_get_mfn(*entry), pte_get_mfn(orig_pte))) )
> -        p2m_free_subtree(p2m, orig_pte, level);
> +    {
> +        struct p2m_pte_ctx tmp_ctx = {
> +            .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
> +            .index = offsets[level],
> +            .level = level,
> +        };

... this initializer are identical. Perhaps (sorry) it wasn't a good idea
after all to move the context variable from function scope to the more
narrow ones?

Jan


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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-09 11:38   ` Jan Beulich
@ 2025-12-09 15:41     ` Oleksii Kurochko
  2025-12-09 15:49       ` Jan Beulich
  0 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-09 15:41 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/9/25 12:38 PM, Jan Beulich wrote:
> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>> --- a/xen/arch/riscv/p2m.c
>> +++ b/xen/arch/riscv/p2m.c
>> @@ -1061,3 +1061,186 @@ int map_regions_p2mt(struct domain *d,
>>   
>>       return rc;
>>   }
>> +
>> +/*
>> + * p2m_get_entry() should always return the correct order value, even if an
>> + * entry is not present (i.e. the GFN is outside the range):
>> + *   [p2m->lowest_mapped_gfn, p2m->max_mapped_gfn]    (1)
>> + *
>> + * This ensures that callers of p2m_get_entry() can determine what range of
>> + * address space would be altered by a corresponding p2m_set_entry().
>> + * Also, it would help to avoid costly page walks for GFNs outside range (1).
>> + *
>> + * Therefore, this function returns true for GFNs outside range (1), and in
>> + * that case the corresponding level is returned via the level_out argument.
>> + * Otherwise, it returns false and p2m_get_entry() performs a page walk to
>> + * find the proper entry.
>> + */
>> +static bool check_outside_boundary(const struct p2m_domain *p2m, gfn_t gfn,
>> +                                   gfn_t boundary, bool is_lower,
>> +                                   unsigned int *level_out)
>> +{
>> +    unsigned int level = P2M_ROOT_LEVEL(p2m);
>> +    bool ret = false;
>> +
>> +    ASSERT(p2m);
>> +
>> +    if ( is_lower ? gfn_x(gfn) < gfn_x(boundary)
>> +                  : gfn_x(gfn) > gfn_x(boundary) )
>> +    {
>> +        unsigned long mask = 0;
>> +
>> +        for ( ; level; level-- )
>> +        {
>> +            unsigned long masked_gfn;
>> +
>> +            mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
>> +            masked_gfn = gfn_x(gfn) & mask;
>> +            masked_gfn |= (is_lower * (BIT(P2M_LEVEL_ORDER(level), UL) - 1));
> I fear I still don't fully understand this. I would have expected the same mask to
> be used for setting / clearing bits (once inverted, obviously). Why would you clear
> only some of the lower bits in one case but set all of them in the other?

Only when is_lower == true do we need to set the lower bits; in all other cases
this is not required, if i am not confusing something.

The idea is that if boundary = 0x1000 and gfn = 0x800, and is_lower == true,
then to return the correct level value we must set all lower bits of gfn to 1.
Otherwise, we would get level = root instead of level = 0 in this case.

I decided not to reuse mask to set the lower bits when is_lower == true, because
doing something like:

     mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
     masked_gfn = gfn_x(gfn) & mask;
     masked_gfn |= (is_lower * ~mask);

would allow ~mask to introduce 1s into the upper bits, which is not what we want.

> Overall, this alternative of clearing / setting of bits may also better (more
> clearly / readably) be expressed using if/else or a conditional operator.

Sure, I will rework it then, unless I have missed something in what I wrote above.

>> +            if ( is_lower ? masked_gfn < gfn_x(boundary)
>> +                          : masked_gfn > gfn_x(boundary) )
>> +                break;
>> +        }
>> +
>> +        ret = true;
>> +    }
>> +
>> +    if ( level_out )
>> +        *level_out = level;
>> +
>> +    return ret;
>> +}
>> +
>> +/*
>> + * Get the details of a given gfn.
>> + *
>> + * If the entry is present, the associated MFN will be returned and the
>> + * p2m type of the mapping.
> There may be a word order issue in this sentence, or there are words missing
> at the end. It more likely being the former, isn't the order being returned
> also worth mentioning, ...
>
>> + * The page_order will correspond to the order of the mapping in the page
>> + * table (i.e it could be a superpage).
> ... since this really is a separate piece of commentary?

I will reword it in the following way then:
  * If the entry is present, the associated MFN, the p2m type of the mapping,
  * and the page order of the mapping in the page table (i.e., it could be a
  * superpage) will be returned.

>
>> + * If the entry is not present, INVALID_MFN will be returned and the
>> + * page_order will be set according to the order of the invalid range.
> ... and type will be "invalid".

And this one I'll reword in the following way:

  * If the entry is not present, INVALID_MFN will be returned,
  * the page_order will be set according to the order of the invalid
  * range, and type will be p2m_invalid.


>
>> + */
>> +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
>> +                           p2m_type_t *t,
>> +                           unsigned int *page_order)
>> +{
>> +    unsigned int level = 0;
>> +    pte_t entry, *table;
>> +    int rc;
>> +    mfn_t mfn = INVALID_MFN;
>> +    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
>> +
>> +    ASSERT(p2m_is_locked(p2m));
>> +
>> +    if ( t )
>> +        *t = p2m_invalid;
> The sole caller passes non-NULL right now. Are you having patches pending
> where NULL would be passed? Else, this being a static helper, I'd suggest
> to drop the check here (and the other one further down).

I don’t have any such call in pending patches. I saw that Arm has a case
where it is called with t = NULL (https://elixir.bootlin.com/xen/v4.21.0/source/xen/arch/arm/mem_access.c#L64),
so I decided to keep the check.

What you wrote makes sense to me, and given that the mem_access code is
Arm-specific, RISC-V will probably never have the same situation.
However, it still seems reasonable to keep this check for flexibility,
so that we don’t risk a NULL-pointer dereference in the future or end up
needing to reintroduce the check (or providing an unused variable for a type)
later. Does that make sense?

~ Oleksii



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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-09 15:41     ` Oleksii Kurochko
@ 2025-12-09 15:49       ` Jan Beulich
  2025-12-10 11:36         ` Oleksii Kurochko
  2025-12-10 15:23         ` Oleksii Kurochko
  0 siblings, 2 replies; 52+ messages in thread
From: Jan Beulich @ 2025-12-09 15:49 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 09.12.2025 16:41, Oleksii Kurochko wrote:
> On 12/9/25 12:38 PM, Jan Beulich wrote:
>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>> --- a/xen/arch/riscv/p2m.c
>>> +++ b/xen/arch/riscv/p2m.c
>>> @@ -1061,3 +1061,186 @@ int map_regions_p2mt(struct domain *d,
>>>   
>>>       return rc;
>>>   }
>>> +
>>> +/*
>>> + * p2m_get_entry() should always return the correct order value, even if an
>>> + * entry is not present (i.e. the GFN is outside the range):
>>> + *   [p2m->lowest_mapped_gfn, p2m->max_mapped_gfn]    (1)
>>> + *
>>> + * This ensures that callers of p2m_get_entry() can determine what range of
>>> + * address space would be altered by a corresponding p2m_set_entry().
>>> + * Also, it would help to avoid costly page walks for GFNs outside range (1).
>>> + *
>>> + * Therefore, this function returns true for GFNs outside range (1), and in
>>> + * that case the corresponding level is returned via the level_out argument.
>>> + * Otherwise, it returns false and p2m_get_entry() performs a page walk to
>>> + * find the proper entry.
>>> + */
>>> +static bool check_outside_boundary(const struct p2m_domain *p2m, gfn_t gfn,
>>> +                                   gfn_t boundary, bool is_lower,
>>> +                                   unsigned int *level_out)
>>> +{
>>> +    unsigned int level = P2M_ROOT_LEVEL(p2m);
>>> +    bool ret = false;
>>> +
>>> +    ASSERT(p2m);
>>> +
>>> +    if ( is_lower ? gfn_x(gfn) < gfn_x(boundary)
>>> +                  : gfn_x(gfn) > gfn_x(boundary) )
>>> +    {
>>> +        unsigned long mask = 0;
>>> +
>>> +        for ( ; level; level-- )
>>> +        {
>>> +            unsigned long masked_gfn;
>>> +
>>> +            mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
>>> +            masked_gfn = gfn_x(gfn) & mask;
>>> +            masked_gfn |= (is_lower * (BIT(P2M_LEVEL_ORDER(level), UL) - 1));
>> I fear I still don't fully understand this. I would have expected the same mask to
>> be used for setting / clearing bits (once inverted, obviously). Why would you clear
>> only some of the lower bits in one case but set all of them in the other?
> 
> Only when is_lower == true do we need to set the lower bits; in all other cases
> this is not required, if i am not confusing something.

That wasn't my point though. I don't follow the !is_lower case: Why would you
clear only the bits for the given level, not all further down as well? Or am
I reading P2M_LEVEL_MASK() incorrectly?

> The idea is that if boundary = 0x1000 and gfn = 0x800, and is_lower == true,
> then to return the correct level value we must set all lower bits of gfn to 1.
> Otherwise, we would get level = root instead of level = 0 in this case.
> 
> I decided not to reuse mask to set the lower bits when is_lower == true, because
> doing something like:
> 
>      mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
>      masked_gfn = gfn_x(gfn) & mask;
>      masked_gfn |= (is_lower * ~mask);
> 
> would allow ~mask to introduce 1s into the upper bits, which is not what we want.

If you set "mask" such that it has suitably many of its low bits set then you
should be able to simply do

      if ( is_lower )
          masked_gfn = gfn_x(gfn) | mask;
      else
          masked_gfn = gfn_x(gfn) & ~mask;

>>> +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
>>> +                           p2m_type_t *t,
>>> +                           unsigned int *page_order)
>>> +{
>>> +    unsigned int level = 0;
>>> +    pte_t entry, *table;
>>> +    int rc;
>>> +    mfn_t mfn = INVALID_MFN;
>>> +    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
>>> +
>>> +    ASSERT(p2m_is_locked(p2m));
>>> +
>>> +    if ( t )
>>> +        *t = p2m_invalid;
>> The sole caller passes non-NULL right now. Are you having patches pending
>> where NULL would be passed? Else, this being a static helper, I'd suggest
>> to drop the check here (and the other one further down).
> 
> I don’t have any such call in pending patches. I saw that Arm has a case
> where it is called with t = NULL (https://elixir.bootlin.com/xen/v4.21.0/source/xen/arch/arm/mem_access.c#L64),
> so I decided to keep the check.
> 
> What you wrote makes sense to me, and given that the mem_access code is
> Arm-specific, RISC-V will probably never have the same situation.
> However, it still seems reasonable to keep this check for flexibility,
> so that we don’t risk a NULL-pointer dereference in the future or end up
> needing to reintroduce the check (or providing an unused variable for a type)
> later. Does that make sense?

To a degree. The other perspective is that the check is dead code right now,
and dead code is often disliked (e.g. by Misra). Introducing the check when
it becomes necessary is pretty simple.

Jan


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

* Re: [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type
  2025-12-09 13:47   ` Jan Beulich
@ 2025-12-09 17:09     ` Oleksii Kurochko
  2025-12-10  7:06       ` Jan Beulich
  0 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-09 17:09 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/9/25 2:47 PM, Jan Beulich wrote:
> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>> @@ -374,24 +399,107 @@ static struct page_info *p2m_alloc_page(struct p2m_domain *p2m)
>>       return pg;
>>   }
>>   
>> -static int p2m_set_type(pte_t *pte, p2m_type_t t)
>> +/*
>> + * `pte` – PTE entry for which the type `t` will be stored.
>> + *
>> + * If `t` is `p2m_ext_storage`, both `ctx` and `p2m` must be provided;
>> + * otherwise, only p2m may be NULL.
>> + */
>> +static void p2m_set_type(pte_t *pte, p2m_type_t t,
>> +                         struct p2m_pte_ctx *ctx,
>> +                         struct p2m_domain *p2m)
> I assume you having struct p2m_domain * separate from ctx here is because the
> "get" counterpart wouldn't need it. The same is true for the level member,
> though. I wonder therefore whether putting p2m in pte_ctx as well wouldn't
> make for an overall cleaner interface. (But this is truly just a thought; I
> don#t mean to insist here.)

I think it really makes sense to put p2m into pte_ctx.

Besides simplifying p2m_{set,get}_type(), it will also allow us to drop the p2m
argument from p2m_pte_from_mfn().

Let’s make this change now.

>>   {
>> -    int rc = 0;
>> +    struct page_info **md_pg;
>> +    struct md_t *metadata = NULL;
>>   
>> -    if ( t > p2m_first_external )
>> -        panic("unimplemeted\n");
>> -    else
>> -        pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
>> +    ASSERT(p2m);
>>   
>> -    return rc;
>> +    /*
>> +     * It is sufficient to compare ctx->index with PAGETABLE_ENTRIES because,
>> +     * even for the p2m root page table (which is a 16 KB page allocated as
>> +     * four 4 KB pages), calc_offset() guarantees that the page-table index
>> +     * will always fall within the range [0, 511].
>> +     */
>> +    ASSERT(ctx && ctx->index < PAGETABLE_ENTRIES);
>> +
>> +    /*
>> +     * At the moment, p2m_get_root_pointer() returns one of four possible p2m
>> +     * root pages, so there is no need to search for the correct ->pt_page
>> +     * here.
>> +     * Non-root page tables are 4 KB pages, so simply using ->pt_page is
>> +     * sufficient.
>> +     */
>> +    md_pg = &ctx->pt_page->v.md.pg;
>> +
>> +    if ( !*md_pg && (t >= p2m_first_external) )
>> +    {
>> +        BUG_ON(ctx->level > P2M_MAX_SUPPORTED_LEVEL_MAPPING);
>> +
>> +        if ( ctx->level <= P2M_MAX_SUPPORTED_LEVEL_MAPPING )
>> +        {
>> +            /*
>> +             * Since p2m_alloc_page() initializes an allocated page with zeros, p2m_invalid
>> +             * is expected to have the value 0 as well. This ensures that if a metadata
>> +             * page is accessed before being properly initialized, the correct type
>> +             * (p2m_invalid in this case) will be returned.
>> +             */
> Nit: Line length.
>
> Also imo "properly initialized" is ambiguous. The clearing of the page can already
> count as such. No access to the page may happen ahead of this clearing.

I will drop then this part of the comment:
+               This ensures that if a metadata
+             * page is accessed before being properly initialized, the correct type
+             * (p2m_invalid in this case) will be returned.

>
>> +            BUILD_BUG_ON(p2m_invalid);
>> +
>> +            *md_pg = p2m_alloc_page(p2m);
>> +            if ( !*md_pg )
>> +            {
>> +                printk("%pd: can't allocate metadata page\n", p2m->domain);
>> +                domain_crash(p2m->domain);
>> +
>> +                return;
>> +            }
>> +        }
>> +    }
>> +
>> +    if ( *md_pg )
>> +        metadata = __map_domain_page(*md_pg);
>> +
>> +    if ( t >= p2m_first_external )
>> +    {
>> +        metadata[ctx->index].type = t;
>> +
>> +        t = p2m_ext_storage;
>> +    }
>> +    else if ( metadata )
>> +        metadata[ctx->index].type = p2m_invalid;
>> +
>> +    pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
>> +
>> +    unmap_domain_page(metadata);
>>   }
> Just to mention (towards future work): Once a metadata page goes back to be
> entirely zero-filled, it could as well be hooked off and returned to the pool.
> Not doing so may mean detaining an unused page indefinitely.

Won’t that already happen when p2m_free_table() is called?

p2m_free_table() calls p2m_free_page(p2m, tbl_pg->v.md.pg), which in turn calls
paging_free_page(), and that returns the page to the pool by doing:
     page_list_add_tail(pg, &d->arch.paging.freelist);

>
>> -static p2m_type_t p2m_get_type(const pte_t pte)
>> +/*
>> + * `pte` -> PTE entry that stores the PTE's type.
>> + *
>> + * If the PTE's type is `p2m_ext_storage`, `ctx` should be provided;
>> + * otherwise it could be NULL.
>> + */
>> +static p2m_type_t p2m_get_type(const pte_t pte, const struct p2m_pte_ctx *ctx)
>>   {
>>       p2m_type_t type = MASK_EXTR(pte.pte, P2M_TYPE_PTE_BITS_MASK);
>>   
>> +    /*
>> +     * Since the PTE is initialized with all zeros by default, p2m_invalid must
>> +     * have the value 0. This ensures that if p2m_get_type() is called for a GFN
>> +     * that hasn't been initialized, the correct type (p2m_invalid in this case)
>> +     * will be returned. It also guarantees that metadata won't be touched when
>> +     * the GFN hasn't been initialized.
>> +     */
>> +    BUILD_BUG_ON(p2m_invalid);
> I don't think comment and BUILD_BUG_ON() need repeating here. That's relevant
> only when (zero-)initializing the page.
>
>>       if ( type == p2m_ext_storage )
>> -        panic("unimplemented\n");
>> +    {
>> +        const struct md_t *md = __map_domain_page(ctx->pt_page->v.md.pg);
>> +
>> +        type = md[ctx->index].type;
> In exchange you may want to assert here that the type found is
>> = p2m_first_external (as - supposedly - guaranteed by p2m_set_type()).

Make sense, will add the following after type = ...:
         /*
          * Since p2m_set_type() guarantees that the type will be greater than
          * p2m_first_external, just check that we received a valid type here.
          */
         ASSERT(type > p2m_first_external);

>> @@ -792,6 +952,13 @@ static bool p2m_split_superpage(struct p2m_domain *p2m, pte_t *entry,
>>           pte = *entry;
>>           pte_set_mfn(&pte, mfn_add(mfn, i << level_order));
>>   
>> +        if ( MASK_EXTR(pte.pte, P2M_TYPE_PTE_BITS_MASK) == p2m_ext_storage )
>> +        {
>> +            p2m_pte_ctx.index = i;
>> +
>> +            p2m_set_type(&pte, old_type, &p2m_pte_ctx, p2m);
> In order to re-use p2m_pte_ctx across multiple iterations without fully re-
> initializing, you want the respective parameter of p2m_set_type() be pointer-
> to-const.

Good point. I will update prototype of p2m_set_type() to:

static void p2m_set_type(...
                          const struct p2m_pte_ctx *ctx)

>
>> @@ -894,13 +1061,21 @@ static int p2m_set_entry(struct p2m_domain *p2m,
>>       {
>>           /* We need to split the original page. */
>>           pte_t split_pte = *entry;
>> +        struct page_info *tbl_pg = mfn_to_page(domain_page_map_to_mfn(table));
>>   
>>           ASSERT(pte_is_superpage(*entry, level));
>>   
>> -        if ( !p2m_split_superpage(p2m, &split_pte, level, target, offsets) )
>> +        if ( !p2m_split_superpage(p2m, &split_pte, level, target, offsets,
>> +                                  tbl_pg) )
>>           {
>> +            struct p2m_pte_ctx tmp_ctx = {
>> +                .pt_page = tbl_pg,
>> +                .index = offsets[level],
>> +                .level = level,
>> +            };
> This, ...
>
>> @@ -938,7 +1113,13 @@ static int p2m_set_entry(struct p2m_domain *p2m,
>>           p2m_clean_pte(entry, p2m->clean_dcache);
>>       else
>>       {
>> -        pte_t pte = p2m_pte_from_mfn(mfn, t, false);
>> +        struct p2m_pte_ctx tmp_ctx = {
>> +            .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
>> +            .index = offsets[level],
>> +            .level = level,
>> +        };
> ... this, and ...
>
>> @@ -974,7 +1155,15 @@ static int p2m_set_entry(struct p2m_domain *p2m,
>>       if ( pte_is_valid(orig_pte) &&
>>            (!pte_is_valid(*entry) ||
>>             !mfn_eq(pte_get_mfn(*entry), pte_get_mfn(orig_pte))) )
>> -        p2m_free_subtree(p2m, orig_pte, level);
>> +    {
>> +        struct p2m_pte_ctx tmp_ctx = {
>> +            .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
>> +            .index = offsets[level],
>> +            .level = level,
>> +        };
> ... this initializer are identical. Perhaps (sorry) it wasn't a good idea
> after all to move the context variable from function scope to the more
> narrow ones?

Probably, but I’m not 100% sure that making it a function-scope variable would be better.
Essentially, it would only help eliminate the last two declarations of `tmp_ctx`:
     struct p2m_pte_ctx tmp_ctx = {
         .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
         .index = offsets[level],
         .level = level,
     };

It would also require re-initializing (or as an option it could done once after the
superpage breakup happened; please look the diff below) all the fields (except the
newly added `tmp_ctx.p2m`) inside the case where a superpage breakup is needed:
     /*
      * If we are here with level > target, we must be at a leaf node,
      * and we need to break up the superpage.
      */
     if (level > target)
In this case, `index`, `level`, and `pt_page` will be changed.

It seems slightly better to use a function-scope variable, so I can rework the code to have
the following:
@@ -1049,6 +1047,8 @@ static int p2m_set_entry(struct p2m_domain *p2m,
  
      entry = table + offsets[level];
  
+    tmp_ctx.p2m = p2m;
+
      /*
       * If we are here with level > target, we must be at a leaf node,
       * and we need to break up the superpage.
@@ -1064,11 +1064,9 @@ static int p2m_set_entry(struct p2m_domain *p2m,
          if ( !p2m_split_superpage(p2m, &split_pte, level, target, offsets,
                                    tbl_pg) )
          {
-            struct p2m_pte_ctx tmp_ctx = {
-                .pt_page = tbl_pg,
-                .index = offsets[level],
-                .level = level,
-            };
+            tmp_ctx.pt_page = tbl_pg;
+            tmp_ctx.index = offsets[level];
+            tmp_ctx.level = level;
  
              /* Free the allocated sub-tree */
              p2m_free_subtree(p2m, split_pte, &tmp_ctx);
@@ -1097,6 +1095,10 @@ static int p2m_set_entry(struct p2m_domain *p2m,
          entry = table + offsets[level];
      }
  
+    tmp_ctx.pt_page = mfn_to_page(domain_page_map_to_mfn(table));
+    tmp_ctx.index = offsets[level];
+    tmp_ctx.level = level;
+
      /*
       * We should always be there with the correct level because all the
       * intermediate tables have been installed if necessary.
@@ -1109,13 +1111,7 @@ static int p2m_set_entry(struct p2m_domain *p2m,
          p2m_clean_pte(entry, p2m->clean_dcache);
      else
      {
-        struct p2m_pte_ctx tmp_ctx = {
-            .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
-            .index = offsets[level],
-            .level = level,
-        };
-
-        pte_t pte = p2m_pte_from_mfn(mfn, t, &tmp_ctx, p2m);
+        pte_t pte = p2m_pte_from_mfn(mfn, t, &tmp_ctx);
  
          p2m_write_pte(entry, pte, p2m->clean_dcache);
  
@@ -1152,12 +1148,6 @@ static int p2m_set_entry(struct p2m_domain *p2m,
           (!pte_is_valid(*entry) ||
            !mfn_eq(pte_get_mfn(*entry), pte_get_mfn(orig_pte))) )
      {
-        struct p2m_pte_ctx tmp_ctx = {
-            .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
-            .index = offsets[level],
-            .level = level,
-        };
-
          p2m_free_subtree(p2m, orig_pte, &tmp_ctx);
      }
  
@@ -1363,6 +1353,7 @@ static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
                  .pt_page = mfn_to_page(domain_page_map_to_mfn(table)),
                  .index = offsets[level],
                  .level = level,
+                .p2m = p2m,
              };
  
              *t = p2m_get_type(entry, &p2m_pte_ctx);

Does it make sense?

Thanks.

~ Oleksii



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

* Re: [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type
  2025-12-09 17:09     ` Oleksii Kurochko
@ 2025-12-10  7:06       ` Jan Beulich
  2025-12-10 12:44         ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-10  7:06 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 09.12.2025 18:09, Oleksii Kurochko wrote:
> On 12/9/25 2:47 PM, Jan Beulich wrote:
>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>> +            *md_pg = p2m_alloc_page(p2m);
>>> +            if ( !*md_pg )
>>> +            {
>>> +                printk("%pd: can't allocate metadata page\n", p2m->domain);
>>> +                domain_crash(p2m->domain);
>>> +
>>> +                return;
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    if ( *md_pg )
>>> +        metadata = __map_domain_page(*md_pg);
>>> +
>>> +    if ( t >= p2m_first_external )
>>> +    {
>>> +        metadata[ctx->index].type = t;
>>> +
>>> +        t = p2m_ext_storage;
>>> +    }
>>> +    else if ( metadata )
>>> +        metadata[ctx->index].type = p2m_invalid;
>>> +
>>> +    pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
>>> +
>>> +    unmap_domain_page(metadata);
>>>   }
>> Just to mention (towards future work): Once a metadata page goes back to be
>> entirely zero-filled, it could as well be hooked off and returned to the pool.
>> Not doing so may mean detaining an unused page indefinitely.
> 
> Won’t that already happen when p2m_free_table() is called?

Well, that's when both page table and metadata table are freed. But what if a
leaf page table is moving back to holding all p2m_ram_rw mappings? Then the
metadata page is unused, but will remain allocated.

Jan


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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-09 15:49       ` Jan Beulich
@ 2025-12-10 11:36         ` Oleksii Kurochko
  2025-12-10 11:44           ` Jan Beulich
  2025-12-10 15:23         ` Oleksii Kurochko
  1 sibling, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-10 11:36 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/9/25 4:49 PM, Jan Beulich wrote:
> On 09.12.2025 16:41, Oleksii Kurochko wrote:
>> On 12/9/25 12:38 PM, Jan Beulich wrote:
>>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>>> --- a/xen/arch/riscv/p2m.c
>>>> +++ b/xen/arch/riscv/p2m.c
>>>> @@ -1061,3 +1061,186 @@ int map_regions_p2mt(struct domain *d,
>>>>    
>>>>        return rc;
>>>>    }
>>>> +
>>>> +/*
>>>> + * p2m_get_entry() should always return the correct order value, even if an
>>>> + * entry is not present (i.e. the GFN is outside the range):
>>>> + *   [p2m->lowest_mapped_gfn, p2m->max_mapped_gfn]    (1)
>>>> + *
>>>> + * This ensures that callers of p2m_get_entry() can determine what range of
>>>> + * address space would be altered by a corresponding p2m_set_entry().
>>>> + * Also, it would help to avoid costly page walks for GFNs outside range (1).
>>>> + *
>>>> + * Therefore, this function returns true for GFNs outside range (1), and in
>>>> + * that case the corresponding level is returned via the level_out argument.
>>>> + * Otherwise, it returns false and p2m_get_entry() performs a page walk to
>>>> + * find the proper entry.
>>>> + */
>>>> +static bool check_outside_boundary(const struct p2m_domain *p2m, gfn_t gfn,
>>>> +                                   gfn_t boundary, bool is_lower,
>>>> +                                   unsigned int *level_out)
>>>> +{
>>>> +    unsigned int level = P2M_ROOT_LEVEL(p2m);
>>>> +    bool ret = false;
>>>> +
>>>> +    ASSERT(p2m);
>>>> +
>>>> +    if ( is_lower ? gfn_x(gfn) < gfn_x(boundary)
>>>> +                  : gfn_x(gfn) > gfn_x(boundary) )
>>>> +    {
>>>> +        unsigned long mask = 0;
>>>> +
>>>> +        for ( ; level; level-- )
>>>> +        {
>>>> +            unsigned long masked_gfn;
>>>> +
>>>> +            mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
>>>> +            masked_gfn = gfn_x(gfn) & mask;
>>>> +            masked_gfn |= (is_lower * (BIT(P2M_LEVEL_ORDER(level), UL) - 1));
>>> I fear I still don't fully understand this. I would have expected the same mask to
>>> be used for setting / clearing bits (once inverted, obviously). Why would you clear
>>> only some of the lower bits in one case but set all of them in the other?
>> Only when is_lower == true do we need to set the lower bits; in all other cases
>> this is not required, if i am not confusing something.
> That wasn't my point though. I don't follow the !is_lower case: Why would you
> clear only the bits for the given level, not all further down as well? Or am
> I reading P2M_LEVEL_MASK() incorrectly?

Maybe I am still misunderstanding your question, but let’s consider what happens
in the loop in the case of !is_lower.

P2M_LEVEL_MASK() returns the mask for a given level, so:

   P2M_LEVEL_MASK(2) = 0x1FFC0000000
   P2M_LEVEL_MASK(1) = 0x0003FE00000
   P2M_LEVEL_MASK(0) = 0x000001FF000  (not really used/checked, because if we need
                                       to calculate it, we already know we are at
                                       level 0)

Since we accumulate the mask across iterations, we get:

   level 2: mask = 0x1FFC0000000
   level 1: mask = 0x1FFFFE00000
   level 0: doesn’t matter for the same reason as above.

So, in the !is_lower case, it is clearing only the low bits for the current level.
On each iteration, we get only the portion of the GFN that corresponds to the
current level, plus the portions from previous level(s) if the level is not the root.


>
>> The idea is that if boundary = 0x1000 and gfn = 0x800, and is_lower == true,
>> then to return the correct level value we must set all lower bits of gfn to 1.
>> Otherwise, we would get level = root instead of level = 0 in this case.
>>
>> I decided not to reuse mask to set the lower bits when is_lower == true, because
>> doing something like:
>>
>>       mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
>>       masked_gfn = gfn_x(gfn) & mask;
>>       masked_gfn |= (is_lower * ~mask);
>>
>> would allow ~mask to introduce 1s into the upper bits, which is not what we want.
> If you set "mask" such that it has suitably many of its low bits set then you
> should be able to simply do
>
>        if ( is_lower )
>            masked_gfn = gfn_x(gfn) | mask;
>        else
>            masked_gfn = gfn_x(gfn) & ~mask;

So, if I understand correctly, your suggestion is to calculate the mask as follows:
   level 2: mask = 0x3fffffff
   level 1: mask = 0x001fffff
(i.e., mask = BIT(P2M_GFN_LEVEL_SHIFT(level), UL) - 1)

I agree that this works fully in the is_lower case, but it may cause issues
in the !is_lower case. According to the spec, the (guest) physical address is
56 bits (and the corresponding GFN is 44 bits). My concern is that bits above
bit 44 must be zero. However, ~mask would have all higher bits set to 1, so
those (above bit 44) upper bits would not be cleared.

Perhaps this is not an issue at all, since a GFN larger than 44 bits should be
considered invalid. In that case, it may be sufficient for check_outside_boundary()
to ensure something like:
   ASSERT(gfn_x(gfn) < (BIT(PADDR_BITS - PAGE_SHIFT + 1, UL) - 1));

Does it make sense or I still continue to confuse something?


>
>>>> +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
>>>> +                           p2m_type_t *t,
>>>> +                           unsigned int *page_order)
>>>> +{
>>>> +    unsigned int level = 0;
>>>> +    pte_t entry, *table;
>>>> +    int rc;
>>>> +    mfn_t mfn = INVALID_MFN;
>>>> +    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
>>>> +
>>>> +    ASSERT(p2m_is_locked(p2m));
>>>> +
>>>> +    if ( t )
>>>> +        *t = p2m_invalid;
>>> The sole caller passes non-NULL right now. Are you having patches pending
>>> where NULL would be passed? Else, this being a static helper, I'd suggest
>>> to drop the check here (and the other one further down).
>> I don’t have any such call in pending patches. I saw that Arm has a case
>> where it is called with t = NULL (https://elixir.bootlin.com/xen/v4.21.0/source/xen/arch/arm/mem_access.c#L64),
>> so I decided to keep the check.
>>
>> What you wrote makes sense to me, and given that the mem_access code is
>> Arm-specific, RISC-V will probably never have the same situation.
>> However, it still seems reasonable to keep this check for flexibility,
>> so that we don’t risk a NULL-pointer dereference in the future or end up
>> needing to reintroduce the check (or providing an unused variable for a type)
>> later. Does that make sense?
> To a degree. The other perspective is that the check is dead code right now,
> and dead code is often disliked (e.g. by Misra). Introducing the check when
> it becomes necessary is pretty simple.

Then it makes sense to me to drop the check for now.

Thanks.

~ Oleksii



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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-10 11:36         ` Oleksii Kurochko
@ 2025-12-10 11:44           ` Jan Beulich
  2025-12-10 12:19             ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-10 11:44 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 10.12.2025 12:36, Oleksii Kurochko wrote:
> On 12/9/25 4:49 PM, Jan Beulich wrote:
>> On 09.12.2025 16:41, Oleksii Kurochko wrote:
>>> On 12/9/25 12:38 PM, Jan Beulich wrote:
>>>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>>>> --- a/xen/arch/riscv/p2m.c
>>>>> +++ b/xen/arch/riscv/p2m.c
>>>>> @@ -1061,3 +1061,186 @@ int map_regions_p2mt(struct domain *d,
>>>>>    
>>>>>        return rc;
>>>>>    }
>>>>> +
>>>>> +/*
>>>>> + * p2m_get_entry() should always return the correct order value, even if an
>>>>> + * entry is not present (i.e. the GFN is outside the range):
>>>>> + *   [p2m->lowest_mapped_gfn, p2m->max_mapped_gfn]    (1)
>>>>> + *
>>>>> + * This ensures that callers of p2m_get_entry() can determine what range of
>>>>> + * address space would be altered by a corresponding p2m_set_entry().
>>>>> + * Also, it would help to avoid costly page walks for GFNs outside range (1).
>>>>> + *
>>>>> + * Therefore, this function returns true for GFNs outside range (1), and in
>>>>> + * that case the corresponding level is returned via the level_out argument.
>>>>> + * Otherwise, it returns false and p2m_get_entry() performs a page walk to
>>>>> + * find the proper entry.
>>>>> + */
>>>>> +static bool check_outside_boundary(const struct p2m_domain *p2m, gfn_t gfn,
>>>>> +                                   gfn_t boundary, bool is_lower,
>>>>> +                                   unsigned int *level_out)
>>>>> +{
>>>>> +    unsigned int level = P2M_ROOT_LEVEL(p2m);
>>>>> +    bool ret = false;
>>>>> +
>>>>> +    ASSERT(p2m);
>>>>> +
>>>>> +    if ( is_lower ? gfn_x(gfn) < gfn_x(boundary)
>>>>> +                  : gfn_x(gfn) > gfn_x(boundary) )
>>>>> +    {
>>>>> +        unsigned long mask = 0;
>>>>> +
>>>>> +        for ( ; level; level-- )
>>>>> +        {
>>>>> +            unsigned long masked_gfn;
>>>>> +
>>>>> +            mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
>>>>> +            masked_gfn = gfn_x(gfn) & mask;
>>>>> +            masked_gfn |= (is_lower * (BIT(P2M_LEVEL_ORDER(level), UL) - 1));
>>>> I fear I still don't fully understand this. I would have expected the same mask to
>>>> be used for setting / clearing bits (once inverted, obviously). Why would you clear
>>>> only some of the lower bits in one case but set all of them in the other?
>>> Only when is_lower == true do we need to set the lower bits; in all other cases
>>> this is not required, if i am not confusing something.
>> That wasn't my point though. I don't follow the !is_lower case: Why would you
>> clear only the bits for the given level, not all further down as well? Or am
>> I reading P2M_LEVEL_MASK() incorrectly?
> 
> Maybe I am still misunderstanding your question, but let’s consider what happens
> in the loop in the case of !is_lower.
> 
> P2M_LEVEL_MASK() returns the mask for a given level, so:
> 
>    P2M_LEVEL_MASK(2) = 0x1FFC0000000
>    P2M_LEVEL_MASK(1) = 0x0003FE00000
>    P2M_LEVEL_MASK(0) = 0x000001FF000  (not really used/checked, because if we need
>                                        to calculate it, we already know we are at
>                                        level 0)
> 
> Since we accumulate the mask across iterations, we get:
> 
>    level 2: mask = 0x1FFC0000000
>    level 1: mask = 0x1FFFFE00000
>    level 0: doesn’t matter for the same reason as above.
> 
> So, in the !is_lower case, it is clearing only the low bits for the current level.
> On each iteration, we get only the portion of the GFN that corresponds to the
> current level, plus the portions from previous level(s) if the level is not the root.

But then you accumulate only for the ANDing, whereas you calculate the same mask
from scratch for ORing. That's inefficient and confusing imo.

>>> The idea is that if boundary = 0x1000 and gfn = 0x800, and is_lower == true,
>>> then to return the correct level value we must set all lower bits of gfn to 1.
>>> Otherwise, we would get level = root instead of level = 0 in this case.
>>>
>>> I decided not to reuse mask to set the lower bits when is_lower == true, because
>>> doing something like:
>>>
>>>       mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
>>>       masked_gfn = gfn_x(gfn) & mask;
>>>       masked_gfn |= (is_lower * ~mask);
>>>
>>> would allow ~mask to introduce 1s into the upper bits, which is not what we want.
>> If you set "mask" such that it has suitably many of its low bits set then you
>> should be able to simply do
>>
>>        if ( is_lower )
>>            masked_gfn = gfn_x(gfn) | mask;
>>        else
>>            masked_gfn = gfn_x(gfn) & ~mask;
> 
> So, if I understand correctly, your suggestion is to calculate the mask as follows:
>    level 2: mask = 0x3fffffff
>    level 1: mask = 0x001fffff
> (i.e., mask = BIT(P2M_GFN_LEVEL_SHIFT(level), UL) - 1)

Yes.

> I agree that this works fully in the is_lower case, but it may cause issues
> in the !is_lower case. According to the spec, the (guest) physical address is
> 56 bits (and the corresponding GFN is 44 bits). My concern is that bits above
> bit 44 must be zero. However, ~mask would have all higher bits set to 1, so
> those (above bit 44) upper bits would not be cleared.
> 
> Perhaps this is not an issue at all, since a GFN larger than 44 bits should be
> considered invalid. In that case, it may be sufficient for check_outside_boundary()
> to ensure something like:
>    ASSERT(gfn_x(gfn) < (BIT(PADDR_BITS - PAGE_SHIFT + 1, UL) - 1));

Invalid GFNs you want to make sure you reject earlier, if they can make it
into here. Only if elsewhere you have code to reject them, you can add such
an assertion here. (Recall: You may not assert on guest controlled input.)

Jan


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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-10 11:44           ` Jan Beulich
@ 2025-12-10 12:19             ` Oleksii Kurochko
  0 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-10 12:19 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/10/25 12:44 PM, Jan Beulich wrote:
> On 10.12.2025 12:36, Oleksii Kurochko wrote:
>> On 12/9/25 4:49 PM, Jan Beulich wrote:
>>> On 09.12.2025 16:41, Oleksii Kurochko wrote:
>>>> On 12/9/25 12:38 PM, Jan Beulich wrote:
>>>>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>>>>> --- a/xen/arch/riscv/p2m.c
>>>>>> +++ b/xen/arch/riscv/p2m.c
>>>>>> @@ -1061,3 +1061,186 @@ int map_regions_p2mt(struct domain *d,
>>>>>>     
>>>>>>         return rc;
>>>>>>     }
>>>>>> +
>>>>>> +/*
>>>>>> + * p2m_get_entry() should always return the correct order value, even if an
>>>>>> + * entry is not present (i.e. the GFN is outside the range):
>>>>>> + *   [p2m->lowest_mapped_gfn, p2m->max_mapped_gfn]    (1)
>>>>>> + *
>>>>>> + * This ensures that callers of p2m_get_entry() can determine what range of
>>>>>> + * address space would be altered by a corresponding p2m_set_entry().
>>>>>> + * Also, it would help to avoid costly page walks for GFNs outside range (1).
>>>>>> + *
>>>>>> + * Therefore, this function returns true for GFNs outside range (1), and in
>>>>>> + * that case the corresponding level is returned via the level_out argument.
>>>>>> + * Otherwise, it returns false and p2m_get_entry() performs a page walk to
>>>>>> + * find the proper entry.
>>>>>> + */
>>>>>> +static bool check_outside_boundary(const struct p2m_domain *p2m, gfn_t gfn,
>>>>>> +                                   gfn_t boundary, bool is_lower,
>>>>>> +                                   unsigned int *level_out)
>>>>>> +{
>>>>>> +    unsigned int level = P2M_ROOT_LEVEL(p2m);
>>>>>> +    bool ret = false;
>>>>>> +
>>>>>> +    ASSERT(p2m);
>>>>>> +
>>>>>> +    if ( is_lower ? gfn_x(gfn) < gfn_x(boundary)
>>>>>> +                  : gfn_x(gfn) > gfn_x(boundary) )
>>>>>> +    {
>>>>>> +        unsigned long mask = 0;
>>>>>> +
>>>>>> +        for ( ; level; level-- )
>>>>>> +        {
>>>>>> +            unsigned long masked_gfn;
>>>>>> +
>>>>>> +            mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
>>>>>> +            masked_gfn = gfn_x(gfn) & mask;
>>>>>> +            masked_gfn |= (is_lower * (BIT(P2M_LEVEL_ORDER(level), UL) - 1));
>>>>> I fear I still don't fully understand this. I would have expected the same mask to
>>>>> be used for setting / clearing bits (once inverted, obviously). Why would you clear
>>>>> only some of the lower bits in one case but set all of them in the other?
>>>> Only when is_lower == true do we need to set the lower bits; in all other cases
>>>> this is not required, if i am not confusing something.
>>> That wasn't my point though. I don't follow the !is_lower case: Why would you
>>> clear only the bits for the given level, not all further down as well? Or am
>>> I reading P2M_LEVEL_MASK() incorrectly?
>> Maybe I am still misunderstanding your question, but let’s consider what happens
>> in the loop in the case of !is_lower.
>>
>> P2M_LEVEL_MASK() returns the mask for a given level, so:
>>
>>     P2M_LEVEL_MASK(2) = 0x1FFC0000000
>>     P2M_LEVEL_MASK(1) = 0x0003FE00000
>>     P2M_LEVEL_MASK(0) = 0x000001FF000  (not really used/checked, because if we need
>>                                         to calculate it, we already know we are at
>>                                         level 0)
>>
>> Since we accumulate the mask across iterations, we get:
>>
>>     level 2: mask = 0x1FFC0000000
>>     level 1: mask = 0x1FFFFE00000
>>     level 0: doesn’t matter for the same reason as above.
>>
>> So, in the !is_lower case, it is clearing only the low bits for the current level.
>> On each iteration, we get only the portion of the GFN that corresponds to the
>> current level, plus the portions from previous level(s) if the level is not the root.
> But then you accumulate only for the ANDing, whereas you calculate the same mask
> from scratch for ORing. That's inefficient and confusing imo.

Agree that it isn't really efficient and it is confusing, so lets stick to suggested
way below.

>
>>>> The idea is that if boundary = 0x1000 and gfn = 0x800, and is_lower == true,
>>>> then to return the correct level value we must set all lower bits of gfn to 1.
>>>> Otherwise, we would get level = root instead of level = 0 in this case.
>>>>
>>>> I decided not to reuse mask to set the lower bits when is_lower == true, because
>>>> doing something like:
>>>>
>>>>        mask |= PFN_DOWN(P2M_LEVEL_MASK(p2m, level));
>>>>        masked_gfn = gfn_x(gfn) & mask;
>>>>        masked_gfn |= (is_lower * ~mask);
>>>>
>>>> would allow ~mask to introduce 1s into the upper bits, which is not what we want.
>>> If you set "mask" such that it has suitably many of its low bits set then you
>>> should be able to simply do
>>>
>>>         if ( is_lower )
>>>             masked_gfn = gfn_x(gfn) | mask;
>>>         else
>>>             masked_gfn = gfn_x(gfn) & ~mask;
>> So, if I understand correctly, your suggestion is to calculate the mask as follows:
>>     level 2: mask = 0x3fffffff
>>     level 1: mask = 0x001fffff
>> (i.e., mask = BIT(P2M_GFN_LEVEL_SHIFT(level), UL) - 1)
> Yes.
>
>> I agree that this works fully in the is_lower case, but it may cause issues
>> in the !is_lower case. According to the spec, the (guest) physical address is
>> 56 bits (and the corresponding GFN is 44 bits). My concern is that bits above
>> bit 44 must be zero. However, ~mask would have all higher bits set to 1, so
>> those (above bit 44) upper bits would not be cleared.
>>
>> Perhaps this is not an issue at all, since a GFN larger than 44 bits should be
>> considered invalid. In that case, it may be sufficient for check_outside_boundary()
>> to ensure something like:
>>     ASSERT(gfn_x(gfn) < (BIT(PADDR_BITS - PAGE_SHIFT + 1, UL) - 1));
> Invalid GFNs you want to make sure you reject earlier, if they can make it
> into here. Only if elsewhere you have code to reject them, you can add such
> an assertion here. (Recall: You may not assert on guest controlled input.)

Then I will reject invalid gfns at the start of p2m_get_entry() instead of ASSERT().

Thanks.

~ Oleksii



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

* Re: [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type
  2025-12-10  7:06       ` Jan Beulich
@ 2025-12-10 12:44         ` Oleksii Kurochko
  2025-12-11  9:39           ` Jan Beulich
  0 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-10 12:44 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/10/25 8:06 AM, Jan Beulich wrote:
> On 09.12.2025 18:09, Oleksii Kurochko wrote:
>> On 12/9/25 2:47 PM, Jan Beulich wrote:
>>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>>> +            *md_pg = p2m_alloc_page(p2m);
>>>> +            if ( !*md_pg )
>>>> +            {
>>>> +                printk("%pd: can't allocate metadata page\n", p2m->domain);
>>>> +                domain_crash(p2m->domain);
>>>> +
>>>> +                return;
>>>> +            }
>>>> +        }
>>>> +    }
>>>> +
>>>> +    if ( *md_pg )
>>>> +        metadata = __map_domain_page(*md_pg);
>>>> +
>>>> +    if ( t >= p2m_first_external )
>>>> +    {
>>>> +        metadata[ctx->index].type = t;
>>>> +
>>>> +        t = p2m_ext_storage;
>>>> +    }
>>>> +    else if ( metadata )
>>>> +        metadata[ctx->index].type = p2m_invalid;
>>>> +
>>>> +    pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
>>>> +
>>>> +    unmap_domain_page(metadata);
>>>>    }
>>> Just to mention (towards future work): Once a metadata page goes back to be
>>> entirely zero-filled, it could as well be hooked off and returned to the pool.
>>> Not doing so may mean detaining an unused page indefinitely.
>> Won’t that already happen when p2m_free_table() is called?
> Well, that's when both page table and metadata table are freed. But what if a
> leaf page table is moving back to holding all p2m_ram_rw mappings? Then the
> metadata page is unused, but will remain allocated.

Good point...

This could be a rather expensive operation, since in the code:
   +    else if ( metadata )
   +        metadata[ctx->index].type = p2m_invalid;
we would have to check all other metadata entries to determine whether they are
(p2m_invalid) or not, and return the page to the pool.

It would be nice to have something like metadata.used_entries_num, but the entire
page is used for type entries.
As an option, we could reserve 8 bits to store a counter of the number of used
entries in the metadata page, and then use metadata[0].used_entries_num to check
whether it is zero. If it is zero, we could simply return the metadata page to the
pool in the “else if (metadata)” case mentioned above.

How bad is this idea? Any better suggestions?

~ Oleksii



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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-09 15:49       ` Jan Beulich
  2025-12-10 11:36         ` Oleksii Kurochko
@ 2025-12-10 15:23         ` Oleksii Kurochko
  2025-12-11  9:34           ` Jan Beulich
  1 sibling, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-10 15:23 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/9/25 4:49 PM, Jan Beulich wrote:
>>>> +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
>>>> +                           p2m_type_t *t,
>>>> +                           unsigned int *page_order)
>>>> +{
>>>> +    unsigned int level = 0;
>>>> +    pte_t entry, *table;
>>>> +    int rc;
>>>> +    mfn_t mfn = INVALID_MFN;
>>>> +    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
>>>> +
>>>> +    ASSERT(p2m_is_locked(p2m));
>>>> +
>>>> +    if ( t )
>>>> +        *t = p2m_invalid;
>>> The sole caller passes non-NULL right now. Are you having patches pending
>>> where NULL would be passed? Else, this being a static helper, I'd suggest
>>> to drop the check here (and the other one further down).
>> I don’t have any such call in pending patches. I saw that Arm has a case
>> where it is called with t = NULL (https://elixir.bootlin.com/xen/v4.21.0/source/xen/arch/arm/mem_access.c#L64),
>> so I decided to keep the check.
>>
>> What you wrote makes sense to me, and given that the mem_access code is
>> Arm-specific, RISC-V will probably never have the same situation.
>> However, it still seems reasonable to keep this check for flexibility,
>> so that we don’t risk a NULL-pointer dereference in the future or end up
>> needing to reintroduce the check (or providing an unused variable for a type)
>> later. Does that make sense?
> To a degree. The other perspective is that the check is dead code right now,
> and dead code is often disliked (e.g. by Misra). Introducing the check when
> it becomes necessary is pretty simple.

Similar check might be needed for p2m_get_page_from_gfn(), because in the pending
patches I have a call where t = NULL:
unsigned long copy_to_guest_phys(struct domain *d, paddr_t gpa, void 
*buf, unsigned int len) { - return -EINVAL; + /* XXX needs to handle 
faults */ + paddr_t addr = gpa; + unsigned offset = PAGE_OFFSET(addr); + 
+ BUILD_BUG_ON((sizeof(addr)) < sizeof(vaddr_t)); + 
BUILD_BUG_ON((sizeof(addr)) < sizeof(paddr_t)); + + printk(XENLOG_INFO 
"copying d%d %#02lx-%#02lx to %#02lx-%#02lx\n", + d->domain_id, 
(unsigned long)buf, (unsigned long)buf+len, addr, + addr+len); + + while 
( len ) + { + void *p; + unsigned size = min(len, (unsigned)PAGE_SIZE - 
offset); + struct page_info *page; + + page = 
p2m_get_page_from_gfn(p2m_get_hostp2m(d) , gaddr_to_gfn(addr), NULL); + 
if ( page == NULL ) + return len; It now seems that I don’t actually 
need p2m_get_page_from_gfn(), as it is no longer used. I could drop it 
for now and reintroduce it later when it is truly needed by 
copy_to_guest_phys() or get_page_from_gfn(). Is it acceptable to keep 
p2m_get_page_from_gfn() as it is now, even without any current callers? 
Would it be considered dead code?

~ Oleksii



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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-10 15:23         ` Oleksii Kurochko
@ 2025-12-11  9:34           ` Jan Beulich
  2025-12-11 13:00             ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-11  9:34 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 10.12.2025 16:23, Oleksii Kurochko wrote:
> 
> On 12/9/25 4:49 PM, Jan Beulich wrote:
>>>>> +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
>>>>> +                           p2m_type_t *t,
>>>>> +                           unsigned int *page_order)
>>>>> +{
>>>>> +    unsigned int level = 0;
>>>>> +    pte_t entry, *table;
>>>>> +    int rc;
>>>>> +    mfn_t mfn = INVALID_MFN;
>>>>> +    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
>>>>> +
>>>>> +    ASSERT(p2m_is_locked(p2m));
>>>>> +
>>>>> +    if ( t )
>>>>> +        *t = p2m_invalid;
>>>> The sole caller passes non-NULL right now. Are you having patches pending
>>>> where NULL would be passed? Else, this being a static helper, I'd suggest
>>>> to drop the check here (and the other one further down).
>>> I don’t have any such call in pending patches. I saw that Arm has a case
>>> where it is called with t = NULL (https://elixir.bootlin.com/xen/v4.21.0/source/xen/arch/arm/mem_access.c#L64),
>>> so I decided to keep the check.
>>>
>>> What you wrote makes sense to me, and given that the mem_access code is
>>> Arm-specific, RISC-V will probably never have the same situation.
>>> However, it still seems reasonable to keep this check for flexibility,
>>> so that we don’t risk a NULL-pointer dereference in the future or end up
>>> needing to reintroduce the check (or providing an unused variable for a type)
>>> later. Does that make sense?
>> To a degree. The other perspective is that the check is dead code right now,
>> and dead code is often disliked (e.g. by Misra). Introducing the check when
>> it becomes necessary is pretty simple.
> 
> Similar check might be needed for p2m_get_page_from_gfn(), because in the pending
> patches I have a call where t = NULL:

My initial reaction would be "add the checking in that patch then".

> unsigned long copy_to_guest_phys(struct domain *d, paddr_t gpa, void 
> *buf, unsigned int len) { - return -EINVAL; + /* XXX needs to handle 
> faults */ + paddr_t addr = gpa; + unsigned offset = PAGE_OFFSET(addr); + 
> + BUILD_BUG_ON((sizeof(addr)) < sizeof(vaddr_t)); + 
> BUILD_BUG_ON((sizeof(addr)) < sizeof(paddr_t)); + + printk(XENLOG_INFO 
> "copying d%d %#02lx-%#02lx to %#02lx-%#02lx\n", + d->domain_id, 
> (unsigned long)buf, (unsigned long)buf+len, addr, + addr+len); + + while 
> ( len ) + { + void *p; + unsigned size = min(len, (unsigned)PAGE_SIZE - 
> offset); + struct page_info *page; + + page = 
> p2m_get_page_from_gfn(p2m_get_hostp2m(d) , gaddr_to_gfn(addr), NULL); + 
> if ( page == NULL ) + return len; It now seems that I don’t actually 
> need p2m_get_page_from_gfn(), as it is no longer used. I could drop it 
> for now and reintroduce it later when it is truly needed by 
> copy_to_guest_phys() or get_page_from_gfn(). Is it acceptable to keep 
> p2m_get_page_from_gfn() as it is now, even without any current callers? 
> Would it be considered dead code?

Sorry, as you may see your response was effectively unreadable. Looks
like all newlines were zapped for whatever reason, and then new were
ones inserted just to wrap the resulting long line.

Jan


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

* Re: [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type
  2025-12-10 12:44         ` Oleksii Kurochko
@ 2025-12-11  9:39           ` Jan Beulich
  2025-12-11 16:42             ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-11  9:39 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 10.12.2025 13:44, Oleksii Kurochko wrote:
> On 12/10/25 8:06 AM, Jan Beulich wrote:
>> On 09.12.2025 18:09, Oleksii Kurochko wrote:
>>> On 12/9/25 2:47 PM, Jan Beulich wrote:
>>>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>>>> +            *md_pg = p2m_alloc_page(p2m);
>>>>> +            if ( !*md_pg )
>>>>> +            {
>>>>> +                printk("%pd: can't allocate metadata page\n", p2m->domain);
>>>>> +                domain_crash(p2m->domain);
>>>>> +
>>>>> +                return;
>>>>> +            }
>>>>> +        }
>>>>> +    }
>>>>> +
>>>>> +    if ( *md_pg )
>>>>> +        metadata = __map_domain_page(*md_pg);
>>>>> +
>>>>> +    if ( t >= p2m_first_external )
>>>>> +    {
>>>>> +        metadata[ctx->index].type = t;
>>>>> +
>>>>> +        t = p2m_ext_storage;
>>>>> +    }
>>>>> +    else if ( metadata )
>>>>> +        metadata[ctx->index].type = p2m_invalid;
>>>>> +
>>>>> +    pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
>>>>> +
>>>>> +    unmap_domain_page(metadata);
>>>>>    }
>>>> Just to mention (towards future work): Once a metadata page goes back to be
>>>> entirely zero-filled, it could as well be hooked off and returned to the pool.
>>>> Not doing so may mean detaining an unused page indefinitely.
>>> Won’t that already happen when p2m_free_table() is called?
>> Well, that's when both page table and metadata table are freed. But what if a
>> leaf page table is moving back to holding all p2m_ram_rw mappings? Then the
>> metadata page is unused, but will remain allocated.
> 
> Good point...
> 
> This could be a rather expensive operation, since in the code:
>    +    else if ( metadata )
>    +        metadata[ctx->index].type = p2m_invalid;
> we would have to check all other metadata entries to determine whether they are
> (p2m_invalid) or not, and return the page to the pool.
> 
> It would be nice to have something like metadata.used_entries_num, but the entire
> page is used for type entries.
> As an option, we could reserve 8 bits to store a counter of the number of used
> entries in the metadata page, and then use metadata[0].used_entries_num to check
> whether it is zero. If it is zero, we could simply return the metadata page to the
> pool in the “else if (metadata)” case mentioned above.
> 
> How bad is this idea? Any better suggestions?

First, as said in my initial reply: This may not need taking care of right away.
It will need keeping in mind, of course.

As to suggestions - hardly any of the fields in struct page_info for the page
can be used when the page is a metadata one. Simply record the count there?

Finally, as to "rather expensive": Scanning a 4k page to hold all zeroes can't
be all that expensive? In any event that expensiveness needs weighing carefully
against the risk of getting the counter maintenance wrong.

Jan


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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-11  9:34           ` Jan Beulich
@ 2025-12-11 13:00             ` Oleksii Kurochko
  2025-12-11 13:40               ` Jan Beulich
  0 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-11 13:00 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/11/25 10:34 AM, Jan Beulich wrote:
> On 10.12.2025 16:23, Oleksii Kurochko wrote:
>> On 12/9/25 4:49 PM, Jan Beulich wrote:
>>>>>> +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
>>>>>> +                           p2m_type_t *t,
>>>>>> +                           unsigned int *page_order)
>>>>>> +{
>>>>>> +    unsigned int level = 0;
>>>>>> +    pte_t entry, *table;
>>>>>> +    int rc;
>>>>>> +    mfn_t mfn = INVALID_MFN;
>>>>>> +    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
>>>>>> +
>>>>>> +    ASSERT(p2m_is_locked(p2m));
>>>>>> +
>>>>>> +    if ( t )
>>>>>> +        *t = p2m_invalid;
>>>>> The sole caller passes non-NULL right now. Are you having patches pending
>>>>> where NULL would be passed? Else, this being a static helper, I'd suggest
>>>>> to drop the check here (and the other one further down).
>>>> I don’t have any such call in pending patches. I saw that Arm has a case
>>>> where it is called with t = NULL (https://elixir.bootlin.com/xen/v4.21.0/source/xen/arch/arm/mem_access.c#L64),
>>>> so I decided to keep the check.
>>>>
>>>> What you wrote makes sense to me, and given that the mem_access code is
>>>> Arm-specific, RISC-V will probably never have the same situation.
>>>> However, it still seems reasonable to keep this check for flexibility,
>>>> so that we don’t risk a NULL-pointer dereference in the future or end up
>>>> needing to reintroduce the check (or providing an unused variable for a type)
>>>> later. Does that make sense?
>>> To a degree. The other perspective is that the check is dead code right now,
>>> and dead code is often disliked (e.g. by Misra). Introducing the check when
>>> it becomes necessary is pretty simple.
>> Similar check might be needed for p2m_get_page_from_gfn(), because in the pending
>> patches I have a call where t = NULL:
> My initial reaction would be "add the checking in that patch then".
>
>> unsigned long copy_to_guest_phys(struct domain *d, paddr_t gpa, void
>> *buf, unsigned int len) { - return -EINVAL; + /* XXX needs to handle
>> faults */ + paddr_t addr = gpa; + unsigned offset = PAGE_OFFSET(addr); +
>> + BUILD_BUG_ON((sizeof(addr)) < sizeof(vaddr_t)); +
>> BUILD_BUG_ON((sizeof(addr)) < sizeof(paddr_t)); + + printk(XENLOG_INFO
>> "copying d%d %#02lx-%#02lx to %#02lx-%#02lx\n", + d->domain_id,
>> (unsigned long)buf, (unsigned long)buf+len, addr, + addr+len); + + while
>> ( len ) + { + void *p; + unsigned size = min(len, (unsigned)PAGE_SIZE -
>> offset); + struct page_info *page; + + page =
>> p2m_get_page_from_gfn(p2m_get_hostp2m(d) , gaddr_to_gfn(addr), NULL); +
>> if ( page == NULL ) + return len; It now seems that I don’t actually
>> need p2m_get_page_from_gfn(), as it is no longer used. I could drop it
>> for now and reintroduce it later when it is truly needed by
>> copy_to_guest_phys() or get_page_from_gfn(). Is it acceptable to keep
>> p2m_get_page_from_gfn() as it is now, even without any current callers?
>> Would it be considered dead code?
> Sorry, as you may see your response was effectively unreadable. Looks
> like all newlines were zapped for whatever reason, and then new were
> ones inserted just to wrap the resulting long line.

Fully unreadable. I wrote there that in the copy_to_guest_phys() here
(https://gitlab.com/xen-project/people/olkur/xen/-/blob/riscv-next-upstreaming/xen/arch/riscv/guestcopy.c?ref_type=heads#L31)
there is a call of p2m_get_page_from_gfn() with t = NULL.

It now seems that I don’t actually need p2m_get_page_from_gfn(), as it
is no longer used in this patch series. I could drop it for now and
reintroduce it later when it is truly needed by copy_to_guest_phys() or
get_page_from_gfn(). Is it acceptable to keep p2m_get_page_from_gfn()
as it is now (with adding a NULL check pointer for 't' argument),
even without any current callers?
Would it be considered dead code?

~ Oleksii



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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-11 13:00             ` Oleksii Kurochko
@ 2025-12-11 13:40               ` Jan Beulich
  2025-12-11 14:20                 ` Oleksii Kurochko
  0 siblings, 1 reply; 52+ messages in thread
From: Jan Beulich @ 2025-12-11 13:40 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 11.12.2025 14:00, Oleksii Kurochko wrote:
> 
> On 12/11/25 10:34 AM, Jan Beulich wrote:
>> On 10.12.2025 16:23, Oleksii Kurochko wrote:
>>> On 12/9/25 4:49 PM, Jan Beulich wrote:
>>>>>>> +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
>>>>>>> +                           p2m_type_t *t,
>>>>>>> +                           unsigned int *page_order)
>>>>>>> +{
>>>>>>> +    unsigned int level = 0;
>>>>>>> +    pte_t entry, *table;
>>>>>>> +    int rc;
>>>>>>> +    mfn_t mfn = INVALID_MFN;
>>>>>>> +    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
>>>>>>> +
>>>>>>> +    ASSERT(p2m_is_locked(p2m));
>>>>>>> +
>>>>>>> +    if ( t )
>>>>>>> +        *t = p2m_invalid;
>>>>>> The sole caller passes non-NULL right now. Are you having patches pending
>>>>>> where NULL would be passed? Else, this being a static helper, I'd suggest
>>>>>> to drop the check here (and the other one further down).
>>>>> I don’t have any such call in pending patches. I saw that Arm has a case
>>>>> where it is called with t = NULL (https://elixir.bootlin.com/xen/v4.21.0/source/xen/arch/arm/mem_access.c#L64),
>>>>> so I decided to keep the check.
>>>>>
>>>>> What you wrote makes sense to me, and given that the mem_access code is
>>>>> Arm-specific, RISC-V will probably never have the same situation.
>>>>> However, it still seems reasonable to keep this check for flexibility,
>>>>> so that we don’t risk a NULL-pointer dereference in the future or end up
>>>>> needing to reintroduce the check (or providing an unused variable for a type)
>>>>> later. Does that make sense?
>>>> To a degree. The other perspective is that the check is dead code right now,
>>>> and dead code is often disliked (e.g. by Misra). Introducing the check when
>>>> it becomes necessary is pretty simple.
>>> Similar check might be needed for p2m_get_page_from_gfn(), because in the pending
>>> patches I have a call where t = NULL:
>> My initial reaction would be "add the checking in that patch then".
>>
>>> unsigned long copy_to_guest_phys(struct domain *d, paddr_t gpa, void
>>> *buf, unsigned int len) { - return -EINVAL; + /* XXX needs to handle
>>> faults */ + paddr_t addr = gpa; + unsigned offset = PAGE_OFFSET(addr); +
>>> + BUILD_BUG_ON((sizeof(addr)) < sizeof(vaddr_t)); +
>>> BUILD_BUG_ON((sizeof(addr)) < sizeof(paddr_t)); + + printk(XENLOG_INFO
>>> "copying d%d %#02lx-%#02lx to %#02lx-%#02lx\n", + d->domain_id,
>>> (unsigned long)buf, (unsigned long)buf+len, addr, + addr+len); + + while
>>> ( len ) + { + void *p; + unsigned size = min(len, (unsigned)PAGE_SIZE -
>>> offset); + struct page_info *page; + + page =
>>> p2m_get_page_from_gfn(p2m_get_hostp2m(d) , gaddr_to_gfn(addr), NULL); +
>>> if ( page == NULL ) + return len; It now seems that I don’t actually
>>> need p2m_get_page_from_gfn(), as it is no longer used. I could drop it
>>> for now and reintroduce it later when it is truly needed by
>>> copy_to_guest_phys() or get_page_from_gfn(). Is it acceptable to keep
>>> p2m_get_page_from_gfn() as it is now, even without any current callers?
>>> Would it be considered dead code?
>> Sorry, as you may see your response was effectively unreadable. Looks
>> like all newlines were zapped for whatever reason, and then new were
>> ones inserted just to wrap the resulting long line.
> 
> Fully unreadable. I wrote there that in the copy_to_guest_phys() here
> (https://gitlab.com/xen-project/people/olkur/xen/-/blob/riscv-next-upstreaming/xen/arch/riscv/guestcopy.c?ref_type=heads#L31)
> there is a call of p2m_get_page_from_gfn() with t = NULL.
> 
> It now seems that I don’t actually need p2m_get_page_from_gfn(), as it
> is no longer used in this patch series. I could drop it for now and
> reintroduce it later when it is truly needed by copy_to_guest_phys() or
> get_page_from_gfn(). Is it acceptable to keep p2m_get_page_from_gfn()
> as it is now (with adding a NULL check pointer for 't' argument),
> even without any current callers?
> Would it be considered dead code?

As said, as long as no Misra checks are run on the RISC-V part of the
tree, no dead code concerns really exist. As to the NULL check - if the
sole (future) caller passes NULL, then why have the parameter at all?

Jan


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

* Re: [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN
  2025-12-11 13:40               ` Jan Beulich
@ 2025-12-11 14:20                 ` Oleksii Kurochko
  0 siblings, 0 replies; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-11 14:20 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/11/25 2:40 PM, Jan Beulich wrote:
> On 11.12.2025 14:00, Oleksii Kurochko wrote:
>> On 12/11/25 10:34 AM, Jan Beulich wrote:
>>> On 10.12.2025 16:23, Oleksii Kurochko wrote:
>>>> On 12/9/25 4:49 PM, Jan Beulich wrote:
>>>>>>>> +static mfn_t p2m_get_entry(struct p2m_domain *p2m, gfn_t gfn,
>>>>>>>> +                           p2m_type_t *t,
>>>>>>>> +                           unsigned int *page_order)
>>>>>>>> +{
>>>>>>>> +    unsigned int level = 0;
>>>>>>>> +    pte_t entry, *table;
>>>>>>>> +    int rc;
>>>>>>>> +    mfn_t mfn = INVALID_MFN;
>>>>>>>> +    P2M_BUILD_LEVEL_OFFSETS(p2m, offsets, gfn_to_gaddr(gfn));
>>>>>>>> +
>>>>>>>> +    ASSERT(p2m_is_locked(p2m));
>>>>>>>> +
>>>>>>>> +    if ( t )
>>>>>>>> +        *t = p2m_invalid;
>>>>>>> The sole caller passes non-NULL right now. Are you having patches pending
>>>>>>> where NULL would be passed? Else, this being a static helper, I'd suggest
>>>>>>> to drop the check here (and the other one further down).
>>>>>> I don’t have any such call in pending patches. I saw that Arm has a case
>>>>>> where it is called with t = NULL (https://elixir.bootlin.com/xen/v4.21.0/source/xen/arch/arm/mem_access.c#L64),
>>>>>> so I decided to keep the check.
>>>>>>
>>>>>> What you wrote makes sense to me, and given that the mem_access code is
>>>>>> Arm-specific, RISC-V will probably never have the same situation.
>>>>>> However, it still seems reasonable to keep this check for flexibility,
>>>>>> so that we don’t risk a NULL-pointer dereference in the future or end up
>>>>>> needing to reintroduce the check (or providing an unused variable for a type)
>>>>>> later. Does that make sense?
>>>>> To a degree. The other perspective is that the check is dead code right now,
>>>>> and dead code is often disliked (e.g. by Misra). Introducing the check when
>>>>> it becomes necessary is pretty simple.
>>>> Similar check might be needed for p2m_get_page_from_gfn(), because in the pending
>>>> patches I have a call where t = NULL:
>>> My initial reaction would be "add the checking in that patch then".
>>>
>>>> unsigned long copy_to_guest_phys(struct domain *d, paddr_t gpa, void
>>>> *buf, unsigned int len) { - return -EINVAL; + /* XXX needs to handle
>>>> faults */ + paddr_t addr = gpa; + unsigned offset = PAGE_OFFSET(addr); +
>>>> + BUILD_BUG_ON((sizeof(addr)) < sizeof(vaddr_t)); +
>>>> BUILD_BUG_ON((sizeof(addr)) < sizeof(paddr_t)); + + printk(XENLOG_INFO
>>>> "copying d%d %#02lx-%#02lx to %#02lx-%#02lx\n", + d->domain_id,
>>>> (unsigned long)buf, (unsigned long)buf+len, addr, + addr+len); + + while
>>>> ( len ) + { + void *p; + unsigned size = min(len, (unsigned)PAGE_SIZE -
>>>> offset); + struct page_info *page; + + page =
>>>> p2m_get_page_from_gfn(p2m_get_hostp2m(d) , gaddr_to_gfn(addr), NULL); +
>>>> if ( page == NULL ) + return len; It now seems that I don’t actually
>>>> need p2m_get_page_from_gfn(), as it is no longer used. I could drop it
>>>> for now and reintroduce it later when it is truly needed by
>>>> copy_to_guest_phys() or get_page_from_gfn(). Is it acceptable to keep
>>>> p2m_get_page_from_gfn() as it is now, even without any current callers?
>>>> Would it be considered dead code?
>>> Sorry, as you may see your response was effectively unreadable. Looks
>>> like all newlines were zapped for whatever reason, and then new were
>>> ones inserted just to wrap the resulting long line.
>> Fully unreadable. I wrote there that in the copy_to_guest_phys() here
>> (https://gitlab.com/xen-project/people/olkur/xen/-/blob/riscv-next-upstreaming/xen/arch/riscv/guestcopy.c?ref_type=heads#L31)
>> there is a call of p2m_get_page_from_gfn() with t = NULL.
>>
>> It now seems that I don’t actually need p2m_get_page_from_gfn(), as it
>> is no longer used in this patch series. I could drop it for now and
>> reintroduce it later when it is truly needed by copy_to_guest_phys() or
>> get_page_from_gfn(). Is it acceptable to keep p2m_get_page_from_gfn()
>> as it is now (with adding a NULL check pointer for 't' argument),
>> even without any current callers?
>> Would it be considered dead code?
> As said, as long as no Misra checks are run on the RISC-V part of the
> tree, no dead code concerns really exist. As to the NULL check - if the
> sole (future) caller passes NULL, then why have the parameter at all?

Because there is another caller of p2m_get_page_from_gfn() — get_page_from_gfn(),
which also uses the 't' parameter, and at least in the common code there are
cases where 't' != NULL.

~ Oleksii



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

* Re: [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type
  2025-12-11  9:39           ` Jan Beulich
@ 2025-12-11 16:42             ` Oleksii Kurochko
  2025-12-15  8:28               ` Jan Beulich
  0 siblings, 1 reply; 52+ messages in thread
From: Oleksii Kurochko @ 2025-12-11 16:42 UTC (permalink / raw)
  To: Jan Beulich
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel


On 12/11/25 10:39 AM, Jan Beulich wrote:
> On 10.12.2025 13:44, Oleksii Kurochko wrote:
>> On 12/10/25 8:06 AM, Jan Beulich wrote:
>>> On 09.12.2025 18:09, Oleksii Kurochko wrote:
>>>> On 12/9/25 2:47 PM, Jan Beulich wrote:
>>>>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>>>>> +            *md_pg = p2m_alloc_page(p2m);
>>>>>> +            if ( !*md_pg )
>>>>>> +            {
>>>>>> +                printk("%pd: can't allocate metadata page\n", p2m->domain);
>>>>>> +                domain_crash(p2m->domain);
>>>>>> +
>>>>>> +                return;
>>>>>> +            }
>>>>>> +        }
>>>>>> +    }
>>>>>> +
>>>>>> +    if ( *md_pg )
>>>>>> +        metadata = __map_domain_page(*md_pg);
>>>>>> +
>>>>>> +    if ( t >= p2m_first_external )
>>>>>> +    {
>>>>>> +        metadata[ctx->index].type = t;
>>>>>> +
>>>>>> +        t = p2m_ext_storage;
>>>>>> +    }
>>>>>> +    else if ( metadata )
>>>>>> +        metadata[ctx->index].type = p2m_invalid;
>>>>>> +
>>>>>> +    pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
>>>>>> +
>>>>>> +    unmap_domain_page(metadata);
>>>>>>     }
>>>>> Just to mention (towards future work): Once a metadata page goes back to be
>>>>> entirely zero-filled, it could as well be hooked off and returned to the pool.
>>>>> Not doing so may mean detaining an unused page indefinitely.
>>>> Won’t that already happen when p2m_free_table() is called?
>>> Well, that's when both page table and metadata table are freed. But what if a
>>> leaf page table is moving back to holding all p2m_ram_rw mappings? Then the
>>> metadata page is unused, but will remain allocated.
>> Good point...
>>
>> This could be a rather expensive operation, since in the code:
>>     +    else if ( metadata )
>>     +        metadata[ctx->index].type = p2m_invalid;
>> we would have to check all other metadata entries to determine whether they are
>> (p2m_invalid) or not, and return the page to the pool.
>>
>> It would be nice to have something like metadata.used_entries_num, but the entire
>> page is used for type entries.
>> As an option, we could reserve 8 bits to store a counter of the number of used
>> entries in the metadata page, and then use metadata[0].used_entries_num to check
>> whether it is zero. If it is zero, we could simply return the metadata page to the
>> pool in the “else if (metadata)” case mentioned above.
>>
>> How bad is this idea? Any better suggestions?
> First, as said in my initial reply: This may not need taking care of right away.
> It will need keeping in mind, of course.

I am thinking if it won't be too intrusive, I think that I am okay to introduce that
now.

>
> As to suggestions - hardly any of the fields in struct page_info for the page
> can be used when the page is a metadata one. Simply record the count there?

I suppose that|union u| could be used.
The only thing that confuses me is the shadow paging implementation on x86.
In|struct page_info|, it has the following:
     /* Context-dependent fields follow... */
     union {

         /* Page is in use: ((count_info & PGC_count_mask) != 0). */
         struct {
             /* Type reference count and various PGT_xxx flags and fields. */
             unsigned long type_info;
         } inuse;

         /* Page is in use as a shadow: count_info == 0. */
         struct {
	   ....
         } sh;

         /* Page is on a free list: ((count_info & PGC_count_mask) == 0). */
         union {

So it seems that something in the shadow code must set|count_info| to zero for
shadow pages. But I cannot find where this happens. I would expect it to be done
in|shadow_alloc()|, when the page is taken from the free list. However, pages
from the free list donot have|count_info == 0| since|alloc_heap_pages() |initializes|count_info|.
What guarantees that|count_info| will be zero for shadow tables?

Interestingly, in the shadow p2m page free code, there is logic that resets
|count_info| to zero:
{
     struct domain *owner = page_get_owner(pg);

     /* Should still have no owner and count zero. */
     if ( owner || (pg->count_info & PGC_count_mask) )
     {
         printk(XENLOG_ERR
                "d%d: Odd p2m page %"PRI_mfn" d=%d c=%lx t=%"PRtype_info"\n",
                d->domain_id, mfn_x(page_to_mfn(pg)),
                owner ? owner->domain_id : DOMID_INVALID,
                pg->count_info, pg->u.inuse.type_info);
         pg->count_info &= ~PGC_count_mask;
         page_set_owner(pg, NULL);
     }
...

And another question: since|u.sh.*| is updated, it effectively overwrites
|u.inuse.type_info|. But|u.inuse.type_info| is used by|free_domheap_pages()|,
which is called from|shadow_free()|:
void free_domheap_pages(struct page_info *pg, unsigned int order)
{
...
                 if ( pg[i].u.inuse.type_info & PGT_count_mask )
                 {
                     printk(XENLOG_ERR
                            "pg[%u] MFN %"PRI_mfn" c=%#lx o=%u v=%#lx t=%#x\n",
                            i, mfn_x(page_to_mfn(pg + i)),
                            pg[i].count_info, pg[i].v.free.order,
                            pg[i].u.free.val, pg[i].tlbflush_timestamp);
                     BUG();
                 }
...

Why is it acceptable that|u.inuse.type_info| will likely not be zero, since
|u.sh.*| modifies the same union field, at least during shadow page allocation?



> Finally, as to "rather expensive": Scanning a 4k page to hold all zeroes can't
> be all that expensive? In any event that expensiveness needs weighing carefully
> against the risk of getting the counter maintenance wrong.

It depends on how often|p2m_set_type()| will be called.
In the worst case, it will require a loop that performs up to 512 iterations
to scan a 4 KB page (512 * sizeof(struct mt_d) = 4096), which I think could be
expensive if|p2m_set_type()| is invoked frequently.

~ Oleksii



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

* Re: [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type
  2025-12-11 16:42             ` Oleksii Kurochko
@ 2025-12-15  8:28               ` Jan Beulich
  0 siblings, 0 replies; 52+ messages in thread
From: Jan Beulich @ 2025-12-15  8:28 UTC (permalink / raw)
  To: Oleksii Kurochko
  Cc: Alistair Francis, Bob Eshleman, Connor Davis, Andrew Cooper,
	Anthony PERARD, Michal Orzel, Julien Grall, Roger Pau Monné,
	Stefano Stabellini, xen-devel

On 11.12.2025 17:42, Oleksii Kurochko wrote:
> On 12/11/25 10:39 AM, Jan Beulich wrote:
>> On 10.12.2025 13:44, Oleksii Kurochko wrote:
>>> On 12/10/25 8:06 AM, Jan Beulich wrote:
>>>> On 09.12.2025 18:09, Oleksii Kurochko wrote:
>>>>> On 12/9/25 2:47 PM, Jan Beulich wrote:
>>>>>> On 24.11.2025 13:33, Oleksii Kurochko wrote:
>>>>>>> +            *md_pg = p2m_alloc_page(p2m);
>>>>>>> +            if ( !*md_pg )
>>>>>>> +            {
>>>>>>> +                printk("%pd: can't allocate metadata page\n", p2m->domain);
>>>>>>> +                domain_crash(p2m->domain);
>>>>>>> +
>>>>>>> +                return;
>>>>>>> +            }
>>>>>>> +        }
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    if ( *md_pg )
>>>>>>> +        metadata = __map_domain_page(*md_pg);
>>>>>>> +
>>>>>>> +    if ( t >= p2m_first_external )
>>>>>>> +    {
>>>>>>> +        metadata[ctx->index].type = t;
>>>>>>> +
>>>>>>> +        t = p2m_ext_storage;
>>>>>>> +    }
>>>>>>> +    else if ( metadata )
>>>>>>> +        metadata[ctx->index].type = p2m_invalid;
>>>>>>> +
>>>>>>> +    pte->pte |= MASK_INSR(t, P2M_TYPE_PTE_BITS_MASK);
>>>>>>> +
>>>>>>> +    unmap_domain_page(metadata);
>>>>>>>     }
>>>>>> Just to mention (towards future work): Once a metadata page goes back to be
>>>>>> entirely zero-filled, it could as well be hooked off and returned to the pool.
>>>>>> Not doing so may mean detaining an unused page indefinitely.
>>>>> Won’t that already happen when p2m_free_table() is called?
>>>> Well, that's when both page table and metadata table are freed. But what if a
>>>> leaf page table is moving back to holding all p2m_ram_rw mappings? Then the
>>>> metadata page is unused, but will remain allocated.
>>> Good point...
>>>
>>> This could be a rather expensive operation, since in the code:
>>>     +    else if ( metadata )
>>>     +        metadata[ctx->index].type = p2m_invalid;
>>> we would have to check all other metadata entries to determine whether they are
>>> (p2m_invalid) or not, and return the page to the pool.
>>>
>>> It would be nice to have something like metadata.used_entries_num, but the entire
>>> page is used for type entries.
>>> As an option, we could reserve 8 bits to store a counter of the number of used
>>> entries in the metadata page, and then use metadata[0].used_entries_num to check
>>> whether it is zero. If it is zero, we could simply return the metadata page to the
>>> pool in the “else if (metadata)” case mentioned above.
>>>
>>> How bad is this idea? Any better suggestions?
>> First, as said in my initial reply: This may not need taking care of right away.
>> It will need keeping in mind, of course.
> 
> I am thinking if it won't be too intrusive, I think that I am okay to introduce that
> now.
> 
>>
>> As to suggestions - hardly any of the fields in struct page_info for the page
>> can be used when the page is a metadata one. Simply record the count there?
> 
> I suppose that|union u| could be used.
> The only thing that confuses me is the shadow paging implementation on x86.
> In|struct page_info|, it has the following:
>      /* Context-dependent fields follow... */
>      union {
> 
>          /* Page is in use: ((count_info & PGC_count_mask) != 0). */
>          struct {
>              /* Type reference count and various PGT_xxx flags and fields. */
>              unsigned long type_info;
>          } inuse;
> 
>          /* Page is in use as a shadow: count_info == 0. */
>          struct {
> 	   ....
>          } sh;
> 
>          /* Page is on a free list: ((count_info & PGC_count_mask) == 0). */
>          union {
> 
> So it seems that something in the shadow code must set|count_info| to zero for
> shadow pages. But I cannot find where this happens. I would expect it to be done
> in|shadow_alloc()|, when the page is taken from the free list. However, pages
> from the free list donot have|count_info == 0| since|alloc_heap_pages() |initializes|count_info|.
> What guarantees that|count_info| will be zero for shadow tables?

This is all a little (or maybe even pretty) subtle. On x86 PGC_state_inuse is 0.
Together with logic guaranteeing that PGC_need_scrub would be clear by the time
a page is handed to the caller, what alloc_heap_pages() does is actually to
clear the field.

> Interestingly, in the shadow p2m page free code, there is logic that resets
> |count_info| to zero:

Yes, because iirc there used to be a strict requirement that pages need to be
handed back to the allocator the way they came out of it: With .count_info
clear. mark_page_free() documents / handles a couple of special cases now.

Jan


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

end of thread, other threads:[~2025-12-15  8:28 UTC | newest]

Thread overview: 52+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-24 12:33 [PATCH v6 00/19] xen/riscv: introduce p2m functionality Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 01/19] xen/riscv: avoid redundant HGATP*_MODE_SHIFT and HGATP*_VMID_SHIFT Oleksii Kurochko
2025-12-08 16:13   ` Jan Beulich
2025-11-24 12:33 ` [PATCH v6 02/19] xen/riscv: detect and initialize G-stage mode Oleksii Kurochko
2025-12-08 16:22   ` Jan Beulich
2025-12-09  9:54     ` Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 03/19] xen/riscv: introduce VMID allocation and manegement Oleksii Kurochko
2025-12-08 16:31   ` Jan Beulich
2025-12-09 10:35     ` Oleksii Kurochko
2025-12-09 10:52       ` Jan Beulich
2025-12-08 17:28   ` Teddy Astie
2025-12-09  7:51     ` Jan Beulich
2025-12-09 10:40       ` Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 04/19] xen/riscv: introduce things necessary for p2m initialization Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 05/19] xen/riscv: construct the P2M pages pool for guests Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 06/19] xen/riscv: add root page table allocation Oleksii Kurochko
2025-12-08 16:40   ` Jan Beulich
2025-11-24 12:33 ` [PATCH v6 07/19] xen/riscv: introduce pte_{set,get}_mfn() Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 08/19] xen/riscv: add new p2m types and helper macros for type classification Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 09/19] xen/dom0less: abstract Arm-specific p2m type name for device MMIO mappings Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 10/19] xen/riscv: implement function to map memory in guest p2m Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 11/19] xen/riscv: implement p2m_set_range() Oleksii Kurochko
2025-12-08 16:52   ` Jan Beulich
2025-12-09 11:47     ` Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 12/19] xen/riscv: Implement p2m_free_subtree() and related helpers Oleksii Kurochko
2025-12-08 16:56   ` Jan Beulich
2025-11-24 12:33 ` [PATCH v6 13/19] xen/riscv: Implement p2m_pte_from_mfn() and support PBMT configuration Oleksii Kurochko
2025-12-09 11:22   ` Jan Beulich
2025-11-24 12:33 ` [PATCH v6 14/19] xen/riscv: implement p2m_next_level() Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 15/19] xen/riscv: Implement superpage splitting for p2m mappings Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 16/19] xen/riscv: implement put_page() Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 17/19] xen/riscv: implement mfn_valid() and page reference, ownership handling helpers Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 18/19] xen/riscv: add support of page lookup by GFN Oleksii Kurochko
2025-12-09 11:38   ` Jan Beulich
2025-12-09 15:41     ` Oleksii Kurochko
2025-12-09 15:49       ` Jan Beulich
2025-12-10 11:36         ` Oleksii Kurochko
2025-12-10 11:44           ` Jan Beulich
2025-12-10 12:19             ` Oleksii Kurochko
2025-12-10 15:23         ` Oleksii Kurochko
2025-12-11  9:34           ` Jan Beulich
2025-12-11 13:00             ` Oleksii Kurochko
2025-12-11 13:40               ` Jan Beulich
2025-12-11 14:20                 ` Oleksii Kurochko
2025-11-24 12:33 ` [PATCH v6 19/19] xen/riscv: introduce metadata table to store P2M type Oleksii Kurochko
2025-12-09 13:47   ` Jan Beulich
2025-12-09 17:09     ` Oleksii Kurochko
2025-12-10  7:06       ` Jan Beulich
2025-12-10 12:44         ` Oleksii Kurochko
2025-12-11  9:39           ` Jan Beulich
2025-12-11 16:42             ` Oleksii Kurochko
2025-12-15  8:28               ` 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.