[PATCH] RISC-V: KVM: Improve dirty log clearing by skipping zero bits in mask
Anup Patel
anup at brainfault.org
Fri Aug 7 09:40:16 PDT 2026
On Fri, Jul 17, 2026 at 1:39 PM Wang Yechao <wang.yechao255 at zte.com.cn> wrote:
>
> The existing kvm_riscv_gstage_wp_range() walks the entire [start, end)
> range to apply write protection for dirty log clearing, even when the
> provided mask has zero bits (i.e., many GFNs do not need protection).
> This leads to unnecessary page table walks when the mask is sparse.
>
> Replace the range-based approach with a new function
> kvm_riscv_gstage_wp_pt_masked() that iterates only over the set bits in
> the mask. For each set bit, it looks up the leaf PTE and applies write
> protection. Once a huge page is encountered, the entire huge-page range
> is processed in one go, and the corresponding bits in the mask are cleared
> using bitmap_clear().
>
> Performance was measured with KVM selftests dirty_log_perf_test on a
> Spacemit k3 host with the following configuration:
> - vCPUs: 2 (-v 2)
> - Memory: 1GB (-b 1G)
> - Iterations: 3 (-i 3)
> - Write percentage varied via -w parameter to simulate different
> dirty mask densities.
>
> The following data shows the time spent in the clear-dirty-log phase
> (i.e., the KVM_CLEAR_DIRTY_LOG ioctl) under each configuration.
>
> +------------------+------------------+------------------+-------------+
> | Write Percentage | Original (s) | Patched (s) | Improvement |
> +------------------+------------------+------------------+-------------+
> | 10% | 0.012905 | 0.009213 | +28.6% |
> | 30% | 0.014217 | 0.010287 | +27.6% |
> | 50% | 0.014606 | 0.011772 | +19.4% |
> | 70% | 0.014735 | 0.013199 | +10.4% |
> | 100% | 0.014759 | 0.015294 | -3.6% |
> +------------------+------------------+------------------+-------------+
>
> The performance improvement is most significant when the dirty mask is
> sparse (low write percentage), which is common in real-world scenarios
> with low to moderate memory write intensity. In the worst-case scenario
> where the mask is fully set (100% write), the optimization introduces
> a slight 3.6% overhead due to the additional bit operations, which is
> acceptable given the substantial gains in common cases.
>
> This change significantly reduces the number of page-table walks when
> the dirty mask has many zero bits, improving the efficiency of
> KVM_CLEAR_DIRTY_LOG and related ioctls.
>
> Signed-off-by: Wang Yechao <wang.yechao255 at zte.com.cn>
LGTM.
Reviewed-by: Anup Patel <anup at brainfault.org>
Queued this patch for Linux-7.3
Thanks,
Anup
> ---
> arch/riscv/include/asm/kvm_gstage.h | 3 ++
> arch/riscv/kvm/gstage.c | 59 +++++++++++++++++++++++++++++
> arch/riscv/kvm/mmu.c | 2 +-
> 3 files changed, 63 insertions(+), 1 deletion(-)
>
> diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
> index 21e2019df0cf..50c74527ddd2 100644
> --- a/arch/riscv/include/asm/kvm_gstage.h
> +++ b/arch/riscv/include/asm/kvm_gstage.h
> @@ -82,6 +82,9 @@ bool kvm_riscv_gstage_unmap_range(struct kvm_gstage *gstage,
>
> bool kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end);
>
> +bool kvm_riscv_gstage_wp_pt_masked(struct kvm_gstage *gstage, gfn_t base_gfn,
> + unsigned long mask);
> +
> void kvm_riscv_gstage_mode_detect(void);
>
> static inline unsigned long kvm_riscv_gstage_mode(unsigned long pgd_levels)
> diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
> index c4c3b79567f1..9af86b1c5e3c 100644
> --- a/arch/riscv/kvm/gstage.c
> +++ b/arch/riscv/kvm/gstage.c
> @@ -472,6 +472,65 @@ bool kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
> return flush;
> }
>
> +static inline void clear_huge_mask(unsigned long *mask, unsigned long page_size,
> + gfn_t base_gfn, gpa_t addr)
> +{
> + unsigned long start_index = 0;
> + unsigned long end_index = BITS_PER_LONG - 1;
> + unsigned long end_gfn = base_gfn + end_index;
> + unsigned long aligned_start_gfn = addr >> PAGE_SHIFT;
> + unsigned long aligned_end_gfn = aligned_start_gfn + (page_size >> PAGE_SHIFT) - 1;
> + unsigned int nbits = 0;
> +
> + if (aligned_start_gfn > base_gfn)
> + start_index = aligned_start_gfn - base_gfn;
> +
> + if (aligned_end_gfn < end_gfn)
> + end_index = aligned_end_gfn - base_gfn;
> +
> + nbits = end_index - start_index + 1;
> + bitmap_clear(mask, start_index, nbits);
> +}
> +
> +bool kvm_riscv_gstage_wp_pt_masked(struct kvm_gstage *gstage, gfn_t base_gfn,
> + unsigned long mask)
> +{
> + unsigned long page_size;
> + bool flush = false;
> + bool found_leaf;
> + u32 ptep_level;
> + pte_t *ptep;
> + gpa_t addr = 0;
> + int ret;
> +
> + while (mask) {
> + addr = (base_gfn + __ffs(mask)) << PAGE_SHIFT;
> +
> + found_leaf = kvm_riscv_gstage_get_leaf(gstage, addr, &ptep, &ptep_level);
> + ret = gstage_level_to_page_size(gstage, ptep_level, &page_size);
> + if (ret)
> + break;
> +
> + if (found_leaf) {
> + if (ptep_level) {
> + addr = ALIGN_DOWN(addr, page_size);
> + clear_huge_mask(&mask, page_size, base_gfn, addr);
> + }
> +
> + flush |= kvm_riscv_gstage_op_pte(gstage, addr, ptep,
> + ptep_level, GSTAGE_OP_WP);
> +
> + if (ptep_level)
> + continue;
> + }
> +
> + /* clear the first set bit*/
> + mask &= mask - 1;
> + }
> +
> + return flush;
> +}
> +
> void __init kvm_riscv_gstage_mode_detect(void)
> {
> #ifdef CONFIG_64BIT
> diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
> index 082f9b261733..7eab58ff63f9 100644
> --- a/arch/riscv/kvm/mmu.c
> +++ b/arch/riscv/kvm/mmu.c
> @@ -110,7 +110,7 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
>
> kvm_riscv_gstage_init(&gstage, kvm);
>
> - flush = kvm_riscv_gstage_wp_range(&gstage, start, end);
> + flush = kvm_riscv_gstage_wp_pt_masked(&gstage, base_gfn, mask);
> if (flush)
> kvm_flush_remote_tlbs_range(kvm, start >> PAGE_SHIFT,
> (end - start) >> PAGE_SHIFT);
> --
> 2.27.0
>
More information about the kvm-riscv
mailing list