arch/arm64/include/asm/vmalloc.h | 8 +- arch/arm64/mm/hugetlbpage.c | 15 ++ mm/vmalloc.c | 264 ++++++++++++++++++++++++------- 3 files changed, 229 insertions(+), 58 deletions(-)
This patchset accelerates ioremap, vmalloc, and vmap when the memory
is physically fully or partially contiguous. Two techniques are used:
1. Avoid page table rewalk when setting PTEs/PMDs for multiple memory
segments
2. Use batched mappings wherever possible in both vmalloc and ARM64
layers
Besides accelerating the mapping path, this also enables large
mappings (PMD and cont-PTE) for vmap, which are currently not
supported.
Patches 1-2 extend ARM64 vmalloc CONT-PTE mapping to support multiple
CONT-PTE regions instead of just one.
Patch 3 extracts a common helper vmap_set_ptes() that consolidates PTE
mapping logic for the ioremap and vmalloc/vmap paths, handling both
CONT_PTE and regular PTE mappings. This prepares for the next patch.
Patch 4 extends the page table walk path to support page shifts other
than PAGE_SHIFT and eliminates the page table rewalk for huge vmalloc
mappings. The function is renamed from vmap_small_pages_range_noflush()
to vmap_pages_range_noflush_walk().
Patch 5 extracts vm_shift() to consolidate vmalloc mapping shift
selection for reuse in the batching path.
Patches 6-7 add huge vmap support for contiguous pages, including
support for non-compound pages with pfn alignment verification.
On the RK3588 8-core ARM64 SoC, with tasks pinned to a little core and
the performance CPUfreq policy enabled, benchmark results:
* ioremap(1 MB): 1.35x faster (3407 ns -> 2526 ns)
* vmalloc(1 MB) mapping time (excluding allocation) with
VM_ALLOW_HUGE_VMAP: 1.42x faster (5.00 us -> 3.53 us)
* vmap(100MB) with order-8 pages: 8.3x faster (1235 us -> 149 us)
Many thanks to Xueyuan Chen for his testing efforts on RK3588 boards.
Large vmap() mappings were also tested by Leo Yan with ARM trace buffer
units, including TRBE and SPE. These units use the CPU page tables for
address translation when writing trace data to DRAM, so using larger
vmap() mapping granules can reduce TLB pressure on the trace writer.
The TRBE test used a 1G CoreSight ETM AUX buffer. Across five runs on an
isolated CPU, the average results were:
* dtlb_walk: 68.4 -> 59.4 (-13.16%)
* l1d_tlb_refill: 155.8 -> 119.6 (-23.23%)
* l2d_tlb_refill: 161435.8 -> 495.0 (-99.69%)
The SPE test used a 512M ARM SPE AUX buffer. Across five runs on an
isolated CPU, the average results were:
* dtlb_walk: 1710.4 -> 1315.6 (-23.08%)
* l1d_tlb_refill: 16000.0 -> 15950.2 (-0.31%)
* l2d_tlb_refill: 4796.0 -> 2931.2 (-38.88%)
These results show that enabling larger vmap() mappings can materially
reduce page table walks and TLB refills for large trace buffers.
Many thanks to Leo Yan for his testing efforts on ARM trace buffers.
Changes since v6:
- Add a clarifying comment about the reuse of hugetlb helpers
by non-hugetlbfs(vmalloc) mm code (patch 1)
- Expand the arm64/vmalloc commit message and comment to clarify that
multi-CONT_PTE_SIZE values are vmalloc mapping spans, not HugeTLB
hstate sizes (patch 2)
- Move the local steps variable change in vmap_pte_range() into the
vmap_set_ptes() extraction patch (patch 3)
- Propagate vmap_pages_pte_range() errors through the upper
vmap_pages_*() levels instead of returning -ENOMEM for all failures
(patch 4)
- Add a preparatory vm_shift() helper patch before the batching patch
(patch 5)
- Guard the PFN alignment clamp in get_vmap_batch_order() against PFN 0
before calling __ffs() (patch 6)
- Fix kmsan_vmap_pages_range_noflush() indentation in the batching path
(patch 6)
Changes since v5:
- No code changes.
- Pick up Reviewed-by and Tested-by tags from Dev, Leo and Uladzislau.
Many thanks!
- Add TRBE/SPE large vmap() test results from Leo Yan to the cover
letter.
Changes since v4:
- Move pgsize update before contig_ptes check (patch 1)
- Use rounddown_pow_of_two instead of __fls in
arch_vmap_pte_range_map_size (patch 2)
- Reword comment to avoid mentioning cont_pte and remove if in
vmap_set_ptes (patch 3)
- Rename vmap_batched() to vmap_pages_range_batched() (patch 5)
- Use batch_end as the batching cursor to avoid an unused start variable
(patch 5)
- Check arch_vmap_pmd_supported before PMD mapping (patch 6)
Changes since v3:
- Squash vmap_pte_range() loop variable fix into patch 4 (patch 3, 4)
- Use shift >= PMD_SHIFT and fix *nr increment in
vmap_pages_pmd_range() (patch 4)
- Pass page_shift directly without capping at PMD_SHIFT (patch 4, 5)
- Add vm_shift() helper and pass pgprot_t to get_vmap_batch_order()
(patch 5)
- Use min(order, __ffs(pfn)) for graceful pfn alignment degradation,
replacing IS_ALIGNED check (patch 5)
- Remove irrelevant ioremap_max_page_shift early-exit (patch 5)
- Add __get_vm_area_node_aligned_caller() wrapper, rename to
vmap_get_aligned_vm_area() (patch 6)
Changes since v2:
- Use __fls instead of fls in arch_vmap_pte_range_map_size (patch 2)
- Add WARN_ON checks in vmap_pages_pmd_range (patch 4)
- Fix flush_cache_vmap to use saved start address instead of the
already-advanced addr (patch 5)
- Rename __vmap_huge() to vmap_batched() (patch 5)
- Add caller parameter and unroll while(1) loop (patch 5)
- Squash patch 7 into patch 5 (stop scanning for compound pages after
encountering small pages)
Changes since v1:
- Fix condition order and use PMD_SIZE instead of CONT_PMD_SIZE in
patch 1 (Dev Jain)
- Squash patch 3+4 and patch 5+7 (Dev Jain)
- Replace "zigzag" with "page table rewalk" in commit messages
(Dev Jain)
- Rename vmap_small_pages_range_noflush() to
vmap_pages_range_noflush_walk() (Dev Jain)
- Extract vmap_set_ptes() as a new patch to consolidate PTE mapping
logic between vmap_pte_range() and vmap_pages_pte_range(), handling
both CONT_PTE and regular mappings (Mike Rapoport)
- Support non-compound pages in get_vmap_batch_order() by falling
back to physical contiguity scanning with pfn alignment check
(Dev Jain, Uladzislau Rezki)
- In get_vmap_batch_order(), filter out orders that the architecture
cannot batch by checking arch_vmap_pte_supported_shift() directly.
This avoids overhead for orders 1-3 on ARM64 CONT_PTE with 4K
pages. (patch 5)
Barry Song (Xiaomi) (5):
arm64/hugetlb: Extend batching of multiple CONT_PTE in a single PTE
setup
arm64/vmalloc: Allow arch_vmap_pte_range_map_size to batch multiple
CONT_PTE
mm/vmalloc: Extend page table walk to support larger page_shift sizes
and eliminate page table rewalk
mm/vmalloc: map contiguous pages in batches for vmap() if possible
mm/vmalloc: align vm_area so vmap() can batch mappings
Wen Jiang (2):
mm/vmalloc: Extract vmap_set_ptes() to consolidate PTE mapping logic
mm/vmalloc: Extract vm_shift() to consolidate mapping shift selection
arch/arm64/include/asm/vmalloc.h | 8 +-
arch/arm64/mm/hugetlbpage.c | 15 ++
mm/vmalloc.c | 264 ++++++++++++++++++++++++-------
3 files changed, 229 insertions(+), 58 deletions(-)
--
2.34.1
On Wed, 15 Jul 2026 20:08:06 +0800 Wen Jiang <jiangwenxiaomi@gmail.com> wrote:
> This patchset accelerates ioremap, vmalloc, and vmap when the memory
> is physically fully or partially contiguous. Two techniques are used:
Thanks, I've updated mm.git's mm-unstable branch to this version.
> Changes since v6:
> - Add a clarifying comment about the reuse of hugetlb helpers
> by non-hugetlbfs(vmalloc) mm code (patch 1)
> - Expand the arm64/vmalloc commit message and comment to clarify that
> multi-CONT_PTE_SIZE values are vmalloc mapping spans, not HugeTLB
> hstate sizes (patch 2)
> - Move the local steps variable change in vmap_pte_range() into the
> vmap_set_ptes() extraction patch (patch 3)
> - Propagate vmap_pages_pte_range() errors through the upper
> vmap_pages_*() levels instead of returning -ENOMEM for all failures
> (patch 4)
> - Add a preparatory vm_shift() helper patch before the batching patch
> (patch 5)
> - Guard the PFN alignment clamp in get_vmap_batch_order() against PFN 0
> before calling __ffs() (patch 6)
> - Fix kmsan_vmap_pages_range_noflush() indentation in the batching path
> (patch 6)
Here's how v7 altered mm.git:
arch/arm64/include/asm/vmalloc.h | 2 +
arch/arm64/mm/hugetlbpage.c | 5 ++++
mm/vmalloc.c | 31 ++++++++++++++++-------------
3 files changed, 25 insertions(+), 13 deletions(-)
--- a/arch/arm64/include/asm/vmalloc.h~b
+++ a/arch/arm64/include/asm/vmalloc.h
@@ -29,6 +29,8 @@ static inline unsigned long arch_vmap_pt
* If the block is at least CONT_PTE_SIZE in size, and is naturally
* aligned in both virtual and physical space, then we can pte-map the
* block using the PTE_CONT bit for more efficient use of the TLB.
+ * The returned mapping size may cover multiple CONT_PTE_SIZE blocks,
+ * capped below PMD_SIZE.
*/
if (max_page_shift < CONT_PTE_SHIFT)
return PAGE_SIZE;
--- a/arch/arm64/mm/hugetlbpage.c~b
+++ a/arch/arm64/mm/hugetlbpage.c
@@ -94,6 +94,11 @@ static int find_num_contig(struct mm_str
return CONT_PTES;
}
+/*
+ * num_contig_ptes(), set_huge_pte_at() and arch_make_huge_pte() can be
+ * used by non-hugetlbfs(vmalloc) mm code to set multiple huge mappings
+ * at the PTE level.
+ */
static inline int num_contig_ptes(unsigned long size, size_t *pgsize)
{
int contig_ptes = 1;
--- a/mm/vmalloc.c~b
+++ a/mm/vmalloc.c
@@ -616,6 +616,7 @@ static int vmap_pages_pmd_range(pud_t *p
{
pmd_t *pmd;
unsigned long next;
+ int err;
pmd = pmd_alloc_track(&init_mm, pud, addr, mask);
if (!pmd)
@@ -642,8 +643,9 @@ static int vmap_pages_pmd_range(pud_t *p
}
}
- if (vmap_pages_pte_range(pmd, addr, next, prot, pages, nr, mask, shift))
- return -ENOMEM;
+ err = vmap_pages_pte_range(pmd, addr, next, prot, pages, nr, mask, shift);
+ if (err)
+ return err;
} while (pmd++, addr = next, addr != end);
return 0;
}
@@ -654,14 +656,16 @@ static int vmap_pages_pud_range(p4d_t *p
{
pud_t *pud;
unsigned long next;
+ int err;
pud = pud_alloc_track(&init_mm, p4d, addr, mask);
if (!pud)
return -ENOMEM;
do {
next = pud_addr_end(addr, end);
- if (vmap_pages_pmd_range(pud, addr, next, prot, pages, nr, mask, shift))
- return -ENOMEM;
+ err = vmap_pages_pmd_range(pud, addr, next, prot, pages, nr, mask, shift);
+ if (err)
+ return err;
} while (pud++, addr = next, addr != end);
return 0;
}
@@ -672,14 +676,16 @@ static int vmap_pages_p4d_range(pgd_t *p
{
p4d_t *p4d;
unsigned long next;
+ int err;
p4d = p4d_alloc_track(&init_mm, pgd, addr, mask);
if (!p4d)
return -ENOMEM;
do {
next = p4d_addr_end(addr, end);
- if (vmap_pages_pud_range(p4d, addr, next, prot, pages, nr, mask, shift))
- return -ENOMEM;
+ err = vmap_pages_pud_range(p4d, addr, next, prot, pages, nr, mask, shift);
+ if (err)
+ return err;
} while (p4d++, addr = next, addr != end);
return 0;
}
@@ -3591,6 +3597,7 @@ static inline unsigned int vm_shift(pgpr
static inline int get_vmap_batch_order(struct page **pages,
pgprot_t prot, unsigned int max_steps, unsigned int idx)
{
+ unsigned long pfn;
unsigned int nr_contig;
int order;
@@ -3602,9 +3609,11 @@ static inline int get_vmap_batch_order(s
return 0;
order = ilog2(nr_contig);
+ pfn = page_to_pfn(pages[idx]);
/* Limit order by pfn alignment */
- order = min_t(int, order, __ffs(page_to_pfn(pages[idx])));
+ if (pfn > 0)
+ order = min_t(int, order, __ffs(pfn));
if (vm_shift(prot, PAGE_SIZE << order) == PAGE_SHIFT)
return 0;
@@ -3621,7 +3630,7 @@ static int vmap_pages_range_batched(unsi
int err;
err = kmsan_vmap_pages_range_noflush(addr, end, prot, pages,
- PAGE_SHIFT, GFP_KERNEL);
+ PAGE_SHIFT, GFP_KERNEL);
if (err)
goto out;
@@ -4199,11 +4208,7 @@ void *__vmalloc_node_range_noprof(unsign
* supporting them.
*/
- if (arch_vmap_pmd_supported(prot) && size >= PMD_SIZE)
- shift = PMD_SHIFT;
- else
- shift = arch_vmap_pte_supported_shift(size);
-
+ shift = vm_shift(prot, size);
align = max(original_align, 1UL << shift);
}
_
© 2016 - 2026 Red Hat, Inc.