AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/09 17:15 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new API for DMA direct liveupdate preservation (e.g., dma_preserve_allocation_attrs, dma_direct_preserve_allocation) and a KUnit test for it. However, the new API is not currently called by any driver or core kernel code outside of the KUnit test itself. Therefore, the modified code is unreachable from userspace and cannot be fuzzed.",
  "WorthFuzzing": false
}

1/1 2026/07/09 17:15 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 3f1ca197680f6e7ebbf244f7282cd3b04645ac57\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Jul 9 17:15:06 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/dma-direct.h b/include/linux/dma-direct.h\nindex c249912456f963..0efe2bc1a81524 100644\n--- a/include/linux/dma-direct.h\n+++ b/include/linux/dma-direct.h\n@@ -141,6 +141,35 @@ static inline bool dma_capable(struct device *dev, dma_addr_t addr, size_t size,\n u64 dma_direct_get_required_mask(struct device *dev);\n void *dma_direct_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle,\n \t\tgfp_t gfp, unsigned long attrs);\n+\n+#ifdef CONFIG_DMA_LIVEUPDATE\n+int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,\n+\t\t\t\t   size_t size, dma_addr_t dma_handle,\n+\t\t\t\t   unsigned long attrs, u64 *state);\n+void dma_direct_unpreserve_allocation(struct device *dev, u64 state);\n+void *dma_direct_restore_allocation(struct device *dev, size_t size,\n+\t\t\t\t    dma_addr_t *dma_handle, gfp_t gfp,\n+\t\t\t\t    unsigned long attrs, u64 state);\n+#else\n+static inline int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,\n+\t\t\t\t\t\t size_t size, dma_addr_t dma_handle,\n+\t\t\t\t\t\t unsigned long attrs, u64 *state)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static inline void dma_direct_unpreserve_allocation(struct device *dev, u64 state)\n+{\n+}\n+\n+static inline void *dma_direct_restore_allocation(struct device *dev, size_t size,\n+\t\t\t\t\t\t  dma_addr_t *dma_handle, gfp_t gfp,\n+\t\t\t\t\t\t  unsigned long attrs, u64 state)\n+{\n+\treturn NULL;\n+}\n+#endif\n+\n void dma_direct_free(struct device *dev, size_t size, void *cpu_addr,\n \t\tdma_addr_t dma_addr, unsigned long attrs);\n struct page *dma_direct_alloc_pages(struct device *dev, size_t size,\ndiff --git a/include/linux/dma-map-ops.h b/include/linux/dma-map-ops.h\nindex 6a1832a73cad8d..b13e5eebe6d3ae 100644\n--- a/include/linux/dma-map-ops.h\n+++ b/include/linux/dma-map-ops.h\n@@ -106,6 +106,7 @@ struct page *dma_alloc_contiguous(struct device *dev, size_t size, gfp_t gfp);\n void dma_free_contiguous(struct device *dev, struct page *page, size_t size);\n \n void dma_contiguous_early_fixup(phys_addr_t base, unsigned long size);\n+bool dma_is_from_cma(phys_addr_t phys, size_t size);\n #else /* CONFIG_DMA_CMA */\n static inline struct cma *dev_get_cma_area(struct device *dev)\n {\n@@ -145,6 +146,11 @@ static inline void dma_free_contiguous(struct device *dev, struct page *page,\n {\n \t__free_pages(page, get_order(size));\n }\n+\n+static inline bool dma_is_from_cma(phys_addr_t phys, size_t size)\n+{\n+\treturn false;\n+}\n #endif /* CONFIG_DMA_CMA*/\n \n #ifdef CONFIG_DMA_DECLARE_COHERENT\n@@ -156,6 +162,7 @@ int dma_alloc_from_dev_coherent(struct device *dev, ssize_t size,\n int dma_release_from_dev_coherent(struct device *dev, int order, void *vaddr);\n int dma_mmap_from_dev_coherent(struct device *dev, struct vm_area_struct *vma,\n \t\tvoid *cpu_addr, size_t size, int *ret);\n+bool dma_is_from_dev_coherent(struct device *dev, void *vaddr);\n #else\n static inline int dma_declare_coherent_memory(struct device *dev,\n \t\tphys_addr_t phys_addr, dma_addr_t device_addr, size_t size)\n@@ -166,6 +173,8 @@ static inline int dma_declare_coherent_memory(struct device *dev,\n #define dma_alloc_from_dev_coherent(dev, size, handle, ret) (0)\n #define dma_release_from_dev_coherent(dev, order, vaddr) (0)\n #define dma_mmap_from_dev_coherent(dev, vma, vaddr, order, ret) (0)\n+#define dma_is_from_dev_coherent(dev, vaddr) (false)\n+\n static inline void dma_release_coherent_memory(struct device *dev) { }\n #endif /* CONFIG_DMA_DECLARE_COHERENT */\n \n@@ -216,6 +225,7 @@ struct page *dma_alloc_from_pool(struct device *dev, size_t size,\n \t\tbool (*phys_addr_ok)(struct device *, phys_addr_t, size_t));\n bool dma_free_from_pool(struct device *dev, void *start, size_t size);\n \n+bool dma_is_from_pool(void *start, size_t size);\n int dma_direct_set_offset(struct device *dev, phys_addr_t cpu_start,\n \t\tdma_addr_t dma_start, u64 size);\n \ndiff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h\nindex db8ab24a54f4a3..4c0c428b6b1cb6 100644\n--- a/include/linux/dma-mapping.h\n+++ b/include/linux/dma-mapping.h\n@@ -210,6 +210,23 @@ void *dma_vmap_noncontiguous(struct device *dev, size_t size,\n void dma_vunmap_noncontiguous(struct device *dev, void *vaddr);\n int dma_mmap_noncontiguous(struct device *dev, struct vm_area_struct *vma,\n \t\tsize_t size, struct sg_table *sgt);\n+#ifdef CONFIG_DMA_LIVEUPDATE\n+int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n+\t\t\t\t  size_t size, dma_addr_t dma_handle,\n+\t\t\t\t  unsigned long attrs, u64 *state);\n+void dma_unpreserve_allocation(struct device *dev, u64 state);\n+void *dma_restore_allocation_attrs(struct device *dev, size_t size,\n+\t\t\t\t   dma_addr_t *dma_handle, gfp_t gfp,\n+\t\t\t\t   unsigned long attrs, u64 state);\n+int dmam_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n+\t\t\t\t   size_t size, dma_addr_t dma_handle,\n+\t\t\t\t   unsigned long attrs, u64 *state);\n+void dmam_unpreserve_allocation(struct device *dev, void *cpu_addr,\n+\t\t\t\tsize_t size, dma_addr_t dma_handle, u64 state);\n+void *dmam_restore_allocation_attrs(struct device *dev, size_t size,\n+\t\t\t\t    dma_addr_t *dma_handle, gfp_t gfp,\n+\t\t\t\t    unsigned long attrs, u64 state);\n+#endif\n #else /* CONFIG_HAS_DMA */\n static inline dma_addr_t dma_map_page_attrs(struct device *dev,\n \t\tstruct page *page, size_t offset, size_t size,\n@@ -496,6 +513,45 @@ static inline bool dma_need_unmap(struct device *dev)\n }\n #endif /* !CONFIG_HAS_DMA || !CONFIG_DMA_NEED_SYNC */\n \n+#if !defined(CONFIG_DMA_LIVEUPDATE) || !defined(CONFIG_HAS_DMA)\n+static inline int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n+\t\t\t\t\t\tsize_t size, dma_addr_t dma_handle,\n+\t\t\t\t\t\tunsigned long attrs, u64 *state)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static inline void dma_unpreserve_allocation(struct device *dev, u64 state)\n+{\n+}\n+\n+static inline void *dma_restore_allocation_attrs(struct device *dev, size_t size,\n+\t\t\t\t\t\t dma_addr_t *dma_handle, gfp_t gfp,\n+\t\t\t\t\t\t unsigned long attrs, u64 state)\n+{\n+\treturn NULL;\n+}\n+\n+static inline int dmam_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n+\t\t\t\t\t\t size_t size, dma_addr_t dma_handle,\n+\t\t\t\t\t\t unsigned long attrs, u64 *state)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static inline void dmam_unpreserve_allocation(struct device *dev, void *cpu_addr,\n+\t\t\t\t\t      size_t size, dma_addr_t dma_handle, u64 state)\n+{\n+}\n+\n+static inline void *dmam_restore_allocation_attrs(struct device *dev, size_t size,\n+\t\t\t\t\t\t  dma_addr_t *dma_handle, gfp_t gfp,\n+\t\t\t\t\t\t  unsigned long attrs, u64 state)\n+{\n+\treturn NULL;\n+}\n+#endif\n+\n struct page *dma_alloc_pages(struct device *dev, size_t size,\n \t\tdma_addr_t *dma_handle, enum dma_data_direction dir, gfp_t gfp);\n void dma_free_pages(struct device *dev, size_t size, struct page *page,\n@@ -618,6 +674,27 @@ static inline void *dma_alloc_coherent(struct device *dev, size_t size,\n \t\t\t(gfp \u0026 __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0);\n }\n \n+static inline int dma_preserve_coherent_allocation(struct device *dev, void *cpu_addr,\n+\t\t\t\t\t\t   size_t size, dma_addr_t dma_handle, u64 *state)\n+{\n+\treturn dma_preserve_allocation_attrs(dev, cpu_addr, size,\n+\t\t\t\t\t     dma_handle, 0, state);\n+}\n+\n+static inline void dma_unpreserve_coherent_allocation(struct device *dev, u64 state)\n+{\n+\tdma_unpreserve_allocation(dev, state);\n+}\n+\n+static inline void *dma_restore_coherent_allocation(struct device *dev, size_t size,\n+\t\t\t\t\t\t    dma_addr_t *dma_handle,\n+\t\t\t\t\t\t    gfp_t gfp, u64 state)\n+{\n+\treturn dma_restore_allocation_attrs(dev, size, dma_handle, gfp,\n+\t\t\t\t\t    (gfp \u0026 __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0,\n+\t\t\t\t\t    state);\n+}\n+\n static inline void dma_free_coherent(struct device *dev, size_t size,\n \t\tvoid *cpu_addr, dma_addr_t dma_handle)\n {\n@@ -747,6 +824,29 @@ static inline void *dmam_alloc_coherent(struct device *dev, size_t size,\n \t\t\t(gfp \u0026 __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0);\n }\n \n+static inline int dmam_preserve_coherent_allocation(struct device *dev, void *cpu_addr,\n+\t\t\t\t\t\t    size_t size, dma_addr_t dma_handle, u64 *state)\n+{\n+\treturn dmam_preserve_allocation_attrs(dev, cpu_addr, size,\n+\t\t\t\t\t      dma_handle, 0, state);\n+}\n+\n+static inline void dmam_unpreserve_coherent_allocation(struct device *dev, void *cpu_addr,\n+\t\t\t\t\t\t       size_t size, dma_addr_t dma_handle,\n+\t\t\t\t\t\t       u64 state)\n+{\n+\tdmam_unpreserve_allocation(dev, cpu_addr, size, dma_handle, state);\n+}\n+\n+static inline void *dmam_restore_coherent_allocation(struct device *dev, size_t size,\n+\t\t\t\t\t\t     dma_addr_t *dma_handle,\n+\t\t\t\t\t\t     gfp_t gfp, u64 state)\n+{\n+\treturn dmam_restore_allocation_attrs(dev, size, dma_handle, gfp,\n+\t\t\t\t\t     (gfp \u0026 __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0,\n+\t\t\t\t\t     state);\n+}\n+\n static inline void *dma_alloc_wc(struct device *dev, size_t size,\n \t\t\t\t dma_addr_t *dma_addr, gfp_t gfp)\n {\ndiff --git a/include/linux/kho/abi/dma_alloc.h b/include/linux/kho/abi/dma_alloc.h\nnew file mode 100644\nindex 00000000000000..ae38cf1a1fc99e\n--- /dev/null\n+++ b/include/linux/kho/abi/dma_alloc.h\n@@ -0,0 +1,32 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#ifndef _LINUX_KHO_ABI_DMA_ALLOC_H\n+#define _LINUX_KHO_ABI_DMA_ALLOC_H\n+\n+#include \u003clinux/types.h\u003e\n+\n+/**\n+ * DOC: DMA Alloc ABI\n+ *\n+ * This header defines the structures used to serialize the state of DMA\n+ * allocations, done by device driver, across a Live Update.\n+ *\n+ * Only DMA allocations done through dma-direct that are contiguous and\n+ * allocated using alloc_page are supported.\n+ */\n+\n+/**\n+ * struct dma_alloc_ser - Serialized state of a single DMA allocation\n+ * @page_phys: Physical address of the preserved pages\n+ * @size: Size of the DMA allocation\n+ * @attrs: DMA allocation attributes\n+ * @force_decrypted: Whether the memory is force decrypted in previous kernel\n+ */\n+struct dma_alloc_ser {\n+\tu64 page_phys;\n+\tu64 size;\n+\tu64 attrs;\n+\tu8 force_decrypted;\n+\tu8 padding[7];\n+} __packed;\n+\n+#endif /* _LINUX_KHO_ABI_DMA_ALLOC_H */\ndiff --git a/kernel/dma/Kconfig b/kernel/dma/Kconfig\nindex bfef21b4a9ae7d..48bc74af551a31 100644\n--- a/kernel/dma/Kconfig\n+++ b/kernel/dma/Kconfig\n@@ -265,3 +265,15 @@ config DMA_MAP_BENCHMARK\n \t  performance of dma_(un)map_page.\n \n \t  See tools/testing/selftests/dma/dma_map_benchmark.c\n+\n+config DMA_LIVEUPDATE\n+\tbool \"Enable preservation of DMA direct allocations\"\n+\tdepends on LIVEUPDATE\n+\n+config DMA_LIVEUPDATE_KUNIT_TEST\n+\ttristate \"KUnit test for DMA direct liveupdate preservation\"\n+\tdepends on KUNIT\n+\tdepends on DMA_LIVEUPDATE\n+\thelp\n+\t  This builds the KUnit tests for the DMA direct liveupdate\n+\t  preservation and restoration logic.\ndiff --git a/kernel/dma/Makefile b/kernel/dma/Makefile\nindex 6977033444a375..03703542ee79bc 100644\n--- a/kernel/dma/Makefile\n+++ b/kernel/dma/Makefile\n@@ -10,3 +10,4 @@ obj-$(CONFIG_SWIOTLB)\t\t\t+= swiotlb.o\n obj-$(CONFIG_DMA_COHERENT_POOL)\t\t+= pool.o\n obj-$(CONFIG_MMU)\t\t\t+= remap.o\n obj-$(CONFIG_DMA_MAP_BENCHMARK)\t\t+= map_benchmark.o\n+obj-$(CONFIG_DMA_LIVEUPDATE_KUNIT_TEST)\t+= direct_test.o\ndiff --git a/kernel/dma/coherent.c b/kernel/dma/coherent.c\nindex bcdc0f76d2e80a..76cbe11e1f90e7 100644\n--- a/kernel/dma/coherent.c\n+++ b/kernel/dma/coherent.c\n@@ -231,6 +231,17 @@ int dma_release_from_dev_coherent(struct device *dev, int order, void *vaddr)\n \treturn __dma_release_from_coherent(mem, order, vaddr);\n }\n \n+bool dma_is_from_dev_coherent(struct device *dev, void *vaddr)\n+{\n+\tstruct dma_coherent_mem *mem = dev_get_coherent_memory(dev);\n+\n+\tif (mem \u0026\u0026 vaddr \u003e= mem-\u003evirt_base \u0026\u0026 vaddr \u003c\n+\t\t   (mem-\u003evirt_base + ((dma_addr_t)mem-\u003esize \u003c\u003c PAGE_SHIFT)))\n+\t\treturn true;\n+\n+\treturn false;\n+}\n+\n static int __dma_mmap_from_coherent(struct dma_coherent_mem *mem,\n \t\tstruct vm_area_struct *vma, void *vaddr, size_t size, int *ret)\n {\ndiff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c\nindex 03f52bd1712062..6d0cf9c7528cb5 100644\n--- a/kernel/dma/contiguous.c\n+++ b/kernel/dma/contiguous.c\n@@ -37,6 +37,8 @@\n \n #define pr_fmt(fmt) \"cma: \" fmt\n \n+#include \u003ckunit/visibility.h\u003e\n+\n #include \u003casm/page.h\u003e\n \n #include \u003clinux/memblock.h\u003e\n@@ -130,6 +132,31 @@ struct cma *dev_get_cma_area(struct device *dev)\n }\n EXPORT_SYMBOL_GPL(dev_get_cma_area);\n \n+struct dma_cma_check_data {\n+\tphys_addr_t start;\n+\tphys_addr_t end;\n+};\n+\n+static int dma_cma_check_area(struct cma *cma, void *data)\n+{\n+\tstruct dma_cma_check_data *chk = data;\n+\n+\tif (cma_intersects(cma, chk-\u003estart, chk-\u003eend))\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+bool dma_is_from_cma(phys_addr_t phys, size_t size)\n+{\n+\tstruct dma_cma_check_data chk = {\n+\t\t.start = phys,\n+\t\t.end = phys + size - 1,\n+\t};\n+\n+\treturn cma_for_each_area(dma_cma_check_area, \u0026chk) != 0;\n+}\n+EXPORT_SYMBOL_IF_KUNIT(dma_is_from_cma);\n+\n #ifdef CONFIG_DMA_NUMA_CMA\n \n static struct cma *dma_contiguous_numa_area[MAX_NUMNODES];\ndiff --git a/kernel/dma/direct.c b/kernel/dma/direct.c\nindex ec887f44374183..117ebae8a81fa1 100644\n--- a/kernel/dma/direct.c\n+++ b/kernel/dma/direct.c\n@@ -6,6 +6,8 @@\n  */\n #include \u003clinux/memblock.h\u003e /* for max_pfn */\n #include \u003clinux/export.h\u003e\n+#include \u003clinux/kexec_handover.h\u003e\n+#include \u003clinux/kho/abi/dma_alloc.h\u003e\n #include \u003clinux/mm.h\u003e\n #include \u003clinux/dma-map-ops.h\u003e\n #include \u003clinux/scatterlist.h\u003e\n@@ -307,6 +309,193 @@ void *dma_direct_alloc(struct device *dev, size_t size,\n \treturn NULL;\n }\n \n+#ifdef CONFIG_DMA_LIVEUPDATE\n+int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,\n+\t\t\t\t   size_t size, dma_addr_t dma_handle,\n+\t\t\t\t   unsigned long attrs, u64 *state)\n+{\n+\tstruct dma_alloc_ser *ser;\n+\tint ret;\n+\n+\tif (!kho_is_enabled())\n+\t\treturn -EOPNOTSUPP;\n+\n+\tsize = PAGE_ALIGN(size);\n+\tif (dma_is_from_cma(dma_to_phys(dev, dma_handle), size))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (is_swiotlb_for_alloc(dev))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif ((attrs \u0026 DMA_ATTR_NO_KERNEL_MAPPING) \u0026\u0026\n+\t    !force_dma_unencrypted(dev))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (IS_ENABLED(CONFIG_ARCH_HAS_DMA_ALLOC) \u0026\u0026\n+\t    !dev_is_dma_coherent(dev))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (IS_ENABLED(CONFIG_DMA_GLOBAL_POOL) \u0026\u0026\n+\t    !dev_is_dma_coherent(dev))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) \u0026\u0026\n+\t    dma_is_from_pool(cpu_addr, size))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tser = kho_alloc_preserve(sizeof(*ser));\n+\tif (IS_ERR(ser))\n+\t\treturn PTR_ERR(ser);\n+\n+\tser-\u003epage_phys = dma_to_phys(dev, dma_handle);\n+\tser-\u003eforce_decrypted = force_dma_unencrypted(dev);\n+\tser-\u003esize = size;\n+\tser-\u003eattrs = (attrs \u0026 ~DMA_ATTR_NO_WARN);\n+\n+\tret = kho_preserve_pages(phys_to_page(ser-\u003epage_phys),\n+\t\t\t\t 1 \u003c\u003c get_order(size));\n+\tif (ret) {\n+\t\tkho_unpreserve_free(ser);\n+\t\treturn ret;\n+\t}\n+\n+\t*state = virt_to_phys(ser);\n+\treturn 0;\n+}\n+\n+void dma_direct_unpreserve_allocation(struct device *dev, u64 state)\n+{\n+\tstruct dma_alloc_ser *ser;\n+\n+\tif (!kho_is_enabled())\n+\t\treturn;\n+\n+\tser = phys_to_virt(state);\n+\tkho_unpreserve_pages(phys_to_page(ser-\u003epage_phys),\n+\t\t\t     1 \u003c\u003c get_order(ser-\u003esize));\n+\tkho_unpreserve_free(ser);\n+}\n+\n+void *dma_direct_restore_allocation(struct device *dev, size_t size,\n+\t\t\t\t    dma_addr_t *dma_handle, gfp_t gfp,\n+\t\t\t\t    unsigned long attrs, u64 state)\n+{\n+\tbool remap = false, set_uncached = false;\n+\tstruct dma_alloc_ser *ser = NULL;\n+\tstruct page *page;\n+\tvoid *cpu_addr = NULL;\n+\tint i;\n+\n+\tif (!kho_is_enabled())\n+\t\treturn NULL;\n+\n+\tser = phys_to_virt(state);\n+\tpage = phys_to_page(ser-\u003epage_phys);\n+\n+\tsize = PAGE_ALIGN(size);\n+\tif (size != ser-\u003esize)\n+\t\treturn NULL;\n+\n+\tWARN_ON_ONCE((attrs \u0026 ~DMA_ATTR_NO_WARN) != ser-\u003eattrs);\n+\n+\t/*\n+\t * Cannot free the restored pages on error here as these might be in use\n+\t * by a device with direct allocation in the previous kernel.\n+\t */\n+\tif (is_swiotlb_for_alloc(dev))\n+\t\tgoto err;\n+\n+\tif ((attrs \u0026 DMA_ATTR_NO_KERNEL_MAPPING) \u0026\u0026\n+\t    !force_dma_unencrypted(dev))\n+\t\tgoto err;\n+\n+\tif (!dev_is_dma_coherent(dev)) {\n+\t\tif (IS_ENABLED(CONFIG_ARCH_HAS_DMA_ALLOC))\n+\t\t\tgoto err;\n+\n+\t\tif (IS_ENABLED(CONFIG_DMA_GLOBAL_POOL))\n+\t\t\tgoto err;\n+\n+\t\tset_uncached = IS_ENABLED(CONFIG_ARCH_HAS_DMA_SET_UNCACHED);\n+\t\tremap = IS_ENABLED(CONFIG_DMA_DIRECT_REMAP);\n+\t\tif (!set_uncached \u0026\u0026 !remap)\n+\t\t\tgoto err;\n+\t}\n+\n+\tif (PageHighMem(page)) {\n+\t\tremap = true;\n+\t\tset_uncached = false;\n+\t}\n+\n+\t/*\n+\t * Remapping will be blocking so return error. The preserved memory\n+\t * might be already decrypted in the previous kernel, but the decryption\n+\t * call is not guaranteed to be non-blocking so return error always if\n+\t * decryption is required.\n+\t */\n+\tif ((remap || force_dma_unencrypted(dev)) \u0026\u0026\n+\t    dma_direct_use_pool(dev, gfp))\n+\t\tgoto err;\n+\n+\t/*\n+\t * Encryption scheme changed between two kernels and this might cause\n+\t * issues if device/driver is not handling it properly.\n+\t */\n+\tWARN_ON_ONCE(ser-\u003eforce_decrypted != force_dma_unencrypted(dev));\n+\n+\t/*\n+\t * arch_dma_prep_coherent() should make sure that any cache lines from\n+\t * the previous kernel, if the device was coherent previously or cached\n+\t * mapping in this kernel during init are not problematic for\n+\t * non-coherent allocations.\n+\t */\n+\tif (remap) {\n+\t\tpgprot_t prot = dma_pgprot(dev, PAGE_KERNEL, attrs);\n+\n+\t\tif (force_dma_unencrypted(dev))\n+\t\t\tprot = pgprot_decrypted(prot);\n+\n+\t\tarch_dma_prep_coherent(page, size);\n+\n+\t\tcpu_addr = dma_common_contiguous_remap(page, size, prot,\n+\t\t\t\t\t\t       __builtin_return_address(0));\n+\t\tif (!cpu_addr)\n+\t\t\tgoto err;\n+\t} else {\n+\t\tcpu_addr = page_address(page);\n+\t\tif (dma_set_decrypted(dev, cpu_addr, size)) {\n+\t\t\tcpu_addr = NULL;\n+\t\t\tgoto err;\n+\t\t}\n+\t}\n+\n+\tif (set_uncached) {\n+\t\tarch_dma_prep_coherent(page, size);\n+\t\tcpu_addr = arch_dma_set_uncached(cpu_addr, size);\n+\t\tif (IS_ERR(cpu_addr)) {\n+\t\t\tcpu_addr = NULL;\n+\t\t\tgoto err;\n+\t\t}\n+\t}\n+\n+\t*dma_handle = phys_to_dma_direct(dev, ser-\u003epage_phys);\n+\n+err:\n+\tWARN_ON(!kho_restore_pages(ser-\u003epage_phys,\n+\t\t\t\t   1 \u003c\u003c get_order(ser-\u003esize)));\n+\tkho_restore_free(ser);\n+\n+\t/*\n+\t * DMA direct API expects high-order allocations, so set refcount of\n+\t * tail pages to zero.\n+\t */\n+\tfor (i = (1 \u003c\u003c get_order(size)) - 1; i \u003e 0; --i)\n+\t\tset_page_count(page + i, 0);\n+\n+\treturn cpu_addr;\n+}\n+#endif\n+\n void dma_direct_free(struct device *dev, size_t size,\n \t\tvoid *cpu_addr, dma_addr_t dma_addr, unsigned long attrs)\n {\ndiff --git a/kernel/dma/direct_test.c b/kernel/dma/direct_test.c\nnew file mode 100644\nindex 00000000000000..f8403ec47807e8\n--- /dev/null\n+++ b/kernel/dma/direct_test.c\n@@ -0,0 +1,208 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * KUnit test for DMA allocation live update preservation.\n+ */\n+\n+#include \u003ckunit/test.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003ckunit/static_stub.h\u003e\n+#include \u003clinux/dma-map-ops.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/dma-direct.h\u003e\n+#include \u003clinux/kho/abi/dma_alloc.h\u003e\n+#include \u003clinux/kexec_handover.h\u003e\n+#include \u003clinux/slab.h\u003e\n+\n+static const size_t dma_test_sizes[] = {\n+\t128,\n+\tPAGE_SIZE,\n+\t2 * PAGE_SIZE,\n+\t3 * PAGE_SIZE,\n+\t8 * PAGE_SIZE,\n+};\n+\n+static void dma_size_desc(const size_t *size, char *desc)\n+{\n+\tsnprintf(desc, KUNIT_PARAM_DESC_SIZE, \"size=%zu\", *size);\n+}\n+\n+KUNIT_ARRAY_PARAM(dma_size, dma_test_sizes, dma_size_desc);\n+\n+static struct page *mock_kho_restore_pages(phys_addr_t phys, unsigned long nr_pages)\n+{\n+\tstruct page *page = phys_to_page(phys);\n+\n+\tif (!kho_test_pages_preserved(phys, nr_pages))\n+\t\treturn NULL;\n+\n+\tkho_unpreserve_pages(page, nr_pages);\n+\treturn page;\n+}\n+\n+static struct folio *mock_kho_restore_folio(phys_addr_t phys)\n+{\n+\tstruct folio *folio = page_folio(phys_to_page(phys));\n+\n+\tif (!kho_test_pages_preserved(phys, (1 \u003c\u003c folio_order(folio))))\n+\t\treturn NULL;\n+\n+\tkho_unpreserve_folio(folio);\n+\treturn folio;\n+}\n+\n+static void test_dma_direct_preserve_restore_common(struct kunit *test,\n+\t\t\t\t\t\t    bool coherent,\n+\t\t\t\t\t\t    bool atomic,\n+\t\t\t\t\t\t    size_t size)\n+{\n+\tunsigned long nr_pages = 1 \u003c\u003c get_order(size);\n+\tdma_addr_t handle1, handle2;\n+\tint ret, expected_ret = 0;\n+\tstruct device dev = {0};\n+\tgfp_t gfp = GFP_KERNEL;\n+\tvoid *addr1, *addr2;\n+\tu64 state;\n+\n+\tkunit_activate_static_stub(test, kho_restore_pages, mock_kho_restore_pages);\n+\tkunit_activate_static_stub(test, kho_restore_folio, mock_kho_restore_folio);\n+\n+\tdevice_initialize(\u0026dev);\n+\tdev.coherent_dma_mask = DMA_BIT_MASK(64);\n+\tdev.dma_mask = \u0026dev.coherent_dma_mask;\n+#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \\\n+    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \\\n+    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)\n+\tdev.dma_coherent = coherent;\n+#endif\n+\n+\tif (atomic)\n+\t\tgfp = GFP_ATOMIC;\n+\n+\taddr1 = dma_alloc_coherent(\u0026dev, size, \u0026handle1, gfp);\n+\tif (!addr1) {\n+\t\tkunit_skip(test, \"DMA allocation failed (unsupported configuration)\");\n+\t\treturn;\n+\t}\n+\n+\tif (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) \u0026\u0026 dma_is_from_pool(addr1, size)) {\n+\t\texpected_ret = -EOPNOTSUPP;\n+\t\tkunit_info(test, \"DMA allocation using pool, expecting -EOPNOTSUPP\");\n+\t} else if (dma_is_from_cma(dma_to_phys(\u0026dev, handle1), size)) {\n+\t\texpected_ret = -EOPNOTSUPP;\n+\t\tkunit_info(test, \"DMA allocation using CMA, expecting -EOPNOTSUPP\");\n+\t}\n+\n+\tret = dma_preserve_coherent_allocation(\u0026dev, addr1, size, handle1, \u0026state);\n+\tKUNIT_ASSERT_EQ(test, ret, expected_ret);\n+\n+\tif (!expected_ret) {\n+\t\tKUNIT_EXPECT_TRUE_MSG(test,\n+\t\t\t\t      kho_test_pages_preserved(dma_to_phys(\u0026dev, handle1), nr_pages),\n+\t\t\t\t      \"Allocated block not tracked in KHO\");\n+\n+\t\taddr2 = dma_restore_coherent_allocation(\u0026dev, size, \u0026handle2, gfp, state);\n+\t\tKUNIT_ASSERT_NOT_NULL(test, addr2);\n+\n+\t\tKUNIT_EXPECT_EQ(test, handle1, handle2);\n+\t\tdma_free_coherent(\u0026dev, size, addr2, handle2);\n+\t\tKUNIT_EXPECT_FALSE_MSG(test,\n+\t\t\t\t       kho_test_pages_preserved(dma_to_phys(\u0026dev, handle1), nr_pages),\n+\t\t\t\t       \"Allocated block still tracked after free\");\n+\t} else {\n+\t\tdma_free_coherent(\u0026dev, size, addr1, handle1);\n+\t}\n+}\n+\n+static void test_dma_direct_coherent(struct kunit *test)\n+{\n+\tconst size_t *size = test-\u003eparam_value;\n+\n+\ttest_dma_direct_preserve_restore_common(test, true, false, *size);\n+}\n+\n+static void test_dma_direct_non_coherent(struct kunit *test)\n+{\n+\tconst size_t *size = test-\u003eparam_value;\n+\n+\ttest_dma_direct_preserve_restore_common(test, false, false, *size);\n+}\n+\n+static void test_dma_direct_coherent_atomic(struct kunit *test)\n+{\n+\tconst size_t *size = test-\u003eparam_value;\n+\n+\ttest_dma_direct_preserve_restore_common(test, true, true, *size);\n+}\n+\n+static void test_dma_direct_non_coherent_atomic(struct kunit *test)\n+{\n+\tconst size_t *size = test-\u003eparam_value;\n+\n+\ttest_dma_direct_preserve_restore_common(test, false, true, *size);\n+}\n+\n+static void test_dmam_preservation(struct kunit *test)\n+{\n+\tdma_addr_t handle, non_dmam_handle;\n+\tvoid *addr, *non_dmam_addr;\n+\tstruct device dev = {0};\n+\tsize_t size = 128;\n+\tu64 state;\n+\tint ret;\n+\n+\tkunit_activate_static_stub(test, kho_restore_pages, mock_kho_restore_pages);\n+\tkunit_activate_static_stub(test, kho_restore_folio, mock_kho_restore_folio);\n+\n+\tdevice_initialize(\u0026dev);\n+\tdev.coherent_dma_mask = DMA_BIT_MASK(64);\n+\tdev.dma_mask = \u0026dev.coherent_dma_mask;\n+#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \\\n+    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \\\n+    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)\n+\tdev.dma_coherent = true;\n+#endif\n+\n+\tnon_dmam_addr = dma_alloc_coherent(\u0026dev, size, \u0026non_dmam_handle, GFP_KERNEL);\n+\tif (!non_dmam_addr) {\n+\t\tkunit_skip(test, \"DMA allocation failed\");\n+\t\treturn;\n+\t}\n+\n+\tret = dmam_preserve_coherent_allocation(\u0026dev, non_dmam_addr, size, non_dmam_handle, \u0026state);\n+\tKUNIT_EXPECT_EQ_MSG(test, ret, -EINVAL, \"Preserving non-devres allocation should fail\");\n+\tdma_free_coherent(\u0026dev, size, non_dmam_addr, non_dmam_handle);\n+\n+\taddr = dmam_alloc_coherent(\u0026dev, size, \u0026handle, GFP_KERNEL);\n+\tif (!addr) {\n+\t\tkunit_skip(test, \"DMA allocation failed\");\n+\t\treturn;\n+\t}\n+\n+\tret = dmam_preserve_coherent_allocation(\u0026dev, addr, size, handle, \u0026state);\n+\tKUNIT_ASSERT_EQ_MSG(test, ret, 0, \"Failed to preserve dmam allocation\");\n+\tKUNIT_EXPECT_TRUE(test, kho_test_pages_preserved(dma_to_phys(\u0026dev, handle), 1));\n+\n+\tdmam_unpreserve_coherent_allocation(\u0026dev, addr, size, handle, state);\n+\tKUNIT_EXPECT_FALSE(test, kho_test_pages_preserved(dma_to_phys(\u0026dev, handle), 1));\n+\n+\tdmam_free_coherent(\u0026dev, size, addr, handle);\n+}\n+\n+static struct kunit_case dma_direct_test_cases[] = {\n+\tKUNIT_CASE_PARAM(test_dma_direct_coherent, dma_size_gen_params),\n+\tKUNIT_CASE_PARAM(test_dma_direct_non_coherent, dma_size_gen_params),\n+\tKUNIT_CASE_PARAM(test_dma_direct_coherent_atomic, dma_size_gen_params),\n+\tKUNIT_CASE_PARAM(test_dma_direct_non_coherent_atomic, dma_size_gen_params),\n+\tKUNIT_CASE(test_dmam_preservation),\n+\t{}\n+};\n+\n+static struct kunit_suite dma_direct_test_suite = {\n+\t.name = \"dma_direct_liveupdate\",\n+\t.test_cases = dma_direct_test_cases,\n+};\n+kunit_test_suite(dma_direct_test_suite);\n+\n+MODULE_IMPORT_NS(\"EXPORTED_FOR_KUNIT_TESTING\");\n+MODULE_DESCRIPTION(\"KUnit test for DMA direct live update preservation\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/kernel/dma/mapping.c b/kernel/dma/mapping.c\nindex 23ed8eb9233e55..aec1222ab5aeb1 100644\n--- a/kernel/dma/mapping.c\n+++ b/kernel/dma/mapping.c\n@@ -12,6 +12,8 @@\n #include \u003clinux/gfp.h\u003e\n #include \u003clinux/iommu-dma.h\u003e\n #include \u003clinux/kmsan.h\u003e\n+#include \u003clinux/kexec_handover.h\u003e\n+#include \u003clinux/kho/abi/dma_alloc.h\u003e\n #include \u003clinux/of_device.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/vmalloc.h\u003e\n@@ -35,12 +37,25 @@ struct dma_devres {\n \tvoid\t\t*vaddr;\n \tdma_addr_t\tdma_handle;\n \tunsigned long\tattrs;\n+#ifdef CONFIG_DMA_LIVEUPDATE\n+\tbool is_preserved;\n+#endif\n };\n \n static void dmam_release(struct device *dev, void *res)\n {\n \tstruct dma_devres *this = res;\n \n+#ifdef CONFIG_DMA_LIVEUPDATE\n+\t/*\n+\t * Freeing the preserved memory is dangerous as it can cause UAF in the\n+\t * current or next kernel if the memory is still being used by the\n+\t * device.\n+\t */\n+\tif (WARN_ON(this-\u003eis_preserved))\n+\t\treturn;\n+#endif\n+\n \tdma_free_attrs(dev, this-\u003esize, this-\u003evaddr, this-\u003edma_handle,\n \t\t\tthis-\u003eattrs);\n }\n@@ -628,6 +643,123 @@ u64 dma_get_required_mask(struct device *dev)\n }\n EXPORT_SYMBOL_GPL(dma_get_required_mask);\n \n+#ifdef CONFIG_DMA_LIVEUPDATE\n+int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n+\t\t\t\t  size_t size, dma_addr_t dma_handle,\n+\t\t\t\t  unsigned long attrs, u64 *state)\n+{\n+\tconst struct dma_map_ops *ops = get_dma_ops(dev);\n+\n+\tif (dma_is_from_dev_coherent(dev, cpu_addr))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (dma_alloc_direct(dev, ops))\n+\t\treturn dma_direct_preserve_allocation(dev, cpu_addr, size,\n+\t\t\t\t\t\t      dma_handle, attrs,\n+\t\t\t\t\t\t      state);\n+\n+\treturn -EOPNOTSUPP;\n+}\n+EXPORT_SYMBOL(dma_preserve_allocation_attrs);\n+\n+void dma_unpreserve_allocation(struct device *dev, u64 state)\n+{\n+\tconst struct dma_map_ops *ops = get_dma_ops(dev);\n+\n+\tif (dma_alloc_direct(dev, ops))\n+\t\tdma_direct_unpreserve_allocation(dev, state);\n+}\n+EXPORT_SYMBOL(dma_unpreserve_allocation);\n+\n+void *dma_restore_allocation_attrs(struct device *dev, size_t size,\n+\t\t\t\t   dma_addr_t *dma_handle, gfp_t gfp,\n+\t\t\t\t   unsigned long attrs, u64 state)\n+{\n+\tconst struct dma_map_ops *ops = get_dma_ops(dev);\n+\tvoid *cpu_addr = NULL;\n+\n+\tWARN_ON_ONCE(!dev-\u003ecoherent_dma_mask);\n+\n+\tif (dma_alloc_direct(dev, ops))\n+\t\tcpu_addr = dma_direct_restore_allocation(dev, size, dma_handle,\n+\t\t\t\t\t\t\t gfp, attrs, state);\n+\n+\tdebug_dma_alloc_coherent(dev, size, *dma_handle, cpu_addr, attrs);\n+\treturn cpu_addr;\n+}\n+EXPORT_SYMBOL(dma_restore_allocation_attrs);\n+\n+int dmam_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n+\t\t\t\t   size_t size, dma_addr_t dma_handle,\n+\t\t\t\t   unsigned long attrs, u64 *state)\n+{\n+\tstruct dma_devres match_data = { size, cpu_addr, dma_handle };\n+\tstruct dma_devres *dr;\n+\tint ret;\n+\n+\tdr = devres_find(dev, dmam_release, dmam_match, \u0026match_data);\n+\tif (!dr)\n+\t\treturn -EINVAL;\n+\n+\tif (dr-\u003eis_preserved)\n+\t\treturn -EINVAL;\n+\n+\tret = dma_preserve_allocation_attrs(dev, cpu_addr, size, dma_handle, attrs, state);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tdr-\u003eis_preserved = true;\n+\treturn 0;\n+}\n+EXPORT_SYMBOL(dmam_preserve_allocation_attrs);\n+\n+void dmam_unpreserve_allocation(struct device *dev, void *cpu_addr,\n+\t\t\t\tsize_t size, dma_addr_t dma_handle, u64 state)\n+{\n+\tstruct dma_devres match_data = { size, cpu_addr, dma_handle };\n+\tstruct dma_devres *dr;\n+\n+\tdr = devres_find(dev, dmam_release, dmam_match, \u0026match_data);\n+\tif (!dr)\n+\t\treturn;\n+\n+\tif (!dr-\u003eis_preserved)\n+\t\treturn;\n+\n+\tdma_unpreserve_allocation(dev, state);\n+\tdr-\u003eis_preserved = false;\n+}\n+EXPORT_SYMBOL(dmam_unpreserve_allocation);\n+\n+void *dmam_restore_allocation_attrs(struct device *dev, size_t size,\n+\t\t\t\t    dma_addr_t *dma_handle, gfp_t gfp,\n+\t\t\t\t    unsigned long attrs, u64 state)\n+{\n+\tstruct dma_devres *dr;\n+\tvoid *vaddr;\n+\n+\tdr = devres_alloc(dmam_release, sizeof(*dr), gfp);\n+\tif (!dr)\n+\t\treturn NULL;\n+\n+\tvaddr = dma_restore_allocation_attrs(dev, size, dma_handle, gfp, attrs, state);\n+\tif (!vaddr) {\n+\t\tdevres_free(dr);\n+\t\treturn NULL;\n+\t}\n+\n+\tdr-\u003evaddr = vaddr;\n+\tdr-\u003edma_handle = *dma_handle;\n+\tdr-\u003esize = size;\n+\tdr-\u003eattrs = attrs;\n+\n+\tdevres_add(dev, dr);\n+\n+\treturn vaddr;\n+}\n+EXPORT_SYMBOL(dmam_restore_allocation_attrs);\n+#endif\n+\n void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle,\n \t\tgfp_t flag, unsigned long attrs)\n {\ndiff --git a/kernel/dma/pool.c b/kernel/dma/pool.c\nindex 2b2fbb7092429a..78496568973feb 100644\n--- a/kernel/dma/pool.c\n+++ b/kernel/dma/pool.c\n@@ -3,6 +3,9 @@\n  * Copyright (C) 2012 ARM Ltd.\n  * Copyright (C) 2020 Google LLC\n  */\n+\n+#include \u003ckunit/visibility.h\u003e\n+\n #include \u003clinux/cma.h\u003e\n #include \u003clinux/debugfs.h\u003e\n #include \u003clinux/dma-map-ops.h\u003e\n@@ -307,3 +310,17 @@ bool dma_free_from_pool(struct device *dev, void *start, size_t size)\n \n \treturn false;\n }\n+\n+bool dma_is_from_pool(void *start, size_t size)\n+{\n+\tstruct gen_pool *pool = NULL;\n+\n+\twhile ((pool = dma_guess_pool(pool, 0))) {\n+\t\tif (!gen_pool_has_addr(pool, (unsigned long)start, size))\n+\t\t\tcontinue;\n+\t\treturn true;\n+\t}\n+\n+\treturn false;\n+}\n+EXPORT_SYMBOL_IF_KUNIT(dma_is_from_pool);\n"
}

2/1 2026/07/09 17:15 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new API for DMA direct liveupdate preservation (e.g., dma_preserve_allocation_attrs, dma_direct_preserve_allocation) and a KUnit test for it. However, the new API is not currently called by any driver or core kernel code outside of the KUnit test itself. Therefore, the modified code is unreachable from userspace and cannot be fuzzed.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 3f1ca197680f6e7ebbf244f7282cd3b04645ac57
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Jul 9 17:15:06 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/dma-direct.h b/include/linux/dma-direct.h
index c249912456f963..0efe2bc1a81524 100644
--- a/include/linux/dma-direct.h
+++ b/include/linux/dma-direct.h
@@ -141,6 +141,35 @@ static inline bool dma_capable(struct device *dev, dma_addr_t addr, size_t size,
 u64 dma_direct_get_required_mask(struct device *dev);
 void *dma_direct_alloc(struct device *dev, size_t size, dma_addr_t *dma_handle,
 		gfp_t gfp, unsigned long attrs);
+
+#ifdef CONFIG_DMA_LIVEUPDATE
+int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,
+				   size_t size, dma_addr_t dma_handle,
+				   unsigned long attrs, u64 *state);
+void dma_direct_unpreserve_allocation(struct device *dev, u64 state);
+void *dma_direct_restore_allocation(struct device *dev, size_t size,
+				    dma_addr_t *dma_handle, gfp_t gfp,
+				    unsigned long attrs, u64 state);
+#else
+static inline int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,
+						 size_t size, dma_addr_t dma_handle,
+						 unsigned long attrs, u64 *state)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void dma_direct_unpreserve_allocation(struct device *dev, u64 state)
+{
+}
+
+static inline void *dma_direct_restore_allocation(struct device *dev, size_t size,
+						  dma_addr_t *dma_handle, gfp_t gfp,
+						  unsigned long attrs, u64 state)
+{
+	return NULL;
+}
+#endif
+
 void dma_direct_free(struct device *dev, size_t size, void *cpu_addr,
 		dma_addr_t dma_addr, unsigned long attrs);
 struct page *dma_direct_alloc_pages(struct device *dev, size_t size,
diff --git a/include/linux/dma-map-ops.h b/include/linux/dma-map-ops.h
index 6a1832a73cad8d..b13e5eebe6d3ae 100644
--- a/include/linux/dma-map-ops.h
+++ b/include/linux/dma-map-ops.h
@@ -106,6 +106,7 @@ struct page *dma_alloc_contiguous(struct device *dev, size_t size, gfp_t gfp);
 void dma_free_contiguous(struct device *dev, struct page *page, size_t size);
 
 void dma_contiguous_early_fixup(phys_addr_t base, unsigned long size);
+bool dma_is_from_cma(phys_addr_t phys, size_t size);
 #else /* CONFIG_DMA_CMA */
 static inline struct cma *dev_get_cma_area(struct device *dev)
 {
@@ -145,6 +146,11 @@ static inline void dma_free_contiguous(struct device *dev, struct page *page,
 {
 	__free_pages(page, get_order(size));
 }
+
+static inline bool dma_is_from_cma(phys_addr_t phys, size_t size)
+{
+	return false;
+}
 #endif /* CONFIG_DMA_CMA*/
 
 #ifdef CONFIG_DMA_DECLARE_COHERENT
@@ -156,6 +162,7 @@ int dma_alloc_from_dev_coherent(struct device *dev, ssize_t size,
 int dma_release_from_dev_coherent(struct device *dev, int order, void *vaddr);
 int dma_mmap_from_dev_coherent(struct device *dev, struct vm_area_struct *vma,
 		void *cpu_addr, size_t size, int *ret);
+bool dma_is_from_dev_coherent(struct device *dev, void *vaddr);
 #else
 static inline int dma_declare_coherent_memory(struct device *dev,
 		phys_addr_t phys_addr, dma_addr_t device_addr, size_t size)
@@ -166,6 +173,8 @@ static inline int dma_declare_coherent_memory(struct device *dev,
 #define dma_alloc_from_dev_coherent(dev, size, handle, ret) (0)
 #define dma_release_from_dev_coherent(dev, order, vaddr) (0)
 #define dma_mmap_from_dev_coherent(dev, vma, vaddr, order, ret) (0)
+#define dma_is_from_dev_coherent(dev, vaddr) (false)
+
 static inline void dma_release_coherent_memory(struct device *dev) { }
 #endif /* CONFIG_DMA_DECLARE_COHERENT */
 
@@ -216,6 +225,7 @@ struct page *dma_alloc_from_pool(struct device *dev, size_t size,
 		bool (*phys_addr_ok)(struct device *, phys_addr_t, size_t));
 bool dma_free_from_pool(struct device *dev, void *start, size_t size);
 
+bool dma_is_from_pool(void *start, size_t size);
 int dma_direct_set_offset(struct device *dev, phys_addr_t cpu_start,
 		dma_addr_t dma_start, u64 size);
 
diff --git a/include/linux/dma-mapping.h b/include/linux/dma-mapping.h
index db8ab24a54f4a3..4c0c428b6b1cb6 100644
--- a/include/linux/dma-mapping.h
+++ b/include/linux/dma-mapping.h
@@ -210,6 +210,23 @@ void *dma_vmap_noncontiguous(struct device *dev, size_t size,
 void dma_vunmap_noncontiguous(struct device *dev, void *vaddr);
 int dma_mmap_noncontiguous(struct device *dev, struct vm_area_struct *vma,
 		size_t size, struct sg_table *sgt);
+#ifdef CONFIG_DMA_LIVEUPDATE
+int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,
+				  size_t size, dma_addr_t dma_handle,
+				  unsigned long attrs, u64 *state);
+void dma_unpreserve_allocation(struct device *dev, u64 state);
+void *dma_restore_allocation_attrs(struct device *dev, size_t size,
+				   dma_addr_t *dma_handle, gfp_t gfp,
+				   unsigned long attrs, u64 state);
+int dmam_preserve_allocation_attrs(struct device *dev, void *cpu_addr,
+				   size_t size, dma_addr_t dma_handle,
+				   unsigned long attrs, u64 *state);
+void dmam_unpreserve_allocation(struct device *dev, void *cpu_addr,
+				size_t size, dma_addr_t dma_handle, u64 state);
+void *dmam_restore_allocation_attrs(struct device *dev, size_t size,
+				    dma_addr_t *dma_handle, gfp_t gfp,
+				    unsigned long attrs, u64 state);
+#endif
 #else /* CONFIG_HAS_DMA */
 static inline dma_addr_t dma_map_page_attrs(struct device *dev,
 		struct page *page, size_t offset, size_t size,
@@ -496,6 +513,45 @@ static inline bool dma_need_unmap(struct device *dev)
 }
 #endif /* !CONFIG_HAS_DMA || !CONFIG_DMA_NEED_SYNC */
 
+#if !defined(CONFIG_DMA_LIVEUPDATE) || !defined(CONFIG_HAS_DMA)
+static inline int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,
+						size_t size, dma_addr_t dma_handle,
+						unsigned long attrs, u64 *state)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void dma_unpreserve_allocation(struct device *dev, u64 state)
+{
+}
+
+static inline void *dma_restore_allocation_attrs(struct device *dev, size_t size,
+						 dma_addr_t *dma_handle, gfp_t gfp,
+						 unsigned long attrs, u64 state)
+{
+	return NULL;
+}
+
+static inline int dmam_preserve_allocation_attrs(struct device *dev, void *cpu_addr,
+						 size_t size, dma_addr_t dma_handle,
+						 unsigned long attrs, u64 *state)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void dmam_unpreserve_allocation(struct device *dev, void *cpu_addr,
+					      size_t size, dma_addr_t dma_handle, u64 state)
+{
+}
+
+static inline void *dmam_restore_allocation_attrs(struct device *dev, size_t size,
+						  dma_addr_t *dma_handle, gfp_t gfp,
+						  unsigned long attrs, u64 state)
+{
+	return NULL;
+}
+#endif
+
 struct page *dma_alloc_pages(struct device *dev, size_t size,
 		dma_addr_t *dma_handle, enum dma_data_direction dir, gfp_t gfp);
 void dma_free_pages(struct device *dev, size_t size, struct page *page,
@@ -618,6 +674,27 @@ static inline void *dma_alloc_coherent(struct device *dev, size_t size,
 			(gfp & __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0);
 }
 
+static inline int dma_preserve_coherent_allocation(struct device *dev, void *cpu_addr,
+						   size_t size, dma_addr_t dma_handle, u64 *state)
+{
+	return dma_preserve_allocation_attrs(dev, cpu_addr, size,
+					     dma_handle, 0, state);
+}
+
+static inline void dma_unpreserve_coherent_allocation(struct device *dev, u64 state)
+{
+	dma_unpreserve_allocation(dev, state);
+}
+
+static inline void *dma_restore_coherent_allocation(struct device *dev, size_t size,
+						    dma_addr_t *dma_handle,
+						    gfp_t gfp, u64 state)
+{
+	return dma_restore_allocation_attrs(dev, size, dma_handle, gfp,
+					    (gfp & __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0,
+					    state);
+}
+
 static inline void dma_free_coherent(struct device *dev, size_t size,
 		void *cpu_addr, dma_addr_t dma_handle)
 {
@@ -747,6 +824,29 @@ static inline void *dmam_alloc_coherent(struct device *dev, size_t size,
 			(gfp & __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0);
 }
 
+static inline int dmam_preserve_coherent_allocation(struct device *dev, void *cpu_addr,
+						    size_t size, dma_addr_t dma_handle, u64 *state)
+{
+	return dmam_preserve_allocation_attrs(dev, cpu_addr, size,
+					      dma_handle, 0, state);
+}
+
+static inline void dmam_unpreserve_coherent_allocation(struct device *dev, void *cpu_addr,
+						       size_t size, dma_addr_t dma_handle,
+						       u64 state)
+{
+	dmam_unpreserve_allocation(dev, cpu_addr, size, dma_handle, state);
+}
+
+static inline void *dmam_restore_coherent_allocation(struct device *dev, size_t size,
+						     dma_addr_t *dma_handle,
+						     gfp_t gfp, u64 state)
+{
+	return dmam_restore_allocation_attrs(dev, size, dma_handle, gfp,
+					     (gfp & __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0,
+					     state);
+}
+
 static inline void *dma_alloc_wc(struct device *dev, size_t size,
 				 dma_addr_t *dma_addr, gfp_t gfp)
 {
diff --git a/include/linux/kho/abi/dma_alloc.h b/include/linux/kho/abi/dma_alloc.h
new file mode 100644
index 00000000000000..ae38cf1a1fc99e
--- /dev/null
+++ b/include/linux/kho/abi/dma_alloc.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_KHO_ABI_DMA_ALLOC_H
+#define _LINUX_KHO_ABI_DMA_ALLOC_H
+
+#include <linux/types.h>
+
+/**
+ * DOC: DMA Alloc ABI
+ *
+ * This header defines the structures used to serialize the state of DMA
+ * allocations, done by device driver, across a Live Update.
+ *
+ * Only DMA allocations done through dma-direct that are contiguous and
+ * allocated using alloc_page are supported.
+ */
+
+/**
+ * struct dma_alloc_ser - Serialized state of a single DMA allocation
+ * @page_phys: Physical address of the preserved pages
+ * @size: Size of the DMA allocation
+ * @attrs: DMA allocation attributes
+ * @force_decrypted: Whether the memory is force decrypted in previous kernel
+ */
+struct dma_alloc_ser {
+	u64 page_phys;
+	u64 size;
+	u64 attrs;
+	u8 force_decrypted;
+	u8 padding[7];
+} __packed;
+
+#endif /* _LINUX_KHO_ABI_DMA_ALLOC_H */
diff --git a/kernel/dma/Kconfig b/kernel/dma/Kconfig
index bfef21b4a9ae7d..48bc74af551a31 100644
--- a/kernel/dma/Kconfig
+++ b/kernel/dma/Kconfig
@@ -265,3 +265,15 @@ config DMA_MAP_BENCHMARK
 	  performance of dma_(un)map_page.
 
 	  See tools/testing/selftests/dma/dma_map_benchmark.c
+
+config DMA_LIVEUPDATE
+	bool "Enable preservation of DMA direct allocations"
+	depends on LIVEUPDATE
+
+config DMA_LIVEUPDATE_KUNIT_TEST
+	tristate "KUnit test for DMA direct liveupdate preservation"
+	depends on KUNIT
+	depends on DMA_LIVEUPDATE
+	help
+	  This builds the KUnit tests for the DMA direct liveupdate
+	  preservation and restoration logic.
diff --git a/kernel/dma/Makefile b/kernel/dma/Makefile
index 6977033444a375..03703542ee79bc 100644
--- a/kernel/dma/Makefile
+++ b/kernel/dma/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_SWIOTLB)			+= swiotlb.o
 obj-$(CONFIG_DMA_COHERENT_POOL)		+= pool.o
 obj-$(CONFIG_MMU)			+= remap.o
 obj-$(CONFIG_DMA_MAP_BENCHMARK)		+= map_benchmark.o
+obj-$(CONFIG_DMA_LIVEUPDATE_KUNIT_TEST)	+= direct_test.o
diff --git a/kernel/dma/coherent.c b/kernel/dma/coherent.c
index bcdc0f76d2e80a..76cbe11e1f90e7 100644
--- a/kernel/dma/coherent.c
+++ b/kernel/dma/coherent.c
@@ -231,6 +231,17 @@ int dma_release_from_dev_coherent(struct device *dev, int order, void *vaddr)
 	return __dma_release_from_coherent(mem, order, vaddr);
 }
 
+bool dma_is_from_dev_coherent(struct device *dev, void *vaddr)
+{
+	struct dma_coherent_mem *mem = dev_get_coherent_memory(dev);
+
+	if (mem && vaddr >= mem->virt_base && vaddr <
+		   (mem->virt_base + ((dma_addr_t)mem->size << PAGE_SHIFT)))
+		return true;
+
+	return false;
+}
+
 static int __dma_mmap_from_coherent(struct dma_coherent_mem *mem,
 		struct vm_area_struct *vma, void *vaddr, size_t size, int *ret)
 {
diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c
index 03f52bd1712062..6d0cf9c7528cb5 100644
--- a/kernel/dma/contiguous.c
+++ b/kernel/dma/contiguous.c
@@ -37,6 +37,8 @@
 
 #define pr_fmt(fmt) "cma: " fmt
 
+#include <kunit/visibility.h>
+
 #include <asm/page.h>
 
 #include <linux/memblock.h>
@@ -130,6 +132,31 @@ struct cma *dev_get_cma_area(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(dev_get_cma_area);
 
+struct dma_cma_check_data {
+	phys_addr_t start;
+	phys_addr_t end;
+};
+
+static int dma_cma_check_area(struct cma *cma, void *data)
+{
+	struct dma_cma_check_data *chk = data;
+
+	if (cma_intersects(cma, chk->start, chk->end))
+		return 1;
+	return 0;
+}
+
+bool dma_is_from_cma(phys_addr_t phys, size_t size)
+{
+	struct dma_cma_check_data chk = {
+		.start = phys,
+		.end = phys + size - 1,
+	};
+
+	return cma_for_each_area(dma_cma_check_area, &chk) != 0;
+}
+EXPORT_SYMBOL_IF_KUNIT(dma_is_from_cma);
+
 #ifdef CONFIG_DMA_NUMA_CMA
 
 static struct cma *dma_contiguous_numa_area[MAX_NUMNODES];
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index ec887f44374183..117ebae8a81fa1 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -6,6 +6,8 @@
  */
 #include <linux/memblock.h> /* for max_pfn */
 #include <linux/export.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/dma_alloc.h>
 #include <linux/mm.h>
 #include <linux/dma-map-ops.h>
 #include <linux/scatterlist.h>
@@ -307,6 +309,193 @@ void *dma_direct_alloc(struct device *dev, size_t size,
 	return NULL;
 }
 
+#ifdef CONFIG_DMA_LIVEUPDATE
+int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,
+				   size_t size, dma_addr_t dma_handle,
+				   unsigned long attrs, u64 *state)
+{
+	struct dma_alloc_ser *ser;
+	int ret;
+
+	if (!kho_is_enabled())
+		return -EOPNOTSUPP;
+
+	size = PAGE_ALIGN(size);
+	if (dma_is_from_cma(dma_to_phys(dev, dma_handle), size))
+		return -EOPNOTSUPP;
+
+	if (is_swiotlb_for_alloc(dev))
+		return -EOPNOTSUPP;
+
+	if ((attrs & DMA_ATTR_NO_KERNEL_MAPPING) &&
+	    !force_dma_unencrypted(dev))
+		return -EOPNOTSUPP;
+
+	if (IS_ENABLED(CONFIG_ARCH_HAS_DMA_ALLOC) &&
+	    !dev_is_dma_coherent(dev))
+		return -EOPNOTSUPP;
+
+	if (IS_ENABLED(CONFIG_DMA_GLOBAL_POOL) &&
+	    !dev_is_dma_coherent(dev))
+		return -EOPNOTSUPP;
+
+	if (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) &&
+	    dma_is_from_pool(cpu_addr, size))
+		return -EOPNOTSUPP;
+
+	ser = kho_alloc_preserve(sizeof(*ser));
+	if (IS_ERR(ser))
+		return PTR_ERR(ser);
+
+	ser->page_phys = dma_to_phys(dev, dma_handle);
+	ser->force_decrypted = force_dma_unencrypted(dev);
+	ser->size = size;
+	ser->attrs = (attrs & ~DMA_ATTR_NO_WARN);
+
+	ret = kho_preserve_pages(phys_to_page(ser->page_phys),
+				 1 << get_order(size));
+	if (ret) {
+		kho_unpreserve_free(ser);
+		return ret;
+	}
+
+	*state = virt_to_phys(ser);
+	return 0;
+}
+
+void dma_direct_unpreserve_allocation(struct device *dev, u64 state)
+{
+	struct dma_alloc_ser *ser;
+
+	if (!kho_is_enabled())
+		return;
+
+	ser = phys_to_virt(state);
+	kho_unpreserve_pages(phys_to_page(ser->page_phys),
+			     1 << get_order(ser->size));
+	kho_unpreserve_free(ser);
+}
+
+void *dma_direct_restore_allocation(struct device *dev, size_t size,
+				    dma_addr_t *dma_handle, gfp_t gfp,
+				    unsigned long attrs, u64 state)
+{
+	bool remap = false, set_uncached = false;
+	struct dma_alloc_ser *ser = NULL;
+	struct page *page;
+	void *cpu_addr = NULL;
+	int i;
+
+	if (!kho_is_enabled())
+		return NULL;
+
+	ser = phys_to_virt(state);
+	page = phys_to_page(ser->page_phys);
+
+	size = PAGE_ALIGN(size);
+	if (size != ser->size)
+		return NULL;
+
+	WARN_ON_ONCE((attrs & ~DMA_ATTR_NO_WARN) != ser->attrs);
+
+	/*
+	 * Cannot free the restored pages on error here as these might be in use
+	 * by a device with direct allocation in the previous kernel.
+	 */
+	if (is_swiotlb_for_alloc(dev))
+		goto err;
+
+	if ((attrs & DMA_ATTR_NO_KERNEL_MAPPING) &&
+	    !force_dma_unencrypted(dev))
+		goto err;
+
+	if (!dev_is_dma_coherent(dev)) {
+		if (IS_ENABLED(CONFIG_ARCH_HAS_DMA_ALLOC))
+			goto err;
+
+		if (IS_ENABLED(CONFIG_DMA_GLOBAL_POOL))
+			goto err;
+
+		set_uncached = IS_ENABLED(CONFIG_ARCH_HAS_DMA_SET_UNCACHED);
+		remap = IS_ENABLED(CONFIG_DMA_DIRECT_REMAP);
+		if (!set_uncached && !remap)
+			goto err;
+	}
+
+	if (PageHighMem(page)) {
+		remap = true;
+		set_uncached = false;
+	}
+
+	/*
+	 * Remapping will be blocking so return error. The preserved memory
+	 * might be already decrypted in the previous kernel, but the decryption
+	 * call is not guaranteed to be non-blocking so return error always if
+	 * decryption is required.
+	 */
+	if ((remap || force_dma_unencrypted(dev)) &&
+	    dma_direct_use_pool(dev, gfp))
+		goto err;
+
+	/*
+	 * Encryption scheme changed between two kernels and this might cause
+	 * issues if device/driver is not handling it properly.
+	 */
+	WARN_ON_ONCE(ser->force_decrypted != force_dma_unencrypted(dev));
+
+	/*
+	 * arch_dma_prep_coherent() should make sure that any cache lines from
+	 * the previous kernel, if the device was coherent previously or cached
+	 * mapping in this kernel during init are not problematic for
+	 * non-coherent allocations.
+	 */
+	if (remap) {
+		pgprot_t prot = dma_pgprot(dev, PAGE_KERNEL, attrs);
+
+		if (force_dma_unencrypted(dev))
+			prot = pgprot_decrypted(prot);
+
+		arch_dma_prep_coherent(page, size);
+
+		cpu_addr = dma_common_contiguous_remap(page, size, prot,
+						       __builtin_return_address(0));
+		if (!cpu_addr)
+			goto err;
+	} else {
+		cpu_addr = page_address(page);
+		if (dma_set_decrypted(dev, cpu_addr, size)) {
+			cpu_addr = NULL;
+			goto err;
+		}
+	}
+
+	if (set_uncached) {
+		arch_dma_prep_coherent(page, size);
+		cpu_addr = arch_dma_set_uncached(cpu_addr, size);
+		if (IS_ERR(cpu_addr)) {
+			cpu_addr = NULL;
+			goto err;
+		}
+	}
+
+	*dma_handle = phys_to_dma_direct(dev, ser->page_phys);
+
+err:
+	WARN_ON(!kho_restore_pages(ser->page_phys,
+				   1 << get_order(ser->size)));
+	kho_restore_free(ser);
+
+	/*
+	 * DMA direct API expects high-order allocations, so set refcount of
+	 * tail pages to zero.
+	 */
+	for (i = (1 << get_order(size)) - 1; i > 0; --i)
+		set_page_count(page + i, 0);
+
+	return cpu_addr;
+}
+#endif
+
 void dma_direct_free(struct device *dev, size_t size,
 		void *cpu_addr, dma_addr_t dma_addr, unsigned long attrs)
 {
diff --git a/kernel/dma/direct_test.c b/kernel/dma/direct_test.c
new file mode 100644
index 00000000000000..f8403ec47807e8
--- /dev/null
+++ b/kernel/dma/direct_test.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit test for DMA allocation live update preservation.
+ */
+
+#include <kunit/test.h>
+#include <linux/module.h>
+#include <kunit/static_stub.h>
+#include <linux/dma-map-ops.h>
+#include <linux/dma-mapping.h>
+#include <linux/dma-direct.h>
+#include <linux/kho/abi/dma_alloc.h>
+#include <linux/kexec_handover.h>
+#include <linux/slab.h>
+
+static const size_t dma_test_sizes[] = {
+	128,
+	PAGE_SIZE,
+	2 * PAGE_SIZE,
+	3 * PAGE_SIZE,
+	8 * PAGE_SIZE,
+};
+
+static void dma_size_desc(const size_t *size, char *desc)
+{
+	snprintf(desc, KUNIT_PARAM_DESC_SIZE, "size=%zu", *size);
+}
+
+KUNIT_ARRAY_PARAM(dma_size, dma_test_sizes, dma_size_desc);
+
+static struct page *mock_kho_restore_pages(phys_addr_t phys, unsigned long nr_pages)
+{
+	struct page *page = phys_to_page(phys);
+
+	if (!kho_test_pages_preserved(phys, nr_pages))
+		return NULL;
+
+	kho_unpreserve_pages(page, nr_pages);
+	return page;
+}
+
+static struct folio *mock_kho_restore_folio(phys_addr_t phys)
+{
+	struct folio *folio = page_folio(phys_to_page(phys));
+
+	if (!kho_test_pages_preserved(phys, (1 << folio_order(folio))))
+		return NULL;
+
+	kho_unpreserve_folio(folio);
+	return folio;
+}
+
+static void test_dma_direct_preserve_restore_common(struct kunit *test,
+						    bool coherent,
+						    bool atomic,
+						    size_t size)
+{
+	unsigned long nr_pages = 1 << get_order(size);
+	dma_addr_t handle1, handle2;
+	int ret, expected_ret = 0;
+	struct device dev = {0};
+	gfp_t gfp = GFP_KERNEL;
+	void *addr1, *addr2;
+	u64 state;
+
+	kunit_activate_static_stub(test, kho_restore_pages, mock_kho_restore_pages);
+	kunit_activate_static_stub(test, kho_restore_folio, mock_kho_restore_folio);
+
+	device_initialize(&dev);
+	dev.coherent_dma_mask = DMA_BIT_MASK(64);
+	dev.dma_mask = &dev.coherent_dma_mask;
+#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
+    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
+    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
+	dev.dma_coherent = coherent;
+#endif
+
+	if (atomic)
+		gfp = GFP_ATOMIC;
+
+	addr1 = dma_alloc_coherent(&dev, size, &handle1, gfp);
+	if (!addr1) {
+		kunit_skip(test, "DMA allocation failed (unsupported configuration)");
+		return;
+	}
+
+	if (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) && dma_is_from_pool(addr1, size)) {
+		expected_ret = -EOPNOTSUPP;
+		kunit_info(test, "DMA allocation using pool, expecting -EOPNOTSUPP");
+	} else if (dma_is_from_cma(dma_to_phys(&dev, handle1), size)) {
+		expected_ret = -EOPNOTSUPP;
+		kunit_info(test, "DMA allocation using CMA, expecting -EOPNOTSUPP");
+	}
+
+	ret = dma_preserve_coherent_allocation(&dev, addr1, size, handle1, &state);
+	KUNIT_ASSERT_EQ(test, ret, expected_ret);
+
+	if (!expected_ret) {
+		KUNIT_EXPECT_TRUE_MSG(test,
+				      kho_test_pages_preserved(dma_to_phys(&dev, handle1), nr_pages),
+				      "Allocated block not tracked in KHO");
+
+		addr2 = dma_restore_coherent_allocation(&dev, size, &handle2, gfp, state);
+		KUNIT_ASSERT_NOT_NULL(test, addr2);
+
+		KUNIT_EXPECT_EQ(test, handle1, handle2);
+		dma_free_coherent(&dev, size, addr2, handle2);
+		KUNIT_EXPECT_FALSE_MSG(test,
+				       kho_test_pages_preserved(dma_to_phys(&dev, handle1), nr_pages),
+				       "Allocated block still tracked after free");
+	} else {
+		dma_free_coherent(&dev, size, addr1, handle1);
+	}
+}
+
+static void test_dma_direct_coherent(struct kunit *test)
+{
+	const size_t *size = test->param_value;
+
+	test_dma_direct_preserve_restore_common(test, true, false, *size);
+}
+
+static void test_dma_direct_non_coherent(struct kunit *test)
+{
+	const size_t *size = test->param_value;
+
+	test_dma_direct_preserve_restore_common(test, false, false, *size);
+}
+
+static void test_dma_direct_coherent_atomic(struct kunit *test)
+{
+	const size_t *size = test->param_value;
+
+	test_dma_direct_preserve_restore_common(test, true, true, *size);
+}
+
+static void test_dma_direct_non_coherent_atomic(struct kunit *test)
+{
+	const size_t *size = test->param_value;
+
+	test_dma_direct_preserve_restore_common(test, false, true, *size);
+}
+
+static void test_dmam_preservation(struct kunit *test)
+{
+	dma_addr_t handle, non_dmam_handle;
+	void *addr, *non_dmam_addr;
+	struct device dev = {0};
+	size_t size = 128;
+	u64 state;
+	int ret;
+
+	kunit_activate_static_stub(test, kho_restore_pages, mock_kho_restore_pages);
+	kunit_activate_static_stub(test, kho_restore_folio, mock_kho_restore_folio);
+
+	device_initialize(&dev);
+	dev.coherent_dma_mask = DMA_BIT_MASK(64);
+	dev.dma_mask = &dev.coherent_dma_mask;
+#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
+    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
+    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
+	dev.dma_coherent = true;
+#endif
+
+	non_dmam_addr = dma_alloc_coherent(&dev, size, &non_dmam_handle, GFP_KERNEL);
+	if (!non_dmam_addr) {
+		kunit_skip(test, "DMA allocation failed");
+		return;
+	}
+
+	ret = dmam_preserve_coherent_allocation(&dev, non_dmam_addr, size, non_dmam_handle, &state);
+	KUNIT_EXPECT_EQ_MSG(test, ret, -EINVAL, "Preserving non-devres allocation should fail");
+	dma_free_coherent(&dev, size, non_dmam_addr, non_dmam_handle);
+
+	addr = dmam_alloc_coherent(&dev, size, &handle, GFP_KERNEL);
+	if (!addr) {
+		kunit_skip(test, "DMA allocation failed");
+		return;
+	}
+
+	ret = dmam_preserve_coherent_allocation(&dev, addr, size, handle, &state);
+	KUNIT_ASSERT_EQ_MSG(test, ret, 0, "Failed to preserve dmam allocation");
+	KUNIT_EXPECT_TRUE(test, kho_test_pages_preserved(dma_to_phys(&dev, handle), 1));
+
+	dmam_unpreserve_coherent_allocation(&dev, addr, size, handle, state);
+	KUNIT_EXPECT_FALSE(test, kho_test_pages_preserved(dma_to_phys(&dev, handle), 1));
+
+	dmam_free_coherent(&dev, size, addr, handle);
+}
+
+static struct kunit_case dma_direct_test_cases[] = {
+	KUNIT_CASE_PARAM(test_dma_direct_coherent, dma_size_gen_params),
+	KUNIT_CASE_PARAM(test_dma_direct_non_coherent, dma_size_gen_params),
+	KUNIT_CASE_PARAM(test_dma_direct_coherent_atomic, dma_size_gen_params),
+	KUNIT_CASE_PARAM(test_dma_direct_non_coherent_atomic, dma_size_gen_params),
+	KUNIT_CASE(test_dmam_preservation),
+	{}
+};
+
+static struct kunit_suite dma_direct_test_suite = {
+	.name = "dma_direct_liveupdate",
+	.test_cases = dma_direct_test_cases,
+};
+kunit_test_suite(dma_direct_test_suite);
+
+MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
+MODULE_DESCRIPTION("KUnit test for DMA direct live update preservation");
+MODULE_LICENSE("GPL");
diff --git a/kernel/dma/mapping.c b/kernel/dma/mapping.c
index 23ed8eb9233e55..aec1222ab5aeb1 100644
--- a/kernel/dma/mapping.c
+++ b/kernel/dma/mapping.c
@@ -12,6 +12,8 @@
 #include <linux/gfp.h>
 #include <linux/iommu-dma.h>
 #include <linux/kmsan.h>
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/dma_alloc.h>
 #include <linux/of_device.h>
 #include <linux/slab.h>
 #include <linux/vmalloc.h>
@@ -35,12 +37,25 @@ struct dma_devres {
 	void		*vaddr;
 	dma_addr_t	dma_handle;
 	unsigned long	attrs;
+#ifdef CONFIG_DMA_LIVEUPDATE
+	bool is_preserved;
+#endif
 };
 
 static void dmam_release(struct device *dev, void *res)
 {
 	struct dma_devres *this = res;
 
+#ifdef CONFIG_DMA_LIVEUPDATE
+	/*
+	 * Freeing the preserved memory is dangerous as it can cause UAF in the
+	 * current or next kernel if the memory is still being used by the
+	 * device.
+	 */
+	if (WARN_ON(this->is_preserved))
+		return;
+#endif
+
 	dma_free_attrs(dev, this->size, this->vaddr, this->dma_handle,
 			this->attrs);
 }
@@ -628,6 +643,123 @@ u64 dma_get_required_mask(struct device *dev)
 }
 EXPORT_SYMBOL_GPL(dma_get_required_mask);
 
+#ifdef CONFIG_DMA_LIVEUPDATE
+int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,
+				  size_t size, dma_addr_t dma_handle,
+				  unsigned long attrs, u64 *state)
+{
+	const struct dma_map_ops *ops = get_dma_ops(dev);
+
+	if (dma_is_from_dev_coherent(dev, cpu_addr))
+		return -EOPNOTSUPP;
+
+	if (dma_alloc_direct(dev, ops))
+		return dma_direct_preserve_allocation(dev, cpu_addr, size,
+						      dma_handle, attrs,
+						      state);
+
+	return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL(dma_preserve_allocation_attrs);
+
+void dma_unpreserve_allocation(struct device *dev, u64 state)
+{
+	const struct dma_map_ops *ops = get_dma_ops(dev);
+
+	if (dma_alloc_direct(dev, ops))
+		dma_direct_unpreserve_allocation(dev, state);
+}
+EXPORT_SYMBOL(dma_unpreserve_allocation);
+
+void *dma_restore_allocation_attrs(struct device *dev, size_t size,
+				   dma_addr_t *dma_handle, gfp_t gfp,
+				   unsigned long attrs, u64 state)
+{
+	const struct dma_map_ops *ops = get_dma_ops(dev);
+	void *cpu_addr = NULL;
+
+	WARN_ON_ONCE(!dev->coherent_dma_mask);
+
+	if (dma_alloc_direct(dev, ops))
+		cpu_addr = dma_direct_restore_allocation(dev, size, dma_handle,
+							 gfp, attrs, state);
+
+	debug_dma_alloc_coherent(dev, size, *dma_handle, cpu_addr, attrs);
+	return cpu_addr;
+}
+EXPORT_SYMBOL(dma_restore_allocation_attrs);
+
+int dmam_preserve_allocation_attrs(struct device *dev, void *cpu_addr,
+				   size_t size, dma_addr_t dma_handle,
+				   unsigned long attrs, u64 *state)
+{
+	struct dma_devres match_data = { size, cpu_addr, dma_handle };
+	struct dma_devres *dr;
+	int ret;
+
+	dr = devres_find(dev, dmam_release, dmam_match, &match_data);
+	if (!dr)
+		return -EINVAL;
+
+	if (dr->is_preserved)
+		return -EINVAL;
+
+	ret = dma_preserve_allocation_attrs(dev, cpu_addr, size, dma_handle, attrs, state);
+	if (ret)
+		return ret;
+
+	dr->is_preserved = true;
+	return 0;
+}
+EXPORT_SYMBOL(dmam_preserve_allocation_attrs);
+
+void dmam_unpreserve_allocation(struct device *dev, void *cpu_addr,
+				size_t size, dma_addr_t dma_handle, u64 state)
+{
+	struct dma_devres match_data = { size, cpu_addr, dma_handle };
+	struct dma_devres *dr;
+
+	dr = devres_find(dev, dmam_release, dmam_match, &match_data);
+	if (!dr)
+		return;
+
+	if (!dr->is_preserved)
+		return;
+
+	dma_unpreserve_allocation(dev, state);
+	dr->is_preserved = false;
+}
+EXPORT_SYMBOL(dmam_unpreserve_allocation);
+
+void *dmam_restore_allocation_attrs(struct device *dev, size_t size,
+				    dma_addr_t *dma_handle, gfp_t gfp,
+				    unsigned long attrs, u64 state)
+{
+	struct dma_devres *dr;
+	void *vaddr;
+
+	dr = devres_alloc(dmam_release, sizeof(*dr), gfp);
+	if (!dr)
+		return NULL;
+
+	vaddr = dma_restore_allocation_attrs(dev, size, dma_handle, gfp, attrs, state);
+	if (!vaddr) {
+		devres_free(dr);
+		return NULL;
+	}
+
+	dr->vaddr = vaddr;
+	dr->dma_handle = *dma_handle;
+	dr->size = size;
+	dr->attrs = attrs;
+
+	devres_add(dev, dr);
+
+	return vaddr;
+}
+EXPORT_SYMBOL(dmam_restore_allocation_attrs);
+#endif
+
 void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle,
 		gfp_t flag, unsigned long attrs)
 {
diff --git a/kernel/dma/pool.c b/kernel/dma/pool.c
index 2b2fbb7092429a..78496568973feb 100644
--- a/kernel/dma/pool.c
+++ b/kernel/dma/pool.c
@@ -3,6 +3,9 @@
  * Copyright (C) 2012 ARM Ltd.
  * Copyright (C) 2020 Google LLC
  */
+
+#include <kunit/visibility.h>
+
 #include <linux/cma.h>
 #include <linux/debugfs.h>
 #include <linux/dma-map-ops.h>
@@ -307,3 +310,17 @@ bool dma_free_from_pool(struct device *dev, void *start, size_t size)
 
 	return false;
 }
+
+bool dma_is_from_pool(void *start, size_t size)
+{
+	struct gen_pool *pool = NULL;
+
+	while ((pool = dma_guess_pool(pool, 0))) {
+		if (!gen_pool_has_addr(pool, (unsigned long)start, size))
+			continue;
+		return true;
+	}
+
+	return false;
+}
+EXPORT_SYMBOL_IF_KUNIT(dma_is_from_pool);

3/2 2026/07/09 17:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13665
							output: 22
							thoughts: 197
						

Thoughts:
**Analyzing the DMA Direct Liveupdate Preservation Patch Series**

Okay, so I'm looking at this patch series, and it's all about adding support for "DMA direct liveupdate preservation," which is clearly tied into the Kexec HandOver (KHO) system. They've introduced new functions like `dma_direct_preserve_allocation`, `dma_direct_unpreserve_allocation`, and `dma_direct_restore_allocation`, along with a KUnit test in `kernel/dma/direct_test.c`. Seems straightforward enough; they've also added the expected configuration options: `DMA_LIVEUPDATE` and `DMA_LIVEUPDATE_KUNIT_TEST`.

But now the real question is, how accessible is this? Is this functionality meant to be purely internal to the kernel, or are we exposing this to userspace somehow? I need to figure out the reachability here. The functions *are* exported, which is a bit of a red flag in terms of being used. `dma_preserve_allocation_attrs` and `dma_restore_allocation_attrs` are exported, for example. So, I need to know if any of them are actually *used* by anything, or if they're just being exported for the sake of completeness. Time to do a quick search. I'll search for calls to `dma_preserve_allocation_attrs` and `dma_preserve_coherent_allocation` to determine its use.



4/2 2026/07/09 17:15 tool grepper
0m Args:
{
  "Expression": "dma_preserve_allocation_attrs"
}

Results:
{
  "Output": "include/linux/dma-mapping.h=211=int dma_mmap_noncontiguous(struct device *dev, struct vm_area_struct *vma,\n--\ninclude/linux/dma-mapping.h-213-#ifdef CONFIG_DMA_LIVEUPDATE\ninclude/linux/dma-mapping.h:214:int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\ninclude/linux/dma-mapping.h-215-\t\t\t\t  size_t size, dma_addr_t dma_handle,\n--\ninclude/linux/dma-mapping.h=510=static inline bool dma_need_unmap(struct device *dev)\n--\ninclude/linux/dma-mapping.h-516-#if !defined(CONFIG_DMA_LIVEUPDATE) || !defined(CONFIG_HAS_DMA)\ninclude/linux/dma-mapping.h:517:static inline int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\ninclude/linux/dma-mapping.h-518-\t\t\t\t\t\tsize_t size, dma_addr_t dma_handle,\n--\ninclude/linux/dma-mapping.h=677=static inline int dma_preserve_coherent_allocation(struct device *dev, void *cpu_addr,\n--\ninclude/linux/dma-mapping.h-679-{\ninclude/linux/dma-mapping.h:680:\treturn dma_preserve_allocation_attrs(dev, cpu_addr, size,\ninclude/linux/dma-mapping.h-681-\t\t\t\t\t     dma_handle, 0, state);\n--\nkernel/dma/mapping.c=644=EXPORT_SYMBOL_GPL(dma_get_required_mask);\n--\nkernel/dma/mapping.c-646-#ifdef CONFIG_DMA_LIVEUPDATE\nkernel/dma/mapping.c:647:int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\nkernel/dma/mapping.c-648-\t\t\t\t  size_t size, dma_addr_t dma_handle,\n--\nkernel/dma/mapping.c-662-}\nkernel/dma/mapping.c:663:EXPORT_SYMBOL(dma_preserve_allocation_attrs);\nkernel/dma/mapping.c-664-\n--\nkernel/dma/mapping.c=692=int dmam_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n--\nkernel/dma/mapping.c-706-\nkernel/dma/mapping.c:707:\tret = dma_preserve_allocation_attrs(dev, cpu_addr, size, dma_handle, attrs, state);\nkernel/dma/mapping.c-708-\tif (ret)\n"
}

5/2 2026/07/09 17:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14906
							output: 22
							thoughts: 55
						

Thoughts:
Okay, let's see what we're dealing with here.

**Analyzing Kernel Function Usage**

Alright, the code defines and exports something, but it's not actually *used* anywhere else in the kernel source tree... interesting. That suggests it's either: a) an incomplete feature, b) a piece of infrastructure for future use, or c) something that has been added and *should* be used, but isn't being correctly called. 

Before I jump to any conclusions, I need to be absolutely sure. Let me quickly grep the codebase for `dma_preserve_coherent_allocation` and `dmam_preserve_coherent_allocation`. I need to rule out the possibility that it's just being invoked under a slightly different name or through a macro or inline function. Checking this will save me a lot of guesswork.



6/2 2026/07/09 17:15 tool grepper
0m Args:
{
  "Expression": "dma_preserve_coherent_allocation"
}

Results:
{
  "Output": "include/linux/dma-mapping.h=670=static inline void *dma_alloc_coherent(struct device *dev, size_t size,\n--\ninclude/linux/dma-mapping.h-676-\ninclude/linux/dma-mapping.h:677:static inline int dma_preserve_coherent_allocation(struct device *dev, void *cpu_addr,\ninclude/linux/dma-mapping.h-678-\t\t\t\t\t\t   size_t size, dma_addr_t dma_handle, u64 *state)\n--\nkernel/dma/direct_test.c=53=static void test_dma_direct_preserve_restore_common(struct kunit *test,\n--\nkernel/dma/direct_test.c-94-\nkernel/dma/direct_test.c:95:\tret = dma_preserve_coherent_allocation(\u0026dev, addr1, size, handle1, \u0026state);\nkernel/dma/direct_test.c-96-\tKUNIT_ASSERT_EQ(test, ret, expected_ret);\n"
}

7/2 2026/07/09 17:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15433
							output: 22
							thoughts: 44
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Kernel DMA Memory Allocation Analysis**

Alright, let me see... based on the initial scan, it *looks* like these `dmam_preserve_coherent_allocation` functions are exclusively utilized within the KUnit test located in `kernel/dma/direct_test.c`. That's a bit surprising. I expected broader usage, given the nature of coherent DMA allocation. I mean, it's a fundamental operation.

However, the immediate conclusion that I can draw is that they are utilized only in this one specific area.

But, before I proceed with any strong conclusion or assumptions, I need to double-check. *Is* that truly the case? I need to perform a more rigorous search to confirm that there isn't some subtle, indirect usage or call path that I missed initially. I need to make *sure* there aren't any other instances of `dmam_preserve_coherent_allocation` being called, perhaps through an intermediary function or a macro. I don't want to jump to the wrong conclusions. I need a broader scope of the test to make my statement correct.



8/2 2026/07/09 17:15 tool grepper
0m Args:
{
  "Expression": "dmam_preserve_coherent_allocation"
}

Results:
{
  "Output": "include/linux/dma-mapping.h=820=static inline void *dmam_alloc_coherent(struct device *dev, size_t size,\n--\ninclude/linux/dma-mapping.h-826-\ninclude/linux/dma-mapping.h:827:static inline int dmam_preserve_coherent_allocation(struct device *dev, void *cpu_addr,\ninclude/linux/dma-mapping.h-828-\t\t\t\t\t\t    size_t size, dma_addr_t dma_handle, u64 *state)\n--\nkernel/dma/direct_test.c=144=static void test_dmam_preservation(struct kunit *test)\n--\nkernel/dma/direct_test.c-170-\nkernel/dma/direct_test.c:171:\tret = dmam_preserve_coherent_allocation(\u0026dev, non_dmam_addr, size, non_dmam_handle, \u0026state);\nkernel/dma/direct_test.c-172-\tKUNIT_EXPECT_EQ_MSG(test, ret, -EINVAL, \"Preserving non-devres allocation should fail\");\n--\nkernel/dma/direct_test.c-180-\nkernel/dma/direct_test.c:181:\tret = dmam_preserve_coherent_allocation(\u0026dev, addr, size, handle, \u0026state);\nkernel/dma/direct_test.c-182-\tKUNIT_ASSERT_EQ_MSG(test, ret, 0, \"Failed to preserve dmam allocation\");\n"
}

9/2 2026/07/09 17:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16144
							output: 23
							thoughts: 279
						

Thoughts:
**My Assessment of the DMA Code Changes**

Okay, so I'm looking at these new functions, and it seems they're exclusively called within the KUnit tests. That raises a red flag. Since they're not exposed to userspace – no system calls, no driver integrations yet – they're completely off the radar for syzkaller. And wait, syzkaller *doesn't* fuzz KUnit tests directly anyway, does it? So even if it *could*, we're looking at a closed-off API right now.

I need to broaden my scope. Are there any other modified files? Let's see... a whole slew of them! `include/linux/dma-direct.h`, `include/linux/dma-map-ops.h`, `include/linux/dma-mapping.h`, `include/linux/kho/abi/dma_alloc.h`, and then a bunch of files under `kernel/dma/`: `Kconfig`, `Makefile`, `coherent.c`, `contiguous.c`, `direct.c`, `direct_test.c`, `mapping.c`, and `pool.c`.

Right. Before I dig too deep, I need to check something quickly. I need to know if these newly added functions: `dma_is_from_cma`, `dma_is_from_dev_coherent`, and `dma_is_from_pool`, are *actually* used anywhere else in the code base. That’s the critical piece of information.



10/2 2026/07/09 17:15 tool grepper
0m Args:
{
  "Expression": "dma_is_from_cma"
}

Results:
{
  "Output": "include/linux/dma-map-ops.h=108=void dma_contiguous_early_fixup(phys_addr_t base, unsigned long size);\ninclude/linux/dma-map-ops.h:109:bool dma_is_from_cma(phys_addr_t phys, size_t size);\ninclude/linux/dma-map-ops.h-110-#else /* CONFIG_DMA_CMA */\n--\ninclude/linux/dma-map-ops.h=144=static inline void dma_free_contiguous(struct device *dev, struct page *page,\n--\ninclude/linux/dma-map-ops.h-149-\ninclude/linux/dma-map-ops.h:150:static inline bool dma_is_from_cma(phys_addr_t phys, size_t size)\ninclude/linux/dma-map-ops.h-151-{\n--\nkernel/dma/contiguous.c=140=static int dma_cma_check_area(struct cma *cma, void *data)\n--\nkernel/dma/contiguous.c-148-\nkernel/dma/contiguous.c:149:bool dma_is_from_cma(phys_addr_t phys, size_t size)\nkernel/dma/contiguous.c-150-{\n--\nkernel/dma/contiguous.c-157-}\nkernel/dma/contiguous.c:158:EXPORT_SYMBOL_IF_KUNIT(dma_is_from_cma);\nkernel/dma/contiguous.c-159-\n--\nkernel/dma/direct.c=313=int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,\n--\nkernel/dma/direct.c-323-\tsize = PAGE_ALIGN(size);\nkernel/dma/direct.c:324:\tif (dma_is_from_cma(dma_to_phys(dev, dma_handle), size))\nkernel/dma/direct.c-325-\t\treturn -EOPNOTSUPP;\n--\nkernel/dma/direct_test.c=53=static void test_dma_direct_preserve_restore_common(struct kunit *test,\n--\nkernel/dma/direct_test.c-89-\t\tkunit_info(test, \"DMA allocation using pool, expecting -EOPNOTSUPP\");\nkernel/dma/direct_test.c:90:\t} else if (dma_is_from_cma(dma_to_phys(\u0026dev, handle1), size)) {\nkernel/dma/direct_test.c-91-\t\texpected_ret = -EOPNOTSUPP;\n"
}

11/2 2026/07/09 17:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17463
							output: 22
							thoughts: 128
						

Thoughts:
**Unreachable Code? A Deep Dive**

Okay, so I've been looking at this code, and it seems pretty isolated. I'm seeing that these functions, particularly `dma_preserve_allocation_attrs` and `dma_direct_preserve_allocation`, are only utilized within the new functions I'm examining and, of course, the KUnit tests. That's the immediate scope. But here's the kicker: as far as I can tell, these new functions aren't actually called *anywhere* else within the kernel proper. No drivers, no core kernel code – nothing is directly calling either `dma_preserve_allocation_attrs` or `dma_direct_preserve_allocation`.

So, the obvious conclusion is this code is unreachable from userspace. But, I need to be *sure*. Wait, could there be a call somewhere else? Could this be related to some Kexec HandOver code? This feels familiar somehow. I need to eliminate the possibility that this is a hidden call in another part of the kernel. Time to go hunting for other instances of `dma_preserve_allocation_attrs` within the kernel source. I need to be absolutely thorough.



12/2 2026/07/09 17:15 tool grepper
0m Args:
{
  "Expression": "dma_preserve_allocation_attrs"
}

Results:
{
  "Output": "include/linux/dma-mapping.h=211=int dma_mmap_noncontiguous(struct device *dev, struct vm_area_struct *vma,\n--\ninclude/linux/dma-mapping.h-213-#ifdef CONFIG_DMA_LIVEUPDATE\ninclude/linux/dma-mapping.h:214:int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\ninclude/linux/dma-mapping.h-215-\t\t\t\t  size_t size, dma_addr_t dma_handle,\n--\ninclude/linux/dma-mapping.h=510=static inline bool dma_need_unmap(struct device *dev)\n--\ninclude/linux/dma-mapping.h-516-#if !defined(CONFIG_DMA_LIVEUPDATE) || !defined(CONFIG_HAS_DMA)\ninclude/linux/dma-mapping.h:517:static inline int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\ninclude/linux/dma-mapping.h-518-\t\t\t\t\t\tsize_t size, dma_addr_t dma_handle,\n--\ninclude/linux/dma-mapping.h=677=static inline int dma_preserve_coherent_allocation(struct device *dev, void *cpu_addr,\n--\ninclude/linux/dma-mapping.h-679-{\ninclude/linux/dma-mapping.h:680:\treturn dma_preserve_allocation_attrs(dev, cpu_addr, size,\ninclude/linux/dma-mapping.h-681-\t\t\t\t\t     dma_handle, 0, state);\n--\nkernel/dma/mapping.c=644=EXPORT_SYMBOL_GPL(dma_get_required_mask);\n--\nkernel/dma/mapping.c-646-#ifdef CONFIG_DMA_LIVEUPDATE\nkernel/dma/mapping.c:647:int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\nkernel/dma/mapping.c-648-\t\t\t\t  size_t size, dma_addr_t dma_handle,\n--\nkernel/dma/mapping.c-662-}\nkernel/dma/mapping.c:663:EXPORT_SYMBOL(dma_preserve_allocation_attrs);\nkernel/dma/mapping.c-664-\n--\nkernel/dma/mapping.c=692=int dmam_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n--\nkernel/dma/mapping.c-706-\nkernel/dma/mapping.c:707:\tret = dma_preserve_allocation_attrs(dev, cpu_addr, size, dma_handle, attrs, state);\nkernel/dma/mapping.c-708-\tif (ret)\n"
}

13/2 2026/07/09 17:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18576
							output: 18
							thoughts: 84
						

Thoughts:
**My Assessment of the DMA Liveupdate Preservation Patch**

Okay, so this patch claims to bring in a new API specifically for DMA liveupdate preservation, right? But hang on a second...it's not actually *used* anywhere yet, by any driver or core system. Hmm. So, logically, that means it's totally inaccessible from userspace at this stage. And if it's not exposed, that means we can't even try to fuzz it. Not very exciting, honestly.

Wait... am I missing something? Are there any subtle changes creeping into existing functions? It’s always the hidden stuff that gets you. Let's not make assumptions. Better to be thorough. I'll take a look at `dmam_release` in `kernel/dma/mapping.c`. That's the logical place to start looking for any collateral effects.



14/2 2026/07/09 17:16 tool grepper
0m Args:
{
  "Expression": "dmam_release"
}

Results:
{
  "Output": "drivers/net/ethernet/freescale/fec_main.c=413=struct fec_dma_devres {\n--\ndrivers/net/ethernet/freescale/fec_main.c-418-\ndrivers/net/ethernet/freescale/fec_main.c:419:static void fec_dmam_release(struct device *dev, void *res)\ndrivers/net/ethernet/freescale/fec_main.c-420-{\n--\ndrivers/net/ethernet/freescale/fec_main.c=426=static void *fec_dmam_alloc(struct device *dev, size_t size, dma_addr_t *handle,\n--\ndrivers/net/ethernet/freescale/fec_main.c-431-\ndrivers/net/ethernet/freescale/fec_main.c:432:\tdr = devres_alloc(fec_dmam_release, sizeof(*dr), gfp);\ndrivers/net/ethernet/freescale/fec_main.c-433-\tif (!dr)\n--\nkernel/dma/mapping.c=35=struct dma_devres {\n--\nkernel/dma/mapping.c-44-\nkernel/dma/mapping.c:45:static void dmam_release(struct device *dev, void *res)\nkernel/dma/mapping.c-46-{\n--\nkernel/dma/mapping.c=84=void dmam_free_coherent(struct device *dev, size_t size, void *vaddr,\n--\nkernel/dma/mapping.c-88-\nkernel/dma/mapping.c:89:\tWARN_ON(devres_destroy(dev, dmam_release, dmam_match, \u0026match_data));\nkernel/dma/mapping.c-90-\tdma_free_coherent(dev, size, vaddr, dma_handle);\n--\nkernel/dma/mapping.c=108=void *dmam_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle,\n--\nkernel/dma/mapping.c-113-\nkernel/dma/mapping.c:114:\tdr = devres_alloc(dmam_release, sizeof(*dr), gfp);\nkernel/dma/mapping.c-115-\tif (!dr)\n--\nkernel/dma/mapping.c=692=int dmam_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n--\nkernel/dma/mapping.c-699-\nkernel/dma/mapping.c:700:\tdr = devres_find(dev, dmam_release, dmam_match, \u0026match_data);\nkernel/dma/mapping.c-701-\tif (!dr)\n--\nkernel/dma/mapping.c=716=void dmam_unpreserve_allocation(struct device *dev, void *cpu_addr,\n--\nkernel/dma/mapping.c-721-\nkernel/dma/mapping.c:722:\tdr = devres_find(dev, dmam_release, dmam_match, \u0026match_data);\nkernel/dma/mapping.c-723-\tif (!dr)\n--\nkernel/dma/mapping.c=734=void *dmam_restore_allocation_attrs(struct device *dev, size_t size,\n--\nkernel/dma/mapping.c-740-\nkernel/dma/mapping.c:741:\tdr = devres_alloc(dmam_release, sizeof(*dr), gfp);\nkernel/dma/mapping.c-742-\tif (!dr)\n"
}

15/2 2026/07/09 17:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19788
							output: 24
							thoughts: 231
						

Thoughts:
**Analyzing the DMA Release Patch**

Okay, so I'm looking at this `dmam_release` patch and it's making some changes related to DMA live updates. Specifically, it's adding a check:

```c
#ifdef CONFIG_DMA_LIVEUPDATE
	/*
	 * Freeing the preserved memory is dangerous as it can cause UAF in the
	 * current or next kernel if the memory is still being used by the
	 * device.
	 */
	if (WARN_ON(this->is_preserved))
		return;
#endif
```

Now, the issue I'm seeing is that `this->is_preserved` seems to *only* be set in `dmam_preserve_allocation_attrs`, and as far as I can tell, *that* function is never actually called! The allocation is zero-initialized by `devres_alloc`, so the flag will always be false or zero. This means this check will never actually trigger. Therefore, this code is functionally dead in any reachable execution path. It's a no-op, at least for the current kernel configuration.

The rest of the patch appears to be introducing new functions and a KUnit test. However, since the new API doesn't appear to be used *anywhere* in the existing code, it's completely unreachable. There are no callers. Thus, fuzzing those new functions wouldn't be valuable since they aren't part of any execution path.

Just to be absolutely certain, I should quickly verify if `dma_is_from_dev_coherent` is utilized anywhere else in the code base. That should be a quick check.



16/2 2026/07/09 17:16 tool grepper
0m Args:
{
  "Expression": "dma_is_from_dev_coherent"
}

Results:
{
  "Output": "include/linux/dma-map-ops.h=163=int dma_mmap_from_dev_coherent(struct device *dev, struct vm_area_struct *vma,\ninclude/linux/dma-map-ops.h-164-\t\tvoid *cpu_addr, size_t size, int *ret);\ninclude/linux/dma-map-ops.h:165:bool dma_is_from_dev_coherent(struct device *dev, void *vaddr);\ninclude/linux/dma-map-ops.h-166-#else\ninclude/linux/dma-map-ops.h=167=static inline int dma_declare_coherent_memory(struct device *dev,\n--\ninclude/linux/dma-map-ops.h-175-#define dma_mmap_from_dev_coherent(dev, vma, vaddr, order, ret) (0)\ninclude/linux/dma-map-ops.h:176:#define dma_is_from_dev_coherent(dev, vaddr) (false)\ninclude/linux/dma-map-ops.h-177-\n--\nkernel/dma/coherent.c=227=int dma_release_from_dev_coherent(struct device *dev, int order, void *vaddr)\n--\nkernel/dma/coherent.c-233-\nkernel/dma/coherent.c:234:bool dma_is_from_dev_coherent(struct device *dev, void *vaddr)\nkernel/dma/coherent.c-235-{\n--\nkernel/dma/mapping.c=647=int dma_preserve_allocation_attrs(struct device *dev, void *cpu_addr,\n--\nkernel/dma/mapping.c-652-\nkernel/dma/mapping.c:653:\tif (dma_is_from_dev_coherent(dev, cpu_addr))\nkernel/dma/mapping.c-654-\t\treturn -EOPNOTSUPP;\n"
}

17/2 2026/07/09 17:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20905
							output: 22
							thoughts: 36
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing DMA Allocation Attributes**

Right, it's clear the core focus here is solely within `dma_preserve_allocation_attrs`. I need to ensure this is the exclusive location.

Before I commit to that, I need to thoroughly verify that `dma_is_from_pool` isn't used elsewhere in the codebase. I need to make *absolutely* certain that I'm not overlooking any other potential calls or dependencies that could be impacted by this. A thorough grep or code inspection is in order. I'll need to confirm that `dma_is_from_pool` is only referenced within that one context.



18/2 2026/07/09 17:16 tool grepper
0m Args:
{
  "Expression": "dma_is_from_pool"
}

Results:
{
  "Output": "include/linux/dma-map-ops.h=226=bool dma_free_from_pool(struct device *dev, void *start, size_t size);\ninclude/linux/dma-map-ops.h-227-\ninclude/linux/dma-map-ops.h:228:bool dma_is_from_pool(void *start, size_t size);\ninclude/linux/dma-map-ops.h-229-int dma_direct_set_offset(struct device *dev, phys_addr_t cpu_start,\n--\nkernel/dma/direct.c=313=int dma_direct_preserve_allocation(struct device *dev, void *cpu_addr,\n--\nkernel/dma/direct.c-342-\tif (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) \u0026\u0026\nkernel/dma/direct.c:343:\t    dma_is_from_pool(cpu_addr, size))\nkernel/dma/direct.c-344-\t\treturn -EOPNOTSUPP;\n--\nkernel/dma/direct_test.c=53=static void test_dma_direct_preserve_restore_common(struct kunit *test,\n--\nkernel/dma/direct_test.c-86-\nkernel/dma/direct_test.c:87:\tif (IS_ENABLED(CONFIG_DMA_COHERENT_POOL) \u0026\u0026 dma_is_from_pool(addr1, size)) {\nkernel/dma/direct_test.c-88-\t\texpected_ret = -EOPNOTSUPP;\n--\nkernel/dma/pool.c=300=bool dma_free_from_pool(struct device *dev, void *start, size_t size)\n--\nkernel/dma/pool.c-313-\nkernel/dma/pool.c:314:bool dma_is_from_pool(void *start, size_t size)\nkernel/dma/pool.c-315-{\n--\nkernel/dma/pool.c-325-}\nkernel/dma/pool.c:326:EXPORT_SYMBOL_IF_KUNIT(dma_is_from_pool);\n"
}

19/2 2026/07/09 17:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21673
							output: 115
							thoughts: 62
						

Thoughts:
**Analysis of the Proposed Patch**

Okay, after reviewing this patch, it seems the changes are exclusively within the `dma_direct_preserve_allocation` function and the associated KUnit test. This means the primary impact is the addition of an API that, as far as I can tell from the context, isn't actually *used* at this stage, along with a test for that unused API.

Given the limited scope and the lack of any actual runtime usage of the added function, it's pretty clear that fuzzing this patch would be a waste of resources. It's essentially testing an unused function in isolation, and any failures here wouldn't necessarily reveal meaningful vulnerabilities or integration issues in the wider system.

Therefore, my assessment is that this patch is *not* worth fuzzing. I'll flag the `WorthFuzzing` property as `false`, and document this reasoning explicitly, because even though KUnit tests are important, they don't always indicate if something needs to be fuzzed.



20/2 2026/07/09 17:16 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new API for DMA direct liveupdate preservation (e.g., dma_preserve_allocation_attrs, dma_direct_preserve_allocation) and a KUnit test for it. However, the new API is not currently called by any driver or core kernel code outside of the KUnit test itself. Therefore, the modified code is unreachable from userspace and cannot be fuzzed.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new API for DMA direct liveupdate preservation (e.g., dma_preserve_allocation_attrs, dma_direct_preserve_allocation) and a KUnit test for it. However, the new API is not currently called by any driver or core kernel code outside of the KUnit test itself. Therefore, the modified code is unreachable from userspace and cannot be fuzzed.",
  "WorthFuzzing": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)