* [PATCH 00/17] arm64/mm: Enable 128 bit page table entries
@ 2026-07-29 12:24 Anshuman Khandual
2026-07-29 12:24 ` [PATCH 01/17] mm: Add read-write accessors for vm_page_prot Anshuman Khandual
` (16 more replies)
0 siblings, 17 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
FEAT_D128 is a new arm architecture feature adding support for VMSAv9-128
translation system. FEAT_D128 is an optional feature from ARMV9.3 onwards.
arm64 platforms with FEAT_D128 enabled will now have two different memory
translation systems i.e VMSAv8-64 and VMSAv9-128.
FEAT_D128 adds 128 bit page table entries, thus supporting larger physical
and virtual address range while also expanding available room for more MMU
feature bits for both HW and SW usage.
A new config option ARM64_D128 is being added thus enabling users to chose
between aforementioned memory translations regimes VMSAv8-64 (aka D64) and
VMSAv9-128 (aka D128) during the build time. If the ARM64_D128 built kernel
does not detect FEAT_D128 feature on the hardware, then it gets stuck with
with a new reason code CPU_STUCK_REASON_NO_D128. So ARM64_D128 built kernel
cannot fallback to D64 translation regime on unsupported HW.
READ_ONCE() on page table entries get routed via level specific pxdp_get()
helpers which platforms could then override when required. These accessors
on arm64 platform help in ensuring page table accesses are performed in an
atomic manner while reading 128 bit page table entries.
ARM64_[VA|PA]_BITS combinations for respective CONFIG_ARM64_XXK_PAGES are
now supported both on VMSAv8-64 and VMSAv9-128 memory translation regimes.
Although FEAT_D128 provided 52 bit VA and PA space along with skip levels
features are not supported currently.
Basic page table geometry has also been changed with VMSAv9-128, as there
are fewer entries per level as compared to VMSAv8-64. Please refer to the
following table for various leaf entry sizes including the cont entries.
------------------------------------------------
| | D64 | D128 |
------------------------------------------------
| PAGE_SIZE | PMD | PUD | PMD | PUD |
-----------------------------|-----------------|
| 4K | 2M | 1G | 1M | 256M |
| 16K | 32M | 64G | 16M | 16G |
| 64K | 512M | 4T | 256M | 1T |
------------------------------------------------
--------------------------------------------------------------------
| | D64 | D128 |
--------------------------------------------------------------------
| PAGE_SIZE | CONT_PTE | CONT_PMD | CONT_PTE | CONT_PMD |
--------------------------|------------|-------------|--------------
| 4K | 64K | 32M | 64K | 16M |
| 16K | 2M | 1G | 1M | 256M |
| 64K | 2M | 16G | 1M | 16G |
--------------------------------------------------------------------
Similarly arm64 kernel features such as KVM, KASAN and UNMAP_KERNEL_AT_EL0
are currently not supported as well.
This series applies on v7.2-rc5 but after applying the following patch that
standardized printing pgtable entries. There are no apparent problems while
running MM kselftests with and without CONFIG_ARM64_D128.
https://lore.kernel.org/all/20260709044334.1741263-1-anshuman.khandual@arm.com/
Changes in V1:
- Rebased on v7.2-rc5
- Pulled in new standardized pgtable entry print patch
- Added standardized pgtable entry print on arm64 platform
- Added KASAN related pgdp_get() changes missing earlier
- Renamed pxxval_get/set() as ptval_get/set()
- Renamed ptval_cmpxchg_relaxed() and ptval_xchg_relaxed()
- Renamed tlbi_args_t as tlbi_arg_t per Ryan
- Split final D128 enablement patch into four separate ones
- Updated all commit messages as required
Changes in RFC V2:
https://lore.kernel.org/all/20260513044547.4128549-1-anshuman.khandual@arm.com/
- Dropped some patches that were merged upstream and rebased on v7.1-rc3
- Updated commit message for the 5-level fixmap change per David
- Moved pxdval_t definition inside generic page table header
- Dropped _once from pgprot_read/write() per Mike
- Updated ARM64_CONT_[PTE|PMD]_SHIFT both for 16K and 64K base pages
- Renamed __PRIpte as __PRIpxx per David
- Added abstraction for tlbi_op
- Adopted TLBIP implementation to recent TLB flush changes
- Moved back all helpers back from arch/arm64/mm/mmu.c into the header
- Moved arm64 pgtable header READ_ONCE() replacements later in the series
- Renamed all ptdesc_ instances as pxxval_ instead
- Updated all commit messages as required and suggested
- Restored print format in __print_bad_page_map_pgtable() per Usama
Changes in RFC V1:
https://lore.kernel.org/linux-arm-kernel/20260224051153.3150613-2-anshuman.khandual@arm.com/
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Lorenzo Stoakes <ljs@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Linu Cherian <linu.cherian@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-mm@kvack.org
Anshuman Khandual (15):
mm: Add read-write accessors for vm_page_prot
arm64/mm: Convert READ_ONCE() as pmdp_get() while accessing PMD
arm64/mm: Convert READ_ONCE() as pudp_get() while accessing PUD
arm64/mm: Convert READ_ONCE() as p4dp_get() while accessing P4D
arm64/mm: Convert READ_ONCE() as pgdp_get() while accessing PGD
arm64/mm: Route all pgtable reads via ptval_get()
arm64/mm: Route all pgtable writes via ptval_set()
arm64/mm: Route all pgtable atomics to central helpers
arm64/mm: Override read-write accessors for vm_page_prot
arm64/mm: Standardize printing for pgtable entries
arm64/mm: Enable fixmap with 5 level page table
arm64/mm: Enable pgtable geometry for FEAT_D128
arm64/mm: Enable pgtable descriptor for FEAT_D128
arm64/mm: Enable 128 bit translation
arm64/mm: Make ARM64_D128 selectable
Linu Cherian (2):
arm64/mm: Add an abstraction level for tlbi_op
arm64/mm: Enable TLBIP instruction based TLB flush
arch/arm64/Kconfig | 51 ++++++-
arch/arm64/Makefile | 4 +
arch/arm64/include/asm/assembler.h | 4 +-
arch/arm64/include/asm/el2_setup.h | 9 ++
arch/arm64/include/asm/pgtable-hwdef.h | 137 ++++++++++++++++++
arch/arm64/include/asm/pgtable-prot.h | 18 ++-
arch/arm64/include/asm/pgtable-types.h | 4 +
arch/arm64/include/asm/pgtable.h | 188 +++++++++++++++++++------
arch/arm64/include/asm/smp.h | 1 +
arch/arm64/include/asm/tlbflush.h | 138 ++++++++++++------
arch/arm64/kernel/head.S | 12 ++
arch/arm64/mm/fault.c | 31 ++--
arch/arm64/mm/fixmap.c | 24 +++-
arch/arm64/mm/hugetlbpage.c | 10 +-
arch/arm64/mm/kasan_init.c | 18 +--
arch/arm64/mm/mmu.c | 81 ++++++-----
arch/arm64/mm/pageattr.c | 8 +-
arch/arm64/mm/proc.S | 25 +++-
arch/arm64/mm/trans_pgd.c | 14 +-
include/linux/mm_types.h | 40 ++++++
include/linux/pgtable.h | 14 ++
mm/huge_memory.c | 4 +-
mm/memory.c | 42 +-----
mm/migrate.c | 2 +-
mm/mmap.c | 2 +-
25 files changed, 668 insertions(+), 213 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 01/17] mm: Add read-write accessors for vm_page_prot
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 02/17] arm64/mm: Convert READ_ONCE() as pmdp_get() while accessing PMD Anshuman Khandual
` (15 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel, Lorenzo Stoakes
Currently vma->vm_page_prot is safely read from and written to, without any
locks with READ_ONCE() and WRITE_ONCE(). But with introduction of D128 page
tables on arm64 platform, vm_page_prot grows to 128 bits which can't safely
be handled with READ_ONCE() and WRITE_ONCE().
Add read and write accessors for vm_page_prot like pgprot_[read|write]()
which any platform can override when required, although still defaulting as
READ_ONCE() and WRITE_ONCE(), thus preserving the functionality for others.
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
include/linux/pgtable.h | 14 ++++++++++++++
mm/huge_memory.c | 4 ++--
mm/memory.c | 2 +-
mm/migrate.c | 2 +-
mm/mmap.c | 2 +-
5 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/include/linux/pgtable.h b/include/linux/pgtable.h
index 2981e386da7b..3ecbeec6b463 100644
--- a/include/linux/pgtable.h
+++ b/include/linux/pgtable.h
@@ -524,6 +524,20 @@ static inline pgd_t pgdp_get(pgd_t *pgdp)
}
#endif
+#ifndef pgprot_read
+static inline pgprot_t pgprot_read(pgprot_t *prot)
+{
+ return READ_ONCE(*prot);
+}
+#endif
+
+#ifndef pgprot_write
+static inline void pgprot_write(pgprot_t *prot, pgprot_t val)
+{
+ WRITE_ONCE(*prot, val);
+}
+#endif
+
#ifndef __HAVE_ARCH_PTEP_TEST_AND_CLEAR_YOUNG
static inline bool ptep_test_and_clear_young(struct vm_area_struct *vma,
unsigned long address, pte_t *ptep)
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index b5d1e9d4463d..2915e7c628ef 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3281,7 +3281,7 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,
} else {
pte_t entry;
- entry = mk_pte(page, READ_ONCE(vma->vm_page_prot));
+ entry = mk_pte(page, pgprot_read(&vma->vm_page_prot));
if (write)
entry = pte_mkwrite(entry, vma);
if (!young)
@@ -4957,7 +4957,7 @@ void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new)
entry = softleaf_from_pmd(*pvmw->pmd);
folio_get(folio);
- pmde = folio_mk_pmd(folio, READ_ONCE(vma->vm_page_prot));
+ pmde = folio_mk_pmd(folio, pgprot_read(&vma->vm_page_prot));
if (pmd_swp_soft_dirty(*pvmw->pmd))
pmde = pmd_mksoft_dirty(pmde);
diff --git a/mm/memory.c b/mm/memory.c
index c8fe2f373fd2..598d0e588e9a 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -945,7 +945,7 @@ static void restore_exclusive_pte(struct vm_area_struct *vma,
VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
- pte = pte_mkold(mk_pte(page, READ_ONCE(vma->vm_page_prot)));
+ pte = pte_mkold(mk_pte(page, pgprot_read(&vma->vm_page_prot)));
if (pte_swp_soft_dirty(orig_pte))
pte = pte_mksoft_dirty(pte);
diff --git a/mm/migrate.c b/mm/migrate.c
index d9b23909d716..4f3492cf3fba 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -377,7 +377,7 @@ static bool remove_migration_pte(struct folio *folio,
continue;
folio_get(folio);
- pte = mk_pte(new, READ_ONCE(vma->vm_page_prot));
+ pte = mk_pte(new, pgprot_read(&vma->vm_page_prot));
entry = softleaf_from_pte(old_pte);
if (!softleaf_is_migration_young(entry))
diff --git a/mm/mmap.c b/mm/mmap.c
index 2311ae7c2ff4..750ba48cdf14 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -89,7 +89,7 @@ void vma_set_page_prot(struct vm_area_struct *vma)
vm_page_prot = vm_pgprot_modify(vm_page_prot, vm_flags);
}
/* remove_protection_ptes reads vma->vm_page_prot without mmap_lock */
- WRITE_ONCE(vma->vm_page_prot, vm_page_prot);
+ pgprot_write(&vma->vm_page_prot, vm_page_prot);
}
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 02/17] arm64/mm: Convert READ_ONCE() as pmdp_get() while accessing PMD
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
2026-07-29 12:24 ` [PATCH 01/17] mm: Add read-write accessors for vm_page_prot Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 03/17] arm64/mm: Convert READ_ONCE() as pudp_get() while accessing PUD Anshuman Khandual
` (14 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel, Mark Rtland,
linx-arm-kernel, linx-kernel, kasan-dev
Convert all READ_ONCE() based PMD accesses as pmdp_get() instead which will
support both D64 and D128 translation regime going forward. That is because
READ_ONCE() would need 128 bit single copy atomic guarantees, while reading
128 bit page table entries which is currently not supported on arm64. Build
fails for READ_ONCE() while accessing beyond 64 bits.
Load Pair/Store Pair (ldp/stp) are only single copy atomic if FEAT_LSE128
is supported (which is required when FEAT_D128 is supported). Currently 128
bit pgtables is a compile time decision - so we could have chosen to extend
READ_ONCE()/WRITE_ONCE() to allow 128 bit for this configuration. But then
it's a general purpose API and we were concerned that other users might
eventually creep in that expect 128 and then fail to compile in the other
configs.
But worse, we are considering eventually making D128 a boot time option, at
which point we'd have to make READ_ONCE() always allow 128 bit at compile
time but then it might silently tear at runtime.
So our preference is to standardize on these existing helpers, which we can
override in arm64 to give the 128 bit single copy guarantee when required.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rtland <mark.rtland@arm.com>
Cc: linx-arm-kernel@lists.infradead.org
Cc: linx-kernel@vger.kernel.org
Cc: kasan-dev@googlegrops.com
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/pgtable.h | 3 ++-
arch/arm64/mm/fault.c | 2 +-
arch/arm64/mm/fixmap.c | 2 +-
arch/arm64/mm/hugetlbpage.c | 2 +-
arch/arm64/mm/kasan_init.c | 4 ++--
arch/arm64/mm/mmu.c | 23 ++++++++++++-----------
arch/arm64/mm/pageattr.c | 2 +-
arch/arm64/mm/trans_pgd.c | 2 +-
8 files changed, 21 insertions(+), 19 deletions(-)
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 27689c62bd25..5c0cc7c676fa 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -852,7 +852,8 @@ static inline unsigned long pmd_page_vaddr(pmd_t pmd)
}
/* Find an entry in the third-level page table. */
-#define pte_offset_phys(dir,addr) (pmd_page_paddr(READ_ONCE(*(dir))) + pte_index(addr) * sizeof(pte_t))
+#define pte_offset_phys(dir, addr) (pmd_page_paddr(pmdp_get(dir)) + \
+ pte_index(addr) * sizeof(pte_t))
#define pte_set_fixmap(addr) ((pte_t *)set_fixmap_offset(FIX_PTE, addr))
#define pte_set_fixmap_offset(pmd, addr) pte_set_fixmap(pte_offset_phys(pmd, addr))
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index 0b52557652be..30f8b3872331 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -180,7 +180,7 @@ static void show_pte(unsigned long addr)
break;
pmdp = pmd_offset(pudp, addr);
- pmd = READ_ONCE(*pmdp);
+ pmd = pmdp_get(pmdp);
pr_cont(", pmd=%016llx", pmd_val(pmd));
if (pmd_none(pmd) || pmd_bad(pmd))
break;
diff --git a/arch/arm64/mm/fixmap.c b/arch/arm64/mm/fixmap.c
index f66a0016dd02..3cdac8021d4f 100644
--- a/arch/arm64/mm/fixmap.c
+++ b/arch/arm64/mm/fixmap.c
@@ -42,7 +42,7 @@ static inline pte_t *fixmap_pte(unsigned long addr)
static void __init early_fixmap_init_pte(pmd_t *pmdp, unsigned long addr)
{
- pmd_t pmd = READ_ONCE(*pmdp);
+ pmd_t pmd = pmdp_get(pmdp);
pte_t *ptep;
if (pmd_none(pmd)) {
diff --git a/arch/arm64/mm/hugetlbpage.c b/arch/arm64/mm/hugetlbpage.c
index 8e799c1fe0aa..cdaa4500faf9 100644
--- a/arch/arm64/mm/hugetlbpage.c
+++ b/arch/arm64/mm/hugetlbpage.c
@@ -304,7 +304,7 @@ pte_t *huge_pte_offset(struct mm_struct *mm,
addr &= CONT_PMD_MASK;
pmdp = pmd_offset(pudp, addr);
- pmd = READ_ONCE(*pmdp);
+ pmd = pmdp_get(pmdp);
if (!(sz == PMD_SIZE || sz == CONT_PMD_SIZE) &&
pmd_none(pmd))
return NULL;
diff --git a/arch/arm64/mm/kasan_init.c b/arch/arm64/mm/kasan_init.c
index 3fcad956fdf7..8cb440379f17 100644
--- a/arch/arm64/mm/kasan_init.c
+++ b/arch/arm64/mm/kasan_init.c
@@ -62,7 +62,7 @@ static phys_addr_t __init kasan_alloc_raw_page(int node)
static pte_t *__init kasan_pte_offset(pmd_t *pmdp, unsigned long addr, int node,
bool early)
{
- if (pmd_none(READ_ONCE(*pmdp))) {
+ if (pmd_none(pmdp_get(pmdp))) {
phys_addr_t pte_phys = early ?
__pa_symbol(kasan_early_shadow_pte)
: kasan_alloc_zeroed_page(node);
@@ -138,7 +138,7 @@ static void __init kasan_pmd_populate(pud_t *pudp, unsigned long addr,
do {
next = pmd_addr_end(addr, end);
kasan_pte_populate(pmdp, addr, next, node, early);
- } while (pmdp++, addr = next, addr != end && pmd_none(READ_ONCE(*pmdp)));
+ } while (pmdp++, addr = next, addr != end && pmd_none(pmdp_get(pmdp)));
}
static void __init kasan_pud_populate(p4d_t *p4dp, unsigned long addr,
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 18a8b0d3714e..85b2e2f9ea3e 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -201,7 +201,7 @@ static int alloc_init_cont_pte(pmd_t *pmdp, unsigned long addr,
int flags)
{
unsigned long next;
- pmd_t pmd = READ_ONCE(*pmdp);
+ pmd_t pmd = pmdp_get(pmdp);
pte_t *ptep;
BUG_ON(pmd_leaf(pmd));
@@ -258,7 +258,7 @@ static int init_pmd(pmd_t *pmdp, unsigned long addr, unsigned long end,
unsigned long next;
do {
- pmd_t old_pmd = READ_ONCE(*pmdp);
+ pmd_t old_pmd = pmdp_get(pmdp);
next = pmd_addr_end(addr, end);
@@ -273,7 +273,7 @@ static int init_pmd(pmd_t *pmdp, unsigned long addr, unsigned long end,
* only allow updates to the permission attributes.
*/
BUG_ON(!pgattr_change_is_safe(pmd_val(old_pmd),
- READ_ONCE(pmd_val(*pmdp))));
+ pmd_val(pmdp_get(pmdp))));
} else {
int ret;
@@ -283,7 +283,7 @@ static int init_pmd(pmd_t *pmdp, unsigned long addr, unsigned long end,
return ret;
VM_WARN_ON_ONCE(pmd_val(old_pmd) != 0 &&
- pmd_val(old_pmd) != READ_ONCE(pmd_val(*pmdp)));
+ pmd_val(old_pmd) != pmd_val(pmdp_get(pmdp)));
}
phys += next - addr;
} while (pmdp++, addr = next, addr != end);
@@ -294,7 +294,7 @@ static int init_pmd(pmd_t *pmdp, unsigned long addr, unsigned long end,
static bool pmd_range_has_valid_noncont(pmd_t *pmdp)
{
for (int i = 0; i < CONT_PMDS; i++) {
- pte_t pte = pmd_pte(READ_ONCE(pmdp[i]));
+ pte_t pte = pmd_pte(pmdp_get(pmdp + i));
if (pte_valid(pte) && !pte_cont(pte))
return true;
@@ -1505,7 +1505,7 @@ static void unmap_hotplug_pmd_range(pud_t *pudp, unsigned long addr,
do {
next = pmd_addr_end(addr, end);
pmdp = pmd_offset(pudp, addr);
- pmd = READ_ONCE(*pmdp);
+ pmd = pmdp_get(pmdp);
if (pmd_none(pmd))
continue;
@@ -1660,7 +1660,7 @@ static void free_empty_pmd_table(pud_t *pudp, unsigned long addr,
do {
next = pmd_addr_end(addr, end);
pmdp = pmd_offset(pudp, addr);
- pmd = READ_ONCE(*pmdp);
+ pmd = pmdp_get(pmdp);
if (pmd_none(pmd))
continue;
@@ -1681,7 +1681,7 @@ static void free_empty_pmd_table(pud_t *pudp, unsigned long addr,
*/
pmdp = pmd_offset(pudp, 0UL);
for (i = 0; i < PTRS_PER_PMD; i++) {
- if (!pmd_none(READ_ONCE(pmdp[i])))
+ if (!pmd_none(pmdp_get(pmdp + i)))
return;
}
@@ -1833,7 +1833,7 @@ int pmd_set_huge(pmd_t *pmdp, phys_addr_t phys, pgprot_t prot)
pmd_t new_pmd = pfn_pmd(__phys_to_pfn(phys), mk_pmd_sect_prot(prot));
/* Only allow permission changes for now */
- if (!pgattr_change_is_safe(READ_ONCE(pmd_val(*pmdp)),
+ if (!pgattr_change_is_safe(pmd_val(pmdp_get(pmdp)),
pmd_val(new_pmd)))
return 0;
@@ -1858,7 +1858,7 @@ int pud_clear_huge(pud_t *pudp)
int pmd_clear_huge(pmd_t *pmdp)
{
- if (!pmd_leaf(READ_ONCE(*pmdp)))
+ if (!pmd_leaf(pmdp_get(pmdp)))
return 0;
pmd_clear(pmdp);
return 1;
@@ -1870,7 +1870,7 @@ static int __pmd_free_pte_page(pmd_t *pmdp, unsigned long addr,
pte_t *table;
pmd_t pmd;
- pmd = READ_ONCE(*pmdp);
+ pmd = pmdp_get(pmdp);
if (!pmd_table(pmd)) {
VM_WARN_ON(1);
@@ -2377,4 +2377,5 @@ int arch_set_user_pkey_access(int pkey, unsigned long init_val)
return 0;
}
+
#endif
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index bbe98ac9ad8c..0ca07bd5ded9 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -414,7 +414,7 @@ bool kernel_page_present(struct page *page)
return pud_valid(pud);
pmdp = pmd_offset(pudp, addr);
- pmd = READ_ONCE(*pmdp);
+ pmd = pmdp_get(pmdp);
if (pmd_none(pmd))
return false;
if (pmd_leaf(pmd))
diff --git a/arch/arm64/mm/trans_pgd.c b/arch/arm64/mm/trans_pgd.c
index cca9706a875c..b27b2d2c20c3 100644
--- a/arch/arm64/mm/trans_pgd.c
+++ b/arch/arm64/mm/trans_pgd.c
@@ -74,7 +74,7 @@ static int copy_pmd(struct trans_pgd_info *info, pud_t *dst_pudp,
src_pmdp = pmd_offset(src_pudp, start);
do {
- pmd_t pmd = READ_ONCE(*src_pmdp);
+ pmd_t pmd = pmdp_get(src_pmdp);
next = pmd_addr_end(addr, end);
if (pmd_none(pmd))
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 03/17] arm64/mm: Convert READ_ONCE() as pudp_get() while accessing PUD
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
2026-07-29 12:24 ` [PATCH 01/17] mm: Add read-write accessors for vm_page_prot Anshuman Khandual
2026-07-29 12:24 ` [PATCH 02/17] arm64/mm: Convert READ_ONCE() as pmdp_get() while accessing PMD Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 04/17] arm64/mm: Convert READ_ONCE() as p4dp_get() while accessing P4D Anshuman Khandual
` (13 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel, kasan-dev
Convert all READ_ONCE() based PUD accesses as pudp_get() instead which will
support both D64 and D128 translation regime going forward. That is because
READ_ONCE() would need 128 bit single copy atomic guarantees, while reading
128 bit page table entries which is currently not supported on arm64. Build
fails for READ_ONCE() while accessing beyond 64 bits.
Load Pair/Store Pair (ldp/stp) are only single copy atomic if FEAT_LSE128
is supported (which is required when FEAT_D128 is supported). Currently 128
bit pgtables is a compile time decision - so we could have chosen to extend
READ_ONCE()/WRITE_ONCE() to allow 128 bit for this configuration. But then
it's a general purpose API and we were concerned that other users might
eventually creep in that expect 128 and then fail to compile in the other
configs.
But worse, we are considering eventually making D128 a boot time option, at
which point we'd have to make READ_ONCE() always allow 128 bit at compile
time but then it might silently tear at runtime.
So our preference is to standardize on these existing helpers, which we can
override in arm64 to give the 128 bit single copy guarantee when required.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: kasan-dev@googlegroups.com
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/pgtable.h | 3 ++-
arch/arm64/mm/fault.c | 2 +-
arch/arm64/mm/fixmap.c | 2 +-
arch/arm64/mm/hugetlbpage.c | 4 ++--
arch/arm64/mm/kasan_init.c | 4 ++--
arch/arm64/mm/mmu.c | 20 ++++++++++----------
arch/arm64/mm/pageattr.c | 2 +-
arch/arm64/mm/trans_pgd.c | 4 ++--
8 files changed, 21 insertions(+), 20 deletions(-)
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 5c0cc7c676fa..c9eef353455f 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -917,7 +917,8 @@ static inline pmd_t *pud_pgtable(pud_t pud)
}
/* Find an entry in the second-level page table. */
-#define pmd_offset_phys(dir, addr) (pud_page_paddr(READ_ONCE(*(dir))) + pmd_index(addr) * sizeof(pmd_t))
+#define pmd_offset_phys(dir, addr) (pud_page_paddr(pudp_get(dir)) + \
+ pmd_index(addr) * sizeof(pmd_t))
#define pmd_set_fixmap(addr) ((pmd_t *)set_fixmap_offset(FIX_PMD, addr))
#define pmd_set_fixmap_offset(pud, addr) pmd_set_fixmap(pmd_offset_phys(pud, addr))
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index 30f8b3872331..ccfd4ff76f88 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -174,7 +174,7 @@ static void show_pte(unsigned long addr)
break;
pudp = pud_offset(p4dp, addr);
- pud = READ_ONCE(*pudp);
+ pud = pudp_get(pudp);
pr_cont(", pud=%016llx", pud_val(pud));
if (pud_none(pud) || pud_bad(pud))
break;
diff --git a/arch/arm64/mm/fixmap.c b/arch/arm64/mm/fixmap.c
index 3cdac8021d4f..d9a870836faf 100644
--- a/arch/arm64/mm/fixmap.c
+++ b/arch/arm64/mm/fixmap.c
@@ -56,7 +56,7 @@ static void __init early_fixmap_init_pmd(pud_t *pudp, unsigned long addr,
unsigned long end)
{
unsigned long next;
- pud_t pud = READ_ONCE(*pudp);
+ pud_t pud = pudp_get(pudp);
pmd_t *pmdp;
if (pud_none(pud))
diff --git a/arch/arm64/mm/hugetlbpage.c b/arch/arm64/mm/hugetlbpage.c
index cdaa4500faf9..c9ad5e75b073 100644
--- a/arch/arm64/mm/hugetlbpage.c
+++ b/arch/arm64/mm/hugetlbpage.c
@@ -262,7 +262,7 @@ pte_t *huge_pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma,
WARN_ON(addr & (sz - 1));
ptep = pte_alloc_huge(mm, pmdp, addr);
} else if (sz == PMD_SIZE) {
- if (want_pmd_share(vma, addr) && pud_none(READ_ONCE(*pudp)))
+ if (want_pmd_share(vma, addr) && pud_none(pudp_get(pudp)))
ptep = huge_pmd_share(mm, vma, addr, pudp);
else
ptep = (pte_t *)pmd_alloc(mm, pudp, addr);
@@ -292,7 +292,7 @@ pte_t *huge_pte_offset(struct mm_struct *mm,
return NULL;
pudp = pud_offset(p4dp, addr);
- pud = READ_ONCE(*pudp);
+ pud = pudp_get(pudp);
if (sz != PUD_SIZE && pud_none(pud))
return NULL;
/* hugepage or swap? */
diff --git a/arch/arm64/mm/kasan_init.c b/arch/arm64/mm/kasan_init.c
index 8cb440379f17..b1926b661efe 100644
--- a/arch/arm64/mm/kasan_init.c
+++ b/arch/arm64/mm/kasan_init.c
@@ -76,7 +76,7 @@ static pte_t *__init kasan_pte_offset(pmd_t *pmdp, unsigned long addr, int node,
static pmd_t *__init kasan_pmd_offset(pud_t *pudp, unsigned long addr, int node,
bool early)
{
- if (pud_none(READ_ONCE(*pudp))) {
+ if (pud_none(pudp_get(pudp))) {
phys_addr_t pmd_phys = early ?
__pa_symbol(kasan_early_shadow_pmd)
: kasan_alloc_zeroed_page(node);
@@ -150,7 +150,7 @@ static void __init kasan_pud_populate(p4d_t *p4dp, unsigned long addr,
do {
next = pud_addr_end(addr, end);
kasan_pmd_populate(pudp, addr, next, node, early);
- } while (pudp++, addr = next, addr != end && pud_none(READ_ONCE(*pudp)));
+ } while (pudp++, addr = next, addr != end && pud_none(pudp_get(pudp)));
}
static void __init kasan_p4d_populate(pgd_t *pgdp, unsigned long addr,
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 85b2e2f9ea3e..fd1ce8649686 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -310,7 +310,7 @@ static int alloc_init_cont_pmd(pud_t *pudp, unsigned long addr,
{
int ret;
unsigned long next;
- pud_t pud = READ_ONCE(*pudp);
+ pud_t pud = pudp_get(pudp);
pmd_t *pmdp;
/*
@@ -391,7 +391,7 @@ static int alloc_init_pud(p4d_t *p4dp, unsigned long addr, unsigned long end,
}
do {
- pud_t old_pud = READ_ONCE(*pudp);
+ pud_t old_pud = pudp_get(pudp);
next = pud_addr_end(addr, end);
@@ -409,7 +409,7 @@ static int alloc_init_pud(p4d_t *p4dp, unsigned long addr, unsigned long end,
* only allow updates to the permission attributes.
*/
BUG_ON(!pgattr_change_is_safe(pud_val(old_pud),
- READ_ONCE(pud_val(*pudp))));
+ pud_val(pudp_get(pudp))));
} else {
ret = alloc_init_cont_pmd(pudp, addr, next, phys, prot,
pgtable_alloc, flags);
@@ -417,7 +417,7 @@ static int alloc_init_pud(p4d_t *p4dp, unsigned long addr, unsigned long end,
goto out;
VM_WARN_ON_ONCE(pud_val(old_pud) != 0 &&
- pud_val(old_pud) != READ_ONCE(pud_val(*pudp)));
+ pud_val(old_pud) != pud_val(pudp_get(pudp)));
}
phys += next - addr;
} while (pudp++, addr = next, addr != end);
@@ -1543,7 +1543,7 @@ static void unmap_hotplug_pud_range(p4d_t *p4dp, unsigned long addr,
do {
next = pud_addr_end(addr, end);
pudp = pud_offset(p4dp, addr);
- pud = READ_ONCE(*pudp);
+ pud = pudp_get(pudp);
if (pud_none(pud))
continue;
@@ -1700,7 +1700,7 @@ static void free_empty_pud_table(p4d_t *p4dp, unsigned long addr,
do {
next = pud_addr_end(addr, end);
pudp = pud_offset(p4dp, addr);
- pud = READ_ONCE(*pudp);
+ pud = pudp_get(pudp);
if (pud_none(pud))
continue;
@@ -1721,7 +1721,7 @@ static void free_empty_pud_table(p4d_t *p4dp, unsigned long addr,
*/
pudp = pud_offset(p4dp, 0UL);
for (i = 0; i < PTRS_PER_PUD; i++) {
- if (!pud_none(READ_ONCE(pudp[i])))
+ if (!pud_none(pudp_get(pudp + i)))
return;
}
@@ -1819,7 +1819,7 @@ int pud_set_huge(pud_t *pudp, phys_addr_t phys, pgprot_t prot)
pud_t new_pud = pfn_pud(__phys_to_pfn(phys), mk_pud_sect_prot(prot));
/* Only allow permission changes for now */
- if (!pgattr_change_is_safe(READ_ONCE(pud_val(*pudp)),
+ if (!pgattr_change_is_safe(pud_val(pudp_get(pudp)),
pud_val(new_pud)))
return 0;
@@ -1850,7 +1850,7 @@ void p4d_clear_huge(p4d_t *p4dp)
int pud_clear_huge(pud_t *pudp)
{
- if (!pud_leaf(READ_ONCE(*pudp)))
+ if (!pud_leaf(pudp_get(pudp)))
return 0;
pud_clear(pudp);
return 1;
@@ -1903,7 +1903,7 @@ int pud_free_pmd_page(pud_t *pudp, unsigned long addr)
pud_t pud;
unsigned long next, end;
- pud = READ_ONCE(*pudp);
+ pud = pudp_get(pudp);
if (!pud_table(pud)) {
VM_WARN_ON(1);
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index 0ca07bd5ded9..07b2fa4de57f 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -407,7 +407,7 @@ bool kernel_page_present(struct page *page)
return false;
pudp = pud_offset(p4dp, addr);
- pud = READ_ONCE(*pudp);
+ pud = pudp_get(pudp);
if (pud_none(pud))
return false;
if (pud_leaf(pud))
diff --git a/arch/arm64/mm/trans_pgd.c b/arch/arm64/mm/trans_pgd.c
index b27b2d2c20c3..d119119455f1 100644
--- a/arch/arm64/mm/trans_pgd.c
+++ b/arch/arm64/mm/trans_pgd.c
@@ -64,7 +64,7 @@ static int copy_pmd(struct trans_pgd_info *info, pud_t *dst_pudp,
unsigned long next;
unsigned long addr = start;
- if (pud_none(READ_ONCE(*dst_pudp))) {
+ if (pud_none(pudp_get(dst_pudp))) {
dst_pmdp = trans_alloc(info);
if (!dst_pmdp)
return -ENOMEM;
@@ -109,7 +109,7 @@ static int copy_pud(struct trans_pgd_info *info, p4d_t *dst_p4dp,
src_pudp = pud_offset(src_p4dp, start);
do {
- pud_t pud = READ_ONCE(*src_pudp);
+ pud_t pud = pudp_get(src_pudp);
next = pud_addr_end(addr, end);
if (pud_none(pud))
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 04/17] arm64/mm: Convert READ_ONCE() as p4dp_get() while accessing P4D
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (2 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 03/17] arm64/mm: Convert READ_ONCE() as pudp_get() while accessing PUD Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 05/17] arm64/mm: Convert READ_ONCE() as pgdp_get() while accessing PGD Anshuman Khandual
` (12 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel, kasan-dev
Convert all READ_ONCE() based P4D accesses as p4dp_get() instead which will
support both D64 and D128 translation regime going forward. That is because
READ_ONCE() would need 128 bit single copy atomic guarantees, while reading
128 bit page table entries which is currently not supported on arm64. Build
fails for READ_ONCE() while accessing beyond 64 bits.
Load Pair/Store Pair (ldp/stp) are only single copy atomic if FEAT_LSE128
is supported (which is required when FEAT_D128 is supported). Currently 128
bit pgtables is a compile time decision - so we could have chosen to extend
READ_ONCE()/WRITE_ONCE() to allow 128 bit for this configuration. But then
it's a general purpose API and we were concerned that other users might
eventually creep in that expect 128 and then fail to compile in the other
configs.
But worse, we are considering eventually making D128 a boot time option, at
which point we'd have to make READ_ONCE() always allow 128 bit at compile
time but then it might silently tear at runtime.
So our preference is to standardize on these existing helpers, which we can
override in arm64 to give the 128 bit single copy guarantee when required.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: kasan-dev@googlegroups.com
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/mm/fault.c | 2 +-
arch/arm64/mm/fixmap.c | 2 +-
arch/arm64/mm/hugetlbpage.c | 2 +-
arch/arm64/mm/kasan_init.c | 4 ++--
arch/arm64/mm/mmu.c | 12 ++++++------
arch/arm64/mm/pageattr.c | 2 +-
arch/arm64/mm/trans_pgd.c | 4 ++--
7 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index ccfd4ff76f88..6f3d30b689ff 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -168,7 +168,7 @@ static void show_pte(unsigned long addr)
break;
p4dp = p4d_offset(pgdp, addr);
- p4d = READ_ONCE(*p4dp);
+ p4d = p4dp_get(p4dp);
pr_cont(", p4d=%016llx", p4d_val(p4d));
if (p4d_none(p4d) || p4d_bad(p4d))
break;
diff --git a/arch/arm64/mm/fixmap.c b/arch/arm64/mm/fixmap.c
index d9a870836faf..2a0fe0b0f771 100644
--- a/arch/arm64/mm/fixmap.c
+++ b/arch/arm64/mm/fixmap.c
@@ -74,7 +74,7 @@ static void __init early_fixmap_init_pmd(pud_t *pudp, unsigned long addr,
static void __init early_fixmap_init_pud(p4d_t *p4dp, unsigned long addr,
unsigned long end)
{
- p4d_t p4d = READ_ONCE(*p4dp);
+ p4d_t p4d = p4dp_get(p4dp);
pud_t *pudp;
if (CONFIG_PGTABLE_LEVELS > 3 && !p4d_none(p4d) &&
diff --git a/arch/arm64/mm/hugetlbpage.c b/arch/arm64/mm/hugetlbpage.c
index c9ad5e75b073..5771bf49e1fe 100644
--- a/arch/arm64/mm/hugetlbpage.c
+++ b/arch/arm64/mm/hugetlbpage.c
@@ -288,7 +288,7 @@ pte_t *huge_pte_offset(struct mm_struct *mm,
return NULL;
p4dp = p4d_offset(pgdp, addr);
- if (!p4d_present(READ_ONCE(*p4dp)))
+ if (!p4d_present(p4dp_get(p4dp)))
return NULL;
pudp = pud_offset(p4dp, addr);
diff --git a/arch/arm64/mm/kasan_init.c b/arch/arm64/mm/kasan_init.c
index b1926b661efe..7e2bec43fd4a 100644
--- a/arch/arm64/mm/kasan_init.c
+++ b/arch/arm64/mm/kasan_init.c
@@ -89,7 +89,7 @@ static pmd_t *__init kasan_pmd_offset(pud_t *pudp, unsigned long addr, int node,
static pud_t *__init kasan_pud_offset(p4d_t *p4dp, unsigned long addr, int node,
bool early)
{
- if (p4d_none(READ_ONCE(*p4dp))) {
+ if (p4d_none(p4dp_get(p4dp))) {
phys_addr_t pud_phys = early ?
__pa_symbol(kasan_early_shadow_pud)
: kasan_alloc_zeroed_page(node);
@@ -162,7 +162,7 @@ static void __init kasan_p4d_populate(pgd_t *pgdp, unsigned long addr,
do {
next = p4d_addr_end(addr, end);
kasan_pud_populate(p4dp, addr, next, node, early);
- } while (p4dp++, addr = next, addr != end && p4d_none(READ_ONCE(*p4dp)));
+ } while (p4dp++, addr = next, addr != end && p4d_none(p4dp_get(p4dp)));
}
static void __init kasan_pgd_populate(unsigned long addr, unsigned long end,
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index fd1ce8649686..aceb20e48251 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -368,7 +368,7 @@ static int alloc_init_pud(p4d_t *p4dp, unsigned long addr, unsigned long end,
{
int ret = 0;
unsigned long next;
- p4d_t p4d = READ_ONCE(*p4dp);
+ p4d_t p4d = p4dp_get(p4dp);
pud_t *pudp;
if (p4d_none(p4d)) {
@@ -458,7 +458,7 @@ static int alloc_init_p4d(pgd_t *pgdp, unsigned long addr, unsigned long end,
}
do {
- p4d_t old_p4d = READ_ONCE(*p4dp);
+ p4d_t old_p4d = p4dp_get(p4dp);
next = p4d_addr_end(addr, end);
@@ -468,7 +468,7 @@ static int alloc_init_p4d(pgd_t *pgdp, unsigned long addr, unsigned long end,
goto out;
VM_WARN_ON_ONCE(p4d_val(old_p4d) != 0 &&
- p4d_val(old_p4d) != READ_ONCE(p4d_val(*p4dp)));
+ p4d_val(old_p4d) != (p4d_val(p4dp_get(p4dp))));
phys += next - addr;
} while (p4dp++, addr = next, addr != end);
@@ -1574,7 +1574,7 @@ static void unmap_hotplug_p4d_range(pgd_t *pgdp, unsigned long addr,
do {
next = p4d_addr_end(addr, end);
p4dp = p4d_offset(pgdp, addr);
- p4d = READ_ONCE(*p4dp);
+ p4d = p4dp_get(p4dp);
if (p4d_none(p4d))
continue;
@@ -1740,7 +1740,7 @@ static void free_empty_p4d_table(pgd_t *pgdp, unsigned long addr,
do {
next = p4d_addr_end(addr, end);
p4dp = p4d_offset(pgdp, addr);
- p4d = READ_ONCE(*p4dp);
+ p4d = p4dp_get(p4dp);
if (p4d_none(p4d))
continue;
@@ -1761,7 +1761,7 @@ static void free_empty_p4d_table(pgd_t *pgdp, unsigned long addr,
*/
p4dp = p4d_offset(pgdp, 0UL);
for (i = 0; i < PTRS_PER_P4D; i++) {
- if (!p4d_none(READ_ONCE(p4dp[i])))
+ if (!p4d_none(p4dp_get(p4dp + i)))
return;
}
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index 07b2fa4de57f..826856ef64a8 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -403,7 +403,7 @@ bool kernel_page_present(struct page *page)
return false;
p4dp = p4d_offset(pgdp, addr);
- if (p4d_none(READ_ONCE(*p4dp)))
+ if (p4d_none(p4dp_get(p4dp)))
return false;
pudp = pud_offset(p4dp, addr);
diff --git a/arch/arm64/mm/trans_pgd.c b/arch/arm64/mm/trans_pgd.c
index d119119455f1..7afe2beca4ba 100644
--- a/arch/arm64/mm/trans_pgd.c
+++ b/arch/arm64/mm/trans_pgd.c
@@ -99,7 +99,7 @@ static int copy_pud(struct trans_pgd_info *info, p4d_t *dst_p4dp,
unsigned long next;
unsigned long addr = start;
- if (p4d_none(READ_ONCE(*dst_p4dp))) {
+ if (p4d_none(p4dp_get(dst_p4dp))) {
dst_pudp = trans_alloc(info);
if (!dst_pudp)
return -ENOMEM;
@@ -145,7 +145,7 @@ static int copy_p4d(struct trans_pgd_info *info, pgd_t *dst_pgdp,
src_p4dp = p4d_offset(src_pgdp, start);
do {
next = p4d_addr_end(addr, end);
- if (p4d_none(READ_ONCE(*src_p4dp)))
+ if (p4d_none(p4dp_get(src_p4dp)))
continue;
if (copy_pud(info, dst_p4dp, src_p4dp, addr, next))
return -ENOMEM;
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 05/17] arm64/mm: Convert READ_ONCE() as pgdp_get() while accessing PGD
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (3 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 04/17] arm64/mm: Convert READ_ONCE() as p4dp_get() while accessing P4D Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 06/17] arm64/mm: Route all pgtable reads via ptval_get() Anshuman Khandual
` (11 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel, kasan-dev
Convert all READ_ONCE() based PGD accesses as pgdp_get() instead which will
support both D64 and D128 translation regime going forward. That is because
READ_ONCE() would need 128 bit single copy atomic guarantees, while reading
128 bit page table entries which is currently not supported on arm64. Build
fails for READ_ONCE() while accessing beyond 64 bits.
Load Pair/Store Pair (ldp/stp) are only single copy atomic if FEAT_LSE128
is supported (which is required when FEAT_D128 is supported). Currently 128
bit pgtables is a compile time decision - so we could have chosen to extend
READ_ONCE()/WRITE_ONCE() to allow 128 bit for this configuration. But then
it's a general purpose API and we were concerned that other users might
eventually creep in that expect 128 and then fail to compile in the other
configs.
But worse, we are considering eventually making D128 a boot time option, at
which point we'd have to make READ_ONCE() always allow 128 bit at compile
time but then it might silently tear at runtime.
So our preference is to standardize on these existing helpers, which we can
override in arm64 to give the 128 bit single copy guarantee when required.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: kasan-dev@googlegroups.com
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/mm/fault.c | 2 +-
arch/arm64/mm/hugetlbpage.c | 2 +-
arch/arm64/mm/kasan_init.c | 6 +++---
arch/arm64/mm/mmu.c | 6 +++---
arch/arm64/mm/pageattr.c | 2 +-
arch/arm64/mm/trans_pgd.c | 4 ++--
6 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index 6f3d30b689ff..fca716ed4513 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -155,7 +155,7 @@ static void show_pte(unsigned long addr)
mm == &init_mm ? "swapper" : "user", PAGE_SIZE / SZ_1K,
vabits_actual, mm_to_pgd_phys(mm));
pgdp = pgd_offset(mm, addr);
- pgd = READ_ONCE(*pgdp);
+ pgd = pgdp_get(pgdp);
pr_alert("[%016lx] pgd=%016llx", addr, pgd_val(pgd));
do {
diff --git a/arch/arm64/mm/hugetlbpage.c b/arch/arm64/mm/hugetlbpage.c
index 5771bf49e1fe..e05e5b41ae50 100644
--- a/arch/arm64/mm/hugetlbpage.c
+++ b/arch/arm64/mm/hugetlbpage.c
@@ -284,7 +284,7 @@ pte_t *huge_pte_offset(struct mm_struct *mm,
pmd_t *pmdp, pmd;
pgdp = pgd_offset(mm, addr);
- if (!pgd_present(READ_ONCE(*pgdp)))
+ if (!pgd_present(pgdp_get(pgdp)))
return NULL;
p4dp = p4d_offset(pgdp, addr);
diff --git a/arch/arm64/mm/kasan_init.c b/arch/arm64/mm/kasan_init.c
index 7e2bec43fd4a..41b65fd31f76 100644
--- a/arch/arm64/mm/kasan_init.c
+++ b/arch/arm64/mm/kasan_init.c
@@ -102,7 +102,7 @@ static pud_t *__init kasan_pud_offset(p4d_t *p4dp, unsigned long addr, int node,
static p4d_t *__init kasan_p4d_offset(pgd_t *pgdp, unsigned long addr, int node,
bool early)
{
- if (pgd_none(READ_ONCE(*pgdp))) {
+ if (pgd_none(pgdp_get(pgdp))) {
phys_addr_t p4d_phys = early ?
__pa_symbol(kasan_early_shadow_p4d)
: kasan_alloc_zeroed_page(node);
@@ -256,7 +256,7 @@ static int __init root_level_idx(u64 addr)
static void __init clone_next_level(u64 addr, pgd_t *tmp_pg_dir, pud_t *pud)
{
int idx = root_level_idx(addr);
- pgd_t pgd = READ_ONCE(swapper_pg_dir[idx]);
+ pgd_t pgd = pgdp_get(swapper_pg_dir + idx);
pud_t *pudp = (pud_t *)__phys_to_kimg(__pgd_to_phys(pgd));
memcpy(pud, pudp, PAGE_SIZE);
@@ -280,7 +280,7 @@ static int __init next_level_idx(u64 addr)
*/
static void __init clear_next_level(int pgd_idx, int start, int end)
{
- pgd_t pgd = READ_ONCE(swapper_pg_dir[pgd_idx]);
+ pgd_t pgd = pgdp_get(swapper_pg_dir + pgd_idx);
pud_t *pudp = (pud_t *)__phys_to_kimg(__pgd_to_phys(pgd));
memset(&pudp[start], 0, (end - start) * sizeof(pud_t));
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index aceb20e48251..d5b6eb544216 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -435,7 +435,7 @@ static int alloc_init_p4d(pgd_t *pgdp, unsigned long addr, unsigned long end,
{
int ret;
unsigned long next;
- pgd_t pgd = READ_ONCE(*pgdp);
+ pgd_t pgd = pgdp_get(pgdp);
p4d_t *p4dp;
if (pgd_none(pgd)) {
@@ -1601,7 +1601,7 @@ static void unmap_hotplug_range(unsigned long addr, unsigned long end,
do {
next = pgd_addr_end(addr, end);
pgdp = pgd_offset_k(addr);
- pgd = READ_ONCE(*pgdp);
+ pgd = pgdp_get(pgdp);
if (pgd_none(pgd))
continue;
@@ -1779,7 +1779,7 @@ static void free_empty_tables(unsigned long addr, unsigned long end,
do {
next = pgd_addr_end(addr, end);
pgdp = pgd_offset_k(addr);
- pgd = READ_ONCE(*pgdp);
+ pgd = pgdp_get(pgdp);
if (pgd_none(pgd))
continue;
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index 826856ef64a8..dfa1356daac2 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -399,7 +399,7 @@ bool kernel_page_present(struct page *page)
unsigned long addr = (unsigned long)page_address(page);
pgdp = pgd_offset_k(addr);
- if (pgd_none(READ_ONCE(*pgdp)))
+ if (pgd_none(pgdp_get(pgdp)))
return false;
p4dp = p4d_offset(pgdp, addr);
diff --git a/arch/arm64/mm/trans_pgd.c b/arch/arm64/mm/trans_pgd.c
index 7afe2beca4ba..06470d690f9f 100644
--- a/arch/arm64/mm/trans_pgd.c
+++ b/arch/arm64/mm/trans_pgd.c
@@ -134,7 +134,7 @@ static int copy_p4d(struct trans_pgd_info *info, pgd_t *dst_pgdp,
unsigned long next;
unsigned long addr = start;
- if (pgd_none(READ_ONCE(*dst_pgdp))) {
+ if (pgd_none(pgdp_get(dst_pgdp))) {
dst_p4dp = trans_alloc(info);
if (!dst_p4dp)
return -ENOMEM;
@@ -164,7 +164,7 @@ static int copy_page_tables(struct trans_pgd_info *info, pgd_t *dst_pgdp,
dst_pgdp = pgd_offset_pgd(dst_pgdp, start);
do {
next = pgd_addr_end(addr, end);
- if (pgd_none(READ_ONCE(*src_pgdp)))
+ if (pgd_none(pgdp_get(src_pgdp)))
continue;
if (copy_p4d(info, dst_pgdp, src_pgdp, addr, next))
return -ENOMEM;
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 06/17] arm64/mm: Route all pgtable reads via ptval_get()
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (4 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 05/17] arm64/mm: Convert READ_ONCE() as pgdp_get() while accessing PGD Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 07/17] arm64/mm: Route all pgtable writes via ptval_set() Anshuman Khandual
` (10 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
Define arm64 platform specific implementations for new pxdp_get() helpers.
These resolve into READ_ONCE(), thus ensuring required single copy atomic
semantics for the page table entry reads.
In future this infrastructure can be used for D128 to maintain single copy
atomicity semantics with inline asm blocks.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/pgtable.h | 38 +++++++++++++++++++++++++++-----
1 file changed, 32 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index c9eef353455f..030ec3582a0d 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -84,6 +84,32 @@ static inline void arch_leave_lazy_mmu_mode(void)
arch_flush_lazy_mmu_mode();
}
+#define ptval_get(x) READ_ONCE(x)
+
+#define pmdp_get pmdp_get
+static inline pmd_t pmdp_get(pmd_t *pmdp)
+{
+ return ptval_get(*pmdp);
+}
+
+#define pudp_get pudp_get
+static inline pud_t pudp_get(pud_t *pudp)
+{
+ return ptval_get(*pudp);
+}
+
+#define p4dp_get p4dp_get
+static inline p4d_t p4dp_get(p4d_t *p4dp)
+{
+ return ptval_get(*p4dp);
+}
+
+#define pgdp_get pgdp_get
+static inline pgd_t pgdp_get(pgd_t *pgdp)
+{
+ return ptval_get(*pgdp);
+}
+
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
#define __HAVE_ARCH_FLUSH_PMD_TLB_RANGE
@@ -380,7 +406,7 @@ static inline void __set_pte(pte_t *ptep, pte_t pte)
static inline pte_t __ptep_get(pte_t *ptep)
{
- return READ_ONCE(*ptep);
+ return ptval_get(*ptep);
}
extern void __sync_icache_dcache(pte_t pteval);
@@ -1011,7 +1037,7 @@ static inline phys_addr_t pud_offset_phys(p4d_t *p4dp, unsigned long addr)
{
VM_WARN_ON_ONCE(!pgtable_l4_enabled());
- return p4d_page_paddr(READ_ONCE(*p4dp)) + pud_index(addr) * sizeof(pud_t);
+ return p4d_page_paddr(p4dp_get(p4dp)) + pud_index(addr) * sizeof(pud_t);
}
static inline
@@ -1025,7 +1051,7 @@ pud_t *pud_offset_lockless(p4d_t *p4dp, p4d_t p4d, unsigned long addr)
static inline pud_t *pud_offset(p4d_t *p4dp, unsigned long addr)
{
- return pud_offset_lockless(p4dp, READ_ONCE(*p4dp), addr);
+ return pud_offset_lockless(p4dp, p4dp_get(p4dp), addr);
}
#define pud_offset pud_offset
@@ -1134,7 +1160,7 @@ static inline phys_addr_t p4d_offset_phys(pgd_t *pgdp, unsigned long addr)
{
VM_WARN_ON_ONCE(!pgtable_l5_enabled());
- return pgd_page_paddr(READ_ONCE(*pgdp)) + p4d_index(addr) * sizeof(p4d_t);
+ return pgd_page_paddr(pgdp_get(pgdp)) + p4d_index(addr) * sizeof(p4d_t);
}
static inline
@@ -1148,7 +1174,7 @@ p4d_t *p4d_offset_lockless(pgd_t *pgdp, pgd_t pgd, unsigned long addr)
static inline p4d_t *p4d_offset(pgd_t *pgdp, unsigned long addr)
{
- return p4d_offset_lockless(pgdp, READ_ONCE(*pgdp), addr);
+ return p4d_offset_lockless(pgdp, pgdp_get(pgdp), addr);
}
static inline p4d_t *p4d_set_fixmap(unsigned long addr)
@@ -1346,7 +1372,7 @@ static inline bool pmdp_test_and_clear_young(struct vm_area_struct *vma,
unsigned long address, pmd_t *pmdp)
{
/* Operation applies to PMD table entry only if FEAT_HAFT is enabled */
- VM_WARN_ON(pmd_table(READ_ONCE(*pmdp)) && !system_supports_haft());
+ VM_WARN_ON(pmd_table(pmdp_get(pmdp)) && !system_supports_haft());
return __ptep_test_and_clear_young(vma, address, (pte_t *)pmdp);
}
#endif /* CONFIG_TRANSPARENT_HUGEPAGE || CONFIG_ARCH_HAS_NONLEAF_PMD_YOUNG */
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 07/17] arm64/mm: Route all pgtable writes via ptval_set()
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (5 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 06/17] arm64/mm: Route all pgtable reads via ptval_get() Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 08/17] arm64/mm: Route all pgtable atomics to central helpers Anshuman Khandual
` (9 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
Currently ptval_set() is defined as WRITE_ONCE() but this will change for
D128 pgtable builds, for which WRITE_ONCE() is not sufficient for single
copy atomicity.
In future this infrastructure can be used for D128 to maintain single copy
atomicity semantics with inline asm blocks.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/pgtable.h | 11 ++++++-----
arch/arm64/mm/mmu.c | 4 ++--
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 030ec3582a0d..1106977e0ef0 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -85,6 +85,7 @@ static inline void arch_leave_lazy_mmu_mode(void)
}
#define ptval_get(x) READ_ONCE(x)
+#define ptval_set(x, val) WRITE_ONCE(x, val)
#define pmdp_get pmdp_get
static inline pmd_t pmdp_get(pmd_t *pmdp)
@@ -385,7 +386,7 @@ static inline pte_t pte_clear_uffd_wp(pte_t pte)
static inline void __set_pte_nosync(pte_t *ptep, pte_t pte)
{
- WRITE_ONCE(*ptep, pte);
+ ptval_set(*ptep, pte);
}
static inline void __set_pte_complete(pte_t pte)
@@ -856,7 +857,7 @@ static inline void set_pmd(pmd_t *pmdp, pmd_t pmd)
}
#endif /* __PAGETABLE_PMD_FOLDED */
- WRITE_ONCE(*pmdp, pmd);
+ ptval_set(*pmdp, pmd);
if (pmd_valid(pmd))
queue_pte_barriers();
@@ -921,7 +922,7 @@ static inline void set_pud(pud_t *pudp, pud_t pud)
return;
}
- WRITE_ONCE(*pudp, pud);
+ ptval_set(*pudp, pud);
if (pud_valid(pud))
queue_pte_barriers();
@@ -1003,7 +1004,7 @@ static inline void set_p4d(p4d_t *p4dp, p4d_t p4d)
return;
}
- WRITE_ONCE(*p4dp, p4d);
+ ptval_set(*p4dp, p4d);
queue_pte_barriers();
}
@@ -1131,7 +1132,7 @@ static inline void set_pgd(pgd_t *pgdp, pgd_t pgd)
return;
}
- WRITE_ONCE(*pgdp, pgd);
+ ptval_set(*pgdp, pgd);
queue_pte_barriers();
}
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index d5b6eb544216..1127917c554b 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -76,7 +76,7 @@ void noinstr set_swapper_pgd(pgd_t *pgdp, pgd_t pgd)
* writable in the kernel mapping.
*/
if (rodata_is_rw) {
- WRITE_ONCE(*pgdp, pgd);
+ ptval_set(*pgdp, pgd);
dsb(ishst);
isb();
return;
@@ -84,7 +84,7 @@ void noinstr set_swapper_pgd(pgd_t *pgdp, pgd_t pgd)
spin_lock(&swapper_pgdir_lock);
fixmap_pgdp = pgd_set_fixmap(__pa_symbol(pgdp));
- WRITE_ONCE(*fixmap_pgdp, pgd);
+ ptval_set(*fixmap_pgdp, pgd);
/*
* We need dsb(ishst) here to ensure the page-table-walker sees
* our new entry before set_p?d() returns. The fixmap's
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 08/17] arm64/mm: Route all pgtable atomics to central helpers
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (6 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 07/17] arm64/mm: Route all pgtable writes via ptval_set() Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 09/17] arm64/mm: Override read-write accessors for vm_page_prot Anshuman Khandual
` (8 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
Route all cmpxchg() operations performed on various page table entries to a
new ptval_cmpxchg_relaxed() helper. Similarly route all xchg() operations
performed on page table entries to a new ptval_xchg_relaxed() helper.
Currently these helpers just forward to the same APIs that were previously
called direct, but in future we will change the routing for D128 which is
too long to use the standard APIs.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/pgtable.h | 26 +++++++++++++++++++-------
arch/arm64/mm/fault.c | 2 +-
2 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 1106977e0ef0..4efcacec6f01 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -87,6 +87,17 @@ static inline void arch_leave_lazy_mmu_mode(void)
#define ptval_get(x) READ_ONCE(x)
#define ptval_set(x, val) WRITE_ONCE(x, val)
+static inline ptval_t ptval_cmpxchg_relaxed(ptval_t *ptep, ptval_t old,
+ ptval_t new)
+{
+ return cmpxchg_relaxed(ptep, old, new);
+}
+
+static inline ptval_t ptval_xchg_relaxed(ptval_t *ptep, ptval_t new)
+{
+ return xchg_relaxed(ptep, new);
+}
+
#define pmdp_get pmdp_get
static inline pmd_t pmdp_get(pmd_t *pmdp)
{
@@ -1340,8 +1351,8 @@ static inline bool __ptep_test_and_clear_young(struct vm_area_struct *vma,
do {
old_pte = pte;
pte = pte_mkold(pte);
- pte_val(pte) = cmpxchg_relaxed(&pte_val(*ptep),
- pte_val(old_pte), pte_val(pte));
+ pte_val(pte) = ptval_cmpxchg_relaxed(&pte_val(*ptep),
+ pte_val(old_pte), pte_val(pte));
} while (pte_val(pte) != pte_val(old_pte));
return pte_young(pte);
@@ -1383,7 +1394,7 @@ static inline pte_t __ptep_get_and_clear_anysz(struct mm_struct *mm,
pte_t *ptep,
unsigned long pgsize)
{
- pte_t pte = __pte(xchg_relaxed(&pte_val(*ptep), 0));
+ pte_t pte = __pte(ptval_xchg_relaxed(&pte_val(*ptep), 0));
switch (pgsize) {
case PAGE_SIZE:
@@ -1459,7 +1470,7 @@ static inline void ___ptep_set_wrprotect(struct mm_struct *mm,
do {
old_pte = pte;
pte = pte_wrprotect(pte);
- pte_val(pte) = cmpxchg_relaxed(&pte_val(*ptep),
+ pte_val(pte) = ptval_cmpxchg_relaxed(&pte_val(*ptep),
pte_val(old_pte), pte_val(pte));
} while (pte_val(pte) != pte_val(old_pte));
}
@@ -1497,7 +1508,7 @@ static inline void __clear_young_dirty_pte(struct vm_area_struct *vma,
if (flags & CYDP_CLEAR_DIRTY)
pte = pte_mkclean(pte);
- pte_val(pte) = cmpxchg_relaxed(&pte_val(*ptep),
+ pte_val(pte) = ptval_cmpxchg_relaxed(&pte_val(*ptep),
pte_val(old_pte), pte_val(pte));
} while (pte_val(pte) != pte_val(old_pte));
}
@@ -1536,7 +1547,7 @@ static inline pmd_t pmdp_establish(struct vm_area_struct *vma,
unsigned long address, pmd_t *pmdp, pmd_t pmd)
{
page_table_check_pmd_set(vma->vm_mm, address, pmdp, pmd);
- return __pmd(xchg_relaxed(&pmd_val(*pmdp), pmd_val(pmd)));
+ return __pmd(ptval_xchg_relaxed(&pmd_val(*pmdp), pmd_val(pmd)));
}
#endif
@@ -1863,7 +1874,8 @@ static inline bool ptep_try_set(pte_t *ptep, pte_t new_pte)
{
pteval_t old = 0;
- if (!try_cmpxchg(&pte_val(*ptep), &old, pte_val(new_pte)))
+ old = ptval_cmpxchg_relaxed(&pte_val(*ptep), old, pte_val(new_pte));
+ if (old != 0)
return false;
/*
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index fca716ed4513..7aff70c5479e 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -234,7 +234,7 @@ int __ptep_set_access_flags_anysz(struct vm_area_struct *vma,
pteval ^= PTE_RDONLY;
pteval |= pte_val(entry);
pteval ^= PTE_RDONLY;
- pteval = cmpxchg_relaxed(&pte_val(*ptep), old_pteval, pteval);
+ pteval = ptval_cmpxchg_relaxed(&pte_val(*ptep), old_pteval, pteval);
} while (pteval != old_pteval);
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 09/17] arm64/mm: Override read-write accessors for vm_page_prot
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (7 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 08/17] arm64/mm: Route all pgtable atomics to central helpers Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 10/17] arm64/mm: Standardize printing for pgtable entries Anshuman Khandual
` (7 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
Override pgprot_[read|write]() accessors using ptval_[get|set]() providing
required single copy atomic operation for vma->vm_page_prot.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/pgtable.h | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 4efcacec6f01..39635f9550c2 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -122,6 +122,18 @@ static inline pgd_t pgdp_get(pgd_t *pgdp)
return ptval_get(*pgdp);
}
+#define pgprot_read pgprot_read
+static inline pgprot_t pgprot_read(pgprot_t *prot)
+{
+ return ptval_get(*prot);
+}
+
+#define pgprot_write pgprot_write
+static inline void pgprot_write(pgprot_t *prot, pgprot_t val)
+{
+ ptval_set(*prot, val);
+}
+
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
#define __HAVE_ARCH_FLUSH_PMD_TLB_RANGE
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 10/17] arm64/mm: Standardize printing for pgtable entries
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (8 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 09/17] arm64/mm: Override read-write accessors for vm_page_prot Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 11/17] arm64/mm: Enable fixmap with 5 level page table Anshuman Khandual
` (6 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
Standardize printing for pgtable entries using recently introduced generic
helper ptval_bytes_to_hex_str() in core MM which automatically enables 128
bits entries later when required. While here also drop unused pte_ERROR().
First move all required helpers in core MM into a common header file which
could then be used in platforms.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: David Hildenbrand <david@kernel.org>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-mm@kvack.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/pgtable.h | 40 ++++++++++++++------------------
arch/arm64/mm/fault.c | 21 +++++++++++++----
arch/arm64/mm/mmu.c | 16 +++++++++++++
include/linux/mm_types.h | 40 ++++++++++++++++++++++++++++++++
mm/memory.c | 40 --------------------------------
5 files changed, 90 insertions(+), 67 deletions(-)
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 39635f9550c2..2270a3e4cc13 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -157,9 +157,6 @@ static inline void pgprot_write(pgprot_t *prot, pgprot_t val)
__flush_tlb_range(vma, address, address + PMD_SIZE, PMD_SIZE, 2, \
TLBF_NOBROADCAST | TLBF_NONOTIFY | TLBF_NOWALKCACHE)
-#define pte_ERROR(e) \
- pr_err("%s:%d: bad pte %016llx.\n", __FILE__, __LINE__, pte_val(e))
-
#ifdef CONFIG_ARM64_PA_BITS_52
static inline phys_addr_t __pte_to_phys(pte_t pte)
{
@@ -455,6 +452,8 @@ bool pgattr_change_is_safe(pteval_t old, pteval_t new);
static inline void __check_safe_pte_update(struct mm_struct *mm, pte_t *ptep,
pte_t pte)
{
+ char pte_str_old[PTVAL_STR_MAX];
+ char pte_str[PTVAL_STR_MAX];
pte_t old_pte;
if (!IS_ENABLED(CONFIG_DEBUG_VM))
@@ -472,15 +471,17 @@ static inline void __check_safe_pte_update(struct mm_struct *mm, pte_t *ptep,
* (__ptep_set_access_flags safely changes valid ptes without going
* through an invalid entry).
*/
+ ptval_to_str(pte_str, pte_val(pte));
+ ptval_to_str(pte_str_old, pte_val(old_pte));
VM_WARN_ONCE(!pte_young(pte),
- "%s: racy access flag clearing: 0x%016llx -> 0x%016llx",
- __func__, pte_val(old_pte), pte_val(pte));
+ "%s: racy access flag clearing: %s -> %s",
+ __func__, pte_str_old, pte_str);
VM_WARN_ONCE(pte_write(old_pte) && !pte_dirty(pte),
- "%s: racy dirty state clearing: 0x%016llx -> 0x%016llx",
- __func__, pte_val(old_pte), pte_val(pte));
+ "%s: racy dirty state clearing: %s -> %s",
+ __func__, pte_str_old, pte_str);
VM_WARN_ONCE(!pgattr_change_is_safe(pte_val(old_pte), pte_val(pte)),
- "%s: unsafe attribute change: 0x%016llx -> 0x%016llx",
- __func__, pte_val(old_pte), pte_val(pte));
+ "%s: unsafe attribute change: %s -> %s",
+ __func__, pte_str_old, pte_str);
}
static inline void __sync_cache_and_tags(pte_t pte, unsigned int nr_pages)
@@ -901,6 +902,10 @@ static inline unsigned long pmd_page_vaddr(pmd_t pmd)
return (unsigned long)__va(pmd_page_paddr(pmd));
}
+enum pgtable_level;
+
+void ptval_ERROR(const char *file, int line, enum pgtable_level level, ptval_t val);
+
/* Find an entry in the third-level page table. */
#define pte_offset_phys(dir, addr) (pmd_page_paddr(pmdp_get(dir)) + \
pte_index(addr) * sizeof(pte_t))
@@ -915,10 +920,7 @@ static inline unsigned long pmd_page_vaddr(pmd_t pmd)
#define pte_offset_kimg(dir,addr) ((pte_t *)__phys_to_kimg(pte_offset_phys((dir), (addr))))
#if CONFIG_PGTABLE_LEVELS > 2
-
-#define pmd_ERROR(e) \
- pr_err("%s:%d: bad pmd %016llx.\n", __FILE__, __LINE__, pmd_val(e))
-
+#define pmd_ERROR(e) ptval_ERROR(__FILE__, __LINE__, PGTABLE_LEVEL_PMD, pmd_val(e))
#define pud_none(pud) (!pud_val(pud))
#define pud_bad(pud) ((pud_val(pud) & PUD_TYPE_MASK) != \
PUD_TYPE_TABLE)
@@ -1011,9 +1013,7 @@ static inline bool mm_pud_folded(const struct mm_struct *mm)
}
#define mm_pud_folded mm_pud_folded
-#define pud_ERROR(e) \
- pr_err("%s:%d: bad pud %016llx.\n", __FILE__, __LINE__, pud_val(e))
-
+#define pud_ERROR(e) ptval_ERROR(__FILE__, __LINE__, PGTABLE_LEVEL_PUD, pud_val(e))
#define p4d_none(p4d) (pgtable_l4_enabled() && !p4d_val(p4d))
#define p4d_bad(p4d) (pgtable_l4_enabled() && \
((p4d_val(p4d) & P4D_TYPE_MASK) != \
@@ -1139,9 +1139,7 @@ static inline bool mm_p4d_folded(const struct mm_struct *mm)
}
#define mm_p4d_folded mm_p4d_folded
-#define p4d_ERROR(e) \
- pr_err("%s:%d: bad p4d %016llx.\n", __FILE__, __LINE__, p4d_val(e))
-
+#define p4d_ERROR(e) ptval_ERROR(__FILE__, __LINE__, PGTABLE_LEVEL_P4D, p4d_val(e))
#define pgd_none(pgd) (pgtable_l5_enabled() && !pgd_val(pgd))
#define pgd_bad(pgd) (pgtable_l5_enabled() && \
((pgd_val(pgd) & PGD_TYPE_MASK) != \
@@ -1268,9 +1266,7 @@ p4d_t *p4d_offset_lockless_folded(pgd_t *pgdp, pgd_t pgd, unsigned long addr)
#endif /* CONFIG_PGTABLE_LEVELS > 4 */
-#define pgd_ERROR(e) \
- pr_err("%s:%d: bad pgd %016llx.\n", __FILE__, __LINE__, pgd_val(e))
-
+#define pgd_ERROR(e) ptval_ERROR(__FILE__, __LINE__, PGTABLE_LEVEL_PGD, pgd_val(e))
#define pgd_set_fixmap(addr) ((pgd_t *)set_fixmap_offset(FIX_PGD, addr))
#define pgd_clear_fixmap() clear_fixmap(FIX_PGD)
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index 7aff70c5479e..a38f4f1eacc4 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -14,6 +14,7 @@
#include <linux/kfence.h>
#include <linux/signal.h>
#include <linux/mm.h>
+#include <linux/memory.h>
#include <linux/hardirq.h>
#include <linux/init.h>
#include <linux/kasan.h>
@@ -130,6 +131,11 @@ static inline unsigned long mm_to_pgd_phys(struct mm_struct *mm)
*/
static void show_pte(unsigned long addr)
{
+ char pgd_str[PTVAL_STR_MAX];
+ char p4d_str[PTVAL_STR_MAX];
+ char pud_str[PTVAL_STR_MAX];
+ char pmd_str[PTVAL_STR_MAX];
+ char pte_str[PTVAL_STR_MAX];
struct mm_struct *mm;
pgd_t *pgdp;
pgd_t pgd;
@@ -156,7 +162,8 @@ static void show_pte(unsigned long addr)
vabits_actual, mm_to_pgd_phys(mm));
pgdp = pgd_offset(mm, addr);
pgd = pgdp_get(pgdp);
- pr_alert("[%016lx] pgd=%016llx", addr, pgd_val(pgd));
+ ptval_to_str(pgd_str, pgd_val(pgd));
+ pr_alert("[%016lx] pgd=%s", addr, pgd_str);
do {
p4d_t *p4dp, p4d;
@@ -169,19 +176,22 @@ static void show_pte(unsigned long addr)
p4dp = p4d_offset(pgdp, addr);
p4d = p4dp_get(p4dp);
- pr_cont(", p4d=%016llx", p4d_val(p4d));
+ ptval_to_str(p4d_str, p4d_val(p4d));
+ pr_cont(", p4d=%s", p4d_str);
if (p4d_none(p4d) || p4d_bad(p4d))
break;
pudp = pud_offset(p4dp, addr);
pud = pudp_get(pudp);
- pr_cont(", pud=%016llx", pud_val(pud));
+ ptval_to_str(pud_str, pud_val(pud));
+ pr_cont(", pud=%s", pud_str);
if (pud_none(pud) || pud_bad(pud))
break;
pmdp = pmd_offset(pudp, addr);
pmd = pmdp_get(pmdp);
- pr_cont(", pmd=%016llx", pmd_val(pmd));
+ ptval_to_str(pmd_str, pmd_val(pmd));
+ pr_cont(", pmd=%s", pmd_str);
if (pmd_none(pmd) || pmd_bad(pmd))
break;
@@ -190,7 +200,8 @@ static void show_pte(unsigned long addr)
break;
pte = __ptep_get(ptep);
- pr_cont(", pte=%016llx", pte_val(pte));
+ ptval_to_str(pte_str, pte_val(pte));
+ pr_cont(", pte=%s", pte_str);
pte_unmap(ptep);
} while(0);
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 1127917c554b..fd2db5f2e6a5 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -2337,6 +2337,22 @@ void __cpu_replace_ttbr1(pgd_t *pgdp, bool cnp)
cpu_uninstall_idmap();
}
+static const char * const pgtable_level_name[] = {
+ [PGTABLE_LEVEL_PTE] = "pte",
+ [PGTABLE_LEVEL_PMD] = "pmd",
+ [PGTABLE_LEVEL_PUD] = "pud",
+ [PGTABLE_LEVEL_P4D] = "p4d",
+ [PGTABLE_LEVEL_PGD] = "pgd",
+};
+
+void ptval_ERROR(const char *file, int line, enum pgtable_level level, ptval_t val)
+{
+ char str[PTVAL_STR_MAX];
+
+ ptval_to_str(str, val);
+ pr_err("%s:%d: bad %s %s.\n", file, line, pgtable_level_name[level], str);
+}
+
#ifdef CONFIG_ARCH_HAS_PKEYS
int arch_set_user_pkey_access(int pkey, unsigned long init_val)
{
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index b18c2b2e7d2c..1025b9140c0b 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -2001,4 +2001,44 @@ static inline unsigned long mmf_init_legacy_flags(unsigned long flags)
return flags & MMF_INIT_LEGACY_MASK;
}
+static inline void ptval_bytes_to_hex_str(char *buf, size_t buf_size, const void *entry, size_t entry_size)
+{
+ if (WARN_ON_ONCE(buf_size < entry_size * 2 + 1)) {
+ snprintf(buf, buf_size, "overflow");
+ return;
+ }
+
+ switch (entry_size) {
+ case sizeof(u32):
+ snprintf(buf, buf_size, "%08x", *(const u32 *)entry);
+ break;
+ case sizeof(u64):
+ snprintf(buf, buf_size, "%016llx", *(const u64 *)entry);
+ break;
+#if defined(__SIZEOF_INT128__)
+ case sizeof(u128):
+ snprintf(buf, buf_size, "%016llx%016llx",
+ (unsigned long long)(*(const u128 *)entry >> 64),
+ (unsigned long long)*(const u128 *)entry);
+ break;
+#endif
+ default:
+ snprintf(buf, buf_size, "unsupported");
+ break;
+ }
+}
+
+#define ptval_to_str(buf, val) \
+ do { \
+ auto __val = (val); \
+ \
+ ptval_bytes_to_hex_str((buf), sizeof(buf), &__val, sizeof(__val)); \
+ } while (0)
+
+#if defined(__SIZEOF_INT128__)
+#define PTVAL_STR_MAX (32 + 1) /* Max 128-bit value in hex + NUL */
+#else
+#define PTVAL_STR_MAX (16 + 1) /* Max 64-bit value in hex + NUL */
+#endif
+
#endif /* _LINUX_MM_TYPES_H */
diff --git a/mm/memory.c b/mm/memory.c
index 598d0e588e9a..253db4143f9c 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -519,46 +519,6 @@ static bool is_bad_page_map_ratelimited(void)
return false;
}
-static void ptval_bytes_to_hex_str(char *buf, size_t buf_size, const void *entry, size_t entry_size)
-{
- if (WARN_ON_ONCE(buf_size < entry_size * 2 + 1)) {
- snprintf(buf, buf_size, "overflow");
- return;
- }
-
- switch (entry_size) {
- case sizeof(u32):
- snprintf(buf, buf_size, "%08x", *(const u32 *)entry);
- break;
- case sizeof(u64):
- snprintf(buf, buf_size, "%016llx", *(const u64 *)entry);
- break;
-#if defined(__SIZEOF_INT128__)
- case sizeof(u128):
- snprintf(buf, buf_size, "%016llx%016llx",
- (unsigned long long)(*(const u128 *)entry >> 64),
- (unsigned long long)*(const u128 *)entry);
- break;
-#endif
- default:
- snprintf(buf, buf_size, "unsupported");
- break;
- }
-}
-
-#define ptval_to_str(buf, val) \
- do { \
- auto __val = (val); \
- \
- ptval_bytes_to_hex_str((buf), sizeof(buf), &__val, sizeof(__val)); \
- } while (0)
-
-#if defined(__SIZEOF_INT128__)
-#define PTVAL_STR_MAX (32 + 1) /* Max 128-bit value in hex + NUL */
-#else
-#define PTVAL_STR_MAX (16 + 1) /* Max 64-bit value in hex + NUL */
-#endif
-
static void __print_bad_page_map_pgtable(struct mm_struct *mm, unsigned long addr)
{
char pgd_str[PTVAL_STR_MAX];
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 11/17] arm64/mm: Enable fixmap with 5 level page table
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (9 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 10/17] arm64/mm: Standardize printing for pgtable entries Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 12/17] arm64/mm: Enable pgtable geometry for FEAT_D128 Anshuman Khandual
` (5 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
FEAT_D128 halves PTRS_PER_PXX thus shrinking the VA range coverage
for each page table level. Hence in order to preserve all existing
VA range configurations, some geometry now need to become 5-level.
Since fixmap is used to build and manipulate page tables early on
during boot the mapping must also gain that additional level which
was not required earlier.
Enable fixmap with 5 level page table when required. This creates table
entries at the PGD level. Add a fallback stub for pgd_page_paddr() when
(PGTBALE_LEVELS <= 4) which helps in intercepting any unintended usage.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/pgtable.h | 1 +
arch/arm64/mm/fixmap.c | 18 +++++++++++++++---
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 2270a3e4cc13..50d673474f47 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -1232,6 +1232,7 @@ static inline p4d_t *p4d_offset_kimg(pgd_t *pgdp, u64 addr)
#else
static inline bool pgtable_l5_enabled(void) { return false; }
+#define pgd_page_paddr(pgd) ({ BUILD_BUG(); 0; })
#define p4d_index(addr) (((addr) >> P4D_SHIFT) & (PTRS_PER_P4D - 1))
diff --git a/arch/arm64/mm/fixmap.c b/arch/arm64/mm/fixmap.c
index 2a0fe0b0f771..5512521fef8f 100644
--- a/arch/arm64/mm/fixmap.c
+++ b/arch/arm64/mm/fixmap.c
@@ -34,6 +34,7 @@ static_assert(NR_BM_PMD_TABLES == 1);
static pte_t bm_pte[NR_BM_PTE_TABLES][PTRS_PER_PTE] __bss_pgtbl;
static pmd_t bm_pmd[PTRS_PER_PMD] __bss_pgtbl __maybe_unused;
static pud_t bm_pud[PTRS_PER_PUD] __bss_pgtbl __maybe_unused;
+static p4d_t bm_p4d[PTRS_PER_P4D] __bss_pgtbl __maybe_unused;
static inline pte_t *fixmap_pte(unsigned long addr)
{
@@ -95,6 +96,19 @@ static void __init early_fixmap_init_pud(p4d_t *p4dp, unsigned long addr,
early_fixmap_init_pmd(pudp, addr, end);
}
+static void __init early_fixmap_init_p4d(pgd_t *pgdp, unsigned long addr,
+ unsigned long end)
+{
+ pgd_t pgd = pgdp_get(pgdp);
+ p4d_t *p4dp;
+
+ if (pgd_none(pgd))
+ __pgd_populate(pgdp, __pa_symbol(bm_p4d),
+ PGD_TYPE_TABLE | PGD_TABLE_AF);
+ p4dp = p4d_offset_kimg(pgdp, addr);
+ early_fixmap_init_pud(p4dp, addr, end);
+}
+
/*
* The p*d_populate functions call virt_to_phys implicitly so they can't be used
* directly on kernel symbols (bm_p*d). This function is called too early to use
@@ -107,9 +121,7 @@ void __init early_fixmap_init(void)
unsigned long end = FIXADDR_TOP;
pgd_t *pgdp = pgd_offset_k(addr);
- p4d_t *p4dp = p4d_offset_kimg(pgdp, addr);
-
- early_fixmap_init_pud(p4dp, addr, end);
+ early_fixmap_init_p4d(pgdp, addr, end);
}
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 12/17] arm64/mm: Enable pgtable geometry for FEAT_D128
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (10 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 11/17] arm64/mm: Enable fixmap with 5 level page table Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 13/17] arm64/mm: Enable pgtable descriptor " Anshuman Khandual
` (4 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
Add build time support for FEAT_D128 page tables with a new Kconfig option
i.e CONFIG_ARM64_D128. When selected, PTE types become 128 bits wide and
PTE bits are mapped to their new locations. Besides the basic page table
geometry is also updated since each table page now holds half the number
of entries (aka PTRS_PER_PXX) as it did previously. Similarly update the
geometry relevant macros ARM64_CONT_[PTE|PMD]_SHIFT, PGTABLE_LEVELS, and
PTDESC_ORDER.
Although for now config ARM64_D128 remains disabled while all the required
dependencies are getting created in subsequent patches.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/Kconfig | 37 ++++++++++++++++++++++----
arch/arm64/include/asm/pgtable-hwdef.h | 4 +++
arch/arm64/include/asm/pgtable.h | 2 ++
3 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b3afe0688919..8459d43afd45 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -283,14 +283,18 @@ config MMU
config ARM64_CONT_PTE_SHIFT
int
- default 5 if PAGE_SIZE_64KB
- default 7 if PAGE_SIZE_16KB
+ default 4 if PAGE_SIZE_64KB && ARM64_D128
+ default 5 if PAGE_SIZE_64KB && !ARM64_D128
+ default 6 if PAGE_SIZE_16KB && ARM64_D128
+ default 7 if PAGE_SIZE_16KB && !ARM64_D128
default 4
config ARM64_CONT_PMD_SHIFT
int
- default 5 if PAGE_SIZE_64KB
- default 5 if PAGE_SIZE_16KB
+ default 6 if PAGE_SIZE_64KB && ARM64_D128
+ default 5 if PAGE_SIZE_64KB && !ARM64_D128
+ default 4 if PAGE_SIZE_16KB && ARM64_D128
+ default 5 if PAGE_SIZE_16KB && !ARM64_D128
default 4
config ARCH_MMAP_RND_BITS_MIN
@@ -361,6 +365,16 @@ config FIX_EARLYCON_MEM
config PGTABLE_LEVELS
int
+ default 4 if ARM64_D128 && ARM64_4K_PAGES && ARM64_VA_BITS_39
+ default 5 if ARM64_D128 && ARM64_4K_PAGES && ARM64_VA_BITS_48
+ default 5 if ARM64_D128 && ARM64_4K_PAGES && ARM64_VA_BITS_52
+ default 3 if ARM64_D128 && ARM64_16K_PAGES && ARM64_VA_BITS_36
+ default 4 if ARM64_D128 && ARM64_16K_PAGES && ARM64_VA_BITS_47
+ default 4 if ARM64_D128 && ARM64_16K_PAGES && ARM64_VA_BITS_48
+ default 4 if ARM64_D128 && ARM64_16K_PAGES && ARM64_VA_BITS_52
+ default 3 if ARM64_D128 && ARM64_64K_PAGES && ARM64_VA_BITS_42
+ default 3 if ARM64_D128 && ARM64_64K_PAGES && ARM64_VA_BITS_48
+ default 3 if ARM64_D128 && ARM64_64K_PAGES && ARM64_VA_BITS_52
default 2 if ARM64_16K_PAGES && ARM64_VA_BITS_36
default 2 if ARM64_64K_PAGES && ARM64_VA_BITS_42
default 3 if ARM64_64K_PAGES && (ARM64_VA_BITS_48 || ARM64_VA_BITS_52)
@@ -1540,7 +1554,7 @@ config ARM64_PA_BITS
config ARM64_LPA2
def_bool y
- depends on ARM64_PA_BITS_52 && !ARM64_64K_PAGES
+ depends on ARM64_PA_BITS_52 && !ARM64_64K_PAGES && !ARM64_D128
choice
prompt "Endianness"
@@ -2233,6 +2247,19 @@ config ARM64_HAFT
endmenu # "ARMv8.9 architectural features"
+menu "ARMv9.3 architectural features"
+
+config ARM64_D128
+ bool
+ default n
+ help
+ ARMv9.3 introduces FEAT_D128, which provides a 128 bit page
+ table format, along with related instructions.
+
+ If unsure, say Y.
+
+endmenu # "ARMv9.3 architectural features"
+
menu "ARMv9.4 architectural features"
config ARM64_GCS
diff --git a/arch/arm64/include/asm/pgtable-hwdef.h b/arch/arm64/include/asm/pgtable-hwdef.h
index 72f31800c703..1ee1a6d9db6d 100644
--- a/arch/arm64/include/asm/pgtable-hwdef.h
+++ b/arch/arm64/include/asm/pgtable-hwdef.h
@@ -7,7 +7,11 @@
#include <asm/memory.h>
+#ifdef CONFIG_ARM64_D128
+#define PTDESC_ORDER 4
+#else
#define PTDESC_ORDER 3
+#endif
/* Number of VA bits resolved by a single translation table level */
#define PTDESC_TABLE_SHIFT (PAGE_SHIFT - PTDESC_ORDER)
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 50d673474f47..ed5ea7d31c0a 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -1128,6 +1128,8 @@ static inline bool pgtable_l4_enabled(void) { return false; }
static __always_inline bool pgtable_l5_enabled(void)
{
+ if (IS_ENABLED(CONFIG_ARM64_D128))
+ return true;
if (!alternative_has_cap_likely(ARM64_ALWAYS_BOOT))
return vabits_actual == VA_BITS;
return alternative_has_cap_unlikely(ARM64_HAS_VA52);
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 13/17] arm64/mm: Enable pgtable descriptor for FEAT_D128
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (11 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 12/17] arm64/mm: Enable pgtable geometry for FEAT_D128 Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 14/17] arm64/mm: Enable 128 bit translation Anshuman Khandual
` (3 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
Enable pgtable descriptor fields and related accessors for FEAT_D128. First
expand ptval_t as 128 bits, add all D128 specific pgtable descriptor fields
and update accessors like ptval_get/set() and ptval_xchg_relaxed(). Also do
ensure that ARM64_PA_BITS_52 specific handlings are not applicable for D128
which does not follow PA bits folding mechanisms as in FEAT_[LPA|LPA2].
Although all the above changes gets enabled only with CONFIG_ARM64_D128.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/assembler.h | 4 +-
arch/arm64/include/asm/pgtable-hwdef.h | 133 +++++++++++++++++++++++++
arch/arm64/include/asm/pgtable-prot.h | 18 +++-
arch/arm64/include/asm/pgtable-types.h | 4 +
arch/arm64/include/asm/pgtable.h | 52 +++++++++-
arch/arm64/mm/proc.S | 2 +-
6 files changed, 207 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/include/asm/assembler.h b/arch/arm64/include/asm/assembler.h
index effae53e9739..b53b7f18c1bd 100644
--- a/arch/arm64/include/asm/assembler.h
+++ b/arch/arm64/include/asm/assembler.h
@@ -627,7 +627,7 @@ alternative_else_nop_endif
* ttbr: returns the TTBR value
*/
.macro phys_to_ttbr, ttbr, phys
-#ifdef CONFIG_ARM64_PA_BITS_52
+#if defined(CONFIG_ARM64_PA_BITS_52) && !defined(CONFIG_ARM64_D128)
orr \ttbr, \phys, \phys, lsr #46
and \ttbr, \ttbr, #TTBR_BADDR_MASK_52
#else
@@ -636,7 +636,7 @@ alternative_else_nop_endif
.endm
.macro phys_to_pte, pte, phys
-#ifdef CONFIG_ARM64_PA_BITS_52
+#if defined(CONFIG_ARM64_PA_BITS_52) && !defined(CONFIG_ARM64_D128)
orr \pte, \phys, \phys, lsr #PTE_ADDR_HIGH_SHIFT
and \pte, \pte, #PHYS_TO_PTE_ADDR_MASK
#else
diff --git a/arch/arm64/include/asm/pgtable-hwdef.h b/arch/arm64/include/asm/pgtable-hwdef.h
index 1ee1a6d9db6d..16fb74c47b74 100644
--- a/arch/arm64/include/asm/pgtable-hwdef.h
+++ b/arch/arm64/include/asm/pgtable-hwdef.h
@@ -101,6 +101,137 @@
#define CONT_PMD_SIZE (CONT_PMDS * PMD_SIZE)
#define CONT_PMD_MASK (~(CONT_PMD_SIZE - 1))
+#ifdef CONFIG_ARM64_D128
+
+/*
+ * Hardware page table definitions.
+ *
+ * Level -1 descriptor (PGD).
+ */
+#define PGD_SKL_SHIFT 109
+#define PGD_SKL_MASK GENMASK_U128(110, 109)
+#define PGD_SKL_TABLE (_AT(pgdval_t, 0) << PGD_SKL_SHIFT)
+
+#define PGD_TYPE_TABLE _AT(pgdval_t, (PTE_VALID | PGD_SKL_TABLE))
+#define PGD_TYPE_MASK _AT(pgdval_t, (PTE_VALID | PGD_SKL_MASK))
+#define PGD_TABLE_AF (_AT(pgdval_t, 1) << 10) /* Ignored if no FEAT_HAFT */
+#define PGD_TABLE_PXN _AT(pgdval_t, 0) /* Not supported for D128 */
+#define PGD_TABLE_UXN _AT(pgdval_t, 0) /* Not supported for D128 */
+
+/*
+ * Level 0 descriptor (P4D).
+ */
+#define P4D_SKL_SHIFT 109
+#define P4D_SKL_MASK GENMASK_U128(110, 109)
+#define P4D_SKL_TABLE (_AT(p4dval_t, 0) << P4D_SKL_SHIFT)
+#define P4D_SKL_SECT (_AT(p4dval_t, 3) << P4D_SKL_SHIFT)
+
+#define P4D_TYPE_TABLE _AT(p4dval_t, (PTE_VALID | P4D_SKL_TABLE))
+#define P4D_TYPE_MASK _AT(p4dval_t, (PTE_VALID | P4D_SKL_MASK))
+#define P4D_TYPE_SECT _AT(p4dval_t, (PTE_VALID | P4D_SKL_SECT))
+#define P4D_SECT_RDONLY (_AT(p4dval_t, 1) << 7) /* nDirty */
+#define P4D_TABLE_AF (_AT(p4dval_t, 1) << 10) /* Ignored if no FEAT_HAFT */
+#define P4D_TABLE_PXN _AT(p4dval_t, 0) /* Not supported for D128 */
+#define P4D_TABLE_UXN _AT(p4dval_t, 0) /* Not supported for D128 */
+
+/*
+ * Level 1 descriptor (PUD).
+ */
+#define PUD_SKL_SHIFT 109
+#define PUD_SKL_MASK GENMASK_U128(110, 109)
+#define PUD_SKL_TABLE (_AT(pudval_t, 0) << PUD_SKL_SHIFT)
+#define PUD_SKL_SECT (_AT(pudval_t, 2) << PUD_SKL_SHIFT)
+
+#define PUD_TYPE_TABLE _AT(pudval_t, (PTE_VALID | PUD_SKL_TABLE))
+#define PUD_TYPE_MASK _AT(pudval_t, (PTE_VALID | PUD_SKL_MASK))
+#define PUD_TYPE_SECT _AT(pudval_t, (PTE_VALID | PUD_SKL_SECT))
+#define PUD_SECT_RDONLY (_AT(pudval_t, 1) << 7) /* nDirty */
+#define PUD_TABLE_AF (_AT(pudval_t, 1) << 10) /* Ignored if no FEAT_HAFT */
+#define PUD_TABLE_PXN _AT(pudval_t, 0) /* Not supported for D128 */
+#define PUD_TABLE_UXN _AT(pudval_t, 0) /* Not supported for D128 */
+
+/*
+ * Level 2 descriptor (PMD).
+ */
+#define PMD_SKL_SHIFT 109
+#define PMD_SKL_MASK GENMASK_U128(110, 109)
+#define PMD_SKL_TABLE (_AT(pmdval_t, 0) << PMD_SKL_SHIFT)
+#define PMD_SKL_SECT (_AT(pmdval_t, 1) << PMD_SKL_SHIFT)
+
+#define PMD_TYPE_MASK _AT(pmdval_t, (PTE_VALID | PMD_SKL_MASK))
+#define PMD_TYPE_TABLE _AT(pmdval_t, (PTE_VALID | PMD_SKL_TABLE))
+#define PMD_TYPE_SECT _AT(pmdval_t, (PTE_VALID | PMD_SKL_SECT))
+#define PMD_TABLE_AF (_AT(pmdval_t, 1) << 10) /* Ignored if no FEAT_HAFT */
+#define PMD_TABLE_PXN _AT(pmdval_t, 0) /* Not supported for D128 */
+#define PMD_TABLE_UXN _AT(pmdval_t, 0) /* Not supported for D128 */
+
+/*
+ * Section
+ */
+#define PMD_SECT_USER (_AT(pmdval_t, 1) << 115) /* PIIndex[0] */
+#define PMD_SECT_RDONLY (_AT(pmdval_t, 1) << 7) /* nDirty */
+#define PMD_SECT_S (_AT(pmdval_t, 3) << 8)
+#define PMD_SECT_AF (_AT(pmdval_t, 1) << 10)
+#define PMD_SECT_NG (_AT(pmdval_t, 1) << 11)
+#define PMD_SECT_CONT (_AT(pmdval_t, 1) << 111)
+#define PMD_SECT_PXN (_AT(pmdval_t, 1) << 117) /* PIIndex[2] */
+#define PMD_SECT_UXN (_AT(pmdval_t, 1) << 118) /* PIIndex[3] */
+
+/*
+ * AttrIndx[2:0] encoding (mapping attributes defined in the MAIR* registers).
+ */
+#define PMD_ATTRINDX(t) (_AT(pmdval_t, (t)) << 2)
+#define PMD_ATTRINDX_MASK (_AT(pmdval_t, 7) << 2)
+
+/*
+ * Level 3 descriptor (PTE).
+ */
+#define PTE_SKL_SHIFT 109
+#define PTE_SKL_MASK GENMASK_U128(110, 109)
+#define PTE_SKL_SECT (_AT(pteval_t, 0) << PTE_SKL_SHIFT)
+
+#define PTE_VALID (_AT(pteval_t, 1) << 0)
+#define PTE_TYPE_MASK _AT(pteval_t, (PTE_VALID | PTE_SKL_MASK))
+#define PTE_TYPE_PAGE _AT(pteval_t, (PTE_VALID | PTE_SKL_SECT))
+#define PTE_USER (_AT(pteval_t, 1) << 115) /* PIIndex[0] */
+#define PTE_RDONLY (_AT(pteval_t, 1) << 7) /* nDirty */
+#define PTE_SHARED (_AT(pteval_t, 3) << 8) /* SH[1:0], inner shareable */
+#define PTE_AF (_AT(pteval_t, 1) << 10) /* Access Flag */
+#define PTE_NG (_AT(pteval_t, 1) << 11) /* nG */
+#define PTE_GP (_AT(pteval_t, 1) << 113) /* BTI guarded */
+#define PTE_DBM (_AT(pteval_t, 1) << 116) /* PIIndex[1] */
+#define PTE_CONT (_AT(pteval_t, 1) << 111) /* Contiguous range */
+#define PTE_PXN (_AT(pteval_t, 1) << 117) /* PIIndex[2] */
+#define PTE_UXN (_AT(pteval_t, 1) << 118) /* PIIndex[3] */
+#define PTE_SWBITS_MASK _AT(pteval_t, GENMASK_U128(100, 91))
+
+#define PTE_ADDR_LOW (((_AT(pteval_t, 1) << (55 - PAGE_SHIFT)) - 1) << PAGE_SHIFT)
+
+/*
+ * AttrIndx[2:0] encoding (mapping attributes defined in the MAIR* registers).
+ */
+#define PTE_ATTRINDX(t) (_AT(pteval_t, (t)) << 2)
+#define PTE_ATTRINDX_MASK (_AT(pteval_t, 7) << 2)
+
+/*
+ * PIIndex[3:0] encoding (Permission Indirection Extension)
+ */
+#define PTE_PI_MASK GENMASK_U128(118, 115)
+#define PTE_PI_SHIFT 115
+
+/*
+ * POIndex[3:0] encoding (Permission Overlay Extension)
+ */
+#define PTE_PO_IDX_0 (_AT(pteval_t, 1) << 121)
+#define PTE_PO_IDX_1 (_AT(pteval_t, 1) << 122)
+#define PTE_PO_IDX_2 (_AT(pteval_t, 1) << 123)
+#define PTE_PO_IDX_3 (_AT(pteval_t, 1) << 124)
+
+#define PTE_PO_IDX_MASK GENMASK_U128(124, 121)
+#define PTE_PO_IDX_SHIFT 121
+
+#else /* !CONFIG_ARM64_D128 */
+
/*
* Hardware page table definitions.
*
@@ -215,7 +346,9 @@
#define PTE_PO_IDX_2 (_AT(pteval_t, 1) << 62)
#define PTE_PO_IDX_MASK GENMASK_ULL(62, 60)
+#define PTE_PO_IDX_SHIFT 60
+#endif /* CONFIG_ARM64_D128 */
/*
* Memory Attribute override for Stage-2 (MemAttr[3:0])
diff --git a/arch/arm64/include/asm/pgtable-prot.h b/arch/arm64/include/asm/pgtable-prot.h
index 212ce1b02e15..aadc577511d6 100644
--- a/arch/arm64/include/asm/pgtable-prot.h
+++ b/arch/arm64/include/asm/pgtable-prot.h
@@ -13,10 +13,15 @@
/*
* Software defined PTE bits definition.
*/
-#define PTE_WRITE (PTE_DBM) /* same as DBM (51) */
+#define PTE_WRITE (PTE_DBM) /* same as DBM (51 / 116) */
#define PTE_SWP_EXCLUSIVE (_AT(pteval_t, 1) << 2) /* only for swp ptes */
+#ifdef CONFIG_ARM64_D128
+#define PTE_DIRTY (_AT(pteval_t, 1) << 91)
+#define PTE_SPECIAL (_AT(pteval_t, 1) << 92)
+#else
#define PTE_DIRTY (_AT(pteval_t, 1) << 55)
#define PTE_SPECIAL (_AT(pteval_t, 1) << 56)
+#endif
/*
* PTE_PRESENT_INVALID=1 & PTE_VALID=0 indicates that the pte's fields should be
@@ -28,7 +33,11 @@
#define PTE_PRESENT_VALID_KERNEL (PTE_VALID | PTE_MAYBE_NG)
#ifdef CONFIG_HAVE_ARCH_USERFAULTFD_WP
+#ifdef CONFIG_ARM64_D128
+#define PTE_UFFD_WP (_AT(pteval_t, 1) << 94) /* uffd-wp tracking */
+#else
#define PTE_UFFD_WP (_AT(pteval_t, 1) << 58) /* uffd-wp tracking */
+#endif
#define PTE_SWP_UFFD_WP (_AT(pteval_t, 1) << 3) /* only for swp ptes */
#else
#define PTE_UFFD_WP (_AT(pteval_t, 0))
@@ -131,11 +140,18 @@ static inline bool __pure lpa2_is_enabled(void)
#endif /* __ASSEMBLER__ */
+#ifdef CONFIG_ARM64_D128
+#define pte_pi_index(pte) (((pte) & PTE_PI_MASK) >> PTE_PI_SHIFT)
+#define pte_po_index(pte) ((pte_val(pte) & PTE_PO_IDX_MASK) >> PTE_PO_IDX_SHIFT)
+#else
#define pte_pi_index(pte) ( \
((pte & BIT(PTE_PI_IDX_3)) >> (PTE_PI_IDX_3 - 3)) | \
((pte & BIT(PTE_PI_IDX_2)) >> (PTE_PI_IDX_2 - 2)) | \
((pte & BIT(PTE_PI_IDX_1)) >> (PTE_PI_IDX_1 - 1)) | \
((pte & BIT(PTE_PI_IDX_0)) >> (PTE_PI_IDX_0 - 0)))
+#define pte_po_index(pte) FIELD_GET(PTE_PO_IDX_MASK, pte_val(pte))
+#endif
+
/*
* Page types used via Permission Indirection Extension (PIE). PIE uses
diff --git a/arch/arm64/include/asm/pgtable-types.h b/arch/arm64/include/asm/pgtable-types.h
index 2f2f5527930f..83160f8a19fd 100644
--- a/arch/arm64/include/asm/pgtable-types.h
+++ b/arch/arm64/include/asm/pgtable-types.h
@@ -17,7 +17,11 @@
* Generic page table descriptor format from which
* all level specific descriptors can be derived.
*/
+#ifdef CONFIG_ARM64_D128
+typedef u128 ptval_t;
+#else
typedef u64 ptval_t;
+#endif
typedef ptval_t pteval_t;
typedef ptval_t pmdval_t;
diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index ed5ea7d31c0a..9e7d891597cc 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -84,18 +84,62 @@ static inline void arch_leave_lazy_mmu_mode(void)
arch_flush_lazy_mmu_mode();
}
+#ifdef CONFIG_ARM64_D128
+#define ptval_get(x) \
+({ \
+ typeof(&(x)) __x = &(x); \
+ union __u128_halves __v; \
+ \
+ asm volatile ("ldp %[lo], %[hi], %[v]\n" \
+ : [lo] "=r"(__v.low), \
+ [hi] "=r"(__v.high) \
+ : [v] "Q"(*__x) \
+ ); \
+ *(typeof(__x))(&__v.full); \
+})
+
+#define ptval_set(x, val) \
+({ \
+ typeof(&(x)) __x = &(x); \
+ union __u128_halves __v = { .full = *(u128*)(&(val)) }; \
+ \
+ asm volatile ("stp %[lo], %[hi], %[v]\n" \
+ : [v] "=Q"(*__x) \
+ : [lo] "r"(__v.low), \
+ [hi] "r"(__v.high) \
+ ); \
+})
+#else
#define ptval_get(x) READ_ONCE(x)
#define ptval_set(x, val) WRITE_ONCE(x, val)
+#endif
static inline ptval_t ptval_cmpxchg_relaxed(ptval_t *ptep, ptval_t old,
ptval_t new)
{
+#ifdef CONFIG_ARM64_D128
+ return cmpxchg128_relaxed(ptep, old, new);
+#else
return cmpxchg_relaxed(ptep, old, new);
+#endif
}
static inline ptval_t ptval_xchg_relaxed(ptval_t *ptep, ptval_t new)
{
+#ifdef CONFIG_ARM64_D128
+ union __u128_halves r = { .full = new };
+
+ asm volatile(
+ ".arch_extension lse128\n"
+ "swpp %[lo], %[hi], %[v]\n"
+ : [lo] "+r" (r.low),
+ [hi] "+r" (r.high),
+ [v] "+Q" (*ptep)
+ :);
+ return r.full;
+#else
return xchg_relaxed(ptep, new);
+#endif
}
#define pmdp_get pmdp_get
@@ -157,7 +201,7 @@ static inline void pgprot_write(pgprot_t *prot, pgprot_t val)
__flush_tlb_range(vma, address, address + PMD_SIZE, PMD_SIZE, 2, \
TLBF_NOBROADCAST | TLBF_NONOTIFY | TLBF_NOWALKCACHE)
-#ifdef CONFIG_ARM64_PA_BITS_52
+#if defined(CONFIG_ARM64_PA_BITS_52) && !defined(CONFIG_ARM64_D128)
static inline phys_addr_t __pte_to_phys(pte_t pte)
{
pte_val(pte) &= ~PTE_MAYBE_SHARED;
@@ -268,7 +312,7 @@ static inline bool por_el0_allows_pkey(u8 pkey, bool write, bool execute)
(((pte_val(pte) & (PTE_VALID | PTE_USER)) == (PTE_VALID | PTE_USER)) && (!(write) || pte_write(pte)))
#define pte_access_permitted(pte, write) \
(pte_access_permitted_no_overlay(pte, write) && \
- por_el0_allows_pkey(FIELD_GET(PTE_PO_IDX_MASK, pte_val(pte)), write, false))
+ por_el0_allows_pkey(pte_po_index(pte), write, false))
#define pmd_access_permitted(pmd, write) \
(pte_access_permitted(pmd_pte(pmd), (write)))
#define pud_access_permitted(pud, write) \
@@ -1637,11 +1681,15 @@ static inline void update_mmu_cache_range(struct vm_fault *vmf,
update_mmu_cache_range(NULL, vma, addr, ptep, 1)
#define update_mmu_cache_pmd(vma, address, pmd) do { } while (0)
+#ifdef CONFIG_ARM64_D128
+#define phys_to_ttbr(addr) (addr)
+#else
#ifdef CONFIG_ARM64_PA_BITS_52
#define phys_to_ttbr(addr) (((addr) | ((addr) >> 46)) & TTBR_BADDR_MASK_52)
#else
#define phys_to_ttbr(addr) (addr)
#endif
+#endif
/*
* On arm64 without hardware Access Flag, copying from user will fail because
diff --git a/arch/arm64/mm/proc.S b/arch/arm64/mm/proc.S
index 22866b49be37..6db62d56e97e 100644
--- a/arch/arm64/mm/proc.S
+++ b/arch/arm64/mm/proc.S
@@ -215,7 +215,7 @@ SYM_FUNC_ALIAS(__pi_idmap_cpu_replace_ttbr1, idmap_cpu_replace_ttbr1)
.macro pte_to_phys, phys, pte
and \phys, \pte, #PTE_ADDR_LOW
-#ifdef CONFIG_ARM64_PA_BITS_52
+#if defined(CONFIG_ARM64_PA_BITS_52) && !defined(CONFIG_ARM64_D128)
and \pte, \pte, #PTE_ADDR_HIGH
orr \phys, \phys, \pte, lsl #PTE_ADDR_HIGH_SHIFT
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 14/17] arm64/mm: Enable 128 bit translation
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (12 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 13/17] arm64/mm: Enable pgtable descriptor " Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 15/17] arm64/mm: Add an abstraction level for tlbi_op Anshuman Khandual
` (2 subsequent siblings)
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
All required changes to page table geometry and descriptor are now in place
for D128 translation to be enabled.
Since FEAT_D128 exclusively supports the permission indirection style for
page table entry permission management, given kernel compiled for FEAT_D128
requires both FEAT_S1PIE and FEAT_D128. If these architecture features are
not present during boot, the kernel panics just like it does when there is
a granule size mismatch. So added a new stuck CPU debug identifier for such
scenarios called CPU_STUCK_REASON_NO_D128.
TTBR0/1_EL1 and PAR_EL1 registers become 128 bit wide when D128 is enabled,
thus requiring MSRR/MRRS instructions for their updates. Because PA_BITS is
still capped at 52 bits, MRS/MSR instructions are currently sufficient for
the register accesses that basically operate on the lower 64 bits. Although
entire 128 bits for these registers get cleared during boot via MSRR.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/Makefile | 4 ++++
arch/arm64/include/asm/el2_setup.h | 9 +++++++++
arch/arm64/include/asm/smp.h | 1 +
arch/arm64/kernel/head.S | 12 ++++++++++++
arch/arm64/mm/proc.S | 23 +++++++++++++++++++++++
5 files changed, 49 insertions(+)
diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile
index 6b005c8fef70..12a6e62520e9 100644
--- a/arch/arm64/Makefile
+++ b/arch/arm64/Makefile
@@ -54,6 +54,10 @@ endif
KBUILD_CFLAGS += $(call cc-option,-mabi=lp64)
KBUILD_AFLAGS += $(call cc-option,-mabi=lp64)
+ifeq ($(CONFIG_ARM64_D128),y)
+KBUILD_AFLAGS += -march=armv9.3-a+d128
+endif
+
# Avoid generating .eh_frame* sections.
ifneq ($(CONFIG_UNWIND_TABLES),y)
KBUILD_CFLAGS += -fno-asynchronous-unwind-tables -fno-unwind-tables
diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h
index aa8ec9df8024..331e94381dd0 100644
--- a/arch/arm64/include/asm/el2_setup.h
+++ b/arch/arm64/include/asm/el2_setup.h
@@ -78,6 +78,15 @@
cbz x0, .Lskip_hcrx_\@
mov_q x0, (HCRX_EL2_MSCEn | HCRX_EL2_TCR2En | HCRX_EL2_EnFPM)
+#ifdef CONFIG_ARM64_D128
+ mrs_s x1, SYS_ID_AA64MMFR3_EL1
+ ubfx x1, x1, #ID_AA64MMFR3_EL1_D128_SHIFT, #4
+ cbz x1, .Lskip_d128_\@
+
+ orr x0, x0, HCRX_EL2_D128En // Disable MRRS/MSRR traps
+.Lskip_d128_\@:
+#endif
+
/* Enable GCS if supported */
mrs_s x1, SYS_ID_AA64PFR1_EL1
ubfx x1, x1, #ID_AA64PFR1_EL1_GCS_SHIFT, #4
diff --git a/arch/arm64/include/asm/smp.h b/arch/arm64/include/asm/smp.h
index 10ea4f543069..1dd675d2b84d 100644
--- a/arch/arm64/include/asm/smp.h
+++ b/arch/arm64/include/asm/smp.h
@@ -22,6 +22,7 @@
#define CPU_STUCK_REASON_52_BIT_VA (UL(1) << CPU_STUCK_REASON_SHIFT)
#define CPU_STUCK_REASON_NO_GRAN (UL(2) << CPU_STUCK_REASON_SHIFT)
+#define CPU_STUCK_REASON_NO_D128 (UL(3) << CPU_STUCK_REASON_SHIFT)
#ifndef __ASSEMBLER__
diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S
index 87a822e5c4ca..4ad8047963ad 100644
--- a/arch/arm64/kernel/head.S
+++ b/arch/arm64/kernel/head.S
@@ -505,6 +505,18 @@ SYM_FUNC_START_LOCAL(__no_granule_support)
b 1b
SYM_FUNC_END(__no_granule_support)
+#ifdef CONFIG_ARM64_D128
+SYM_FUNC_START(__no_d128_support)
+ /* Indicate that this CPU can't boot and is stuck in the kernel */
+ update_early_cpu_boot_status \
+ CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_D128, x1, x2
+1:
+ wfe
+ wfi
+ b 1b
+SYM_FUNC_END(__no_d128_support)
+#endif
+
SYM_FUNC_START_LOCAL(__primary_switch)
adrp x1, reserved_pg_dir
adrp x2, __pi_init_idmap_pg_dir
diff --git a/arch/arm64/mm/proc.S b/arch/arm64/mm/proc.S
index 6db62d56e97e..5c8bfd56a781 100644
--- a/arch/arm64/mm/proc.S
+++ b/arch/arm64/mm/proc.S
@@ -541,7 +541,30 @@ alternative_else_nop_endif
mrs_s x1, SYS_ID_AA64MMFR3_EL1
ubfx x1, x1, #ID_AA64MMFR3_EL1_S1PIE_SHIFT, #4
+#ifdef CONFIG_ARM64_D128
+ cbnz x1, .Lcheck_d128
+ bl __no_d128_support
+.Lcheck_d128:
+ mrs_s x1, SYS_ID_AA64MMFR3_EL1
+ ubfx x1, x1, #ID_AA64MMFR3_EL1_D128_SHIFT, #4
+ cbnz x1, .Linit_d128
+ bl __no_d128_support
+.Linit_d128:
+ /*
+ * Although the lower 64 bits in TTBRx_EL1 registers are now
+ * being used it is prudent to clear out the entire 128 bits
+ * just in case the kernel receives non-zero value in higher
+ * 64 bits from the EL3 which might corrupt the page tables.
+ */
+ mov x4, xzr
+ mov x5, xzr
+
+ msrr ttbr0_el1, x4, x5
+ msrr ttbr1_el1, x4, x5
+ orr tcr2, tcr2, #TCR2_EL1_D128
+#else
cbz x1, .Lskip_indirection
+#endif
mov_q x0, PIE_E0_ASM
msr REG_PIRE0_EL1, x0
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 15/17] arm64/mm: Add an abstraction level for tlbi_op
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (13 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 14/17] arm64/mm: Enable 128 bit translation Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 16/17] arm64/mm: Enable TLBIP instruction based TLB flush Anshuman Khandual
2026-07-29 12:24 ` [PATCH 17/17] arm64/mm: Make ARM64_D128 selectable Anshuman Khandual
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
From: Linu Cherian <linu.cherian@arm.com>
With FEAT_D128, a new instruction aka TLBIP is being introduced for the TLB
-by-range operations which has an argument size of 128 bit.
Add an abstraction level to void (*tlbi_op)(u64 arg) helpers to support the
D128 variations when applicable.
No functional changes are introduced with this patch.
Signed-off-by: Linu Cherian <linu.cherian@arm.com>
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/tlbflush.h | 70 ++++++++++++++++---------------
1 file changed, 37 insertions(+), 33 deletions(-)
diff --git a/arch/arm64/include/asm/tlbflush.h b/arch/arm64/include/asm/tlbflush.h
index e0e84332f51b..c103380ec87e 100644
--- a/arch/arm64/include/asm/tlbflush.h
+++ b/arch/arm64/include/asm/tlbflush.h
@@ -130,49 +130,53 @@ static inline void sme_dvmsync_batch(void)
#define TLBI_TTL_UNKNOWN INT_MAX
-typedef void (*tlbi_op)(u64 arg);
+typedef u64 tlbi_arg_t;
+#define __tlbi_wrapper(op, arg) __tlbi(op, arg)
+#define __tlbi_user_wrapper(op, arg) __tlbi_user(op, arg)
-static __always_inline void vae1is(u64 arg)
+typedef void (*tlbi_op)(tlbi_arg_t arg);
+
+static __always_inline void vae1is(tlbi_arg_t arg)
{
- __tlbi(vae1is, arg);
- __tlbi_user(vae1is, arg);
+ __tlbi_wrapper(vae1is, arg);
+ __tlbi_user_wrapper(vae1is, arg);
}
-static __always_inline void vae2is(u64 arg)
+static __always_inline void vae2is(tlbi_arg_t arg)
{
- __tlbi(vae2is, arg);
+ __tlbi_wrapper(vae2is, arg);
}
-static __always_inline void vale1(u64 arg)
+static __always_inline void vale1(tlbi_arg_t arg)
{
- __tlbi(vale1, arg);
- __tlbi_user(vale1, arg);
+ __tlbi_wrapper(vale1, arg);
+ __tlbi_user_wrapper(vale1, arg);
}
-static __always_inline void vale1is(u64 arg)
+static __always_inline void vale1is(tlbi_arg_t arg)
{
- __tlbi(vale1is, arg);
- __tlbi_user(vale1is, arg);
+ __tlbi_wrapper(vale1is, arg);
+ __tlbi_user_wrapper(vale1is, arg);
}
-static __always_inline void vale2is(u64 arg)
+static __always_inline void vale2is(tlbi_arg_t arg)
{
- __tlbi(vale2is, arg);
+ __tlbi_wrapper(vale2is, arg);
}
-static __always_inline void vaale1is(u64 arg)
+static __always_inline void vaale1is(tlbi_arg_t arg)
{
- __tlbi(vaale1is, arg);
+ __tlbi_wrapper(vaale1is, arg);
}
-static __always_inline void ipas2e1(u64 arg)
+static __always_inline void ipas2e1(tlbi_arg_t arg)
{
- __tlbi(ipas2e1, arg);
+ __tlbi_wrapper(ipas2e1, arg);
}
-static __always_inline void ipas2e1is(u64 arg)
+static __always_inline void ipas2e1is(tlbi_arg_t arg)
{
- __tlbi(ipas2e1is, arg);
+ __tlbi_wrapper(ipas2e1is, arg);
}
static __always_inline void __tlbi_level_asid(tlbi_op op, u64 addr, u32 level,
@@ -443,32 +447,32 @@ static inline void arch_tlbbatch_flush(struct arch_tlbflush_unmap_batch *batch)
* operations can only span an even number of pages. We save this for last to
* ensure 64KB start alignment is maintained for the LPA2 case.
*/
-static __always_inline void rvae1is(u64 arg)
+static __always_inline void rvae1is(tlbi_arg_t arg)
{
- __tlbi(rvae1is, arg);
- __tlbi_user(rvae1is, arg);
+ __tlbi_wrapper(rvae1is, arg);
+ __tlbi_user_wrapper(rvae1is, arg);
}
-static __always_inline void rvale1(u64 arg)
+static __always_inline void rvale1(tlbi_arg_t arg)
{
- __tlbi(rvale1, arg);
- __tlbi_user(rvale1, arg);
+ __tlbi_wrapper(rvale1, arg);
+ __tlbi_user_wrapper(rvale1, arg);
}
-static __always_inline void rvale1is(u64 arg)
+static __always_inline void rvale1is(tlbi_arg_t arg)
{
- __tlbi(rvale1is, arg);
- __tlbi_user(rvale1is, arg);
+ __tlbi_wrapper(rvale1is, arg);
+ __tlbi_user_wrapper(rvale1is, arg);
}
-static __always_inline void rvaale1is(u64 arg)
+static __always_inline void rvaale1is(tlbi_arg_t arg)
{
- __tlbi(rvaale1is, arg);
+ __tlbi_wrapper(rvaale1is, arg);
}
-static __always_inline void ripas2e1is(u64 arg)
+static __always_inline void ripas2e1is(tlbi_arg_t arg)
{
- __tlbi(ripas2e1is, arg);
+ __tlbi_wrapper(ripas2e1is, arg);
}
static __always_inline void __tlbi_range(tlbi_op op, u64 addr,
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 16/17] arm64/mm: Enable TLBIP instruction based TLB flush
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (14 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 15/17] arm64/mm: Add an abstraction level for tlbi_op Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
2026-07-29 12:24 ` [PATCH 17/17] arm64/mm: Make ARM64_D128 selectable Anshuman Khandual
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
From: Linu Cherian <linu.cherian@arm.com>
Add TLBIP instruction support for TLB flush operation updating all relevant
macros as necessary. This enables back pgtable level hint and address range
support during TLB flush operation for D128 translation.
Signed-off-by: Linu Cherian <linu.cherian@arm.com>
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/include/asm/tlbflush.h | 68 +++++++++++++++++++++++++++----
1 file changed, 59 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/include/asm/tlbflush.h b/arch/arm64/include/asm/tlbflush.h
index c103380ec87e..3b6384b9c200 100644
--- a/arch/arm64/include/asm/tlbflush.h
+++ b/arch/arm64/include/asm/tlbflush.h
@@ -41,6 +41,25 @@
#define __tlbi(op, ...) __TLBI_N(op, ##__VA_ARGS__, 1, 0)
+#ifdef CONFIG_ARM64_D128
+#define __tlbip(op, arg) do { \
+ asm (ARM64_ASM_PREAMBLE \
+ ".arch_extension d128\n\t" \
+ "tlbip " #op ", %0, %H0\n" \
+ : : "r" (arg.full)); \
+} while (0)
+
+#define __tlbip_user(op, arg) do { \
+ if (arm64_kernel_unmapped_at_el0()) { \
+ arg.low |= USER_ASID_FLAG; \
+ __tlbip(op, (arg)); \
+ } \
+} while (0)
+
+#endif
+
+#define TLBI_ASID_MASK GENMASK_ULL(63, 48)
+
#define __tlbi_user(op, arg) do { \
if (arm64_kernel_unmapped_at_el0()) \
__tlbi(op, (arg) | USER_ASID_FLAG); \
@@ -130,9 +149,15 @@ static inline void sme_dvmsync_batch(void)
#define TLBI_TTL_UNKNOWN INT_MAX
+#ifdef CONFIG_ARM64_D128
+typedef union __u128_halves tlbi_arg_t;
+#define __tlbi_wrapper(op, arg) __tlbip(op, arg)
+#define __tlbi_user_wrapper(op, arg) __tlbip_user(op, arg)
+#else
typedef u64 tlbi_arg_t;
#define __tlbi_wrapper(op, arg) __tlbi(op, arg)
#define __tlbi_user_wrapper(op, arg) __tlbi_user(op, arg)
+#endif
typedef void (*tlbi_op)(tlbi_arg_t arg);
@@ -179,17 +204,28 @@ static __always_inline void ipas2e1is(tlbi_arg_t arg)
__tlbi_wrapper(ipas2e1is, arg);
}
-static __always_inline void __tlbi_level_asid(tlbi_op op, u64 addr, u32 level,
- u16 asid)
+static __always_inline void __tlbi_update_level(u32 level, u64 *arg)
{
- u64 arg = __TLBI_VADDR(addr, asid);
-
if (alternative_has_cap_unlikely(ARM64_HAS_ARMv8_4_TTL) && level <= 3) {
u64 ttl = level | (get_trans_granule() << 2);
- FIELD_MODIFY(TLBI_TTL_MASK, &arg, ttl);
+ FIELD_MODIFY(TLBI_TTL_MASK, arg, ttl);
}
+}
+
+static __always_inline void __tlbi_level_asid(tlbi_op op, u64 addr, u32 level, u16 asid)
+{
+#ifdef CONFIG_ARM64_D128
+ union __u128_halves arg;
+
+ arg.low = FIELD_PREP(TLBI_ASID_MASK, asid);
+ __tlbi_update_level(level, &arg.low);
+ arg.high = addr >> 12;
+#else
+ u64 arg = __TLBI_VADDR(addr, asid);
+ __tlbi_update_level(level, &arg);
+#endif
op(arg);
}
@@ -475,19 +511,33 @@ static __always_inline void ripas2e1is(tlbi_arg_t arg)
__tlbi_wrapper(ripas2e1is, arg);
}
-static __always_inline void __tlbi_range(tlbi_op op, u64 addr,
- u16 asid, int scale, int num,
- u32 level, bool lpa2)
+static __always_inline u64 __tlbi_range_args_encode_comm(u16 asid, int scale, int num, u32 level)
{
u64 arg = 0;
- arg |= FIELD_PREP(TLBIR_BADDR_MASK, addr >> (lpa2 ? 16 : PAGE_SHIFT));
arg |= FIELD_PREP(TLBIR_TTL_MASK, level > 3 ? 0 : level);
arg |= FIELD_PREP(TLBIR_NUM_MASK, num);
arg |= FIELD_PREP(TLBIR_SCALE_MASK, scale);
arg |= FIELD_PREP(TLBIR_TG_MASK, get_trans_granule());
arg |= FIELD_PREP(TLBIR_ASID_MASK, asid);
+ return arg;
+}
+
+static __always_inline void __tlbi_range(tlbi_op op, u64 addr,
+ u16 asid, int scale, int num,
+ u32 level, bool lpa2)
+{
+#ifdef CONFIG_ARM64_D128
+ union __u128_halves arg;
+
+ arg.low = __tlbi_range_args_encode_comm(asid, scale, num, level);
+ arg.high = addr >> 12;
+#else
+ u64 arg = __tlbi_range_args_encode_comm(asid, scale, num, level);
+
+ arg |= FIELD_PREP(TLBIR_BADDR_MASK, addr >> (lpa2 ? 16 : PAGE_SHIFT));
+#endif
op(arg);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 17/17] arm64/mm: Make ARM64_D128 selectable
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
` (15 preceding siblings ...)
2026-07-29 12:24 ` [PATCH 16/17] arm64/mm: Enable TLBIP instruction based TLB flush Anshuman Khandual
@ 2026-07-29 12:24 ` Anshuman Khandual
16 siblings, 0 replies; 18+ messages in thread
From: Anshuman Khandual @ 2026-07-29 12:24 UTC (permalink / raw)
To: linux-mm, linux-arm-kernel
Cc: Anshuman Khandual, Catalin Marinas, Will Deacon, Ryan Roberts,
Mark Rutland, Lorenzo Stoakes, Andrew Morton, David Hildenbrand,
Mike Rapoport, Linu Cherian, linux-kernel
All required changes are already in place including the TLBIP instruction
support for enabling D128 translation. Just make this new ARM64_D128 user
selectable, after ensuring necessary tools chain support required for the
new instructions being used for D128.
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com>
---
arch/arm64/Kconfig | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 8459d43afd45..6c88906d48ec 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -275,6 +275,10 @@ config GCC_SUPPORTS_DYNAMIC_FTRACE_WITH_ARGS
def_bool CC_IS_GCC
depends on $(cc-option,-fpatchable-function-entry=2)
+config CC_SUPPORTS_LSE128
+ def_bool CC_IS_GCC
+ depends on $(cc-option, -march=armv8.1-a+lse128)
+
config 64BIT
def_bool y
@@ -2249,8 +2253,18 @@ endmenu # "ARMv8.9 architectural features"
menu "ARMv9.3 architectural features"
+config AS_HAS_ARMV9_3
+ def_bool $(cc-option,-Wa$(comma)-march=armv9.3-a)
+
config ARM64_D128
- bool
+ bool "Enable support for 128 bit page table (FEAT_D128)"
+ depends on ARCH_SUPPORTS_INT128
+ depends on CC_SUPPORTS_LSE128
+ depends on AS_HAS_ARMV9_3
+ depends on EXPERT
+ depends on !VIRTUALIZATION
+ depends on !KASAN
+ depends on !UNMAP_KERNEL_AT_EL0
default n
help
ARMv9.3 introduces FEAT_D128, which provides a 128 bit page
--
2.43.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-07-29 14:08 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 12:24 [PATCH 00/17] arm64/mm: Enable 128 bit page table entries Anshuman Khandual
2026-07-29 12:24 ` [PATCH 01/17] mm: Add read-write accessors for vm_page_prot Anshuman Khandual
2026-07-29 12:24 ` [PATCH 02/17] arm64/mm: Convert READ_ONCE() as pmdp_get() while accessing PMD Anshuman Khandual
2026-07-29 12:24 ` [PATCH 03/17] arm64/mm: Convert READ_ONCE() as pudp_get() while accessing PUD Anshuman Khandual
2026-07-29 12:24 ` [PATCH 04/17] arm64/mm: Convert READ_ONCE() as p4dp_get() while accessing P4D Anshuman Khandual
2026-07-29 12:24 ` [PATCH 05/17] arm64/mm: Convert READ_ONCE() as pgdp_get() while accessing PGD Anshuman Khandual
2026-07-29 12:24 ` [PATCH 06/17] arm64/mm: Route all pgtable reads via ptval_get() Anshuman Khandual
2026-07-29 12:24 ` [PATCH 07/17] arm64/mm: Route all pgtable writes via ptval_set() Anshuman Khandual
2026-07-29 12:24 ` [PATCH 08/17] arm64/mm: Route all pgtable atomics to central helpers Anshuman Khandual
2026-07-29 12:24 ` [PATCH 09/17] arm64/mm: Override read-write accessors for vm_page_prot Anshuman Khandual
2026-07-29 12:24 ` [PATCH 10/17] arm64/mm: Standardize printing for pgtable entries Anshuman Khandual
2026-07-29 12:24 ` [PATCH 11/17] arm64/mm: Enable fixmap with 5 level page table Anshuman Khandual
2026-07-29 12:24 ` [PATCH 12/17] arm64/mm: Enable pgtable geometry for FEAT_D128 Anshuman Khandual
2026-07-29 12:24 ` [PATCH 13/17] arm64/mm: Enable pgtable descriptor " Anshuman Khandual
2026-07-29 12:24 ` [PATCH 14/17] arm64/mm: Enable 128 bit translation Anshuman Khandual
2026-07-29 12:24 ` [PATCH 15/17] arm64/mm: Add an abstraction level for tlbi_op Anshuman Khandual
2026-07-29 12:24 ` [PATCH 16/17] arm64/mm: Enable TLBIP instruction based TLB flush Anshuman Khandual
2026-07-29 12:24 ` [PATCH 17/17] arm64/mm: Make ARM64_D128 selectable Anshuman Khandual
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox