FAILED: patch "[PATCH] iommu/amd/pgtbl: Fix possible race while increase page table" failed to apply to 6.6-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x 1e56310b40fd2e7e0b9493da9ff488af145bdd0c
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2025092102-unbutton-entire-9371@gregkh' --subject-prefix 'PATCH 6.6.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 1e56310b40fd2e7e0b9493da9ff488af145bdd0c Mon Sep 17 00:00:00 2001
From: Vasant Hegde <[email protected]>
Date: Sat, 13 Sep 2025 06:26:57 +0000
Subject: [PATCH] iommu/amd/pgtbl: Fix possible race while increase page table
level
The AMD IOMMU host page table implementation supports dynamic page table levels (up to 6 levels), starting with a 3-level configuration that expands based on IOVA address. The kernel maintains a root pointer and current page table level to enable proper page table walks in alloc_pte()/fetch_pte() operations.
The IOMMU IOVA allocator initially starts with 32-bit address and onces its exhuasted it switches to 64-bit address (max address is determined based on IOMMU and device DMA capability). To support larger IOVA, AMD IOMMU driver increases page table level.
But in unmap path (iommu_v1_unmap_pages()), fetch_pte() reads pgtable->[root/mode] without lock. So its possible that in exteme corner case, when increase_address_space() is updating pgtable->[root/mode], fetch_pte() reads wrong page table level (pgtable->mode). It does compare the value with level encoded in page table and returns NULL. This will result is iommu_unmap ops to fail and upper layer may retry/log WARN_ON.
CPU 0 CPU 1
------ ------ map pages unmap pages alloc_pte() -> increase_address_space() iommu_v1_unmap_pages() -> fetch_pte()
pgtable->root = pte (new root value)
READ pgtable->[mode/root]
Reads new root, old mode
Updates mode (pgtable->mode += 1)
Since Page table level updates are infrequent and already synchronized with a spinlock, implement seqcount to enable lock-free read operations on the read path.
drivers/iommu/amd/amd_iommu_types.h +1 -0
@@ -555,6 +555,7 @@ struct gcr3_tbl_info { };struct amd_io_pgtable { +seqcount_t seqcount;/* Protects root/mode update */ struct io_pgtable pgtbl;int mode;u64 *root;
drivers/iommu/amd/io_pgtable.c +21 -4
@@ -17,6 +17,7 @@#include <linux/slab.h> #include <linux/types.h> #include <linux/dma-mapping.h> +#include <linux/seqlock.h> #include <asm/barrier.h>
@@ -130,8 +131,11 @@ static bool increase_address_space(struct amd_io_pgtable *pgtable, *pte =PM_LEVEL_PDE (pgtable->mode,iommu_virt_to_phys (pgtable->root)); +write_seqcount_begin (&pgtable->seqcount); pgtable->root = pte; pgtable->mode +=1 ; +write_seqcount_end (&pgtable->seqcount); +amd_iommu_update_and_flush_device_table (domain); pte =NULL ;
@@ -153,6 +157,7 @@ static u64 *alloc_pte(struct amd_io_pgtable *pgtable, {unsigned long last_addr = address + (page_size -1 );struct io_pgtable_cfg *cfg = &pgtable->pgtbl.cfg; +unsigned int seqcount;int level, end_lvl;u64 *pte, *page;
@@ -170,8 +175,14 @@ static u64 *alloc_pte(struct amd_io_pgtable *pgtable, } - level = pgtable->mode -1 ; - pte = &pgtable->root[PM_LEVEL_INDEX (level, address)]; +do { + seqcount =read_seqcount_begin (&pgtable->seqcount); + + level = pgtable->mode -1 ; + pte = &pgtable->root[PM_LEVEL_INDEX (level, address)]; + }while (read_seqcount_retry (&pgtable->seqcount, seqcount)); + + address =PAGE_SIZE_ALIGN (address, page_size); end_lvl =PAGE_SIZE_LEVEL (page_size);
@@ -249,6 +260,7 @@ static u64 *fetch_pte(struct amd_io_pgtable *pgtable, unsigned long *page_size) {int level; +unsigned int seqcount;u64 *pte; *page_size =0 ;
@@ -256,8 +268,12 @@ static u64 *fetch_pte(struct amd_io_pgtable *pgtable,if (address >PM_LEVEL_SIZE (pgtable->mode))return NULL ; - level = pgtable->mode -1 ; - pte = &pgtable->root[PM_LEVEL_INDEX (level, address)]; +do { + seqcount =read_seqcount_begin (&pgtable->seqcount); + level = pgtable->mode -1 ; + pte = &pgtable->root[PM_LEVEL_INDEX (level, address)]; + }while (read_seqcount_retry (&pgtable->seqcount, seqcount)); + *page_size =PTE_LEVEL_PAGE_SIZE (level);while (level >0 ) {
@@ -541,6 +557,7 @@ static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cooif (!pgtable->root)return NULL ; pgtable->mode =PAGE_MODE_3_LEVEL ; +seqcount_init (&pgtable->seqcount); cfg->pgsize_bitmap = amd_iommu_pgsize_bitmap; cfg->ias =IOMMU_IN_ADDR_BIT_SIZE ;
Sasha Levin
maintainerPATCH 1/1 iommu/amd/pgtbl: Fix possible race while increase page table level
From: Vasant Hegde <[email protected]>
[ Upstream commit 1e56310b40fd2e7e0b9493da9ff488af145bdd0c ]
The AMD IOMMU host page table implementation supports dynamic page table levels (up to 6 levels), starting with a 3-level configuration that expands based on IOVA address. The kernel maintains a root pointer and current page table level to enable proper page table walks in alloc_pte()/fetch_pte() operations.
The IOMMU IOVA allocator initially starts with 32-bit address and onces its exhuasted it switches to 64-bit address (max address is determined based on IOMMU and device DMA capability). To support larger IOVA, AMD IOMMU driver increases page table level.
But in unmap path (iommu_v1_unmap_pages()), fetch_pte() reads pgtable->[root/mode] without lock. So its possible that in exteme corner case, when increase_address_space() is updating pgtable->[root/mode], fetch_pte() reads wrong page table level (pgtable->mode). It does compare the value with level encoded in page table and returns NULL. This will result is iommu_unmap ops to fail and upper layer may retry/log WARN_ON.
CPU 0 CPU 1
------ ------ map pages unmap pages alloc_pte() -> increase_address_space() iommu_v1_unmap_pages() -> fetch_pte()
pgtable->root = pte (new root value)
READ pgtable->[mode/root]
Reads new root, old mode
Updates mode (pgtable->mode += 1)
Since Page table level updates are infrequent and already synchronized with a spinlock, implement seqcount to enable lock-free read operations on the read path.
--- drivers/iommu/amd/amd_iommu_types.h | 1 + drivers/iommu/amd/io_pgtable.c | 26 ++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-)
drivers/iommu/amd/amd_iommu_types.h +1 -0
@@ -540,6 +540,7 @@ struct amd_irte_ops; container_of((x), struct amd_io_pgtable, pgtbl_cfg)struct amd_io_pgtable { +seqcount_t seqcount;/* Protects root/mode update */ struct io_pgtable_cfg pgtbl_cfg;struct io_pgtable iop;int mode;
drivers/iommu/amd/io_pgtable.c +22 -4
@@ -17,6 +17,7 @@#include <linux/slab.h> #include <linux/types.h> #include <linux/dma-mapping.h> +#include <linux/seqlock.h> #include <asm/barrier.h>
@@ -171,8 +172,11 @@ static bool increase_address_space(struct protection_domain *domain, *pte =PM_LEVEL_PDE (domain->iop.mode,iommu_virt_to_phys (domain->iop.root)); +write_seqcount_begin (&domain->iop.seqcount); domain->iop.root = pte; domain->iop.mode +=1 ; +write_seqcount_end (&domain->iop.seqcount); +amd_iommu_update_and_flush_device_table (domain);amd_iommu_domain_flush_complete (domain);
@@ -199,6 +203,7 @@ static u64 *alloc_pte(struct protection_domain *domain,gfp_t gfp, bool *updated) { + unsigned int seqcount;int level, end_lvl;u64 *pte, *page;
@@ -214,8 +219,14 @@ static u64 *alloc_pte(struct protection_domain *domain, } - level = domain->iop.mode -1 ; - pte = &domain->iop.root[PM_LEVEL_INDEX (level, address)]; +do { + seqcount =read_seqcount_begin (&domain->iop.seqcount); + + level = domain->iop.mode -1 ; + pte = &domain->iop.root[PM_LEVEL_INDEX (level, address)]; + }while (read_seqcount_retry (&domain->iop.seqcount, seqcount)); + + address =PAGE_SIZE_ALIGN (address, page_size); end_lvl =PAGE_SIZE_LEVEL (page_size);
@@ -292,6 +303,7 @@ static u64 *fetch_pte(struct amd_io_pgtable *pgtable, unsigned long *page_size) {int level; +unsigned int seqcount;u64 *pte; *page_size =0 ;
@@ -299,8 +311,12 @@ static u64 *fetch_pte(struct amd_io_pgtable *pgtable,if (address >PM_LEVEL_SIZE (pgtable->mode))return NULL ; - level = pgtable->mode -1 ; - pte = &pgtable->root[PM_LEVEL_INDEX (level, address)]; +do { + seqcount =read_seqcount_begin (&pgtable->seqcount); + level = pgtable->mode -1 ; + pte = &pgtable->root[PM_LEVEL_INDEX (level, address)]; + }while (read_seqcount_retry (&pgtable->seqcount, seqcount)); + *page_size =PTE_LEVEL_PAGE_SIZE (level);while (level >0 ) {
@@ -524,6 +540,8 @@ static struct io_pgtable *v1_alloc_pgtable(struct io_pgtable_cfg *cfg, void *coo cfg->oas =IOMMU_OUT_ADDR_BIT_SIZE , cfg->tlb = &v1_flush_ops; +seqcount_init (&pgtable->seqcount); + pgtable->iop.ops.map_pages = iommu_v1_map_pages; pgtable->iop.ops.unmap_pages = iommu_v1_unmap_pages; pgtable->iop.ops.iova_to_phys = iommu_v1_iova_to_phys;
Signature · 1 line
2.51.0