* [PATCH v8 15/23] dma-direct: pass attrs to dma_capable() for DMA_ATTR_CC_SHARED checks
From: Aneesh Kumar K.V (Arm) @ 2026-07-17 18:04 UTC (permalink / raw)
To: iommu, linux-arm-kernel, linux-kernel, linux-coco
Cc: Aneesh Kumar K.V (Arm), Robin Murphy, Marek Szyprowski,
Will Deacon, Marc Zyngier, Steven Price, Suzuki K Poulose,
Catalin Marinas, Jiri Pirko, Jason Gunthorpe, Mostafa Saleh,
Petr Tesarik, Alexey Kardashevskiy, Dan Williams, Xu Yilun,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Alexander Gordeev,
Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, x86, Jason Gunthorpe,
Jiri Pirko, Michael Kelley
In-Reply-To: <20260717180442.110954-1-aneesh.kumar@kernel.org>
Teach dma_capable() about DMA_ATTR_CC_SHARED so the capability
check can reject encrypted DMA addresses for devices that require
unencrypted/shared DMA.
Also propagate DMA_ATTR_CC_SHARED in swiotlb_map() when the selected
SWIOTLB pool is decrypted so the capability check sees the correct DMA
address attribute.
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Tested-by: Jiri Pirko <jiri@nvidia.com>
Tested-by: Michael Kelley <mhklinux@outlook.com>
Tested-by: Mostafa Saleh <smostafa@google.com>
Reviewed-by: Petr Tesarik <ptesarik@suse.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/x86/kernel/amd_gart_64.c | 30 ++++++++++++++++--------------
drivers/xen/swiotlb-xen.c | 6 +++---
include/linux/dma-direct.h | 10 +++++++++-
kernel/dma/direct.h | 6 +++---
kernel/dma/swiotlb.c | 2 +-
5 files changed, 32 insertions(+), 22 deletions(-)
diff --git a/arch/x86/kernel/amd_gart_64.c b/arch/x86/kernel/amd_gart_64.c
index e8000a56732e..b5f1f031d45b 100644
--- a/arch/x86/kernel/amd_gart_64.c
+++ b/arch/x86/kernel/amd_gart_64.c
@@ -180,22 +180,23 @@ static void iommu_full(struct device *dev, size_t size, int dir)
}
static inline int
-need_iommu(struct device *dev, unsigned long addr, size_t size)
+need_iommu(struct device *dev, unsigned long addr, size_t size, unsigned long attrs)
{
- return force_iommu || !dma_capable(dev, addr, size, true);
+ return force_iommu || !dma_capable(dev, addr, size, true, attrs);
}
static inline int
-nonforced_iommu(struct device *dev, unsigned long addr, size_t size)
+nonforced_iommu(struct device *dev, unsigned long addr, size_t size,
+ unsigned long attrs)
{
- return !dma_capable(dev, addr, size, true);
+ return !dma_capable(dev, addr, size, true, attrs);
}
/* Map a single continuous physical area into the IOMMU.
* Caller needs to check if the iommu is needed and flush.
*/
static dma_addr_t dma_map_area(struct device *dev, dma_addr_t phys_mem,
- size_t size, int dir, unsigned long align_mask)
+ size_t size, int dir, unsigned long align_mask, unsigned long attrs)
{
unsigned long npages = iommu_num_pages(phys_mem, size, PAGE_SIZE);
unsigned long iommu_page;
@@ -206,7 +207,7 @@ static dma_addr_t dma_map_area(struct device *dev, dma_addr_t phys_mem,
iommu_page = alloc_iommu(dev, npages, align_mask);
if (iommu_page == -1) {
- if (!nonforced_iommu(dev, phys_mem, size))
+ if (!nonforced_iommu(dev, phys_mem, size, attrs))
return phys_mem;
if (panic_on_overflow)
panic("dma_map_area overflow %lu bytes\n", size);
@@ -231,10 +232,10 @@ static dma_addr_t gart_map_phys(struct device *dev, phys_addr_t paddr,
if (unlikely(attrs & DMA_ATTR_MMIO))
return DMA_MAPPING_ERROR;
- if (!need_iommu(dev, paddr, size))
+ if (!need_iommu(dev, paddr, size, attrs))
return paddr;
- bus = dma_map_area(dev, paddr, size, dir, 0);
+ bus = dma_map_area(dev, paddr, size, dir, 0, attrs);
flush_gart();
return bus;
@@ -289,7 +290,7 @@ static void gart_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
/* Fallback for dma_map_sg in case of overflow */
static int dma_map_sg_nonforce(struct device *dev, struct scatterlist *sg,
- int nents, int dir)
+ int nents, int dir, unsigned long attrs)
{
struct scatterlist *s;
int i;
@@ -301,8 +302,8 @@ static int dma_map_sg_nonforce(struct device *dev, struct scatterlist *sg,
for_each_sg(sg, s, nents, i) {
unsigned long addr = sg_phys(s);
- if (nonforced_iommu(dev, addr, s->length)) {
- addr = dma_map_area(dev, addr, s->length, dir, 0);
+ if (nonforced_iommu(dev, addr, s->length, attrs)) {
+ addr = dma_map_area(dev, addr, s->length, dir, 0, attrs);
if (addr == DMA_MAPPING_ERROR) {
if (i > 0)
gart_unmap_sg(dev, sg, i, dir, 0);
@@ -401,7 +402,7 @@ static int gart_map_sg(struct device *dev, struct scatterlist *sg, int nents,
s->dma_address = addr;
BUG_ON(s->length == 0);
- nextneed = need_iommu(dev, addr, s->length);
+ nextneed = need_iommu(dev, addr, s->length, attrs);
/* Handle the previous not yet processed entries */
if (i > start) {
@@ -449,7 +450,7 @@ static int gart_map_sg(struct device *dev, struct scatterlist *sg, int nents,
/* When it was forced or merged try again in a dumb way */
if (force_iommu || iommu_merge) {
- out = dma_map_sg_nonforce(dev, sg, nents, dir);
+ out = dma_map_sg_nonforce(dev, sg, nents, dir, attrs);
if (out > 0)
return out;
}
@@ -473,7 +474,8 @@ gart_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_addr,
return vaddr;
*dma_addr = dma_map_area(dev, virt_to_phys(vaddr), size,
- DMA_BIDIRECTIONAL, (1UL << get_order(size)) - 1);
+ DMA_BIDIRECTIONAL,
+ (1UL << get_order(size)) - 1, attrs);
flush_gart();
if (unlikely(*dma_addr == DMA_MAPPING_ERROR))
goto out_free;
diff --git a/drivers/xen/swiotlb-xen.c b/drivers/xen/swiotlb-xen.c
index 8c4abe65cd49..e2538824ef52 100644
--- a/drivers/xen/swiotlb-xen.c
+++ b/drivers/xen/swiotlb-xen.c
@@ -212,7 +212,7 @@ static dma_addr_t xen_swiotlb_map_phys(struct device *dev, phys_addr_t phys,
BUG_ON(dir == DMA_NONE);
if (attrs & DMA_ATTR_MMIO) {
- if (unlikely(!dma_capable(dev, phys, size, false))) {
+ if (unlikely(!dma_capable(dev, phys, size, false, attrs))) {
dev_err_once(
dev,
"DMA addr %pa+%zu overflow (mask %llx, bus limit %llx).\n",
@@ -231,7 +231,7 @@ static dma_addr_t xen_swiotlb_map_phys(struct device *dev, phys_addr_t phys,
* we can safely return the device addr and not worry about bounce
* buffering it.
*/
- if (dma_capable(dev, dev_addr, size, true) &&
+ if (dma_capable(dev, dev_addr, size, true, attrs) &&
!dma_kmalloc_needs_bounce(dev, size, dir) &&
!range_straddles_page_boundary(phys, size) &&
!xen_arch_need_swiotlb(dev, phys, dev_addr) &&
@@ -253,7 +253,7 @@ static dma_addr_t xen_swiotlb_map_phys(struct device *dev, phys_addr_t phys,
/*
* Ensure that the address returned is DMA'ble
*/
- if (unlikely(!dma_capable(dev, dev_addr, size, true))) {
+ if (unlikely(!dma_capable(dev, dev_addr, size, true, attrs))) {
__swiotlb_tbl_unmap_single(dev, map, size, dir,
attrs | DMA_ATTR_SKIP_CPU_SYNC,
swiotlb_find_pool(dev, map));
diff --git a/include/linux/dma-direct.h b/include/linux/dma-direct.h
index 94fad4e7c11e..daa31a1adf7b 100644
--- a/include/linux/dma-direct.h
+++ b/include/linux/dma-direct.h
@@ -135,12 +135,20 @@ static inline bool force_dma_unencrypted(struct device *dev)
#endif /* CONFIG_ARCH_HAS_FORCE_DMA_UNENCRYPTED */
static inline bool dma_capable(struct device *dev, dma_addr_t addr, size_t size,
- bool is_ram)
+ bool is_ram, unsigned long attrs)
{
dma_addr_t end = addr + size - 1;
if (addr == DMA_MAPPING_ERROR)
return false;
+ /*
+ * The DMA address was derived from encrypted RAM, but this device
+ * requires unencrypted DMA addresses. Treat it as not DMA-capable
+ * so the caller can fall back to a suitable SWIOTLB pool.
+ */
+ if (!(attrs & DMA_ATTR_CC_SHARED) && force_dma_unencrypted(dev))
+ return false;
+
if (is_ram && !IS_ENABLED(CONFIG_ARCH_DMA_ADDR_T_64BIT) &&
min(addr, end) < phys_to_dma(dev, PFN_PHYS(min_low_pfn)))
return false;
diff --git a/kernel/dma/direct.h b/kernel/dma/direct.h
index 7140c208c123..e05dc7649366 100644
--- a/kernel/dma/direct.h
+++ b/kernel/dma/direct.h
@@ -101,15 +101,15 @@ static inline dma_addr_t dma_direct_map_phys(struct device *dev,
if (attrs & DMA_ATTR_MMIO) {
dma_addr = phys;
- if (unlikely(!dma_capable(dev, dma_addr, size, false)))
+ if (unlikely(!dma_capable(dev, dma_addr, size, false, attrs)))
goto err_overflow;
} else if (attrs & DMA_ATTR_CC_SHARED) {
dma_addr = phys_to_dma_unencrypted(dev, phys);
- if (unlikely(!dma_capable(dev, dma_addr, size, false)))
+ if (unlikely(!dma_capable(dev, dma_addr, size, false, attrs)))
goto err_overflow;
} else {
dma_addr = phys_to_dma(dev, phys);
- if (unlikely(!dma_capable(dev, dma_addr, size, true)) ||
+ if (unlikely(!dma_capable(dev, dma_addr, size, true, attrs)) ||
dma_kmalloc_needs_bounce(dev, size, dir)) {
if (is_swiotlb_active(dev) &&
!(attrs & DMA_ATTR_REQUIRE_COHERENT))
diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index 335e27bc1e1f..b5960c2e98d8 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -1697,7 +1697,7 @@ dma_addr_t swiotlb_map(struct device *dev, phys_addr_t paddr, size_t size,
else
dma_addr = phys_to_dma_encrypted(dev, swiotlb_addr);
- if (unlikely(!dma_capable(dev, dma_addr, size, true))) {
+ if (unlikely(!dma_capable(dev, dma_addr, size, true, attrs))) {
__swiotlb_tbl_unmap_single(dev, swiotlb_addr, size, dir,
attrs | DMA_ATTR_SKIP_CPU_SYNC,
swiotlb_find_pool(dev, swiotlb_addr));
--
2.43.0
^ permalink raw reply related
* [PATCH v8 16/23] dma-direct: Move dma_direct_map_phys() to dma/direct.c
From: Aneesh Kumar K.V (Arm) @ 2026-07-17 18:04 UTC (permalink / raw)
To: iommu, linux-arm-kernel, linux-kernel, linux-coco
Cc: Aneesh Kumar K.V (Arm), Robin Murphy, Marek Szyprowski,
Will Deacon, Marc Zyngier, Steven Price, Suzuki K Poulose,
Catalin Marinas, Jiri Pirko, Jason Gunthorpe, Mostafa Saleh,
Petr Tesarik, Alexey Kardashevskiy, Dan Williams, Xu Yilun,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Alexander Gordeev,
Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, x86
In-Reply-To: <20260717180442.110954-1-aneesh.kumar@kernel.org>
dma_direct_map_phys() is too large to benefit from being inlined. Move its
implementation to direct.c and leave the declaration in direct.h.
No functional change in this patch
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
kernel/dma/direct.c | 53 ++++++++++++++++++++++++++++++++++++++++++
kernel/dma/direct.h | 56 +++------------------------------------------
2 files changed, 56 insertions(+), 53 deletions(-)
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index f54c7929b97f..2413235c50f0 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -620,6 +620,59 @@ int dma_direct_mmap(struct device *dev, struct vm_area_struct *vma,
user_count << PAGE_SHIFT, vma->vm_page_prot);
}
+dma_addr_t dma_direct_map_phys(struct device *dev, phys_addr_t phys,
+ size_t size, enum dma_data_direction dir,
+ unsigned long attrs, bool flush)
+{
+ dma_addr_t dma_addr;
+
+ if (is_swiotlb_force_bounce(dev)) {
+ if (!(attrs & DMA_ATTR_CC_SHARED)) {
+ if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
+ return DMA_MAPPING_ERROR;
+
+ return swiotlb_map(dev, phys, size, dir, attrs);
+ }
+ } else if (attrs & DMA_ATTR_CC_SHARED) {
+ return DMA_MAPPING_ERROR;
+ }
+
+ if (attrs & DMA_ATTR_MMIO) {
+ dma_addr = phys;
+ if (unlikely(!dma_capable(dev, dma_addr, size, false, attrs)))
+ goto err_overflow;
+ } else if (attrs & DMA_ATTR_CC_SHARED) {
+ dma_addr = phys_to_dma_unencrypted(dev, phys);
+ if (unlikely(!dma_capable(dev, dma_addr, size, false, attrs)))
+ goto err_overflow;
+ } else {
+ dma_addr = phys_to_dma(dev, phys);
+ if (unlikely(!dma_capable(dev, dma_addr, size, true, attrs)) ||
+ dma_kmalloc_needs_bounce(dev, size, dir)) {
+ if (is_swiotlb_active(dev) &&
+ !(attrs & DMA_ATTR_REQUIRE_COHERENT))
+ return swiotlb_map(dev, phys, size, dir, attrs);
+
+ goto err_overflow;
+ }
+ }
+
+ if (!dev_is_dma_coherent(dev) &&
+ !(attrs & (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_MMIO))) {
+ arch_sync_dma_for_device(phys, size, dir);
+ if (flush)
+ arch_sync_dma_flush();
+ }
+ return dma_addr;
+
+err_overflow:
+ dev_WARN_ONCE(
+ dev, 1,
+ "DMA addr %pad+%zu overflow (mask %llx, bus limit %llx).\n",
+ &dma_addr, size, *dev->dma_mask, dev->bus_dma_limit);
+ return DMA_MAPPING_ERROR;
+}
+
int dma_direct_supported(struct device *dev, u64 mask)
{
u64 min_mask = ((u64)max_pfn << PAGE_SHIFT) - 1;
diff --git a/kernel/dma/direct.h b/kernel/dma/direct.h
index e05dc7649366..a7adadb1b2a5 100644
--- a/kernel/dma/direct.h
+++ b/kernel/dma/direct.h
@@ -17,6 +17,9 @@ bool dma_direct_can_mmap(struct device *dev);
int dma_direct_mmap(struct device *dev, struct vm_area_struct *vma,
void *cpu_addr, dma_addr_t dma_addr, size_t size,
unsigned long attrs);
+dma_addr_t dma_direct_map_phys(struct device *dev, phys_addr_t phys,
+ size_t size, enum dma_data_direction dir,
+ unsigned long attrs, bool flush);
bool dma_direct_need_sync(struct device *dev, dma_addr_t dma_addr);
int dma_direct_map_sg(struct device *dev, struct scatterlist *sgl, int nents,
enum dma_data_direction dir, unsigned long attrs);
@@ -82,59 +85,6 @@ static inline void dma_direct_sync_single_for_cpu(struct device *dev,
swiotlb_sync_single_for_cpu(dev, paddr, size, dir);
}
-static inline dma_addr_t dma_direct_map_phys(struct device *dev,
- phys_addr_t phys, size_t size, enum dma_data_direction dir,
- unsigned long attrs, bool flush)
-{
- dma_addr_t dma_addr;
-
- if (is_swiotlb_force_bounce(dev)) {
- if (!(attrs & DMA_ATTR_CC_SHARED)) {
- if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
- return DMA_MAPPING_ERROR;
-
- return swiotlb_map(dev, phys, size, dir, attrs);
- }
- } else if (attrs & DMA_ATTR_CC_SHARED) {
- return DMA_MAPPING_ERROR;
- }
-
- if (attrs & DMA_ATTR_MMIO) {
- dma_addr = phys;
- if (unlikely(!dma_capable(dev, dma_addr, size, false, attrs)))
- goto err_overflow;
- } else if (attrs & DMA_ATTR_CC_SHARED) {
- dma_addr = phys_to_dma_unencrypted(dev, phys);
- if (unlikely(!dma_capable(dev, dma_addr, size, false, attrs)))
- goto err_overflow;
- } else {
- dma_addr = phys_to_dma(dev, phys);
- if (unlikely(!dma_capable(dev, dma_addr, size, true, attrs)) ||
- dma_kmalloc_needs_bounce(dev, size, dir)) {
- if (is_swiotlb_active(dev) &&
- !(attrs & DMA_ATTR_REQUIRE_COHERENT))
- return swiotlb_map(dev, phys, size, dir, attrs);
-
- goto err_overflow;
- }
- }
-
- if (!dev_is_dma_coherent(dev) &&
- !(attrs & (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_MMIO))) {
- arch_sync_dma_for_device(phys, size, dir);
- if (flush)
- arch_sync_dma_flush();
- }
- return dma_addr;
-
-err_overflow:
- dev_WARN_ONCE(
- dev, 1,
- "DMA addr %pad+%zu overflow (mask %llx, bus limit %llx).\n",
- &dma_addr, size, *dev->dma_mask, dev->bus_dma_limit);
- return DMA_MAPPING_ERROR;
-}
-
static inline void dma_direct_unmap_phys(struct device *dev, dma_addr_t addr,
size_t size, enum dma_data_direction dir, unsigned long attrs,
bool flush)
--
2.43.0
^ permalink raw reply related
* [PATCH v8 17/23] dma-direct: make dma_direct_map_phys() honor DMA_ATTR_CC_SHARED
From: Aneesh Kumar K.V (Arm) @ 2026-07-17 18:04 UTC (permalink / raw)
To: iommu, linux-arm-kernel, linux-kernel, linux-coco
Cc: Aneesh Kumar K.V (Arm), Robin Murphy, Marek Szyprowski,
Will Deacon, Marc Zyngier, Steven Price, Suzuki K Poulose,
Catalin Marinas, Jiri Pirko, Jason Gunthorpe, Mostafa Saleh,
Petr Tesarik, Alexey Kardashevskiy, Dan Williams, Xu Yilun,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Alexander Gordeev,
Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, x86, Jiri Pirko,
Michael Kelley
In-Reply-To: <20260717180442.110954-1-aneesh.kumar@kernel.org>
Teach dma_direct_map_phys() to select the DMA address encoding based on
DMA_ATTR_CC_SHARED.
Use phys_to_dma_unencrypted() for decrypted mappings and
phys_to_dma_encrypted() otherwise. If a device requires unencrypted DMA
but the source physical address is still encrypted, force the mapping
through swiotlb so the DMA address and backing memory attributes remain
consistent.
Update the arm64, x86, s390 and powerpc secure-guest setup to not use
swiotlb force option
Tested-by: Jiri Pirko <jiri@nvidia.com>
Tested-by: Michael Kelley <mhklinux@outlook.com>
Tested-by: Mostafa Saleh <smostafa@google.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
Changes from v3:
* Handle DMA_ATTR_MMIO
---
arch/arm64/mm/init.c | 4 +--
arch/powerpc/platforms/pseries/svm.c | 2 +-
arch/s390/mm/init.c | 2 +-
arch/x86/kernel/pci-dma.c | 4 +--
kernel/dma/direct.c | 52 ++++++++++++++++------------
5 files changed, 34 insertions(+), 30 deletions(-)
diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c
index 48d69e390158..3c390ef7b41b 100644
--- a/arch/arm64/mm/init.c
+++ b/arch/arm64/mm/init.c
@@ -340,9 +340,7 @@ void __init arch_mm_preinit(void)
unsigned int flags = SWIOTLB_VERBOSE;
bool limited_addressing = max_pfn > PFN_DOWN(arm64_dma_phys_limit);
- if (is_realm_world() || is_protected_kvm_guest()) {
- flags |= SWIOTLB_FORCE;
- } else if (!limited_addressing) {
+ if (!limited_addressing) {
/*
* If no bouncing needed for ZONE_DMA, reduce the swiotlb
* buffer for kmalloc() bouncing to 1MB per 1GB of RAM.
diff --git a/arch/powerpc/platforms/pseries/svm.c b/arch/powerpc/platforms/pseries/svm.c
index 384c9dc1899a..7a403dbd35ee 100644
--- a/arch/powerpc/platforms/pseries/svm.c
+++ b/arch/powerpc/platforms/pseries/svm.c
@@ -29,7 +29,7 @@ static int __init init_svm(void)
* need to use the SWIOTLB buffer for DMA even if dma_capable() says
* otherwise.
*/
- ppc_swiotlb_flags |= SWIOTLB_ANY | SWIOTLB_FORCE;
+ ppc_swiotlb_flags |= SWIOTLB_ANY;
/* Share the SWIOTLB buffer with the host. */
swiotlb_update_mem_attributes();
diff --git a/arch/s390/mm/init.c b/arch/s390/mm/init.c
index 6b1c5a4fa9ce..8d1de5a2e554 100644
--- a/arch/s390/mm/init.c
+++ b/arch/s390/mm/init.c
@@ -166,7 +166,7 @@ static void __init pv_init(void)
virtio_set_mem_acc_cb(virtio_require_restricted_mem_acc);
/* make sure bounce buffers are shared */
- swiotlb_init(true, SWIOTLB_FORCE | SWIOTLB_VERBOSE);
+ swiotlb_init(true, SWIOTLB_VERBOSE);
swiotlb_update_mem_attributes();
}
diff --git a/arch/x86/kernel/pci-dma.c b/arch/x86/kernel/pci-dma.c
index 6267363e0189..75cf8f6ae8cd 100644
--- a/arch/x86/kernel/pci-dma.c
+++ b/arch/x86/kernel/pci-dma.c
@@ -59,10 +59,8 @@ static void __init pci_swiotlb_detect(void)
* bounce buffers as the hypervisor can't access arbitrary VM memory
* that is not explicitly shared with it.
*/
- if (cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT)) {
+ if (cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT))
x86_swiotlb_enable = true;
- x86_swiotlb_flags |= SWIOTLB_FORCE;
- }
}
#else
static inline void __init pci_swiotlb_detect(void)
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index 2413235c50f0..fed901c0224e 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -14,6 +14,8 @@
#include <linux/set_memory.h>
#include <linux/slab.h>
#include <linux/pci-p2pdma.h>
+#include <linux/cc_platform.h>
+
#include "direct.h"
/*
@@ -626,37 +628,41 @@ dma_addr_t dma_direct_map_phys(struct device *dev, phys_addr_t phys,
{
dma_addr_t dma_addr;
+ if (attrs & DMA_ATTR_MMIO) {
+ /*
+ * For host memory encryption treat MMIO memory as shared
+ */
+ if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT))
+ attrs |= DMA_ATTR_CC_SHARED;
+ }
+
if (is_swiotlb_force_bounce(dev)) {
- if (!(attrs & DMA_ATTR_CC_SHARED)) {
- if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
- return DMA_MAPPING_ERROR;
+ if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
+ return DMA_MAPPING_ERROR;
- return swiotlb_map(dev, phys, size, dir, attrs);
- }
- } else if (attrs & DMA_ATTR_CC_SHARED) {
- return DMA_MAPPING_ERROR;
+ return swiotlb_map(dev, phys, size, dir, attrs);
}
- if (attrs & DMA_ATTR_MMIO) {
- dma_addr = phys;
- if (unlikely(!dma_capable(dev, dma_addr, size, false, attrs)))
- goto err_overflow;
- } else if (attrs & DMA_ATTR_CC_SHARED) {
+ if (attrs & DMA_ATTR_CC_SHARED)
dma_addr = phys_to_dma_unencrypted(dev, phys);
+ else
+ dma_addr = phys_to_dma_encrypted(dev, phys);
+
+ if (attrs & DMA_ATTR_MMIO) {
if (unlikely(!dma_capable(dev, dma_addr, size, false, attrs)))
goto err_overflow;
- } else {
- dma_addr = phys_to_dma(dev, phys);
- if (unlikely(!dma_capable(dev, dma_addr, size, true, attrs)) ||
- dma_kmalloc_needs_bounce(dev, size, dir)) {
- if (is_swiotlb_active(dev) &&
- !(attrs & DMA_ATTR_REQUIRE_COHERENT))
- return swiotlb_map(dev, phys, size, dir, attrs);
+ goto dma_mapped;
+ }
- goto err_overflow;
- }
+ if (unlikely(!dma_capable(dev, dma_addr, size, true, attrs)) ||
+ dma_kmalloc_needs_bounce(dev, size, dir)) {
+ if (is_swiotlb_active(dev) &&
+ !(attrs & DMA_ATTR_REQUIRE_COHERENT))
+ return swiotlb_map(dev, phys, size, dir, attrs);
+ goto err_overflow;
}
+dma_mapped:
if (!dev_is_dma_coherent(dev) &&
!(attrs & (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_MMIO))) {
arch_sync_dma_for_device(phys, size, dir);
@@ -748,8 +754,10 @@ size_t dma_direct_max_mapping_size(struct device *dev)
{
/* If SWIOTLB is active, use its maximum mapping size */
if (is_swiotlb_active(dev) &&
- (dma_addressing_limited(dev) || is_swiotlb_force_bounce(dev)))
+ (dma_addressing_limited(dev) || is_swiotlb_force_bounce(dev) ||
+ force_dma_unencrypted(dev)))
return swiotlb_max_mapping_size(dev);
+
return SIZE_MAX;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v8 18/23] dma-direct: set decrypted flag for remapped DMA allocations
From: Aneesh Kumar K.V (Arm) @ 2026-07-17 18:04 UTC (permalink / raw)
To: iommu, linux-arm-kernel, linux-kernel, linux-coco
Cc: Aneesh Kumar K.V (Arm), Robin Murphy, Marek Szyprowski,
Will Deacon, Marc Zyngier, Steven Price, Suzuki K Poulose,
Catalin Marinas, Jiri Pirko, Jason Gunthorpe, Mostafa Saleh,
Petr Tesarik, Alexey Kardashevskiy, Dan Williams, Xu Yilun,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Alexander Gordeev,
Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, x86, Jason Gunthorpe,
Jiri Pirko, Michael Kelley
In-Reply-To: <20260717180442.110954-1-aneesh.kumar@kernel.org>
Devices that are DMA non-coherent and require a remap were skipping
dma_set_decrypted(), leaving DMA buffers encrypted even when the device
requires unencrypted access. Move the call after the if (remap) branch
so that both the direct and remapped allocation paths correctly mark the
allocation as decrypted (or fail cleanly) before use.
Fix dma_direct_alloc() and dma_direct_free() to apply set_memory_*() to the
linear-map alias of the backing pages instead of the remapped CPU address.
Also disallow highmem pages for __DMA_ATTR_ALLOC_CC_SHARED, because highmem
buffers do not provide a usable linear-map address.
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Tested-by: Jiri Pirko <jiri@nvidia.com>
Tested-by: Michael Kelley <mhklinux@outlook.com>
Tested-by: Mostafa Saleh <smostafa@google.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
kernel/dma/direct.c | 56 +++++++++++++++++++++++++++++++++++----------
1 file changed, 44 insertions(+), 12 deletions(-)
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index fed901c0224e..f7f064323bd9 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -198,14 +198,23 @@ void *dma_direct_alloc(struct device *dev, size_t size,
{
bool remap = false, set_uncached = false;
bool mark_mem_decrypt = false;
+ bool allow_highmem = true;
struct page *page;
void *ret;
if (force_dma_unencrypted(dev))
attrs |= __DMA_ATTR_ALLOC_CC_SHARED;
- if (attrs & __DMA_ATTR_ALLOC_CC_SHARED)
+ if (attrs & __DMA_ATTR_ALLOC_CC_SHARED) {
+ /*
+ * Unencrypted/shared DMA requires a linear-mapped buffer
+ * address to look up the PFN and set architecture-required PFN
+ * attributes. This is not possible with HighMem. Avoid HighMem
+ * allocation.
+ */
+ allow_highmem = false;
mark_mem_decrypt = true;
+ }
size = PAGE_ALIGN(size);
if (attrs & DMA_ATTR_NO_WARN)
@@ -270,7 +279,7 @@ void *dma_direct_alloc(struct device *dev, size_t size,
}
/* we always manually zero the memory once we are done */
- page = __dma_direct_alloc_pages(dev, size, gfp & ~__GFP_ZERO, true);
+ page = __dma_direct_alloc_pages(dev, size, gfp & ~__GFP_ZERO, allow_highmem);
if (!page)
return NULL;
@@ -285,6 +294,14 @@ void *dma_direct_alloc(struct device *dev, size_t size,
set_uncached = false;
}
+ if (mark_mem_decrypt) {
+ void *lm_addr;
+
+ lm_addr = page_address(page);
+ if (set_memory_decrypted((unsigned long)lm_addr, PFN_UP(size)))
+ goto out_leak_pages;
+ }
+
if (remap) {
pgprot_t prot = dma_pgprot(dev, PAGE_KERNEL, attrs);
@@ -295,29 +312,36 @@ void *dma_direct_alloc(struct device *dev, size_t size,
ret = dma_common_contiguous_remap(page, size, prot,
__builtin_return_address(0));
if (!ret)
- goto out_free_pages;
+ goto out_encrypt_pages;
} else {
ret = page_address(page);
- if (mark_mem_decrypt && dma_set_decrypted(dev, ret, size))
- goto out_leak_pages;
}
memset(ret, 0, size);
if (set_uncached) {
+ void *uncached_cpu_addr;
+
arch_dma_prep_coherent(page, size);
- ret = arch_dma_set_uncached(ret, size);
- if (IS_ERR(ret))
- goto out_encrypt_pages;
+ uncached_cpu_addr = arch_dma_set_uncached(ret, size);
+ if (IS_ERR(uncached_cpu_addr))
+ goto out_free_remap_pages;
+ ret = uncached_cpu_addr;
}
*dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
return ret;
+
+out_free_remap_pages:
+ if (remap)
+ dma_common_free_remap(ret, size);
+
out_encrypt_pages:
- if (mark_mem_decrypt && dma_set_encrypted(dev, page_address(page), size))
- return NULL;
-out_free_pages:
+ if (mark_mem_decrypt &&
+ dma_set_encrypted(dev, page_address(page), size))
+ goto out_leak_pages;
+
if (!swiotlb_free(dev, page, size))
dma_free_contiguous(dev, page, size);
return NULL;
@@ -380,8 +404,16 @@ void dma_direct_free(struct device *dev, size_t size,
} else {
if (IS_ENABLED(CONFIG_ARCH_HAS_DMA_CLEAR_UNCACHED))
arch_dma_clear_uncached(cpu_addr, size);
- if (mark_mem_encrypted && dma_set_encrypted(dev, cpu_addr, size))
+ }
+
+ if (mark_mem_encrypted) {
+ void *lm_addr;
+
+ lm_addr = phys_to_virt(phys);
+ if (set_memory_encrypted((unsigned long)lm_addr, PFN_UP(size))) {
+ pr_warn_ratelimited("leaking DMA memory that can't be re-encrypted\n");
return;
+ }
}
if (swiotlb_pool)
--
2.43.0
^ permalink raw reply related
* [PATCH v8 19/23] dma-direct: select DMA address encoding from __DMA_ATTR_ALLOC_CC_SHARED
From: Aneesh Kumar K.V (Arm) @ 2026-07-17 18:04 UTC (permalink / raw)
To: iommu, linux-arm-kernel, linux-kernel, linux-coco
Cc: Aneesh Kumar K.V (Arm), Robin Murphy, Marek Szyprowski,
Will Deacon, Marc Zyngier, Steven Price, Suzuki K Poulose,
Catalin Marinas, Jiri Pirko, Jason Gunthorpe, Mostafa Saleh,
Petr Tesarik, Alexey Kardashevskiy, Dan Williams, Xu Yilun,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Alexander Gordeev,
Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, x86, Jason Gunthorpe,
Jiri Pirko, Michael Kelley
In-Reply-To: <20260717180442.110954-1-aneesh.kumar@kernel.org>
Make the dma-direct helpers derive the DMA address encoding from
__DMA_ATTR_ALLOC_CC_SHARED instead of implicitly relying on
force_dma_unencrypted() inside phys_to_dma_direct()
Pass an explicit unencrypted/decrypted state into phys_to_dma_direct(),
make the alloc paths return DMA addresses that match the requested buffer
encryption state. Also only call dma_set_decrypted() when
__DMA_ATTR_ALLOC_CC_SHARED is actually set.
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Tested-by: Jiri Pirko <jiri@nvidia.com>
Tested-by: Michael Kelley <mhklinux@outlook.com>
Tested-by: Mostafa Saleh <smostafa@google.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
kernel/dma/direct.c | 43 ++++++++++++++++++++++++++-----------------
1 file changed, 26 insertions(+), 17 deletions(-)
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index f7f064323bd9..eb0ce0787885 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -26,11 +26,11 @@
u64 zone_dma_limit __ro_after_init = DMA_BIT_MASK(24);
static inline dma_addr_t phys_to_dma_direct(struct device *dev,
- phys_addr_t phys)
+ phys_addr_t phys, bool unencrypted)
{
- if (force_dma_unencrypted(dev))
+ if (unencrypted)
return phys_to_dma_unencrypted(dev, phys);
- return phys_to_dma(dev, phys);
+ return phys_to_dma_encrypted(dev, phys);
}
static inline struct page *dma_direct_to_page(struct device *dev,
@@ -41,8 +41,9 @@ static inline struct page *dma_direct_to_page(struct device *dev,
u64 dma_direct_get_required_mask(struct device *dev)
{
+ bool require_decrypted = force_dma_unencrypted(dev);
phys_addr_t phys = ((phys_addr_t)max_pfn << PAGE_SHIFT) - 1;
- u64 max_dma = phys_to_dma_direct(dev, phys);
+ u64 max_dma = phys_to_dma_direct(dev, phys, require_decrypted);
return (1ULL << (fls64(max_dma) - 1)) * 2 - 1;
}
@@ -71,7 +72,8 @@ static gfp_t dma_direct_optimal_gfp_mask(struct device *dev, u64 *phys_limit)
bool dma_coherent_ok(struct device *dev, phys_addr_t phys, size_t size)
{
- dma_addr_t dma_addr = phys_to_dma_direct(dev, phys);
+ bool require_decrypted = force_dma_unencrypted(dev);
+ dma_addr_t dma_addr = phys_to_dma_direct(dev, phys, require_decrypted);
if (dma_addr == DMA_MAPPING_ERROR)
return false;
@@ -81,17 +83,18 @@ bool dma_coherent_ok(struct device *dev, phys_addr_t phys, size_t size)
static int dma_set_decrypted(struct device *dev, void *vaddr, size_t size)
{
- if (!force_dma_unencrypted(dev))
- return 0;
- return set_memory_decrypted((unsigned long)vaddr, PFN_UP(size));
+ int ret;
+
+ ret = set_memory_decrypted((unsigned long)vaddr, PFN_UP(size));
+ if (ret)
+ pr_warn_ratelimited("leaking DMA memory that can't be decrypted\n");
+ return ret;
}
static int dma_set_encrypted(struct device *dev, void *vaddr, size_t size)
{
int ret;
- if (!force_dma_unencrypted(dev))
- return 0;
ret = set_memory_encrypted((unsigned long)vaddr, PFN_UP(size));
if (ret)
pr_warn_ratelimited("leaking DMA memory that can't be re-encrypted\n");
@@ -171,7 +174,8 @@ static struct page *dma_direct_alloc_from_pool(struct device *dev, size_t size,
dma_coherent_ok);
if (!page)
return NULL;
- *dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
+ *dma_handle = phys_to_dma_direct(dev, page_to_phys(page),
+ attrs & __DMA_ATTR_ALLOC_CC_SHARED);
return page;
}
@@ -187,9 +191,11 @@ static void *dma_direct_alloc_no_mapping(struct device *dev, size_t size,
/* remove any dirty cache lines on the kernel alias */
if (!PageHighMem(page))
arch_dma_prep_coherent(page, size);
-
- /* return the page pointer as the opaque cookie */
- *dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
+ /*
+ * return the page pointer as the opaque cookie.
+ * Never used for unencrypted allocation
+ */
+ *dma_handle = phys_to_dma_encrypted(dev, page_to_phys(page));
return page;
}
@@ -329,7 +335,8 @@ void *dma_direct_alloc(struct device *dev, size_t size,
ret = uncached_cpu_addr;
}
- *dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
+ *dma_handle = phys_to_dma_direct(dev, page_to_phys(page),
+ attrs & __DMA_ATTR_ALLOC_CC_SHARED);
return ret;
@@ -450,11 +457,13 @@ struct page *dma_direct_alloc_pages(struct device *dev, size_t size,
return NULL;
ret = page_address(page);
- if (dma_set_decrypted(dev, ret, size))
+ if ((attrs & __DMA_ATTR_ALLOC_CC_SHARED) &&
+ dma_set_decrypted(dev, ret, size))
goto out_leak_pages;
setup_page:
memset(ret, 0, size);
- *dma_handle = phys_to_dma_direct(dev, page_to_phys(page));
+ *dma_handle = phys_to_dma_direct(dev, page_to_phys(page),
+ attrs & __DMA_ATTR_ALLOC_CC_SHARED);
return page;
out_leak_pages:
return NULL;
--
2.43.0
^ permalink raw reply related
* [PATCH v8 20/23] dma-direct: rename ret to cpu_addr in alloc helpers
From: Aneesh Kumar K.V (Arm) @ 2026-07-17 18:04 UTC (permalink / raw)
To: iommu, linux-arm-kernel, linux-kernel, linux-coco
Cc: Aneesh Kumar K.V (Arm), Robin Murphy, Marek Szyprowski,
Will Deacon, Marc Zyngier, Steven Price, Suzuki K Poulose,
Catalin Marinas, Jiri Pirko, Jason Gunthorpe, Mostafa Saleh,
Petr Tesarik, Alexey Kardashevskiy, Dan Williams, Xu Yilun,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Alexander Gordeev,
Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, x86, Jason Gunthorpe,
Michael Kelley
In-Reply-To: <20260717180442.110954-1-aneesh.kumar@kernel.org>
ret in dma_direct_alloc() and dma_direct_alloc_pages() holds the returned
CPU mapping, not a generic return value. Rename it to cpu_addr and update
the remaining uses to match.
This makes the allocation paths easier to follow and keeps the local naming
consistent with what the variable actually represents.
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Tested-by: Michael Kelley <mhklinux@outlook.com>
Tested-by: Mostafa Saleh <smostafa@google.com>
Reviewed-by: Petr Tesarik <ptesarik@suse.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
kernel/dma/direct.c | 40 ++++++++++++++++++++--------------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index eb0ce0787885..50a8335f3830 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -206,7 +206,7 @@ void *dma_direct_alloc(struct device *dev, size_t size,
bool mark_mem_decrypt = false;
bool allow_highmem = true;
struct page *page;
- void *ret;
+ void *cpu_addr;
if (force_dma_unencrypted(dev))
attrs |= __DMA_ATTR_ALLOC_CC_SHARED;
@@ -266,9 +266,10 @@ void *dma_direct_alloc(struct device *dev, size_t size,
*/
if ((remap || (attrs & __DMA_ATTR_ALLOC_CC_SHARED)) &&
dma_direct_use_pool(dev, gfp)) {
- page = dma_direct_alloc_from_pool(dev, size, dma_handle,
- &ret, gfp, attrs);
- return page ? ret : NULL;
+ page = dma_direct_alloc_from_pool(dev, size,
+ dma_handle, &cpu_addr,
+ gfp, attrs);
+ return page ? cpu_addr : NULL;
}
if (is_swiotlb_for_alloc(dev)) {
@@ -315,34 +316,33 @@ void *dma_direct_alloc(struct device *dev, size_t size,
arch_dma_prep_coherent(page, size);
/* create a coherent mapping */
- ret = dma_common_contiguous_remap(page, size, prot,
- __builtin_return_address(0));
- if (!ret)
+ cpu_addr = dma_common_contiguous_remap(page, size, prot,
+ __builtin_return_address(0));
+ if (!cpu_addr)
goto out_encrypt_pages;
} else {
- ret = page_address(page);
+ cpu_addr = page_address(page);
}
- memset(ret, 0, size);
+ memset(cpu_addr, 0, size);
if (set_uncached) {
void *uncached_cpu_addr;
arch_dma_prep_coherent(page, size);
- uncached_cpu_addr = arch_dma_set_uncached(ret, size);
+ uncached_cpu_addr = arch_dma_set_uncached(cpu_addr, size);
if (IS_ERR(uncached_cpu_addr))
goto out_free_remap_pages;
- ret = uncached_cpu_addr;
+ cpu_addr = uncached_cpu_addr;
}
*dma_handle = phys_to_dma_direct(dev, page_to_phys(page),
attrs & __DMA_ATTR_ALLOC_CC_SHARED);
- return ret;
-
+ return cpu_addr;
out_free_remap_pages:
if (remap)
- dma_common_free_remap(ret, size);
+ dma_common_free_remap(cpu_addr, size);
out_encrypt_pages:
if (mark_mem_decrypt &&
@@ -434,21 +434,21 @@ struct page *dma_direct_alloc_pages(struct device *dev, size_t size,
{
unsigned long attrs = 0;
struct page *page;
- void *ret;
+ void *cpu_addr;
if (force_dma_unencrypted(dev))
attrs |= __DMA_ATTR_ALLOC_CC_SHARED;
if ((attrs & __DMA_ATTR_ALLOC_CC_SHARED) && dma_direct_use_pool(dev, gfp))
return dma_direct_alloc_from_pool(dev, size, dma_handle,
- &ret, gfp, attrs);
+ &cpu_addr, gfp, attrs);
if (is_swiotlb_for_alloc(dev)) {
page = dma_direct_alloc_swiotlb(dev, size, attrs);
if (!page)
return NULL;
- ret = page_address(page);
+ cpu_addr = page_address(page);
goto setup_page;
}
@@ -456,12 +456,12 @@ struct page *dma_direct_alloc_pages(struct device *dev, size_t size,
if (!page)
return NULL;
- ret = page_address(page);
+ cpu_addr = page_address(page);
if ((attrs & __DMA_ATTR_ALLOC_CC_SHARED) &&
- dma_set_decrypted(dev, ret, size))
+ dma_set_decrypted(dev, cpu_addr, size))
goto out_leak_pages;
setup_page:
- memset(ret, 0, size);
+ memset(cpu_addr, 0, size);
*dma_handle = phys_to_dma_direct(dev, page_to_phys(page),
attrs & __DMA_ATTR_ALLOC_CC_SHARED);
return page;
--
2.43.0
^ permalink raw reply related
* [PATCH v8 21/23] dma: swiotlb: free dynamic pools from process context
From: Aneesh Kumar K.V (Arm) @ 2026-07-17 18:04 UTC (permalink / raw)
To: iommu, linux-arm-kernel, linux-kernel, linux-coco
Cc: Aneesh Kumar K.V (Arm), Robin Murphy, Marek Szyprowski,
Will Deacon, Marc Zyngier, Steven Price, Suzuki K Poulose,
Catalin Marinas, Jiri Pirko, Jason Gunthorpe, Mostafa Saleh,
Petr Tesarik, Alexey Kardashevskiy, Dan Williams, Xu Yilun,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Alexander Gordeev,
Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, x86, Michael Kelley
In-Reply-To: <20260717180442.110954-1-aneesh.kumar@kernel.org>
swiotlb_dyn_free() is used after removing a dynamic swiotlb pool from
RCU-protected lists. It can call swiotlb_free_tlb(), which may need to
restore the encryption state of an unencrypted pool with
set_memory_encrypted() before freeing the pages.
RCU callbacks run in atomic context, but set_memory_encrypted() is not
guaranteed to be atomic-safe on all architectures. For example, page
attribute updates may allocate page tables or take sleeping locks.
Use queue_rcu_work() for dynamic pool freeing instead. This keeps the RCU
grace period before freeing a published pool, while running the actual pool
teardown from workqueue context. Use the same helper for the transient-pool
error path, since that path may also be reached from atomic DMA mapping
context.
Tested-by: Michael Kelley <mhklinux@outlook.com>
Tested-by: Mostafa Saleh <smostafa@google.com>
Reviewed-by: Petr Tesarik <ptesarik@suse.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
include/linux/swiotlb.h | 4 ++--
kernel/dma/swiotlb.c | 19 +++++++++++--------
2 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/include/linux/swiotlb.h b/include/linux/swiotlb.h
index ee42f7588847..c3bf7ed6f7a6 100644
--- a/include/linux/swiotlb.h
+++ b/include/linux/swiotlb.h
@@ -64,7 +64,7 @@ extern void __init swiotlb_update_mem_attributes(void);
* @areas: Array of memory area descriptors.
* @slots: Array of slot descriptors.
* @node: Member of the IO TLB memory pool list.
- * @rcu: RCU head for swiotlb_dyn_free().
+ * @dyn_free: RCU work item used to free the pool from process context.
* @transient: %true if transient memory pool.
* @cc_shared: %true if the pool memory is shared for confidential computing.
*/
@@ -80,7 +80,7 @@ struct io_tlb_pool {
struct io_tlb_slot *slots;
#ifdef CONFIG_SWIOTLB_DYNAMIC
struct list_head node;
- struct rcu_head rcu;
+ struct rcu_work dyn_free;
bool transient;
bool cc_shared;
#endif
diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index b5960c2e98d8..4d0f2c04d891 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -779,13 +779,10 @@ static void swiotlb_dyn_alloc(struct work_struct *work)
add_mem_pool(mem, pool);
}
-/**
- * swiotlb_dyn_free() - RCU callback to free a memory pool
- * @rcu: RCU head in the corresponding struct io_tlb_pool.
- */
-static void swiotlb_dyn_free(struct rcu_head *rcu)
+static void swiotlb_dyn_free_work(struct work_struct *work)
{
- struct io_tlb_pool *pool = container_of(rcu, struct io_tlb_pool, rcu);
+ struct io_tlb_pool *pool =
+ container_of(to_rcu_work(work), struct io_tlb_pool, dyn_free);
size_t slots_size = array_size(sizeof(*pool->slots), pool->nslabs);
size_t tlb_size = pool->end - pool->start;
@@ -794,6 +791,12 @@ static void swiotlb_dyn_free(struct rcu_head *rcu)
kfree(pool);
}
+static void swiotlb_schedule_dyn_free(struct io_tlb_pool *pool)
+{
+ INIT_RCU_WORK(&pool->dyn_free, swiotlb_dyn_free_work);
+ queue_rcu_work(system_wq, &pool->dyn_free);
+}
+
/**
* __swiotlb_find_pool() - find the IO TLB pool for a physical address
* @dev: Device which has mapped the DMA buffer.
@@ -840,7 +843,7 @@ static void swiotlb_del_pool(struct device *dev, struct io_tlb_pool *pool)
list_del_rcu(&pool->node);
spin_unlock_irqrestore(&dev->dma_io_tlb_lock, flags);
- call_rcu(&pool->rcu, swiotlb_dyn_free);
+ swiotlb_schedule_dyn_free(pool);
}
#endif /* CONFIG_SWIOTLB_DYNAMIC */
@@ -1281,7 +1284,7 @@ static int swiotlb_find_slots(struct device *dev, phys_addr_t orig_addr,
index = swiotlb_search_pool_area(dev, pool, 0, orig_addr, tbl_dma_addr,
alloc_size, alloc_align_mask);
if (index < 0) {
- swiotlb_dyn_free(&pool->rcu);
+ swiotlb_schedule_dyn_free(pool);
return -1;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v8 22/23] dma: swiotlb: handle set_memory_decrypted() failures
From: Aneesh Kumar K.V (Arm) @ 2026-07-17 18:04 UTC (permalink / raw)
To: iommu, linux-arm-kernel, linux-kernel, linux-coco
Cc: Aneesh Kumar K.V (Arm), Robin Murphy, Marek Szyprowski,
Will Deacon, Marc Zyngier, Steven Price, Suzuki K Poulose,
Catalin Marinas, Jiri Pirko, Jason Gunthorpe, Mostafa Saleh,
Petr Tesarik, Alexey Kardashevskiy, Dan Williams, Xu Yilun,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Alexander Gordeev,
Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, x86, Jason Gunthorpe,
Michael Kelley
In-Reply-To: <20260717180442.110954-1-aneesh.kumar@kernel.org>
Check the return value when converting swiotlb pools between encrypted and
decrypted mappings. If the default pool cannot be decrypted after early
initialization, mark the pool fully used so it cannot satisfy future bounce
allocations.
For late initialization, return the `set_memory_decrypted()` failure. For
restricted DMA pools, fail device initialization if the reserved pool
cannot be decrypted.
This prevents swiotlb from using pools whose encryption attributes do not
match their metadata, and avoids returning pages with uncertain encryption
state back to the allocator.
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Tested-by: Michael Kelley <mhklinux@outlook.com>
Tested-by: Mostafa Saleh <smostafa@google.com>
Reviewed-by: Petr Tesarik <ptesarik@suse.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
kernel/dma/swiotlb.c | 80 +++++++++++++++++++++++++++++++++++---------
1 file changed, 65 insertions(+), 15 deletions(-)
diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index 4d0f2c04d891..8b7e47504304 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -248,6 +248,23 @@ static inline unsigned long nr_slots(u64 val)
return DIV_ROUND_UP(val, IO_TLB_SIZE);
}
+static void swiotlb_mark_pool_used(struct io_tlb_pool *pool)
+{
+ unsigned long i;
+
+ for (i = 0; i < pool->nareas; i++) {
+ pool->areas[i].index = 0;
+ pool->areas[i].used = pool->area_nslabs;
+ }
+
+ for (i = 0; i < pool->nslabs; i++) {
+ pool->slots[i].list = 0;
+ pool->slots[i].orig_addr = INVALID_PHYS_ADDR;
+ pool->slots[i].alloc_size = 0;
+ pool->slots[i].pad_slots = 0;
+ }
+}
+
/*
* Early SWIOTLB allocation may be too early to allow an architecture to
* perform the desired operations. This function allows the architecture to
@@ -272,8 +289,16 @@ void __init swiotlb_update_mem_attributes(void)
return;
bytes = PAGE_ALIGN(mem->nslabs << IO_TLB_SHIFT);
- if (io_tlb_default_mem.cc_shared)
- set_memory_decrypted((unsigned long)mem->vaddr, bytes >> PAGE_SHIFT);
+ if (io_tlb_default_mem.cc_shared) {
+ int ret;
+
+ ret = set_memory_decrypted((unsigned long)mem->vaddr,
+ bytes >> PAGE_SHIFT);
+ if (ret) {
+ pr_warn("Failed to decrypt default memory pool, disabling it\n");
+ swiotlb_mark_pool_used(mem);
+ }
+ }
}
static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,
@@ -442,9 +467,10 @@ int swiotlb_init_late(size_t size, gfp_t gfp_mask,
{
struct io_tlb_pool *mem = &io_tlb_default_mem.defpool;
unsigned long nslabs = ALIGN(size >> IO_TLB_SHIFT, IO_TLB_SEGSIZE);
+ unsigned int order, area_order, slot_order;
+ bool leak_pages = false;
unsigned int nareas;
unsigned char *vstart = NULL;
- unsigned int order, area_order;
bool retried = false;
int rc = 0;
@@ -504,6 +530,7 @@ int swiotlb_init_late(size_t size, gfp_t gfp_mask,
(PAGE_SIZE << order) >> 20);
}
+ rc = -ENOMEM;
nareas = limit_nareas(default_nareas, nslabs);
area_order = get_order(array_size(sizeof(*mem->areas), nareas));
mem->areas = (struct io_tlb_area *)
@@ -511,14 +538,20 @@ int swiotlb_init_late(size_t size, gfp_t gfp_mask,
if (!mem->areas)
goto error_area;
+ slot_order = get_order(array_size(sizeof(*mem->slots), nslabs));
mem->slots = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
- get_order(array_size(sizeof(*mem->slots), nslabs)));
+ slot_order);
if (!mem->slots)
goto error_slots;
- if (io_tlb_default_mem.cc_shared)
- set_memory_decrypted((unsigned long)vstart,
- (nslabs << IO_TLB_SHIFT) >> PAGE_SHIFT);
+ if (io_tlb_default_mem.cc_shared) {
+ rc = set_memory_decrypted((unsigned long)vstart,
+ (nslabs << IO_TLB_SHIFT) >> PAGE_SHIFT);
+ if (rc) {
+ leak_pages = true;
+ goto error_decrypt;
+ }
+ }
swiotlb_init_io_tlb_pool(mem, virt_to_phys(vstart), vstart, nslabs, true,
nareas);
@@ -527,16 +560,20 @@ int swiotlb_init_late(size_t size, gfp_t gfp_mask,
swiotlb_print_info();
return 0;
+error_decrypt:
+ free_pages((unsigned long)mem->slots, slot_order);
error_slots:
free_pages((unsigned long)mem->areas, area_order);
error_area:
- free_pages((unsigned long)vstart, order);
- return -ENOMEM;
+ if (!leak_pages)
+ free_pages((unsigned long)vstart, order);
+ return rc;
}
void __init swiotlb_exit(void)
{
struct io_tlb_pool *mem = &io_tlb_default_mem.defpool;
+ bool leak_pages = false;
unsigned long tbl_vaddr;
size_t tbl_size, slots_size;
unsigned int area_order;
@@ -552,19 +589,23 @@ void __init swiotlb_exit(void)
tbl_size = PAGE_ALIGN(mem->end - mem->start);
slots_size = PAGE_ALIGN(array_size(sizeof(*mem->slots), mem->nslabs));
- if (io_tlb_default_mem.cc_shared)
- set_memory_encrypted(tbl_vaddr, tbl_size >> PAGE_SHIFT);
+ if (io_tlb_default_mem.cc_shared) {
+ if (set_memory_encrypted(tbl_vaddr, tbl_size >> PAGE_SHIFT))
+ leak_pages = true;
+ }
if (mem->late_alloc) {
area_order = get_order(array_size(sizeof(*mem->areas),
mem->nareas));
free_pages((unsigned long)mem->areas, area_order);
- free_pages(tbl_vaddr, get_order(tbl_size));
+ if (!leak_pages)
+ free_pages(tbl_vaddr, get_order(tbl_size));
free_pages((unsigned long)mem->slots, get_order(slots_size));
} else {
memblock_free(mem->areas,
array_size(sizeof(*mem->areas), mem->nareas));
- memblock_phys_free(mem->start, tbl_size);
+ if (!leak_pages)
+ memblock_phys_free(mem->start, tbl_size);
memblock_free(mem->slots, slots_size);
}
@@ -1957,9 +1998,18 @@ static int rmem_swiotlb_device_init(struct reserved_mem *rmem,
* restricted mem pool is shared by default
*/
if (cc_platform_has(CC_ATTR_MEM_ENCRYPT)) {
+ int ret;
+
mem->cc_shared = true;
- set_memory_decrypted((unsigned long)phys_to_virt(rmem->base),
- rmem->size >> PAGE_SHIFT);
+ ret = set_memory_decrypted((unsigned long)phys_to_virt(rmem->base),
+ rmem->size >> PAGE_SHIFT);
+ if (ret) {
+ dev_err(dev, "Failed to decrypt restricted DMA pool\n");
+ kfree(pool->areas);
+ kfree(pool->slots);
+ kfree(mem);
+ return ret;
+ }
} else {
mem->cc_shared = false;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v8 23/23] swiotlb: remove unused SWIOTLB_FORCE flag
From: Aneesh Kumar K.V (Arm) @ 2026-07-17 18:04 UTC (permalink / raw)
To: iommu, linux-arm-kernel, linux-kernel, linux-coco
Cc: Aneesh Kumar K.V (Arm), Robin Murphy, Marek Szyprowski,
Will Deacon, Marc Zyngier, Steven Price, Suzuki K Poulose,
Catalin Marinas, Jiri Pirko, Jason Gunthorpe, Mostafa Saleh,
Petr Tesarik, Alexey Kardashevskiy, Dan Williams, Xu Yilun,
linuxppc-dev, linux-s390, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Alexander Gordeev,
Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, x86, Jason Gunthorpe
In-Reply-To: <20260717180442.110954-1-aneesh.kumar@kernel.org>
SWIOTLB_FORCE has no remaining in-tree users. Forced bouncing is now
controlled through the swiotlb=force command line option via
swiotlb_force_bounce.
Remove the unused flag and simplify the force_bounce initialization.
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
include/linux/swiotlb.h | 3 +--
kernel/dma/swiotlb.c | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/include/linux/swiotlb.h b/include/linux/swiotlb.h
index c3bf7ed6f7a6..9caca923c380 100644
--- a/include/linux/swiotlb.h
+++ b/include/linux/swiotlb.h
@@ -15,8 +15,7 @@ struct page;
struct scatterlist;
#define SWIOTLB_VERBOSE (1 << 0) /* verbose initialization */
-#define SWIOTLB_FORCE (1 << 1) /* force bounce buffering */
-#define SWIOTLB_ANY (1 << 2) /* allow any memory for the buffer */
+#define SWIOTLB_ANY (1 << 1) /* allow any memory for the buffer */
/*
* Maximum allowable number of contiguous slabs to map,
diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index 8b7e47504304..897aba538c5b 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -400,8 +400,7 @@ void __init swiotlb_init_remap(bool addressing_limit, unsigned int flags,
if (swiotlb_force_disable)
return;
- io_tlb_default_mem.force_bounce =
- swiotlb_force_bounce || (flags & SWIOTLB_FORCE);
+ io_tlb_default_mem.force_bounce = swiotlb_force_bounce;
#ifdef CONFIG_SWIOTLB_DYNAMIC
if (!remap)
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v3 3/7] gpio: regmap: Add gpio_regmap_operation and write-enable support
From: Andy Shevchenko @ 2026-07-17 18:11 UTC (permalink / raw)
To: Yu-Chun Lin [林祐君]
Cc: Michael Walle, Michael.Hennerich@analog.com, afaerber@suse.com,
andy@kernel.org, brgl@kernel.org, conor+dt@kernel.org,
CY_Huang[黃鉦晏], devicetree@vger.kernel.org,
dlechner@baylibre.com, James Tai [戴志峰],
jic23@kernel.org, krzk+dt@kernel.org, lars@metafoo.de,
linus.walleij@linaro.org, linusw@kernel.org,
linux-arm-kernel@lists.infradead.org, linux-gpio@vger.kernel.org,
linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-realtek-soc@lists.infradead.org,
mathieu.dubois-briand@bootlin.com, nuno.sa@analog.com,
robh@kernel.org, Stanley Chang[昌育德],
TY_Chang[張子逸], wbg@kernel.org
In-Reply-To: <82f3f73764ac4553a8e2743bbebd90e8@realtek.com>
On Fri, Jul 17, 2026 at 03:38:35PM +0000, Yu-Chun Lin [林祐君] wrote:
> > On Thu, Jul 16, 2026 at 12:55:37PM +0200, Michael Walle wrote:
> > > On Thu Jul 16, 2026 at 11:40 AM CEST, Andy Shevchenko wrote:
> > > > On Thu, Jul 16, 2026 at 11:08:55AM +0200, Michael Walle wrote:
> > > >> On Thu Jul 16, 2026 at 10:27 AM CEST, Andy Shevchenko wrote:
> > > >> > On Thu, Jul 16, 2026 at 02:26:14PM +0800, Yu-Chun Lin wrote:
...
> > > >> > From the above list I tend to the approach 2, but this might
> > > >> > require to have GPIO regmap level of locking. I'm a bit lost in
> > > >> > the context, though. I assume we need a fresh start, id est issue
> > > >> > a v6 with approach 2 or 3 in place and summarize the choices in
> > > >> > the cover letter, so we can understand what has been considered.
> > > >>
> > > >> I don't really like approach 3. You'd need to check if the regs of
> > > >> both xlate calls are the same. With the sample code above, you
> > > >> silently drop the first xlate'd reg.
> > > >
> > > > If I rank the proposals, the worst is #1, the best is #2.
> > > >
> > > >> And honestly, it really seems like a one-off. What controllers, are
> > > >> there that need a write enable bit. The real problem seems to be
> > > >> the assumption that we operate on just one bit. IOW we either set
> > > >> mask or don't set mask in gpio_regmap_set().
> > > >
> > > > Yes, we should KISS.
> > >
> > > But IMHO #2 and #3 are not KISS. Approach 2 is just a way of adding
> > > some kind of pre op to a gpio set. Just tying it to a write enable
> > > feature. That kinda bothers me. It might also be useful for other
> > > things, too. So don't tie it to just write enable. And who is doing a
> > > write disable if it's not self clearing for example. Probably Some
> > > kind of post op :)
> > >
> > > Approach 3 is a way to change the value of the written value - in a
> > > restricted way, as is is just doing a OR with both values.
> > >
> > > Also approach 2 might not even work if the hardware requires the write
> > > enable bit set in the *same* write as the gpio set bit. Thus, we might
> > > need both anyway in the future.
> > >
> > > >> For a more generic solution, we should be able to control the
> > > >> written value. We could add another .value_xlate().
> > > >
> > > > Maybe not now?
> > >
> > > But if not now, then when? I wouldn't add the write enable feature and
> > > later a more generic solution which also covers the write enable
> > > feature.
> >
> > Taking into account how it's done in HW, I think the whole approach can be
> > folded to just a boolean flag (or a simply bit shift) in the config.
> > Wouldn't it work?
>
> To respect Michael's perspective on keeping WREN-specific code out of the
> framework, I think adding a value_xlate callback in the config is a cleaner
> approach. It allows us to wrap this hardware quirk within the callback and let
> the consumer driver handle it entirely.
>
> To ensure it is truly generic, I will not introduce any WREN-specific operation
> flags. Instead, value_xlate will reuse the gpio_regmap_operation enum
> (e.g., GPIO_REGMAP_OP_SET, GPIO_REGMAP_OP_SET_DIR).
OK! But don't forget add a good documentation that users will understand
the logic behind it.
> > > > As per IPs, Synopsys IPs (not exactly GPIO) likes to have that kind
> > > > of "protection". So, from HW perspective it's kinda pattern, and it
> > > > might be possible to see more IPs (including GPIO) that follow it in
> > > > some cases.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [RFC v3 0/2] rm64: kprobes: Fix single-step fault and reentry handling
From: Will Deacon @ 2026-07-17 18:23 UTC (permalink / raw)
To: mhiramat, Pu Hu
Cc: Hongyan Xia, Will Deacon, Jiazi Li, catalin.marinas, linux-kernel,
naveen, yang, kernel-team, davem, linux-arm-kernel,
linux-trace-kernel
In-Reply-To: <20260710063242.228714-1-hupu@transsion.com>
On Fri, 10 Jul 2026 06:32:52 +0000, Pu Hu wrote:
> This series fixes two arm64 kprobes issues observed when running
> simpleperf with preemptirq tracepoints and dwarf callchains while a
> kprobe is active on a frequently executed kernel function.
>
> The crash happens in the kprobe debug exception path. While a kprobe is
> preparing or executing its XOL single-step instruction, perf/trace code
> can run in the same window. That code may either take a fault of its own
> or hit another kprobe.
>
> [...]
Applied to arm64 (for-next/fixes), thanks!
[1/2] arm64: kprobes: Only handle faults originating from XOL slot
https://git.kernel.org/arm64/c/879a6754d3d1
[2/2] arm64: kprobes: Allow reentering kprobes while single-stepping
https://git.kernel.org/arm64/c/23f851ac0078
Cheers,
--
Will
https://fixes.arm64.dev
https://next.arm64.dev
https://will.arm64.dev
^ permalink raw reply
* Re: [PATCH 0/3] arm64/coco: Convert pKVM to a CC platform
From: Will Deacon @ 2026-07-17 18:23 UTC (permalink / raw)
To: linux-arm-kernel, linux-kernel, Mostafa Saleh
Cc: catalin.marinas, kernel-team, Will Deacon, akpm, rppt, maz,
aneesh.kumar
In-Reply-To: <20260603110522.3331819-1-smostafa@google.com>
On Wed, 03 Jun 2026 11:05:19 +0000, Mostafa Saleh wrote:
> The goal of series is to convert pKVM as a CC platform similar to
> ARM CCA, that makes CC guests consistently discovered which avoid
> breaking it by the ongoing DMA rework [1]
>
> The first patch is a minor clean up I noticed while reading the code.
>
> And the second patch is a bug fix found by Sashiko.
>
> [...]
Applied second patch to arm64 (for-next/fixes), thanks!
[2/3] drivers/virt: pkvm: Fix end calculation in mmio_guard_ioremap_hook()
https://git.kernel.org/arm64/c/62c740f823a8
Cheers,
--
Will
https://fixes.arm64.dev
https://next.arm64.dev
https://will.arm64.dev
^ permalink raw reply
* [PATCH v3] arm64: ptrace: Keep 'orig_x0' in-sync with x0 on syscall entry
From: Will Deacon @ 2026-07-17 18:27 UTC (permalink / raw)
To: linux-arm-kernel
Cc: linux-kernel, Will Deacon, Kees Cook, Jinjie Ruan, Mark Rutland,
Yiqi Sun
Commit e057b9477232 ("arm64: syscall: Ensure saved x0 is kept in-sync
with tracer updates") attempted to resolve a long-standing issue with
syscall entry tracing, where a tracer is able to manipulate the first
syscall argument without being subjected to seccomp or audit checking.
Unfortunately, that fix was incomplete [1], as it failed to update
'orig_x0' between a tracer updating x0 during a seccomp ptrace exit
(SECCOMP_RET_TRACE) and the seccomp filter being re-evaluated.
Rather than add hooks to the core seccomp code, instead move the
synchronisation code into the ptrace GPR and syscall setting code so
that 'orig_x0' is kept up to date with x0 whenever we're stopped on the
syscall entry path.
Cc: Kees Cook <kees@kernel.org>
Cc: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Link: https://sashiko.dev/#/patchset/20260716120640.6590-1-will@kernel.org [1]
Reported-by: Yiqi Sun <sunyiqixm@gmail.com>
Link: https://lore.kernel.org/all/20260529065444.1336608-1-sunyiqixm@gmail.com/
Fixes: e057b9477232 ("arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates")
Fixes: a5cd110cb836 ("arm64/ptrace: run seccomp after ptrace")
Signed-off-by: Will Deacon <will@kernel.org>
---
arch/arm64/kernel/ptrace.c | 50 ++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
v2: https://lore.kernel.org/r/20260716120640.6590-1-will@kernel.org
diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
index 4d08598e2891..2b716fa11c02 100644
--- a/arch/arm64/kernel/ptrace.c
+++ b/arch/arm64/kernel/ptrace.c
@@ -560,6 +560,42 @@ static int gpr_get(struct task_struct *target,
return membuf_write(&to, uregs, sizeof(*uregs));
}
+static void update_syscall_orig_x0_after_ptrace(struct task_struct *target)
+{
+ struct pt_regs *regs = task_pt_regs(target);
+ struct kernel_siginfo *info = target->last_siginfo;
+
+ /* We should only be called when target is in a ptrace stop */
+ if (WARN_ON_ONCE(!info))
+ return;
+
+ /*
+ * Skip the update for NO_SYSCALL (set either by the user or the
+ * tracer), as regs[0] holds the return value (see the comment in
+ * el0_svc_common()) and can be unwound using syscall_rollback().
+ */
+ if (regs->syscallno == NO_SYSCALL)
+ return;
+
+ /*
+ * For compat tasks, orig_r0 is provided directly through GPR index
+ * 17.
+ */
+ if (is_compat_thread(task_thread_info(target)))
+ return;
+
+ /*
+ * Don't update orig_x0 for a syscall-exit-stop, as x0 now contains the
+ * return value of the system call.
+ */
+ if ((info->si_code & ~0x80) == SIGTRAP &&
+ target->ptrace_message == PTRACE_EVENTMSG_SYSCALL_EXIT) {
+ return;
+ }
+
+ regs->orig_x0 = regs->regs[0];
+}
+
static int gpr_set(struct task_struct *target, const struct user_regset *regset,
unsigned int pos, unsigned int count,
const void *kbuf, const void __user *ubuf)
@@ -575,6 +611,14 @@ static int gpr_set(struct task_struct *target, const struct user_regset *regset,
return -EINVAL;
task_pt_regs(target)->user_regs = newregs;
+
+ /*
+ * Keep orig_x0 authoritative so that seccomp (via
+ * syscall_get_arguments()), audit and the restart path all see the same
+ * first argument the syscall is dispatched with, even if it has been
+ * updated by a tracer.
+ */
+ update_syscall_orig_x0_after_ptrace(target);
return 0;
}
@@ -753,6 +797,12 @@ static int system_call_set(struct task_struct *target,
return ret;
task_pt_regs(target)->syscallno = syscallno;
+
+ /*
+ * Re-sync orig_x0 in case the syscall number has been changed
+ * from NO_SYSCALL.
+ */
+ update_syscall_orig_x0_after_ptrace(target);
return ret;
}
--
2.55.0.229.g6434b31f56-goog
^ permalink raw reply related
* Re: [PATCH v5 2/4] arm64: vdso: Implement __vdso_futex_robust_try_unlock()
From: Mark Rutland @ 2026-07-17 18:31 UTC (permalink / raw)
To: André Almeida
Cc: Catalin Marinas, Will Deacon, Thomas Gleixner, Mathieu Desnoyers,
Sebastian Andrzej Siewior, Carlos O'Donell, Peter Zijlstra,
Florian Weimer, Rich Felker, Torvald Riegel, Darren Hart,
Ingo Molnar, Davidlohr Bueso, Arnd Bergmann, Uros Bizjak,
Thomas Weißschuh, Liam R. Howlett, linux-arm-kernel,
linux-kernel, linux-arch, kernel-dev
In-Reply-To: <20260717-tonyk-robust_arm-v5-2-ffd1ad318d17@igalia.com>
On Fri, Jul 17, 2026 at 11:41:41AM -0300, André Almeida wrote:
> Based on the x86 implementation, implement the vDSO function for unlocking
> a robust futex correctly.
Hi Andre,
As mentioned on the prior patch, it would be easier to review this if
all of the compat VDSO additions were in this patch, rather than half of
those being in the prior patch. I left some suggestions there as to how
to split that.
> Commit a2274cc0091e ("x86/vdso: Implement __vdso_futex_robust_try_unlock()")
> has the full explanation about why this mechanism is needed.
Can we please have a short explanation here? IIUC there's a race where
one thread has a dangling pending op for a VA, but another thread has
re-allocated that VA, and the kernel can corrupt the memory at that VA.
TBH, from reading a2274cc0091e I'm struggling to follow the race, and
how this mechanism helps, but that might just be due to my brain not
working at the end of the week.
> The unlock assembly sequence for arm64 is:
>
> __vdso_futex_robust_list64_try_unlock:
> retry:
> ldxr w8, [x0] // Load the value from *futex
> cmp w1, w8 // Compare with TID
> b.ne __vdso_futex_list64_try_unlock_cs_end
> stlxr w3, wzr, [x0] // Try to zero *futex
> __vdso_futex_list64_try_unlock_cs_start:
> cbnz w3, retry
> str xzr, [x2] // After zeroing *futex, zero *op_pending
> __vdso_futex_list64_try_unlock_cs_end>:
>
> The decision regarding if the pointer should be cleared or not lies on
> checking the w3 register:
>
> return (regs->user_regs[3]) ? NULL : (void __user *)
> regs->user_regs.regs[2];
I don't think the description of the assembly helps without a complete
description of the problem, and it'd be best to just remove the assembly
from the commit message, and just describe what the kernel does
depending on whether the userspace cmpxchg (using LDXR + STLXR) succeded
or failed.
> If it's zero, that means that the exclusive store worked and the kernel
> should clear op_pending (if userspace didn't managed to) stored at x2.
>
> Signed-off-by: André Almeida <andrealmeid@igalia.com>
> ---
> Notes:
> - Only LL/SC for now but I can add LSE later if this looks good
>
> v4:
> - Guard makefile for vfutex.o with ifdef
> - Moved _start label one instruction above
> - Use results register (w3) to check for store success instead of using zero
> flag
>
> v3:
> - Managed to get pop to always be stored at x2
> ---
> arch/arm64/Kconfig | 1 +
> arch/arm64/include/asm/futex_robust.h | 19 +++++++++++++++++++
> arch/arm64/kernel/vdso/Makefile | 10 ++++++++++
> arch/arm64/kernel/vdso/vfutex.c | 35 +++++++++++++++++++++++++++++++++++
> 4 files changed, 65 insertions(+)
>
> diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
> index b3afe0688919..0582172811d9 100644
> --- a/arch/arm64/Kconfig
> +++ b/arch/arm64/Kconfig
> @@ -221,6 +221,7 @@ config ARM64
> select HAVE_RELIABLE_STACKTRACE
> select HAVE_POSIX_CPU_TIMERS_TASK_WORK
> select HAVE_FUNCTION_ARG_ACCESS_API
> + select HAVE_FUTEX_ROBUST_UNLOCK
> select MMU_GATHER_RCU_TABLE_FREE
> select HAVE_RSEQ
> select HAVE_RUST if RUSTC_SUPPORTS_ARM64
> diff --git a/arch/arm64/include/asm/futex_robust.h b/arch/arm64/include/asm/futex_robust.h
> new file mode 100644
> index 000000000000..64f22166756a
> --- /dev/null
> +++ b/arch/arm64/include/asm/futex_robust.h
> @@ -0,0 +1,19 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _ASM_ARM64_FUTEX_ROBUST_H
> +#define _ASM_ARM64_FUTEX_ROBUST_H
> +
> +#include <asm/ptrace.h>
> +
> +static __always_inline void __user *arm64_futex_robust_unlock_get_pop(struct pt_regs *regs)
> +{
> + /*
> + * w3 is stores the result of the stlxr instruction. If it's zero, the then
> + * the ll/sc cmpxchg succeeded and the pending op pointer needs to be cleared.
> + */
> + return (regs->user_regs.regs[3]) ? NULL : (void __user *) regs->user_regs.regs[2];
> +}
> +
> +#define arch_futex_robust_unlock_get_pop(regs) \
> + arm64_futex_robust_unlock_get_pop(regs)
> +
> +#endif /* _ASM_ARM64_FUTEX_ROBUST_H */
> diff --git a/arch/arm64/kernel/vdso/Makefile b/arch/arm64/kernel/vdso/Makefile
> index 7dec05dd33b7..985346c7a0bb 100644
> --- a/arch/arm64/kernel/vdso/Makefile
> +++ b/arch/arm64/kernel/vdso/Makefile
> @@ -11,6 +11,10 @@ include $(srctree)/lib/vdso/Makefile.include
>
> obj-vdso := vgettimeofday.o note.o sigreturn.o vgetrandom.o vgetrandom-chacha.o
>
> +ifdef CONFIG_FUTEX_ROBUST_UNLOCK
> + obj-vdso += vfutex.o
> +endif
> +
> # Build rules
> targets := $(obj-vdso) vdso.so vdso.so.dbg
> obj-vdso := $(addprefix $(obj)/, $(obj-vdso))
> @@ -45,9 +49,11 @@ CC_FLAGS_ADD_VDSO := -O2 -mcmodel=tiny -fasynchronous-unwind-tables
>
> CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_REMOVE_VDSO)
> CFLAGS_REMOVE_vgetrandom.o = $(CC_FLAGS_REMOVE_VDSO)
> +CFLAGS_REMOVE_vfutex.o = $(CC_FLAGS_REMOVE_VDSO)
>
> CFLAGS_vgettimeofday.o = $(CC_FLAGS_ADD_VDSO)
> CFLAGS_vgetrandom.o = $(CC_FLAGS_ADD_VDSO)
> +CFLAGS_vfutex.o = $(CC_FLAGS_ADD_VDSO)
>
> ifneq ($(c-gettimeofday-y),)
> CFLAGS_vgettimeofday.o += -include $(c-gettimeofday-y)
> @@ -57,6 +63,10 @@ ifneq ($(c-getrandom-y),)
> CFLAGS_vgetrandom.o += -include $(c-getrandom-y)
> endif
>
> +ifneq ($(c-futex-y),)
> + CFLAGS_vfutex.o += -include $(c-futex-y)
> +endif
> +
> targets += vdso.lds
> CPPFLAGS_vdso.lds += -P -C -U$(ARCH)
>
> diff --git a/arch/arm64/kernel/vdso/vfutex.c b/arch/arm64/kernel/vdso/vfutex.c
> new file mode 100644
> index 000000000000..4c69d92426fd
> --- /dev/null
> +++ b/arch/arm64/kernel/vdso/vfutex.c
> @@ -0,0 +1,35 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +#include <linux/stringify.h>
> +#include <vdso/futex.h>
> +
> +#define LABEL(name, sz) __stringify(__futex_list##sz##_try_unlock_cs_##name)
> +
> +#define GLOBLS(sz) ".globl " LABEL(start, sz) ", " LABEL(success, sz) ", " LABEL(end, sz) "\n"
Since we don't share the assembly between native and compat, it would be
clearer to have these inline within the assembly block, and without the
'sz' parameter.
Using the full names would make this easier to grep for.
Mark.
> +
> +__u32 __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop)
> +{
> + register __u64 *pop_reg asm("x2") = pop;
> + register __u32 result_reg asm("w3") = 0;
> + __u32 val;
> +
> + asm volatile (
> + GLOBLS(64)
> + " prfm pstl1strm, %[lock] \n"
> + "retry: \n"
> + " ldxr %w[val], %[lock] \n"
> + " cmp %w[tid], %w[val] \n"
> + " bne " LABEL(end, 64)" \n"
> + " stlxr %w[result], wzr, %[lock] \n"
> + LABEL(start, 64)": \n"
> + " cbnz %w[result], retry \n"
> + LABEL(success, 64)": \n"
> + " str xzr, %[pop_reg] \n"
> + LABEL(end, 64)": \n"
> +
> + : [val] "=&r" (val), [result] "=&r" (result_reg)
> + : [tid] "r" (tid), [lock] "Q" (*lock), [pop_reg] "Q" (*pop_reg)
> + : "cc", "memory"
> + );
> +
> + return val;
> +}
>
> --
> 2.55.0
>
^ permalink raw reply
* Re: [PATCH 1/2] dt-bindings: soc: imx: Add fsl,eim-bus
From: Rob Herring (Arm) @ 2026-07-17 18:43 UTC (permalink / raw)
To: Frank.Li
Cc: Krzysztof Kozlowski, Sascha Hauer, Pengutronix Kernel Team,
Shawn Guo, Frank Li, devicetree, linux-arm-kernel, Fabio Estevam,
Conor Dooley, imx, linux-kernel
In-Reply-To: <20260708-imx53-eim-v1-1-913b4559e5b5@nxp.com>
On Wed, 08 Jul 2026 16:00:44 -0400, Frank.Li@oss.nxp.com wrote:
> From: Frank Li <Frank.Li@nxp.com>
>
> Add the fsl,eim-bus compatible strings for i.MX51 variants.
>
> These compatibles are only intended for existing legacy chips (more than 15
> years old) and will not be used for new device trees.
>
> Fix below CHECK_DTBS warnings
> arch/arm/boot/dts/nxp/imx/imx53-ard.dtb: /eim-cs1@f4000000: failed to match any schema with compatible: ['fsl,eim-bus', 'simple-bus']
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> Documentation/devicetree/bindings/bus/fsl,spba-bus.yaml | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply
* Re: [PATCH 1/4] dt-bindings: display: bridge: ldb: allow a single reg for fsl,imx6sx-ldb
From: Rob Herring (Arm) @ 2026-07-17 18:44 UTC (permalink / raw)
To: Frank.Li
Cc: linux-kernel, Maarten Lankhorst, Fabio Estevam, dri-devel,
Stefan Agner, Krzysztof Kozlowski, David Airlie, Frank Li,
Pengutronix Kernel Team, imx, devicetree, Maxime Ripard,
Thomas Zimmermann, Peng Fan, linux-arm-kernel, Sascha Hauer,
Marek Vasut, Conor Dooley, Simona Vetter
In-Reply-To: <20260708-arm_dts_ldb-v1-1-4f5579b85797@nxp.com>
On Wed, 08 Jul 2026 16:29:02 -0400, Frank.Li@oss.nxp.com wrote:
> From: Frank Li <Frank.Li@nxp.com>
>
> The i.MX6SX LDB only provides a single register region for the LDB block,
> while other supported LDB variants require two register regions.
>
> Update the binding schema to allow a single reg entry for
> fsl,imx6sx-ldb while keeping the existing constraints unchanged for the
> other compatible strings.
>
> Fix below DTB_CHECK warings:
> arch/arm/boot/dts/nxp/imx/imx6sx-nitrogen6sx.dtb: bridge@18 (fsl,imx6sx-ldb): reg: [[24, 4]] is too short
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> Related two theads:
> - https://lore.kernel.org/imx/178154922879.1630652.11500293336634076421.robh@kernel.org/
>
> Mixing the addressable and non-addressable child nodes is allowed according to discussion [1].
>
> Link: https://lore.kernel.org/all/n6akxiayi3g6gxcqhreb4iaohmeokoalnqup6h5r2fwdt4zijt@u2wyps55ayqm/ [1]
> ---
> .../bindings/display/bridge/fsl,ldb.yaml | 23 +++++++++++++---------
> 1 file changed, 14 insertions(+), 9 deletions(-)
>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply
* Re: [PATCH 2/4] dt-bindings: soc: imx-iomuxc-gpr: allow bridge@18 as child node
From: Rob Herring @ 2026-07-17 18:45 UTC (permalink / raw)
To: Frank.Li
Cc: Krzysztof Kozlowski, Conor Dooley, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Peng Fan, Marek Vasut,
Stefan Agner, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, devicetree, imx, linux-arm-kernel,
linux-kernel, dri-devel, Frank Li
In-Reply-To: <20260708-arm_dts_ldb-v1-2-4f5579b85797@nxp.com>
On Wed, Jul 08, 2026 at 04:29:03PM -0400, Frank.Li@oss.nxp.com wrote:
> From: Frank Li <Frank.Li@nxp.com>
>
> The legacy i.MX6SX (>15 year) SoC imx-iomuxc-gpr contains one LDB_CTRL
> register. Allow the LVDS Display Bridge(LDB) child node under
> imx-iomuxc-gpr.
>
> Fix below CHECK_DTBS warnings:
> arch/arm/boot/dts/nxp/imx/imx6sx-nitrogen6sx.dtb: syscon@20e4000 (fsl,imx6sx-iomuxc-gpr): '#address-cells', '#size-cells', 'bridge@18' do not match any of the regexes: '^ipu[12]_csi[01]_mux$', '^pinctrl-[0-9]+$
>
> Signed-off-by: Frank Li <Frank.Li@nxp.com>
> ---
> .../bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml | 59 ++++++++++++++++++++++
> 1 file changed, 59 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml b/Documentation/devicetree/bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml
> index 721a67e84c137..c3b942bbcd6da 100644
> --- a/Documentation/devicetree/bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml
> +++ b/Documentation/devicetree/bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml
> @@ -47,10 +47,21 @@ properties:
> reg:
> maxItems: 1
>
> + '#address-cells':
> + const: 1
> +
> + '#size-cells':
> + const: 1
> +
> mux-controller:
> type: object
> $ref: /schemas/mux/reg-mux.yaml
>
> + bridge@18:
> + type: object
> + $ref: /schemas/display/bridge/fsl,ldb.yaml#
> + unevaluatedProperties: false
> +
> patternProperties:
> "^ipu[12]_csi[01]_mux$":
> type: object
> @@ -67,6 +78,18 @@ allOf:
> patternProperties:
> '^ipu[12]_csi[01]_mux$': false
>
> + - if:
> + properties:
> + compatible:
> + not:
> + contains:
> + const: fsl,imx6sx-iomuxc-gpr
> + then:
> + properties:
> + bridge@18: false
> + '#address-cells': false
> + '#size-cells': false
> +
> additionalProperties: false
>
> required:
> @@ -87,4 +110,40 @@ examples:
> };
> };
>
> + - |
> + #include <dt-bindings/clock/imx6sx-clock.h>
> +
> + syscon@20e4000 {
> + compatible = "fsl,imx6sx-iomuxc-gpr", "fsl,imx6q-iomuxc-gpr", "syscon", "simple-mfd";
> + reg = <0x020e4000 0x4000>;
> + #address-cells = <1>;
> + #size-cells = <1>;
> +
> + bridge@18 {
> + compatible = "fsl,imx6sx-ldb";
> + reg = <0x18 0x4>;
> + clocks = <&clks IMX6SX_CLK_LDB_DI0>;
> + clock-names = "ldb";
> + status = "disabled";
Examples should be enabled. Drop.
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> +
> + endpoint {
> + };
> + };
> +
> + port@1 {
> + reg = <1>;
> +
> + endpoint {
> + };
> + };
> + };
> + };
> + };
> ...
>
> --
> 2.43.0
>
^ permalink raw reply
* Re: [RFC v3 2/2] arm64: kprobes: Allow reentering kprobes while single-stepping
From: Will Deacon @ 2026-07-17 18:02 UTC (permalink / raw)
To: Hongyan Xia
Cc: Pu Hu, Jiazi Li, catalin.marinas@arm.com,
linux-kernel@vger.kernel.org, naveen@kernel.org,
mhiramat@kernel.org, yang@os.amperecomputing.com,
davem@davemloft.net, linux-arm-kernel@lists.infradead.org,
linux-trace-kernel@vger.kernel.org
In-Reply-To: <be182a18-4cea-445d-a319-1ed3a6c17598@transsion.com>
On Fri, Jul 17, 2026 at 11:31:31AM +0000, Hongyan Xia wrote:
> On 7/17/2026 7:01 PM, Will Deacon wrote:
> > Thanks. So perf is run synchronously from the debug exception entry path,
>
> Yes, exactly.
>
> > rather than because of a second exception taking place. Got it. But then
> > it sounds like we should really make the debug exception handling path (at
> > least, the part that runs for handling the kprobe step) noinstr to avoid
> > getting into this state to begin with. Is that practical?
>
> Not sure about making the whole path noinstr (@Masami might have a
> better opinion on this than me). Personally I don't mind either
> disallowing it or making it correct.
>
> But it might be a good idea not to diverge too much between ISAs. This
> patch is pretty much mirroring what the x86 side handles this situation.
Ok, so how about this. I'll take these fixes for now, but let's try to
make these paths noinstr in the future? That's a much bigger job, but I
do worry that we're going to otherwise end up adding special logic every
time we run into an unexpected re-entrant case.
Will
^ permalink raw reply
* [PATCH] pwm: meson: Convert to waveform API
From: Alexandre Mergnat @ 2026-07-17 19:18 UTC (permalink / raw)
To: Uwe Kleine-König, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl
Cc: linux-pwm, linux-arm-kernel, linux-amlogic, linux-kernel,
Alexandre Mergnat
The PWM subsystem introduced a new waveform abstraction that
describes a PWM signal by period length, duty length and duty offset
instead of period, duty cycle and polarity. The meson driver still
uses the legacy .apply() and .get_state() callbacks and does not
implement the waveform callbacks, so consumers cannot use the
pwm_*_waveform() helper functions with it.
Adopt the new API by implementing .round_waveform_tohw(),
.round_waveform_fromhw(), .read_waveform() and .write_waveform()
with a private meson_pwm_waveform struct holding the hardware
representation. The cached channel state fields (rate, hi, lo,
constant, inverted) are removed; counter values are now computed in
.round_waveform_tohw() via clk_round_rate() without register writes,
then committed in .write_waveform().
The hardware invert bit places the duty active phase at the end of the
period on revisions that have it. Input clock rates are kept at or
below 1 GHz so each counter cycle remains representable in nanoseconds
by .round_waveform_fromhw(). Requests for which clk_round_rate() cannot
provide such a rate are rejected. Otherwise, period counts above the
16-bit range are clamped, while a zero count is rounded up to one and
signalled by .round_waveform_tohw() returning 1.
Signed-off-by: Alexandre Mergnat <amergnat@baylibre.com>
---
The PWM core gained a new waveform abstraction that lets drivers
describe the hardware more directly and gives consumers access to
rounding and read-back without touching registers. The meson driver
still uses the legacy .apply() and .get_state() callbacks, so
consumers of its PWMs cannot benefit from the new pwm_*_waveform()
helpers.
This series implements the four waveform callbacks and removes the
cached channel state that the legacy .apply() path relied on.
Settings are now computed without side effects and committed in a
separate step, matching the split the waveform API requires.
Behavioural changes visible to consumers:
- On revisions without the invert bit, a nonzero duty offset is
rounded down to zero. An inverted polarity request through the
legacy path produces the same register settings as the previous
"duty = period - duty" emulation, so the generated signal is
unchanged for those consumers.
- Disabling a PWM no longer emulates an inactive-high output on
revisions without the invert bit. The PWM API defines the output
of a disabled PWM as undefined (this was already the case for
.apply()), so affected consumers were relying on unspecified
behaviour. Consumers that need an inactive high output should keep
the PWM enabled with inverted polarity and zero duty cycle.
- Input clock rates are kept at or below 1 GHz so counter cycles remain
representable in nanoseconds. Requests for which clk_round_rate()
cannot provide such a rate are rejected. Otherwise, period counts
exceeding the 16-bit range are clamped. A zero count is rounded up to
one, signalled by .round_waveform_tohw() returning 1.
- Reported period and duty cycle values are rounded up instead of
down, so a returned setting reproduces the same counter values
when applied again. The constant output bit is now derived from
the converted counter values, which also covers cases where the
ns-to-counter conversion yields an empty high or low phase.
clk_set_rate() failures now propagate as write errors instead of
being logged and ignored, and .write_waveform() now also fails if
the input clock rate changed between rounding and writing.
Testing
=======
Tested on a Libre Computer Le Potato (AML-S905X-CC, Amlogic S905X /
GXL SoC) with CONFIG_PWM_DEBUG=y, built in tree. Three PWM nodes were
enabled in the board device tree (pwm_ab, pwm_ef, pwm_AO_ab), exposing
6 channels across the EE and AO clock domains.
The libpwm tools drive the /dev/pwmchipN character device directly, so
the new waveform callbacks are exercised through their own ioctls. All
6 channels went through pwmround for rounding queries (ROUNDWF:
standard, too-short and too-long periods, zero and full duty, duty
offsets, non-representable values), pwmset for rounded and exact
settings (SETROUNDEDWF / SETEXACTWF) with hardware read-back through
debugfs (.read_waveform()), and pwmtestperf duty sweeps. pwmtestperf
was run in all four direction/polarity combinations at 100 us, 1 ms and
10 ms (72 runs), then with a 1 ns step at a non-tick-aligned period so
every duty and duty-offset rounding boundary is crossed (about 24000
boundary checks). PWM_DEBUG's rounding and read-back checks stayed
silent throughout.
ROUNDWF and SETEXACTWF agree as specified: SETEXACTWF applies exactly
representable waveforms and returns -EDOM otherwise. 1 ms is exact on
the EE chips and rounds to 999990 ns on the AO chip, reflecting
each PWM's clock granularity. A 1 ns period request is
rounded up to the shortest representable period with zero duty,
signalled by .round_waveform_tohw() returning 1. Periods too long for
the source clock to represent (349.52 ms on this board) are rejected
with -EINVAL when clk_round_rate() cannot provide a low enough rate.
GXL has no invert bit (has_polarity = false), so nonzero duty offsets
are rounded down to zero and the axg/g12/s4 invert paths are not
exercisable on this board.
The sysfs interface (50%/25%/0%/100% duty, polarity inversion,
enable/disable on all 6 channels) was also exercised and reads back
consistently, confirming no regression for legacy consumers.
---
drivers/pwm/pwm-meson.c | 372 ++++++++++++++++++++++++++++--------------------
1 file changed, 215 insertions(+), 157 deletions(-)
diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c
index 8c6bf3d49753..be17aae0941b 100644
--- a/drivers/pwm/pwm-meson.c
+++ b/drivers/pwm/pwm-meson.c
@@ -6,11 +6,11 @@
* PWM output is achieved by calculating a clock that permits calculating
* two periods (low and high). The counter then has to be set to switch after
* N cycles for the first half period.
- * Partly the hardware has no "polarity" setting. This driver reverses the period
- * cycles (the low length is inverted with the high length) for
- * PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity
- * from the hardware.
- * Setting the duty cycle will disable and re-enable the PWM output.
+ * Some of the IP block revisions have an invert bit that swaps the high and
+ * low parts of the output. This is used to implement waveforms with
+ * duty_offset_ns + duty_length_ns == period_length_ns, i.e. what the legacy
+ * API calls inverted polarity. On the other revisions a nonzero duty_offset_ns
+ * is rounded down to zero.
* Disabling the PWM stops the output immediately (without waiting for the
* current period to complete first).
*
@@ -35,6 +35,7 @@
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/math64.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
@@ -96,12 +97,6 @@ static struct meson_pwm_channel_data {
};
struct meson_pwm_channel {
- unsigned long rate;
- unsigned int hi;
- unsigned int lo;
- bool constant;
- bool inverted;
-
struct clk_mux mux;
struct clk_divider div;
struct clk_gate gate;
@@ -156,30 +151,44 @@ static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
clk_disable_unprepare(channel->clk);
}
-static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm,
- const struct pwm_state *state)
+struct meson_pwm_waveform {
+ unsigned long rate;
+ u16 hi;
+ u16 lo;
+ bool enabled;
+ bool inverted;
+};
+
+static int meson_pwm_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
{
+ struct meson_pwm_waveform *wfhw = _wfhw;
struct meson_pwm *meson = to_meson_pwm(chip);
struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
- unsigned int cnt, duty_cnt;
+ u64 cnt, duty_cnt, freq;
long fin_freq;
- u64 duty, period, freq;
+ int ret = 0;
- duty = state->duty_cycle;
- period = state->period;
+ if (wf->period_length_ns == 0) {
+ *wfhw = (struct meson_pwm_waveform){
+ .enabled = false,
+ };
+
+ return 0;
+ }
/*
- * Note this is wrong. The result is an output wave that isn't really
- * inverted and so is wrongly identified by .get_state as normal.
- * Fixing this needs some care however as some machines might rely on
- * this.
+ * Get the highest input clock rate that makes the requested period
+ * representable with the 16 bit wide counters, but at most 1 GHz so
+ * one counter cycle stays at least a nanosecond long, which
+ * .round_waveform_fromhw() relies on. clk_round_rate() may round up
+ * past the requested rate, so reject a too-high result below.
*/
- if (state->polarity == PWM_POLARITY_INVERSED && !meson->data->has_polarity)
- duty = period - duty;
-
- freq = div64_u64(NSEC_PER_SEC * 0xffffULL, period);
- if (freq > ULONG_MAX)
- freq = ULONG_MAX;
+ freq = div64_u64((u64)NSEC_PER_SEC * FIELD_MAX(PWM_LOW_MASK),
+ wf->period_length_ns);
+ freq = min_t(u64, freq, NSEC_PER_SEC);
fin_freq = clk_round_rate(channel->clk, freq);
if (fin_freq <= 0) {
@@ -188,180 +197,226 @@ static int meson_pwm_calc(struct pwm_chip *chip, struct pwm_device *pwm,
return fin_freq ? fin_freq : -EINVAL;
}
- dev_dbg(pwmchip_parent(chip), "fin_freq: %ld Hz\n", fin_freq);
-
- cnt = mul_u64_u64_div_u64(fin_freq, period, NSEC_PER_SEC);
- if (cnt > 0xffff) {
- dev_err(pwmchip_parent(chip), "unable to get period cnt\n");
+ if (fin_freq > NSEC_PER_SEC) {
+ dev_err(pwmchip_parent(chip),
+ "source clock frequency %ld too high\n", fin_freq);
return -EINVAL;
}
- dev_dbg(pwmchip_parent(chip), "period=%llu cnt=%u\n", period, cnt);
-
- if (duty == period) {
- channel->hi = cnt;
- channel->lo = 0;
- channel->constant = true;
- } else if (duty == 0) {
- channel->hi = 0;
- channel->lo = cnt;
- channel->constant = true;
- } else {
- duty_cnt = mul_u64_u64_div_u64(fin_freq, duty, NSEC_PER_SEC);
-
- dev_dbg(pwmchip_parent(chip), "duty=%llu duty_cnt=%u\n", duty, duty_cnt);
+ /*
+ * If the requested period is too long to be counted even at the
+ * lowest possible input clock rate, implement the longest possible
+ * period instead.
+ */
+ cnt = mul_u64_u64_div_u64(fin_freq, wf->period_length_ns, NSEC_PER_SEC);
+ cnt = min_t(u64, cnt, FIELD_MAX(PWM_LOW_MASK));
+
+ if (cnt == 0) {
+ /*
+ * The requested period is shorter than one cycle of the
+ * fastest available input clock, implement the minimal
+ * period with zero duty cycle (the general calculation below
+ * yields that as duty_length_ns and duty_offset_ns cannot
+ * exceed the requested period) and signal the rounding up
+ * in the return value.
+ */
+ cnt = 1;
+ ret = 1;
+ }
- channel->hi = duty_cnt;
- channel->lo = cnt - duty_cnt;
- channel->constant = false;
+ duty_cnt = mul_u64_u64_div_u64(fin_freq, wf->duty_length_ns, NSEC_PER_SEC);
+ duty_cnt = min(duty_cnt, cnt);
+
+ *wfhw = (struct meson_pwm_waveform){
+ .rate = fin_freq,
+ .hi = duty_cnt,
+ .lo = cnt - duty_cnt,
+ .enabled = true,
+ };
+
+ if (meson->data->has_polarity && duty_cnt) {
+ u64 offset_cnt = mul_u64_u64_div_u64(fin_freq, wf->duty_offset_ns,
+ NSEC_PER_SEC);
+
+ /*
+ * Waveforms with duty_offset_ns + duty_length_ns ==
+ * period_length_ns are implemented by inverting the output:
+ * The signal then is low for hi counts (the offset) followed
+ * by high for lo counts (the duty cycle). Other nonzero
+ * duty_offset_ns values are rounded down to 0.
+ */
+ if (offset_cnt && duty_cnt + offset_cnt >= cnt) {
+ swap(wfhw->hi, wfhw->lo);
+ wfhw->inverted = true;
+ }
}
- channel->rate = fin_freq;
+ dev_dbg(pwmchip_parent(chip),
+ "pwm#%u: %llu/%llu [+%llu] @%lu -> hi: %u, lo: %u, inverted: %d\n",
+ pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns,
+ wf->duty_offset_ns, wfhw->rate, wfhw->hi, wfhw->lo,
+ wfhw->inverted);
- return 0;
+ return ret;
}
-static void meson_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+static u64 meson_pwm_cnt_to_ns(unsigned long fin_freq, u32 cnt)
{
- struct meson_pwm *meson = to_meson_pwm(chip);
- struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
- struct meson_pwm_channel_data *channel_data;
- unsigned long flags;
- u32 value;
- int err;
-
- channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
-
- err = clk_set_rate(channel->clk, channel->rate);
- if (err)
- dev_err(pwmchip_parent(chip), "setting clock rate failed\n");
-
- spin_lock_irqsave(&meson->lock, flags);
+ return DIV64_U64_ROUND_UP(NSEC_PER_SEC * (u64)cnt, fin_freq);
+}
- value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) |
- FIELD_PREP(PWM_LOW_MASK, channel->lo);
- writel(value, meson->base + channel_data->reg_offset);
+static int meson_pwm_round_waveform_fromhw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw,
+ struct pwm_waveform *wf)
+{
+ const struct meson_pwm_waveform *wfhw = _wfhw;
- value = readl(meson->base + REG_MISC_AB);
- value |= channel_data->pwm_en_mask;
+ if (!wfhw->enabled || !wfhw->rate) {
+ *wf = (struct pwm_waveform){
+ .period_length_ns = 0,
+ };
- if (meson->data->has_constant) {
- value &= ~channel_data->const_en_mask;
- if (channel->constant)
- value |= channel_data->const_en_mask;
+ return 0;
}
- if (meson->data->has_polarity) {
- value &= ~channel_data->inv_en_mask;
- if (channel->inverted)
- value |= channel_data->inv_en_mask;
+ /*
+ * wfhw->hi and wfhw->lo are u16, so their sum doesn't overflow and
+ * NSEC_PER_SEC * (hi + lo) fits into the u64 dividend in
+ * meson_pwm_cnt_to_ns().
+ */
+ wf->period_length_ns = meson_pwm_cnt_to_ns(wfhw->rate, wfhw->hi + wfhw->lo);
+
+ if (wfhw->inverted && wfhw->lo == 0) {
+ /*
+ * The output is constant low. Report a normal constant-low
+ * waveform instead of duty_offset_ns == period_length_ns
+ * which wouldn't be a valid waveform description. This
+ * setting is only reachable when the hardware was programmed
+ * by the bootloader as .round_waveform_tohw() never produces
+ * it.
+ */
+ wf->duty_length_ns = 0;
+ wf->duty_offset_ns = 0;
+ } else if (wfhw->inverted) {
+ wf->duty_length_ns = meson_pwm_cnt_to_ns(wfhw->rate, wfhw->lo);
+ wf->duty_offset_ns = meson_pwm_cnt_to_ns(wfhw->rate, wfhw->hi);
+ } else {
+ wf->duty_length_ns = meson_pwm_cnt_to_ns(wfhw->rate, wfhw->hi);
+ wf->duty_offset_ns = 0;
}
- writel(value, meson->base + REG_MISC_AB);
-
- spin_unlock_irqrestore(&meson->lock, flags);
+ return 0;
}
-static void meson_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+static int meson_pwm_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
{
+ struct meson_pwm_waveform *wfhw = _wfhw;
struct meson_pwm *meson = to_meson_pwm(chip);
struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
struct meson_pwm_channel_data *channel_data;
- unsigned long flags;
- u32 value;
+ u32 value, pwm_reg;
channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
- spin_lock_irqsave(&meson->lock, flags);
-
value = readl(meson->base + REG_MISC_AB);
- value &= ~channel_data->pwm_en_mask;
+ if (!(value & channel_data->pwm_en_mask)) {
+ *wfhw = (struct meson_pwm_waveform){
+ .enabled = false,
+ };
- if (meson->data->has_polarity) {
- value &= ~channel_data->inv_en_mask;
- if (channel->inverted)
- value |= channel_data->inv_en_mask;
+ return 0;
}
- writel(value, meson->base + REG_MISC_AB);
+ pwm_reg = readl(meson->base + channel_data->reg_offset);
- spin_unlock_irqrestore(&meson->lock, flags);
+ *wfhw = (struct meson_pwm_waveform){
+ .rate = clk_get_rate(channel->clk),
+ .hi = FIELD_GET(PWM_HIGH_MASK, pwm_reg),
+ .lo = FIELD_GET(PWM_LOW_MASK, pwm_reg),
+ .enabled = true,
+ .inverted = meson->data->has_polarity &&
+ (value & channel_data->inv_en_mask),
+ };
+
+ return 0;
}
-static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
- const struct pwm_state *state)
+static int meson_pwm_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
{
+ const struct meson_pwm_waveform *wfhw = _wfhw;
struct meson_pwm *meson = to_meson_pwm(chip);
struct meson_pwm_channel *channel = &meson->channels[pwm->hwpwm];
- int err = 0;
-
- channel->inverted = (state->polarity == PWM_POLARITY_INVERSED);
-
- if (!state->enabled) {
- if (channel->inverted && !meson->data->has_polarity) {
- /*
- * Some of IP block revisions don't have an "always high"
- * setting which we can use for "inverted disabled".
- * Instead we achieve this by setting mux parent with
- * highest rate and minimum divider value, resulting
- * in the shortest possible duration for one "count"
- * and "period == duty_cycle". This results in a signal
- * which is LOW for one "count", while being HIGH for
- * the rest of the (so the signal is HIGH for slightly
- * less than 100% of the period, but this is the best
- * we can achieve).
- */
- channel->rate = ULONG_MAX;
- channel->hi = ~0;
- channel->lo = 0;
- channel->constant = true;
-
- meson_pwm_enable(chip, pwm);
- } else {
- meson_pwm_disable(chip, pwm);
- }
- } else {
- err = meson_pwm_calc(chip, pwm, state);
- if (err < 0)
+ struct meson_pwm_channel_data *channel_data;
+ unsigned long flags;
+ u32 value;
+ int err;
+
+ channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
+
+ /*
+ * Channel clock operations also modify REG_MISC_AB under meson->lock,
+ * including from .request()/.free() outside the pwmchip lock. Set the
+ * rate before taking meson->lock to avoid recursive locking. Skip
+ * clk_set_rate() when unchanged because even a no-op re-evaluates the
+ * mux parent and reparses the device tree.
+ */
+ if (wfhw->enabled && clk_get_rate(channel->clk) != wfhw->rate) {
+ err = clk_set_rate(channel->clk, wfhw->rate);
+ if (err) {
+ dev_err(pwmchip_parent(chip),
+ "setting clock rate failed: %d\n", err);
return err;
+ }
- meson_pwm_enable(chip, pwm);
+ /*
+ * The rate computed by .round_waveform_tohw() might not be
+ * hit if an input clock changed its rate in between; the
+ * counter values only implement the promised waveform at
+ * exactly wfhw->rate.
+ */
+ if (clk_get_rate(channel->clk) != wfhw->rate) {
+ dev_err(pwmchip_parent(chip),
+ "clock rate changed since rounding\n");
+ return -EINVAL;
+ }
}
- return 0;
-}
+ spin_lock_irqsave(&meson->lock, flags);
-static u64 meson_pwm_cnt_to_ns(unsigned long fin_freq, u32 cnt)
-{
- return fin_freq ? div64_ul(NSEC_PER_SEC * (u64)cnt, fin_freq) : 0;
-}
+ if (wfhw->enabled) {
+ value = FIELD_PREP(PWM_HIGH_MASK, wfhw->hi) |
+ FIELD_PREP(PWM_LOW_MASK, wfhw->lo);
+ writel(value, meson->base + channel_data->reg_offset);
-static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
- struct pwm_state *state)
-{
- struct meson_pwm *meson = to_meson_pwm(chip);
- struct meson_pwm_channel_data *channel_data;
- unsigned long fin_freq;
- unsigned int hi, lo;
- u32 value;
-
- channel_data = &meson_pwm_per_channel_data[pwm->hwpwm];
- fin_freq = clk_get_rate(meson->channels[pwm->hwpwm].clk);
+ value = readl(meson->base + REG_MISC_AB);
+ value |= channel_data->pwm_en_mask;
- value = readl(meson->base + REG_MISC_AB);
- state->enabled = value & channel_data->pwm_en_mask;
+ if (meson->data->has_constant) {
+ value &= ~channel_data->const_en_mask;
+ /* The signal is constant when one phase is empty */
+ if (!wfhw->hi || !wfhw->lo)
+ value |= channel_data->const_en_mask;
+ }
- if (meson->data->has_polarity && (value & channel_data->inv_en_mask))
- state->polarity = PWM_POLARITY_INVERSED;
- else
- state->polarity = PWM_POLARITY_NORMAL;
+ if (meson->data->has_polarity) {
+ value &= ~channel_data->inv_en_mask;
+ if (wfhw->inverted)
+ value |= channel_data->inv_en_mask;
+ }
- value = readl(meson->base + channel_data->reg_offset);
- lo = FIELD_GET(PWM_LOW_MASK, value);
- hi = FIELD_GET(PWM_HIGH_MASK, value);
+ writel(value, meson->base + REG_MISC_AB);
+ } else {
+ value = readl(meson->base + REG_MISC_AB);
+ value &= ~channel_data->pwm_en_mask;
+ writel(value, meson->base + REG_MISC_AB);
+ }
- state->period = meson_pwm_cnt_to_ns(fin_freq, lo + hi);
- state->duty_cycle = meson_pwm_cnt_to_ns(fin_freq, hi);
+ spin_unlock_irqrestore(&meson->lock, flags);
return 0;
}
@@ -369,8 +424,11 @@ static int meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
static const struct pwm_ops meson_pwm_ops = {
.request = meson_pwm_request,
.free = meson_pwm_free,
- .apply = meson_pwm_apply,
- .get_state = meson_pwm_get_state,
+ .sizeof_wfhw = sizeof(struct meson_pwm_waveform),
+ .round_waveform_tohw = meson_pwm_round_waveform_tohw,
+ .round_waveform_fromhw = meson_pwm_round_waveform_fromhw,
+ .read_waveform = meson_pwm_read_waveform,
+ .write_waveform = meson_pwm_write_waveform,
};
static int meson_pwm_init_clocks_meson8b(struct pwm_chip *chip,
---
base-commit: 2e0a43a9e6f2e846f370aa63ea274f5403628b79
change-id: 20260710-pwm_meson_convert_to_waveform_api-8711b7f8c755
Best regards,
--
Alexandre Mergnat <amergnat@baylibre.com>
^ permalink raw reply related
* Re: [PATCH mm-hotfixes v5 0/5] mm: fix UAF caused by race between ptdump and vmap pgtable freeing
From: Andrew Morton @ 2026-07-17 19:29 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Suren Baghdasaryan, Liam R. Howlett, Vlastimil Babka,
Shakeel Butt, David Hildenbrand, Mike Rapoport, Michal Hocko,
Uladzislau Rezki, Toshi Kani, Dave Hansen, Andy Lutomirski,
Peter Zijlstra, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
x86, H. Peter Anvin, Kiryl Shutsemau, Catalin Marinas,
Will Deacon, Dev Jain, Ryan Roberts, David Carlier, linux-mm,
linux-kernel, bpf, linux-arm-kernel, Denis V. Lunev, stable,
syzbot+fd95a72470f5a44e464c
In-Reply-To: <20260717-series-vmap-race-fix-v5-0-606a0ac6d3e5@kernel.org>
On Fri, 17 Jul 2026 18:30:06 +0100 "Lorenzo Stoakes (ARM)" <ljs@kernel.org> wrote:
> Kernel page table walkers fall into two broad categories - those ranges
> where no exclusion is required via walk_kernel_page_table_range_lockless()
> and those where exclusion is required via walk_kernel_page_table_range()
> or walk_page_range_debug().
>
> The former category is used only by arm64 arch code operating on ranges it
> both wholly owns and does not concurrently write.
>
> The latter category consists of kernel page table walkers operating on
> ranges that are wholly owned (but which need exclusion against concurrent
> writers).
>
> The lock used for exclusion is the mmap lock, and for kernel ranges this
> the mmap lock on init_mm.
>
> ptdump is a special case being both the only user of
> walk_page_range_debug(), and the only case in which it walks ranges it does
> not own.
>
> This presents a problem, as page tables may be freed under ptdump. And
> indeed there is a use-after-free bug in the kernel as a result, which this
> series addresses.
>
> ...
>
> This series works around this by #ifndef CONFIG_ARM64'ing the mmap read
> lock in vmap logic, then partially reverting commit fa93b45fd397 ("arm64:
> Enable vmalloc-huge with ptdump"), keeping the enablement of huge vmap
> support, and removing the ifdeffery with the partial revert patch.
>
Thanks, I updated mm-hotfixes-unstable to this version.
> v5:
> * Rebased on latest master of Linus's tree.
> * Accumulated tags, thanks everybody!
> * Added additional commit to fix race between CPA collapse/attribute set.
> * Slight commit message tweaks.
Here's how v5 altered mm.git. This is from the addition of [3/5] - no
other patches changed.
arch/x86/mm/pat/set_memory.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
--- a/arch/x86/mm/pat/set_memory.c~b
+++ a/arch/x86/mm/pat/set_memory.c
@@ -2122,7 +2122,9 @@ static int change_page_attr_set_clr(unsi
cpa.curpage = 0;
cpa.force_split = force_split;
- ret = __change_page_attr_set_clr(&cpa, 1);
+ /* Avoid race with concurrent CPA collapse. */
+ scoped_guard(mmap_read_lock, &init_mm)
+ ret = __change_page_attr_set_clr(&cpa, 1);
/*
* Check whether we really changed something:
_
^ permalink raw reply
* Re: [PATCH v7 1/3] iommu/arm-smmu-v3: Factor out CMDQ batch force-sync conditions
From: Jason Gunthorpe @ 2026-07-17 19:59 UTC (permalink / raw)
To: Ashish Mhetre
Cc: catalin.marinas, will, corbet, skhan, robin.murphy, joro,
nicolinc, linux-arm-kernel, linux-doc, linux-kernel, iommu,
linux-tegra
In-Reply-To: <20260714104202.1664187-2-amhetre@nvidia.com>
On Tue, Jul 14, 2026 at 10:42:00AM +0000, Ashish Mhetre wrote:
> From: Nicolin Chen <nicolinc@nvidia.com>
>
> arm_smmu_cmdq_batch_add_cmd_p() carries two distinct reasons for
> flushing the current batch with a CMD_SYNC before appending the
> new command:
>
> - The batch's pre-assigned cmdq does not support the new command.
> - The Arm erratum 2812531 workaround (ARM_SMMU_OPT_CMDQ_FORCE_SYNC)
> forces a SYNC at one entry before the batch is full.
>
> Lift those checks into a new arm_smmu_cmdq_batch_force_sync() helper
> so that adding another force-sync condition becomes a one-line
> addition. No functional change.
>
> Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
> Signed-off-by: Ashish Mhetre <amhetre@nvidia.com>
> ---
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 23 +++++++++++++++------
> 1 file changed, 17 insertions(+), 6 deletions(-)
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Jason
^ permalink raw reply
* Re: [RFC PATCH v1 8/8] misc/arm-cla: Add userspace interface
From: Arnd Bergmann @ 2026-07-17 20:11 UTC (permalink / raw)
To: Ryan Roberts, Greg Kroah-Hartman, Catalin Marinas, Will Deacon,
Mark Rutland, Jean-Philippe Brucker, Oded Gabbay, Jonathan Corbet
Cc: linux-kernel, linux-arm-kernel, dri-devel, linux-doc
In-Reply-To: <2a69d96e-d37e-4e3d-8eaa-3ea31caf3893@arm.com>
On Fri, Jul 17, 2026, at 18:21, Ryan Roberts wrote:
> On 17/07/2026 16:31, Arnd Bergmann wrote:
>>
>> Without concrete implementation examples, I find it hard to imagine
>> how granular the CLA and accelerator blocks are. What I'm interested
>> in is separating things into special character devices when they
>> refer to units that you want to manage separately in userspace.
>>
>> If you have e.g. one accelerator for tensor operations and one for
>> handling gzip, I would very much want to see those have a separate
>> chardev nodes so a local administrator can give permissions to each
>> one separately, and have device names that are sensible to the
>> functionality underneath.
>
> Unfortunately this doesn't map well to the HW: the MMIO is for the
> CLA interface (each CPU has 1 CLA).
I'm sure you mentioned it in the documentation, but I missed that
there are never multiple CLA instances.
If the CLA is defined only in terms of its MMIO/DMA interface
and listening to TLB broadcast operations, is having a single
instance actually required by the design, or just an implementation
choice?
> Once you have access to that interface, you can
> communicate with all of the accelerators that are connected to the CLA. We could
> potentially use the availability masking control to only expose a single
> accelerator for a given context (which would be chosen based on which file you
> opened), but it wouldn't be possible for (e.g.) 2 different processes to access
> the different accelerators concurrently - they would have to be subject to the
> time slice model.
Right, that sounds a bit awkward. It sounds like this would also get
simpler with a model that ensures the current CLA ttbr0 matches the
CPU ttbr0 and the iotlb_mm setting, since you would never have concurrent
uses of a single CLA from multipel tasks. On the other hand, that
model would make it harder to use multiple accelerators from a
single task, if they have to go through multiple file descriptors
but could be accessed on a single mapping.
>> If you have separate accelerators for AES encryption and decryption,
>> or a large set of identical accelerators that can run concurrently,
>> those would of course get managed as a single device file.
>>
>> Most importantly, I don't think a global /dev/cla device node
>> is a sensible interface from a management perspective as that
>> would give unprivileged userspace direct control to something
>> that is essentially arbitrary (or buggy) vendor firmware
>> with DMA permissions.
>
> OK I see your point.
>
> While the interface supports up to 8 connected accelerators, we anticpate there
> only being a single compute accelerator in practice. We decided to keep the
> driver interface generic given the CLA spec, but perhaps it would be more
> straightforward to limit the driver implementation to only permitting a single
> accelerator?
Probably, yes. From a kernel perspective, I think we're a bit better
off with an abstraction like
- One CLA MMIO range per /type/ of accelerator on a given CPU
- One firmware node per CLA, possibly spanning multiple CPUs
if the accelerators behind them are shared.
- One character device per firmware node, named according to the
type of CLA
- Possibly multiple CLA nodes on a given CPU if multiple
unrelated accelerators are present
- optionally multiple related accelerators on the eight ports of
a CLA, if that makes sense for that device type
Not sure how far that is off from what you currently have in
the hardware design.
Arnd
^ permalink raw reply
* [PATCH v2 1/1] dt-bindings: soc: imx-iomuxc-gpr: allow bridge@18 as child node
From: Frank.Li @ 2026-07-17 20:15 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Shawn Guo,
Peng Fan,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
open list
Cc: imx
From: Frank Li <Frank.Li@nxp.com>
The legacy i.MX6SX (>15 year) SoC imx-iomuxc-gpr contains one LDB_CTRL
register. Allow the LVDS Display Bridge(LDB) child node under
imx-iomuxc-gpr.
Fix below CHECK_DTBS warnings:
arch/arm/boot/dts/nxp/imx/imx6sx-nitrogen6sx.dtb: syscon@20e4000 (fsl,imx6sx-iomuxc-gpr): '#address-cells', '#size-cells', 'bridge@18' do not match any of the regexes: '^ipu[12]_csi[01]_mux$', '^pinctrl-[0-9]+$
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
change in v2
- remove status = "disabled" at example
---
.../bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml | 58 +++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/Documentation/devicetree/bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml b/Documentation/devicetree/bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml
index 721a67e84c137..e1fbdc7e4e057 100644
--- a/Documentation/devicetree/bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml
+++ b/Documentation/devicetree/bindings/soc/imx/fsl,imx-iomuxc-gpr.yaml
@@ -47,10 +47,21 @@ properties:
reg:
maxItems: 1
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 1
+
mux-controller:
type: object
$ref: /schemas/mux/reg-mux.yaml
+ bridge@18:
+ type: object
+ $ref: /schemas/display/bridge/fsl,ldb.yaml#
+ unevaluatedProperties: false
+
patternProperties:
"^ipu[12]_csi[01]_mux$":
type: object
@@ -67,6 +78,18 @@ allOf:
patternProperties:
'^ipu[12]_csi[01]_mux$': false
+ - if:
+ properties:
+ compatible:
+ not:
+ contains:
+ const: fsl,imx6sx-iomuxc-gpr
+ then:
+ properties:
+ bridge@18: false
+ '#address-cells': false
+ '#size-cells': false
+
additionalProperties: false
required:
@@ -87,4 +110,39 @@ examples:
};
};
+ - |
+ #include <dt-bindings/clock/imx6sx-clock.h>
+
+ syscon@20e4000 {
+ compatible = "fsl,imx6sx-iomuxc-gpr", "fsl,imx6q-iomuxc-gpr", "syscon", "simple-mfd";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0x020e4000 0x4000>;
+
+ bridge@18 {
+ compatible = "fsl,imx6sx-ldb";
+ reg = <0x18 0x4>;
+ clocks = <&clks IMX6SX_CLK_LDB_DI0>;
+ clock-names = "ldb";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ endpoint {
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+
+ endpoint {
+ };
+ };
+ };
+ };
+ };
...
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v7 2/3] iommu/arm-smmu-v3: Introduce CFGI/TLBI-repeat workaround infrastructure
From: Jason Gunthorpe @ 2026-07-17 20:18 UTC (permalink / raw)
To: Ashish Mhetre
Cc: catalin.marinas, will, corbet, skhan, robin.murphy, joro,
nicolinc, linux-arm-kernel, linux-doc, linux-kernel, iommu,
linux-tegra
In-Reply-To: <20260714104202.1664187-3-amhetre@nvidia.com>
On Tue, Jul 14, 2026 at 10:42:01AM +0000, Ashish Mhetre wrote:
> Tegra264 SMMU instances need every CFGI/TLBI command sequence issued
> twice, with the second issue executing only after the first issue's
> CMD_SYNC has completed:
>
> TLBI/CFGI ... CMD_SYNC TLBI/CFGI ... CMD_SYNC
>
> ATC_INV is not affected and must never be doubled.
>
> Add arm_smmu_erratum_repeat_tlbi_cfgi_key and an
> arm_smmu_erratum_cmd_needs_repeating() helper that gates on the static
> key first and then range-checks the opcode (CFGI_STE .. ATC_INV), so
> subsequent changes wiring the workaround into the CMDQ submission and
> iommufd batching paths can share a single predicate.
>
> Rename the existing arm_smmu_cmdq_issue_cmdlist() to
> __arm_smmu_cmdq_issue_cmdlist() and add a thin wrapper that re-issues
> the same cmdlist a second time when the predicate fires. Register the
> new condition with arm_smmu_cmdq_batch_force_sync() and add
> arm_vsmmu_can_batch_cmd() so iommufd batches split at every "needs
> repeating" transition.
>
> No callers enable the static key yet, so there is no functional change.
> A subsequent change will enable the key on affected instances.
>
> Suggested-by: Nicolin Chen <nicolinc@nvidia.com>
> Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
> Signed-off-by: Ashish Mhetre <amhetre@nvidia.com>
> ---
> .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 14 ++++-
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 57 +++++++++++++++++--
> drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 +
> 3 files changed, 67 insertions(+), 5 deletions(-)
Because the VM has access to VCMDQ/etc it can issue commands directly
(maybe not on this chip, but as a general comment), thus invalidation
errata need to be made visible to the VM and we need to expect the VM
will generate invalidations to properly deal with any errata.
Ie I'd expect the VM to see a "nvidia,tegra264-smmu" compatible string
to activate the errata fix.
If so then we will already get duplicated invalidations here and then
we will duplicate them again. That's not great.
On the other hand if you want to emulate a generic and actually
functional SMMU device that has nothing like VCMDQ/etc then you must
do something like this in this patch.
I was imagining a general direction that we would expose the errata
information to the guest and the guest would have to deal with it.
Given this is the opposite I wonder if we want to do it. It does make
sense for a chip that probably doesn't have vCMDQ and I don't think
qemu can even create a DT description to trigger the errata anyhow.
But it starts to become confusing down the road if we decide other
invalidation errata (like the CONT must be RIL thing) must be delt
with by the guest.
So.. Maybe add a comment why this one is different, or maybe just
disable FEAT_NESTING if there isn't a use case?
Otherwise the rest is fine
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Jason
^ permalink raw reply
* Re: [PATCH v7 3/3] iommu/arm-smmu-v3: Enable CFGI/TLBI-repeat workaround on Tegra264
From: Jason Gunthorpe @ 2026-07-17 20:19 UTC (permalink / raw)
To: Ashish Mhetre
Cc: catalin.marinas, will, corbet, skhan, robin.murphy, joro,
nicolinc, linux-arm-kernel, linux-doc, linux-kernel, iommu,
linux-tegra
In-Reply-To: <20260714104202.1664187-4-amhetre@nvidia.com>
On Tue, Jul 14, 2026 at 10:42:02AM +0000, Ashish Mhetre wrote:
> --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
> @@ -5357,8 +5357,10 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev,
> if (of_dma_is_coherent(dev->of_node))
> smmu->features |= ARM_SMMU_FEAT_COHERENCY;
>
> - if (of_device_is_compatible(dev->of_node, "nvidia,tegra264-smmu"))
> + if (of_device_is_compatible(dev->of_node, "nvidia,tegra264-smmu")) {
> tegra_cmdqv_dt_probe(dev->of_node, smmu);
> + static_branch_enable(&arm_smmu_erratum_repeat_tlbi_cfgi_key);
> + }
It is a bit of a big hammer to not have the per-instance FEAT but it
probably doesn't matter
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Jason
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox