Re:  [PATCH v4 2/2] RISC-V: KVM: Split huge pages during fault handling for dirty logging

Anup Patel apatel at ventanamicro.com
Mon Mar 30 01:47:16 PDT 2026


On Mon, Mar 30, 2026 at 1:43 PM <wang.yechao255 at zte.com.cn> wrote:
>
> From: Wang Yechao <wang.yechao255 at zte.com.cn>
>
> During dirty logging, all huge pages are write-protected. When the guest
> writes to a write-protected huge page, a page fault is triggered. Before
> recovering the write permission, the huge page must be split into smaller
> pages (e.g., 4K). After splitting, the normal mapping process proceeds,
> allowing write permission to be restored at the smaller page granularity.
>
> If dirty logging is disabled because migration failed or was cancelled,
> only recover the write permission at the 4K level, and skip recovering the
> huge page mapping at this time to avoid the overhead of freeing page tables.
> The huge page mapping can be recovered in the ioctl context, similar to x86,
> in a later patch.
>
> Signed-off-by: Wang Yechao <wang.yechao255 at zte.com.cn>

LGTM.

Reviewed-by: Anup Patel <anup at brainfault.org>

Thanks,
Anup

> ---
>  arch/riscv/include/asm/kvm_gstage.h |   4 +
>  arch/riscv/kvm/gstage.c             | 126 ++++++++++++++++++++++++++++
>  2 files changed, 130 insertions(+)
>
> diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
> index 595e2183173e..373748c6745e 100644
> --- a/arch/riscv/include/asm/kvm_gstage.h
> +++ b/arch/riscv/include/asm/kvm_gstage.h
> @@ -53,6 +53,10 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
>                               bool page_rdonly, bool page_exec,
>                               struct kvm_gstage_mapping *out_map);
>
> +int kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage,
> +                                struct kvm_mmu_memory_cache *pcache,
> +                                gpa_t addr, u32 target_level, bool flush);
> +
>  enum kvm_riscv_gstage_op {
>         GSTAGE_OP_NOP = 0,      /* Nothing */
>         GSTAGE_OP_CLEAR,        /* Clear/Unmap */
> diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
> index d2001d508046..ffec3e5ddcaf 100644
> --- a/arch/riscv/kvm/gstage.c
> +++ b/arch/riscv/kvm/gstage.c
> @@ -163,13 +163,32 @@ int kvm_riscv_gstage_set_pte(struct kvm_gstage *gstage,
>         return 0;
>  }
>
> +static void kvm_riscv_gstage_update_pte_prot(struct kvm_gstage *gstage, u32 level,
> +                                            gpa_t addr, pte_t *ptep, pgprot_t prot)
> +{
> +       pte_t new_pte;
> +
> +       if (pgprot_val(pte_pgprot(ptep_get(ptep))) == pgprot_val(prot))
> +               return;
> +
> +       new_pte = pfn_pte(pte_pfn(ptep_get(ptep)), prot);
> +       new_pte = pte_mkdirty(new_pte);
> +
> +       set_pte(ptep, new_pte);
> +
> +       gstage_tlb_flush(gstage, level, addr);
> +}
> +
>  int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
>                               struct kvm_mmu_memory_cache *pcache,
>                               gpa_t gpa, phys_addr_t hpa, unsigned long page_size,
>                               bool page_rdonly, bool page_exec,
>                               struct kvm_gstage_mapping *out_map)
>  {
> +       bool found_leaf;
> +       u32 ptep_level;
>         pgprot_t prot;
> +       pte_t *ptep;
>         int ret;
>
>         out_map->addr = gpa;
> @@ -203,12 +222,119 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
>                 else
>                         prot = PAGE_WRITE;
>         }
> +
> +       found_leaf = kvm_riscv_gstage_get_leaf(gstage, gpa, &ptep, &ptep_level);
> +       if (found_leaf) {
> +               /*
> +                * ptep_level is the current gstage mapping level of addr, out_map->level
> +                * is the required mapping level during fault handling.
> +                *
> +                * 1) ptep_level > out_map->level
> +                * This happens when dirty logging is enabled and huge pages are used.
> +                * KVM must track the pages at 4K level, and split the huge mapping
> +                * into 4K mappings.
> +                *
> +                * 2) ptep_level < out_map->level
> +                * This happens when dirty logging is disabled and huge pages are used.
> +                * The gstage is split into 4K mappings, but the out_map level is now
> +                * back to the huge page level. Ignore the out_map level this time, and
> +                * just update the pte prot here. Otherwise, we would fall back to mapping
> +                * the gstage at huge page level in `kvm_riscv_gstage_set_pte`, with the
> +                * overhead of freeing the page tables(not support now), which would slow
> +                * down the vCPUs' performance.
> +                *
> +                * It is better to recover the huge page mapping in the ioctl context when
> +                * disabling dirty logging.
> +                *
> +                * 3) ptep_level == out_map->level
> +                * We already have the ptep, just update the pte prot if the pfn not change.
> +                * There is no need to invoke `kvm_riscv_gstage_set_pte` again.
> +                */
> +               if (ptep_level > out_map->level) {
> +                       kvm_riscv_gstage_split_huge(gstage, pcache, gpa,
> +                                                   out_map->level, true);
> +               } else if (ALIGN_DOWN(PFN_PHYS(pte_pfn(ptep_get(ptep))), page_size) == hpa) {
> +                       kvm_riscv_gstage_update_pte_prot(gstage, ptep_level, gpa, ptep, prot);
> +                       return 0;
> +               }
> +       }
> +
>         out_map->pte = pfn_pte(PFN_DOWN(hpa), prot);
>         out_map->pte = pte_mkdirty(out_map->pte);
>
>         return kvm_riscv_gstage_set_pte(gstage, pcache, out_map);
>  }
>
> +static inline unsigned long make_child_pte(unsigned long huge_pte, int index,
> +                                          unsigned long child_page_size)
> +{
> +       unsigned long child_pte = huge_pte;
> +       unsigned long child_pfn_offset;
> +
> +       /*
> +        * The child_pte already has the base address of the huge page being
> +        * split. So we just have to OR in the offset to the page at the next
> +        * lower level for the given index.
> +        */
> +       child_pfn_offset = index * (child_page_size / PAGE_SIZE);
> +       child_pte |= pte_val(pfn_pte(child_pfn_offset, __pgprot(0)));
> +
> +       return child_pte;
> +}
> +
> +int kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage,
> +                               struct kvm_mmu_memory_cache *pcache,
> +                               gpa_t addr, u32 target_level, bool flush)
> +{
> +       u32 current_level = kvm_riscv_gstage_pgd_levels - 1;
> +       pte_t *next_ptep = (pte_t *)gstage->pgd;
> +       unsigned long huge_pte, child_pte;
> +       unsigned long child_page_size;
> +       pte_t *ptep;
> +       int i, ret;
> +
> +       if (!pcache)
> +               return -ENOMEM;
> +
> +       while(current_level > target_level) {
> +               ptep = (pte_t *)&next_ptep[gstage_pte_index(addr, current_level)];
> +
> +               if (!pte_val(ptep_get(ptep)))
> +                       break;
> +
> +               if (!gstage_pte_leaf(ptep)) {
> +                       next_ptep = (pte_t *)gstage_pte_page_vaddr(ptep_get(ptep));
> +                       current_level--;
> +                       continue;
> +               }
> +
> +               huge_pte = pte_val(ptep_get(ptep));
> +
> +               ret = gstage_level_to_page_size(current_level - 1, &child_page_size);
> +               if (ret)
> +                       return ret;
> +
> +               next_ptep = kvm_mmu_memory_cache_alloc(pcache);
> +               if (!next_ptep)
> +                       return -ENOMEM;
> +
> +               for (i = 0; i < PTRS_PER_PTE; i++) {
> +                       child_pte = make_child_pte(huge_pte, i, child_page_size);
> +                       set_pte((pte_t *)&next_ptep[i], __pte(child_pte));
> +               }
> +
> +               set_pte(ptep, pfn_pte(PFN_DOWN(__pa(next_ptep)),
> +                               __pgprot(_PAGE_TABLE)));
> +
> +               if (flush)
> +                       gstage_tlb_flush(gstage, current_level, addr);
> +
> +               current_level--;
> +       }
> +
> +       return 0;
> +}
> +
>  void kvm_riscv_gstage_op_pte(struct kvm_gstage *gstage, gpa_t addr,
>                              pte_t *ptep, u32 ptep_level, enum kvm_riscv_gstage_op op)
>  {
> --
> 2.47.3
>
> --
> kvm-riscv mailing list
> kvm-riscv at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/kvm-riscv



More information about the linux-riscv mailing list