AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/01 23:35 flow patch-triage
3m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new DMA-BUF heap for NVIDIA Tegra Video-Protection-Region (VPR). The driver (tegra-vpr.c) only initializes if a specific device tree node ('nvidia,tegra-video-protection-region') is present, which is not the case in standard virtualized environments like QEMU. Furthermore, the driver relies on Tegra-specific SMC calls to a secure monitor. The changes to core CMA logic (mm/cma.c) are either executed only during early boot or are dead code (e.g., cma_for_each_area has no callers). Therefore, the functional changes are impossible to reach and fuzz in a virtualized environment.",
  "WorthFuzzing": false
}

1/1 2026/07/01 23:35 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit b30d69576a1da6241030cb4edb32e0197943e1b9\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Jul 1 23:35:04 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml\nindex 7200095ef19e3c..1e27a731ad9ad2 100644\n--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml\n+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml\n@@ -67,6 +67,14 @@ properties:\n       - const: dma-mem # read\n       - const: write\n \n+  memory-region:\n+    items:\n+      - description: reference to the video protection memory region\n+\n+  memory-region-names:\n+    items:\n+      - const: protected\n+\n   dma-coherent: true\n \n additionalProperties: false\ndiff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra186-dc.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra186-dc.yaml\nindex ce4589466a186b..881bfbf4764dfa 100644\n--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra186-dc.yaml\n+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra186-dc.yaml\n@@ -57,6 +57,16 @@ properties:\n       - const: dma-mem # read-0\n       - const: read-1\n \n+  memory-region:\n+    minItems: 1\n+    maxItems: 2\n+\n+  memory-region-names:\n+    items:\n+      enum: [ framebuffer, protected ]\n+    minItems: 1\n+    maxItems: 2\n+\n   nvidia,outputs:\n     description: A list of phandles of outputs that this display\n       controller can drive.\ndiff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml\nindex 69be95afd56277..a012644eeb7d7a 100644\n--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml\n+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml\n@@ -65,7 +65,15 @@ properties:\n     items:\n       - description: phandle to the core power domain\n \n-  memory-region: true\n+  memory-region:\n+    minItems: 1\n+    maxItems: 2\n+\n+  memory-region-names:\n+    items:\n+      enum: [ framebuffer, protected ]\n+    minItems: 1\n+    maxitems: 2\n \n   nvidia,head:\n     $ref: /schemas/types.yaml#/definitions/uint32\ndiff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml\nindex 3563378a01af47..f45be30835a8b3 100644\n--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml\n+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml\n@@ -96,6 +96,13 @@ properties:\n     items:\n       - description: phandle to the HEG or core power domain\n \n+  memory-region:\n+    maxItems: 1\n+\n+  memory-region-names:\n+    items:\n+      - const: protected\n+\n required:\n   - compatible\n   - interrupts\ndiff --git a/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml b/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml\nindex 0b7561c8b9bb2c..72313f776dbd8f 100644\n--- a/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml\n+++ b/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml\n@@ -60,6 +60,14 @@ properties:\n       - const: dma-mem\n       - const: write\n \n+  memory-region:\n+    items:\n+      - description: reference to the video protection memory region\n+\n+  memory-region-names:\n+    items:\n+      - const: protected\n+\n   nvidia,memory-controller:\n     $ref: /schemas/types.yaml#/definitions/phandle\n     description:\ndiff --git a/Documentation/devicetree/bindings/reserved-memory/nvidia,tegra-video-protection-region.yaml b/Documentation/devicetree/bindings/reserved-memory/nvidia,tegra-video-protection-region.yaml\nnew file mode 100644\nindex 00000000000000..1c524bae9ce320\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/reserved-memory/nvidia,tegra-video-protection-region.yaml\n@@ -0,0 +1,76 @@\n+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/reserved-memory/nvidia,tegra-video-protection-region.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: NVIDIA Tegra Video Protection Region (VPR)\n+\n+maintainers:\n+  - Thierry Reding \u003cthierry.reding@gmail.com\u003e\n+  - Jon Hunter \u003cjonathanh@nvidia.com\u003e\n+\n+description: |\n+  NVIDIA Tegra chips have long supported a mechanism to protect a single,\n+  contiguous memory region from non-secure memory accesses. Typically this\n+  region is used for decoding and playback of DRM protected content. Various\n+  devices, such as the display controller and multimedia engines (video\n+  decoder) can access this region in a secure way. Access from the CPU is\n+  generally forbidden.\n+\n+  Two variants exist for VPR: one is fixed in both the base address and size,\n+  while the other is resizable. Fixed VPR can be described by just a \"reg\"\n+  property specifying the base address and size, whereas the resizable VPR\n+  is defined by a size/alignment pair of properties. For resizable VPR the\n+  memory is reusable by the rest of the system when it's unused for VPR and\n+  therefore the \"reusable\" property must be specified along with it. For a\n+  fixed VPR, the memory is permanently protected, and therefore it's not\n+  reusable and must also be marked as \"no-map\" to prevent any (including\n+  speculative) accesses to it.\n+\n+allOf:\n+  - $ref: reserved-memory.yaml\n+\n+properties:\n+  compatible:\n+    const: nvidia,tegra-video-protection-region\n+\n+dependencies:\n+  size: [alignment, reusable]\n+  alignment: [size, reusable]\n+  reusable: [alignment, size]\n+\n+  reg: [no-map]\n+  no-map: [reg]\n+\n+unevaluatedProperties: false\n+\n+oneOf:\n+  - required:\n+      - compatible\n+      - reg\n+\n+  - required:\n+      - compatible\n+      - size\n+\n+examples:\n+  - |\n+    /* resizable VPR */\n+    protected {\n+      compatible = \"nvidia,tegra-video-protection-region\";\n+\n+      size = \u003c0x0 0x70000000\u003e;\n+      alignment = \u003c0x0 0x100000\u003e;\n+      reusable;\n+    };\n+\n+  - |\n+    /* fixed VPR */\n+    protected@2a8000000 {\n+      compatible = \"nvidia,tegra-video-protection-region\";\n+\n+      /* fixed VPR */\n+      reg = \u003c0x2 0xa8000000 0x0 0x70000000\u003e;\n+      no-map;\n+    };\ndiff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c\nindex f9bc53b60f9974..934952ab210275 100644\n--- a/arch/arm/mm/dma-mapping.c\n+++ b/arch/arm/mm/dma-mapping.c\n@@ -254,7 +254,7 @@ struct dma_contig_early_reserve {\n \tunsigned long size;\n };\n \n-static struct dma_contig_early_reserve dma_mmu_remap[MAX_CMA_AREAS] __initdata;\n+static struct dma_contig_early_reserve dma_mmu_remap[MAX_EARLY_CMA_AREAS] __initdata;\n \n static int dma_mmu_remap_num __initdata;\n \ndiff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi\nindex 8e0c51e496e20d..38637e8e6fc92c 100644\n--- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi\n+++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi\n@@ -29,6 +29,45 @@ aliases {\n \t\ti2c8 = \u0026dp_aux_ch3_i2c;\n \t};\n \n+\treserved-memory {\n+\t\t#address-cells = \u003c2\u003e;\n+\t\t#size-cells = \u003c2\u003e;\n+\t\tranges;\n+\n+\t\tvpr: protected {\n+\t\t\tcompatible = \"nvidia,tegra-video-protection-region\";\n+\t\t\tstatus = \"disabled\";\n+\n+\t\t\t/*\n+\t\t\t * Two variants exist for this. For fixed VPR, the\n+\t\t\t * firmware is supposed to update the \"reg\" property\n+\t\t\t * with the fixed memory region configured as VPR.\n+\t\t\t *\n+\t\t\t * For resizable VPR we don't care about the exact\n+\t\t\t * address and instead want a reserved region to be\n+\t\t\t * allocated with a certain size and alignment at\n+\t\t\t * boot time.\n+\t\t\t *\n+\t\t\t * The below assumes resizable VPR by default. If the\n+\t\t\t * firmwares sets up fixed VPR, it is responsible for\n+\t\t\t * adding the missing \"reg\" property, removing any of\n+\t\t\t * the unused properties, as well as adding a unit-\n+\t\t\t * address matching the \"reg\" property.\n+\t\t\t */\n+\n+\t\t\t/* fixed VPR */\n+\t\t\t/*\n+\t\t\treg = \u003c0x0 0x0 0x0 0x0\u003e;\n+\t\t\tno-map;\n+\t\t\t*/\n+\n+\t\t\t/* resizable VPR */\n+\t\t\tsize = \u003c0x0 0x70000000\u003e;\n+\t\t\talignment = \u003c0x0 0x100000\u003e;\n+\t\t\treusable;\n+\t\t};\n+\t};\n+\n \tbus@0 {\n \t\tcompatible = \"simple-bus\";\n \n@@ -4440,6 +4479,9 @@ vic@15340000 {\n \t\t\t\tinterconnect-names = \"dma-mem\", \"write\";\n \t\t\t\tiommus = \u003c\u0026smmu_niso1 TEGRA234_SID_VIC\u003e;\n \t\t\t\tdma-coherent;\n+\n+\t\t\t\tmemory-region = \u003c\u0026vpr\u003e;\n+\t\t\t\tmemory-region-names = \"protected\";\n \t\t\t};\n \n \t\t\tnvdec@15480000 {\n@@ -4458,6 +4500,9 @@ nvdec@15480000 {\n \t\t\t\tiommus = \u003c\u0026smmu_niso1 TEGRA234_SID_NVDEC\u003e;\n \t\t\t\tdma-coherent;\n \n+\t\t\t\tmemory-region = \u003c\u0026vpr\u003e;\n+\t\t\t\tmemory-region-names = \"protected\";\n+\n \t\t\t\tnvidia,memory-controller = \u003c\u0026mc\u003e;\n \n \t\t\t\t/*\ndiff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi\nindex 2d8e7e37830ff2..cc93c6822a7397 100644\n--- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi\n+++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi\n@@ -23,6 +23,39 @@ shmem_bpmp: shmem@86070000 {\n \t\t\treg = \u003c0x0 0x86070000 0x0 0x2000\u003e;\n \t\t\tno-map;\n \t\t};\n+\n+\t\tvpr: protected {\n+\t\t\tcompatible = \"nvidia,tegra-video-protection-region\";\n+\t\t\tstatus = \"disabled\";\n+\n+\t\t\t/*\n+\t\t\t * Two variants exist for this. For fixed VPR, the\n+\t\t\t * firmware is supposed to update the \"reg\" property\n+\t\t\t * with the fixed memory region configured as VPR.\n+\t\t\t *\n+\t\t\t * For resizable VPR we don't care about the exact\n+\t\t\t * address and instead want a reserved region to be\n+\t\t\t * allocated with a certain size and alignment at\n+\t\t\t * boot time.\n+\t\t\t *\n+\t\t\t * The below assumes resizable VPR by default. If the\n+\t\t\t * firmwares sets up fixed VPR, it is responsible for\n+\t\t\t * adding the missing \"reg\" property, removing any of\n+\t\t\t * the unused properties, as well as adding a unit-\n+\t\t\t * address matching the \"reg\" property.\n+\t\t\t */\n+\n+\t\t\t/* fixed VPR */\n+\t\t\t/*\n+\t\t\treg = \u003c0x0 0x0 0x0 0x0\u003e;\n+\t\t\tno-map;\n+\t\t\t*/\n+\n+\t\t\t/* resizable VPR */\n+\t\t\tsize = \u003c0x0 0x70000000\u003e;\n+\t\t\talignment = \u003c0x0 0x100000\u003e;\n+\t\t\treusable;\n+\t\t};\n \t};\n \n \t/* SYSTEM MMIO */\ndiff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h\nindex 90f61b17275e1b..6a7030609789bb 100644\n--- a/arch/arm64/include/asm/set_memory.h\n+++ b/arch/arm64/include/asm/set_memory.h\n@@ -10,6 +10,8 @@ bool can_set_direct_map(void);\n #define can_set_direct_map can_set_direct_map\n \n int set_memory_valid(unsigned long addr, int numpages, int enable);\n+int set_memory_device(unsigned long addr, int numpages);\n+int set_memory_normal(unsigned long addr, int numpages);\n \n int set_direct_map_invalid_noflush(struct page *page);\n int set_direct_map_default_noflush(struct page *page);\ndiff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c\nindex bbe98ac9ad8c67..871b59a6c9eaf3 100644\n--- a/arch/arm64/mm/pageattr.c\n+++ b/arch/arm64/mm/pageattr.c\n@@ -251,6 +251,22 @@ int set_memory_valid(unsigned long addr, int numpages, int enable)\n \t\t\t\t\t__pgprot(PTE_PRESENT_VALID_KERNEL));\n }\n \n+int set_memory_device(unsigned long addr, int numpages)\n+{\n+\treturn __change_memory_common(addr, PAGE_SIZE * numpages,\n+\t\t\t\t__pgprot(PROT_DEVICE_nGnRnE),\n+\t\t\t\t__pgprot(PROT_NORMAL));\n+}\n+EXPORT_SYMBOL_GPL(set_memory_device);\n+\n+int set_memory_normal(unsigned long addr, int numpages)\n+{\n+\treturn __change_memory_common(addr, PAGE_SIZE * numpages,\n+\t\t\t\t__pgprot(PROT_NORMAL),\n+\t\t\t\t__pgprot(PROT_DEVICE_nGnRnE));\n+}\n+EXPORT_SYMBOL_GPL(set_memory_normal);\n+\n int set_direct_map_invalid_noflush(struct page *page)\n {\n \tpgprot_t clear_mask = __pgprot(PTE_PRESENT_VALID_KERNEL);\ndiff --git a/arch/s390/mm/init.c b/arch/s390/mm/init.c\nindex f07168a0d3dd34..f8f78f1434eafb 100644\n--- a/arch/s390/mm/init.c\n+++ b/arch/s390/mm/init.c\n@@ -241,7 +241,7 @@ static int s390_cma_mem_notifier(struct notifier_block *nb,\n \tmem_data.start = arg-\u003estart_pfn \u003c\u003c PAGE_SHIFT;\n \tmem_data.end = mem_data.start + (arg-\u003enr_pages \u003c\u003c PAGE_SHIFT);\n \tif (action == MEM_GOING_OFFLINE)\n-\t\trc = cma_for_each_area(s390_cma_check_range, \u0026mem_data);\n+\t\trc = cma_for_each_early_area(s390_cma_check_range, \u0026mem_data);\n \treturn notifier_from_errno(rc);\n }\n \ndiff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c\nindex a76bf3f8b071a3..1ceb6ee8c05a21 100644\n--- a/drivers/dma-buf/dma-heap.c\n+++ b/drivers/dma-buf/dma-heap.c\n@@ -7,6 +7,7 @@\n  */\n \n #include \u003clinux/cdev.h\u003e\n+#include \u003clinux/debugfs.h\u003e\n #include \u003clinux/device.h\u003e\n #include \u003clinux/dma-buf.h\u003e\n #include \u003clinux/dma-heap.h\u003e\n@@ -224,6 +225,46 @@ const char *dma_heap_get_name(struct dma_heap *heap)\n }\n EXPORT_SYMBOL_NS_GPL(dma_heap_get_name, \"DMA_BUF_HEAP\");\n \n+#ifdef CONFIG_DEBUG_FS\n+static int dma_heap_debug_show(struct seq_file *s, void *unused)\n+{\n+\tstruct dma_heap *heap = s-\u003eprivate;\n+\tint err = 0;\n+\n+\tif (heap-\u003eops \u0026\u0026 heap-\u003eops-\u003eshow)\n+\t\terr = heap-\u003eops-\u003eshow(s, heap);\n+\n+\treturn err;\n+}\n+DEFINE_SHOW_ATTRIBUTE(dma_heap_debug);\n+\n+static struct dentry *dma_heap_debugfs_dir;\n+\n+static void dma_heap_init_debugfs(void)\n+{\n+\tstruct dentry *dir;\n+\n+\tdir = debugfs_create_dir(\"dma_heap\", NULL);\n+\tif (IS_ERR(dir))\n+\t\treturn;\n+\n+\tdma_heap_debugfs_dir = dir;\n+}\n+\n+static void dma_heap_exit_debugfs(void)\n+{\n+\tdebugfs_remove_recursive(dma_heap_debugfs_dir);\n+}\n+#else\n+static void dma_heap_init_debugfs(void)\n+{\n+}\n+\n+static void dma_heap_exit_debugfs(void)\n+{\n+}\n+#endif\n+\n /**\n  * dma_heap_add - adds a heap to dmabuf heaps\n  * @exp_info: information needed to register this heap\n@@ -298,6 +339,13 @@ struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info)\n \n \t/* Add heap to the list */\n \tlist_add(\u0026heap-\u003elist, \u0026heap_list);\n+\n+#ifdef CONFIG_DEBUG_FS\n+\tif (heap-\u003eops \u0026\u0026 heap-\u003eops-\u003eshow)\n+\t\tdebugfs_create_file(heap-\u003ename, 0444, dma_heap_debugfs_dir,\n+\t\t\t\t    heap, \u0026dma_heap_debug_fops);\n+#endif\n+\n \tmutex_unlock(\u0026heap_list_lock);\n \n \treturn heap;\n@@ -334,6 +382,14 @@ static int dma_heap_init(void)\n \t}\n \tdma_heap_class-\u003edevnode = dma_heap_devnode;\n \n+\tdma_heap_init_debugfs();\n+\n \treturn 0;\n }\n subsys_initcall(dma_heap_init);\n+\n+static void __exit dma_heap_exit(void)\n+{\n+\tdma_heap_exit_debugfs();\n+}\n+__exitcall(dma_heap_exit);\ndiff --git a/drivers/dma-buf/heaps/Kconfig b/drivers/dma-buf/heaps/Kconfig\nindex bb729e91545c40..dd6035598d0288 100644\n--- a/drivers/dma-buf/heaps/Kconfig\n+++ b/drivers/dma-buf/heaps/Kconfig\n@@ -20,3 +20,10 @@ config DMABUF_HEAPS_CMA\n \t  Choose this option to enable dma-buf CMA heap. This heap is backed\n \t  by the Contiguous Memory Allocator (CMA). If your system has these\n \t  regions, you should say Y here.\n+\n+config DMABUF_HEAPS_TEGRA_VPR\n+\tbool \"NVIDIA Tegra Video-Protected-Region DMA-BUF Heap\"\n+\tdepends on DMABUF_HEAPS \u0026\u0026 DMA_CMA\n+\thelp\n+\t  Choose this option to enable Video-Protected-Region (VPR) support on\n+\t  a range of NVIDIA Tegra devices.\ndiff --git a/drivers/dma-buf/heaps/Makefile b/drivers/dma-buf/heaps/Makefile\nindex 974467791032ff..265b77a7b88998 100644\n--- a/drivers/dma-buf/heaps/Makefile\n+++ b/drivers/dma-buf/heaps/Makefile\n@@ -1,3 +1,4 @@\n # SPDX-License-Identifier: GPL-2.0\n obj-$(CONFIG_DMABUF_HEAPS_SYSTEM)\t+= system_heap.o\n obj-$(CONFIG_DMABUF_HEAPS_CMA)\t\t+= cma_heap.o\n+obj-$(CONFIG_DMABUF_HEAPS_TEGRA_VPR)\t+= tegra-vpr.o\ndiff --git a/drivers/dma-buf/heaps/tegra-vpr.c b/drivers/dma-buf/heaps/tegra-vpr.c\nnew file mode 100644\nindex 00000000000000..918cf66ec222c9\n--- /dev/null\n+++ b/drivers/dma-buf/heaps/tegra-vpr.c\n@@ -0,0 +1,1242 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * DMA-BUF restricted heap exporter for NVIDIA Video-Protection-Region (VPR)\n+ *\n+ * Copyright (C) 2024-2026 NVIDIA Corporation\n+ */\n+\n+#define pr_fmt(fmt) \"tegra-vpr: \" fmt\n+\n+#include \u003clinux/arm-smccc.h\u003e\n+#include \u003clinux/cma.h\u003e\n+#include \u003clinux/debugfs.h\u003e\n+#include \u003clinux/dma-buf.h\u003e\n+#include \u003clinux/dma-heap.h\u003e\n+#include \u003clinux/find.h\u003e\n+#include \u003clinux/of_reserved_mem.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+#include \u003clinux/pm_runtime.h\u003e\n+#include \u003clinux/reset.h\u003e\n+#include \u003clinux/set_memory.h\u003e\n+\n+#include \u003clinux/freezer.h\u003e\n+\n+#define CREATE_TRACE_POINTS\n+#include \u003ctrace/events/tegra_vpr.h\u003e\n+\n+#define TEGRA_VPR_MAX_CHUNKS 64\n+\n+struct tegra_vpr;\n+\n+struct tegra_vpr_device {\n+\tstruct list_head node;\n+\tstruct device *dev;\n+};\n+\n+struct tegra_vpr_chunk {\n+\tphys_addr_t start;\n+\tphys_addr_t limit;\n+\tsize_t size;\n+\n+\tstruct tegra_vpr *vpr;\n+\tstruct cma *cma;\n+\tbool active;\n+\n+\tstruct page *start_page;\n+\tunsigned int offset;\n+\tunsigned long virt;\n+\tpgoff_t num_pages;\n+\n+\tunsigned int num_buffers;\n+};\n+\n+struct tegra_vpr {\n+\tstruct device_node *dev_node;\n+\tunsigned long align;\n+\tphys_addr_t base;\n+\tphys_addr_t size;\n+\tbool use_freezer;\n+\tbool resizable;\n+\n+\tstruct list_head buffers;\n+\tstruct page *start_page;\n+\tunsigned long *bitmap;\n+\tpgoff_t num_pages;\n+\n+\t/* resizable VPR */\n+\tDECLARE_BITMAP(active, TEGRA_VPR_MAX_CHUNKS);\n+\tstruct tegra_vpr_chunk *chunks;\n+\tunsigned int num_chunks;\n+\n+\tunsigned int first;\n+\tunsigned int last;\n+\n+\tstruct list_head devices;\n+\tstruct mutex lock;\n+};\n+\n+struct tegra_vpr_buffer {\n+\tstruct list_head attachments;\n+\tstruct tegra_vpr *vpr;\n+\tstruct list_head list;\n+\tstruct mutex lock;\n+\n+\tstruct page *start_page;\n+\tstruct page **pages;\n+\tpgoff_t num_pages;\n+\tphys_addr_t start;\n+\tphys_addr_t limit;\n+\tsize_t size;\n+\tint pageno;\n+\tint order;\n+\n+\tDECLARE_BITMAP(chunks, TEGRA_VPR_MAX_CHUNKS);\n+};\n+\n+struct tegra_vpr_attachment {\n+\tstruct device *dev;\n+\tstruct sg_table sgt;\n+\tstruct list_head list;\n+};\n+\n+#define ARM_SMCCC_TE_FUNC_PROGRAM_VPR 0x3\n+\n+#define ARM_SMCCC_VENDOR_SIP_TE_PROGRAM_VPR_FUNC_ID\t\t\\\n+\tARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL,\t\t\t\\\n+\t\t\t   ARM_SMCCC_SMC_32,\t\t\t\\\n+\t\t\t   ARM_SMCCC_OWNER_SIP,\t\t\t\\\n+\t\t\t   ARM_SMCCC_TE_FUNC_PROGRAM_VPR)\n+\n+static int tegra_vpr_set(phys_addr_t base, phys_addr_t size)\n+{\n+\tstruct arm_smccc_res res;\n+\n+\tarm_smccc_smc(ARM_SMCCC_VENDOR_SIP_TE_PROGRAM_VPR_FUNC_ID, base, size,\n+\t\t      0, 0, 0, 0, 0, \u0026res);\n+\n+\treturn res.a0;\n+}\n+\n+static int tegra_vpr_get_extents(struct tegra_vpr *vpr, phys_addr_t *base,\n+\t\t\t\t phys_addr_t *size)\n+{\n+\tphys_addr_t start = ~0, limit = 0;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i \u003c vpr-\u003enum_chunks; i++) {\n+\t\tstruct tegra_vpr_chunk *chunk = \u0026vpr-\u003echunks[i];\n+\n+\t\tif (chunk-\u003eactive) {\n+\t\t\tif (chunk-\u003estart \u003c start)\n+\t\t\t\tstart = chunk-\u003estart;\n+\n+\t\t\tif (chunk-\u003elimit \u003e limit)\n+\t\t\t\tlimit = chunk-\u003elimit;\n+\t\t}\n+\t}\n+\n+\tif (limit \u003e start) {\n+\t\t*size = limit - start;\n+\t\t*base = start;\n+\t} else {\n+\t\t*base = *size = 0;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int tegra_vpr_resize(struct tegra_vpr *vpr)\n+{\n+\tstruct tegra_vpr_device *node;\n+\tphys_addr_t base, size;\n+\tint err, status = 0;\n+\n+\terr = tegra_vpr_get_extents(vpr, \u0026base, \u0026size);\n+\tif (err \u003c 0) {\n+\t\tpr_err(\"%s(): failed to get VPR extents: %d\\n\", __func__, err);\n+\t\treturn err;\n+\t}\n+\n+\tif (vpr-\u003euse_freezer) {\n+\t\terr = freeze_processes();\n+\t\tif (err \u003c 0) {\n+\t\t\tpr_err(\"%s(): failed to freeze processes: %d\\n\",\n+\t\t\t       __func__, err);\n+\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\tlist_for_each_entry(node, \u0026vpr-\u003edevices, node) {\n+\t\terr = pm_generic_freeze(node-\u003edev);\n+\t\tif (err \u003c 0) {\n+\t\t\tpr_err(\"failed to runtime suspend %s: %d\\n\",\n+\t\t\t       dev_name(node-\u003edev), err);\n+\t\t\tgoto thaw;\n+\t\t}\n+\t}\n+\n+\ttrace_tegra_vpr_set(base, size);\n+\n+\terr = tegra_vpr_set(base, size);\n+\tif (err \u003c 0) {\n+\t\tpr_err(\"failed to secure VPR: %d\\n\", err);\n+\t\tstatus = err;\n+\t}\n+\n+thaw:\n+\tlist_for_each_entry_continue_reverse(node, \u0026vpr-\u003edevices, node) {\n+\t\terr = pm_generic_thaw(node-\u003edev);\n+\t\tif (err \u003c 0) {\n+\t\t\tpr_err(\"failed to runtime resume %s\\n\",\n+\t\t\t       dev_name(node-\u003edev));\n+\t\t\tcontinue;\n+\t\t}\n+\t}\n+\n+\tif (vpr-\u003euse_freezer)\n+\t\tthaw_processes();\n+\n+\treturn status;\n+}\n+\n+static int __init tegra_vpr_chunk_init(struct tegra_vpr *vpr,\n+\t\t\t\t       struct tegra_vpr_chunk *chunk,\n+\t\t\t\t       phys_addr_t start, size_t size,\n+\t\t\t\t       unsigned int order, const char *name)\n+{\n+\tchunk-\u003estart = start;\n+\tchunk-\u003elimit = start + size;\n+\tchunk-\u003esize = size;\n+\tchunk-\u003evpr = vpr;\n+\n+\tchunk-\u003ecma = cma_create(start, size, order, name);\n+\tif (IS_ERR(chunk-\u003ecma)) {\n+\t\tpr_err(\"cma_create() failed: %ld\\n\", PTR_ERR(chunk-\u003ecma));\n+\t\treturn PTR_ERR(chunk-\u003ecma);\n+\t}\n+\n+\tchunk-\u003eoffset = (start - vpr-\u003ebase) \u003e\u003e PAGE_SHIFT;\n+\tchunk-\u003enum_pages = size \u003e\u003e PAGE_SHIFT;\n+\tchunk-\u003enum_buffers = 0;\n+\n+\t/* CMA area is not reserved yet */\n+\tchunk-\u003estart_page = NULL;\n+\tchunk-\u003evirt = 0;\n+\n+\treturn 0;\n+}\n+\n+static void tegra_vpr_chunk_free(struct tegra_vpr_chunk *chunk)\n+{\n+\tcma_free(chunk-\u003ecma);\n+}\n+\n+static inline bool tegra_vpr_chunk_is_last(const struct tegra_vpr_chunk *chunk)\n+{\n+\tphys_addr_t limit = chunk-\u003evpr-\u003ebase + chunk-\u003evpr-\u003esize;\n+\n+\treturn chunk-\u003elimit == limit;\n+}\n+\n+static inline bool tegra_vpr_chunk_is_leaf(const struct tegra_vpr_chunk *chunk)\n+{\n+\tconst struct tegra_vpr_chunk *next = chunk + 1;\n+\n+\tif (tegra_vpr_chunk_is_last(chunk))\n+\t\treturn true;\n+\n+\treturn !next-\u003eactive;\n+}\n+\n+static int tegra_vpr_chunk_activate(struct tegra_vpr_chunk *chunk)\n+{\n+\tunsigned long align = get_order(chunk-\u003evpr-\u003ealign);\n+\tint err;\n+\n+\ttrace_tegra_vpr_chunk_activate(chunk-\u003estart, chunk-\u003elimit);\n+\n+\tchunk-\u003estart_page = cma_alloc(chunk-\u003ecma, chunk-\u003enum_pages, align,\n+\t\t\t\t      false);\n+\tif (!chunk-\u003estart_page) {\n+\t\terr = -ENOMEM;\n+\t\tgoto fail;\n+\t}\n+\n+\tchunk-\u003evirt = (unsigned long)page_to_virt(chunk-\u003estart_page);\n+\n+\terr = set_memory_device(chunk-\u003evirt, chunk-\u003enum_pages);\n+\tif (err \u003c 0)\n+\t\tgoto free;\n+\n+\tchunk-\u003eactive = true;\n+\n+\treturn 0;\n+\n+free:\n+\tcma_release(chunk-\u003ecma, chunk-\u003estart_page, chunk-\u003enum_pages);\n+fail:\n+\tchunk-\u003estart_page = NULL;\n+\tchunk-\u003evirt = 0;\n+\treturn err;\n+}\n+\n+static int tegra_vpr_chunk_deactivate(struct tegra_vpr_chunk *chunk)\n+{\n+\tint err;\n+\n+\tif (!chunk-\u003eactive)\n+\t\treturn 0;\n+\n+\t/* do not deactivate if there are buffers left in this chunk */\n+\tif (WARN_ON(chunk-\u003enum_buffers \u003e 0))\n+\t\treturn -EBUSY;\n+\n+\ttrace_tegra_vpr_chunk_deactivate(chunk-\u003estart, chunk-\u003elimit);\n+\n+\terr = set_memory_normal(chunk-\u003evirt, chunk-\u003enum_pages);\n+\tif (err \u003c 0)\n+\t\treturn err;\n+\n+\tchunk-\u003eactive = false;\n+\n+\tcma_release(chunk-\u003ecma, chunk-\u003estart_page, chunk-\u003enum_pages);\n+\tchunk-\u003estart_page = NULL;\n+\tchunk-\u003evirt = 0;\n+\n+\treturn 0;\n+}\n+\n+static bool tegra_vpr_chunk_overlaps(struct tegra_vpr_chunk *chunk,\n+\t\t\t\t     unsigned int start, unsigned int limit)\n+{\n+\tunsigned int first = chunk-\u003eoffset;\n+\tunsigned int last = chunk-\u003eoffset + chunk-\u003enum_pages - 1;\n+\n+\tif (last \u003c start || first \u003e= limit)\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static int tegra_vpr_activate_chunks(struct tegra_vpr *vpr,\n+\t\t\t\t     struct tegra_vpr_buffer *buffer)\n+{\n+\tDECLARE_BITMAP(dirty, vpr-\u003enum_chunks);\n+\tunsigned int i, bottom, top;\n+\tint err = 0, ret;\n+\n+\tbitmap_zero(dirty, vpr-\u003enum_chunks);\n+\n+\t/* activate any inactive chunks that overlap this buffer */\n+\tfor_each_set_bit(i, buffer-\u003echunks, vpr-\u003enum_chunks) {\n+\t\tstruct tegra_vpr_chunk *chunk = \u0026vpr-\u003echunks[i];\n+\n+\t\tif (chunk-\u003eactive)\n+\t\t\tcontinue;\n+\n+\t\terr = tegra_vpr_chunk_activate(chunk);\n+\t\tif (err \u003c 0)\n+\t\t\tgoto deactivate;\n+\n+\t\tset_bit(i, vpr-\u003eactive);\n+\t\tset_bit(i, dirty);\n+\t}\n+\n+\t/*\n+\t * Activating chunks above may have created holes, but since the VPR\n+\t * can only ever be a single contiguous region, make sure to activate\n+\t * any missing chunks.\n+\t */\n+\tfor_each_clear_bitrange(bottom, top, vpr-\u003eactive, vpr-\u003enum_chunks) {\n+\t\t/* inactive chunks at the bottom or the top are harmless */\n+\t\tif (bottom == 0 || top == vpr-\u003enum_chunks)\n+\t\t\tcontinue;\n+\n+\t\tfor (i = bottom; i \u003c top; i++) {\n+\t\t\tstruct tegra_vpr_chunk *chunk = \u0026vpr-\u003echunks[i];\n+\n+\t\t\terr = tegra_vpr_chunk_activate(chunk);\n+\t\t\tif (err \u003c 0)\n+\t\t\t\tgoto deactivate;\n+\n+\t\t\tset_bit(i, vpr-\u003eactive);\n+\t\t\tset_bit(i, dirty);\n+\t\t}\n+\t}\n+\n+\t/* if any chunks have been activated, VPR needs to be resized */\n+\tif (!bitmap_empty(dirty, vpr-\u003enum_chunks)) {\n+\t\terr = tegra_vpr_resize(vpr);\n+\t\tif (err \u003c 0) {\n+\t\t\tpr_err(\"failed to grow VPR: %d\\n\", err);\n+\t\t\tgoto deactivate;\n+\t\t}\n+\t}\n+\n+\t/* increment buffer count for each chunk */\n+\tfor_each_set_bit(i, buffer-\u003echunks, vpr-\u003enum_chunks)\n+\t\tvpr-\u003echunks[i].num_buffers++;\n+\n+\treturn 0;\n+\n+deactivate:\n+\t/* deactivate any of the previously inactive chunks on failure */\n+\tfor_each_set_bit(i, dirty, vpr-\u003enum_chunks) {\n+\t\tret = tegra_vpr_chunk_deactivate(\u0026vpr-\u003echunks[i]);\n+\t\tif (ret \u003c 0)\n+\t\t\tWARN(1, \"failed to deactivate chunk #%u: %d\\n\", i, ret);\n+\n+\t\tclear_bit(i, vpr-\u003eactive);\n+\t}\n+\n+\treturn err;\n+}\n+\n+/*\n+ * Retrieve the range of pages within the activate region of the VPR.\n+ */\n+static bool tegra_vpr_get_active_range(struct tegra_vpr *vpr,\n+\t\t\t\t       unsigned int *first,\n+\t\t\t\t       unsigned int *last)\n+{\n+\tunsigned long i, j;\n+\n+\ti = find_first_bit(vpr-\u003eactive, vpr-\u003enum_chunks);\n+\tif (i \u003e= vpr-\u003enum_chunks)\n+\t\treturn false;\n+\n+\tj = find_last_bit(vpr-\u003eactive, vpr-\u003enum_chunks);\n+\tif (j \u003e= vpr-\u003enum_chunks)\n+\t\treturn false;\n+\n+\t*first = vpr-\u003echunks[i].offset;\n+\t*last = vpr-\u003echunks[j].offset + vpr-\u003echunks[j].num_pages;\n+\n+\treturn true;\n+}\n+\n+/*\n+ * Try to find and allocate a free region within a specific page range.\n+ * Returns the page number if successful, -ENOSPC otherwise.\n+ *\n+ * This function mimics bitmap_find_free_region() but restricts the search\n+ * to a specific range to enable allocation within individual chunks.\n+ */\n+static int tegra_vpr_find_free_region_in_range(struct tegra_vpr *vpr,\n+\t\t\t\t\t       unsigned int start_page,\n+\t\t\t\t\t       unsigned int end_page,\n+\t\t\t\t\t       unsigned int num_pages,\n+\t\t\t\t\t       unsigned int align)\n+{\n+\tunsigned int pos, next = ALIGN(start_page, align);\n+\n+\t/* Scan through aligned positions, trying to allocate at each one */\n+\tfor (pos = next; pos + num_pages \u003c= end_page; pos = next) {\n+\t\tnext = find_next_bit(vpr-\u003ebitmap, pos + num_pages, pos);\n+\n+\t\tif (next \u003e= pos + num_pages) {\n+\t\t\tbitmap_set(vpr-\u003ebitmap, pos, num_pages);\n+\t\t\treturn pos;\n+\t\t}\n+\n+\t\tnext = find_next_zero_bit(vpr-\u003ebitmap, vpr-\u003enum_pages, next);\n+\t\tnext = ALIGN(next, align);\n+\t}\n+\n+\treturn -ENOSPC;\n+}\n+\n+static int tegra_vpr_find_free_region(struct tegra_vpr *vpr,\n+\t\t\t\t      unsigned int num_pages,\n+\t\t\t\t      unsigned long align)\n+{\n+\treturn tegra_vpr_find_free_region_in_range(vpr, 0, vpr-\u003enum_pages - 1,\n+\t\t\t\t\t\t   num_pages, align);\n+}\n+\n+static int tegra_vpr_find_free_region_clustered(struct tegra_vpr *vpr,\n+\t\t\t\t\t\tunsigned int num_pages,\n+\t\t\t\t\t\tunsigned int align)\n+{\n+\tunsigned int target, first, last;\n+\tint pageno;\n+\n+\t/*\n+\t * If there are no allocations, abort the clustered allocation scheme\n+\t * and use the generic allocation scheme instead.\n+\t */\n+\tif (vpr-\u003efirst \u003e vpr-\u003elast)\n+\t\treturn -ENOSPC;\n+\n+\t/*\n+\t * First, try to allocate within the currently allocated region. This\n+\t * keeps allocations tightly packed and minimizes the VPR size needed.\n+\t */\n+\tpageno = tegra_vpr_find_free_region_in_range(vpr, vpr-\u003efirst,\n+\t\t\t\t\t\t     vpr-\u003elast + 1, num_pages,\n+\t\t\t\t\t\t     align);\n+\tif (pageno \u003e= 0)\n+\t\treturn pageno;\n+\n+\t/*\n+\t * If not enough free space exists within the currently allocated\n+\t * region, check to see if the allocation fits anywhere within the\n+\t * active region, avoiding the need to resize the VPR.\n+\t */\n+\tif (tegra_vpr_get_active_range(vpr, \u0026first, \u0026last)) {\n+\t\tpageno = tegra_vpr_find_free_region_in_range(vpr, first, last,\n+\t\t\t\t\t\t\t     num_pages, align);\n+\t\tif (pageno \u003e= 0)\n+\t\t\treturn pageno;\n+\t}\n+\n+\t/*\n+\t * If not enough free space exists within the currently active region,\n+\t * try to allocate adjacent to it to grow it contiguously and ensure\n+\t * optimal packing.\n+\t */\n+\n+\t/*\n+\t * Calculate where the allocation should start to end right at the\n+\t * first allocated page, with proper alignment.\n+\t */\n+\tif (vpr-\u003efirst \u003e= num_pages) {\n+\t\ttarget = ALIGN_DOWN(vpr-\u003efirst - num_pages, align);\n+\n+\t\tif (!bitmap_allocate(vpr-\u003ebitmap, target, num_pages))\n+\t\t\treturn target;\n+\t}\n+\n+\t/* Try after the last allocation */\n+\ttarget = ALIGN(vpr-\u003elast + 1, align);\n+\n+\tif (target + num_pages \u003c= vpr-\u003enum_pages \u0026\u0026\n+\t    !bitmap_allocate(vpr-\u003ebitmap, target, num_pages))\n+\t\treturn target;\n+\n+\t/*\n+\t * Couldn't allocate at the ideal adjacent position, search for any\n+\t * available space before the first allocated page.\n+\t */\n+\tpageno = tegra_vpr_find_free_region_in_range(vpr, 0, vpr-\u003efirst,\n+\t\t\t\t\t\t     num_pages, align);\n+\tif (pageno \u003e= 0)\n+\t\treturn pageno;\n+\n+\t/*\n+\t * Couldn't allocate at the ideal adjacent position, search\n+\t * for any available space after the last allocated page.\n+\t */\n+\tpageno = tegra_vpr_find_free_region_in_range(vpr, vpr-\u003elast + 1,\n+\t\t\t\t\t\t     vpr-\u003enum_pages, num_pages,\n+\t\t\t\t\t\t     align);\n+\tif (pageno \u003e= 0)\n+\t\treturn pageno;\n+\n+\treturn -ENOSPC;\n+}\n+\n+/*\n+ * Find a free region, preferring locations near existing allocations to\n+ * minimize VPR fragmentation. The allocation strategy is to first allocate\n+ * within or adjacent to the existing region to keep allocations clustered.\n+ * Otherwise fall back to a generic allocation using the first available\n+ * space.\n+ *\n+ * This approach focuses on page-level allocation first, then the chunk\n+ * system determines which chunks need to be activated based on where the\n+ * pages ended up.\n+ */\n+static int tegra_vpr_allocate_region(struct tegra_vpr *vpr,\n+\t\t\t\t     unsigned int num_pages,\n+\t\t\t\t     unsigned int align)\n+{\n+\tint pageno;\n+\n+\t/*\n+\t * For non-resizable VPR (no chunks), use simple first-fit allocation.\n+\t * Clustering optimization is only beneficial for resizable VPR where\n+\t * keeping allocations together minimizes the active VPR size.\n+\t */\n+\tif (vpr-\u003enum_chunks == 0)\n+\t\treturn tegra_vpr_find_free_region(vpr, num_pages, align);\n+\n+\t/*\n+\t * Check if there are any existing allocations in the bitmap. If so,\n+\t * try to allocate near them to minimize fragmentation.\n+\t */\n+\tpageno = tegra_vpr_find_free_region_clustered(vpr, num_pages, align);\n+\tif (pageno \u003e= 0)\n+\t\treturn pageno;\n+\n+\t/*\n+\t * If there are no existing allocations, or no space adjacent to them,\n+\t * fall back to the first available space anywhere in the VPR.\n+\t */\n+\tpageno = tegra_vpr_find_free_region(vpr, num_pages, align);\n+\tif (pageno \u003e= 0)\n+\t\treturn pageno;\n+\n+\treturn -ENOSPC;\n+}\n+\n+static struct tegra_vpr_buffer *\n+tegra_vpr_buffer_allocate(struct tegra_vpr *vpr, size_t size)\n+{\n+\tunsigned int num_pages = size \u003e\u003e PAGE_SHIFT;\n+\tunsigned int order = get_order(size);\n+\tstruct tegra_vpr_buffer *buffer;\n+\tunsigned long first, last;\n+\tint pageno, err;\n+\tpgoff_t i;\n+\n+\t/*\n+\t * \"order\" defines the alignment and size, so this may result in\n+\t * fragmented memory depending on the allocation patterns. However,\n+\t * since this is used primarily for video frames, it is expected that\n+\t * a number of buffers of the same size will be allocated, so\n+\t * fragmentation should be negligible.\n+\t */\n+\tpageno = tegra_vpr_allocate_region(vpr, num_pages, 1);\n+\tif (pageno \u003c 0)\n+\t\treturn ERR_PTR(pageno);\n+\n+\tfirst = find_first_bit(vpr-\u003ebitmap, vpr-\u003enum_pages);\n+\tlast = find_last_bit(vpr-\u003ebitmap, vpr-\u003enum_pages);\n+\n+\tbuffer = kzalloc_obj(*buffer, GFP_KERNEL);\n+\tif (!buffer) {\n+\t\terr = -ENOMEM;\n+\t\tgoto release;\n+\t}\n+\n+\tINIT_LIST_HEAD(\u0026buffer-\u003eattachments);\n+\tINIT_LIST_HEAD(\u0026buffer-\u003elist);\n+\tmutex_init(\u0026buffer-\u003elock);\n+\tbuffer-\u003estart = vpr-\u003ebase + (pageno \u003c\u003c PAGE_SHIFT);\n+\tbuffer-\u003elimit = buffer-\u003estart + size;\n+\tbuffer-\u003esize = size;\n+\tbuffer-\u003enum_pages = num_pages;\n+\tbuffer-\u003epageno = pageno;\n+\tbuffer-\u003eorder = order;\n+\n+\tbuffer-\u003epages = kmalloc_array(buffer-\u003enum_pages,\n+\t\t\t\t      sizeof(*buffer-\u003epages),\n+\t\t\t\t      GFP_KERNEL);\n+\tif (!buffer-\u003epages) {\n+\t\terr = -ENOMEM;\n+\t\tgoto free;\n+\t}\n+\n+\t/* track which chunks this buffer overlaps */\n+\tif (vpr-\u003enum_chunks \u003e 0) {\n+\t\tunsigned int limit = buffer-\u003epageno + buffer-\u003enum_pages, i;\n+\n+\t\tfor (i = 0; i \u003c vpr-\u003enum_chunks; i++) {\n+\t\t\tstruct tegra_vpr_chunk *chunk = \u0026vpr-\u003echunks[i];\n+\n+\t\t\tif (tegra_vpr_chunk_overlaps(chunk, pageno, limit))\n+\t\t\t\tset_bit(i, buffer-\u003echunks);\n+\t\t}\n+\n+\t\t/* activate chunks if necessary */\n+\t\terr = tegra_vpr_activate_chunks(vpr, buffer);\n+\t\tif (err \u003c 0)\n+\t\t\tgoto free;\n+\n+\t\t/* track first and last allocated pages */\n+\t\tif (buffer-\u003epageno \u003c vpr-\u003efirst)\n+\t\t\tvpr-\u003efirst = buffer-\u003epageno;\n+\n+\t\tif (limit - 1 \u003e vpr-\u003elast)\n+\t\t\tvpr-\u003elast = limit - 1;\n+\t}\n+\n+\tfor (i = 0; i \u003c buffer-\u003enum_pages; i++)\n+\t\tbuffer-\u003epages[i] = \u0026vpr-\u003estart_page[pageno + i];\n+\n+\treturn buffer;\n+\n+free:\n+\tkfree(buffer-\u003epages);\n+\tkfree(buffer);\n+release:\n+\tbitmap_release_region(vpr-\u003ebitmap, pageno, order);\n+\treturn ERR_PTR(err);\n+}\n+\n+static void tegra_vpr_buffer_release(struct tegra_vpr_buffer *buffer)\n+{\n+\tstruct tegra_vpr *vpr = buffer-\u003evpr;\n+\tstruct tegra_vpr_buffer *entry;\n+\tunsigned long first, last;\n+\tunsigned int i;\n+\n+\t/*\n+\t * Decrement buffer count for each overlapping chunk. Note that chunks\n+\t * are not deactivated here yet, that's done in tegra_vpr_recycle()\n+\t * instead.\n+\t */\n+\tfor_each_set_bit(i, buffer-\u003echunks, vpr-\u003enum_chunks) {\n+\t\tif (!WARN_ON(vpr-\u003echunks[i].num_buffers == 0))\n+\t\t\tvpr-\u003echunks[i].num_buffers--;\n+\t}\n+\n+\t/* track first and last allocated pages */\n+\tif (list_is_first(\u0026buffer-\u003elist, \u0026vpr-\u003ebuffers) \u0026\u0026\n+\t    list_is_last(\u0026buffer-\u003elist, \u0026vpr-\u003ebuffers)) {\n+\t\t/* if there are no remaining buffers after this, reset */\n+\t\tvpr-\u003efirst = ~0U;\n+\t\tvpr-\u003elast = 0U;\n+\t} else if (list_is_first(\u0026buffer-\u003elist, \u0026vpr-\u003ebuffers)) {\n+\t\tentry = list_next_entry(buffer, list);\n+\t\tvpr-\u003efirst = entry-\u003epageno;\n+\t} else if (list_is_last(\u0026buffer-\u003elist, \u0026vpr-\u003ebuffers)) {\n+\t\tentry = list_prev_entry(buffer, list);\n+\t\tvpr-\u003elast = entry-\u003epageno + entry-\u003enum_pages - 1;\n+\t}\n+\n+\tbitmap_release_region(vpr-\u003ebitmap, buffer-\u003epageno, buffer-\u003eorder);\n+\tlist_del(\u0026buffer-\u003elist);\n+\tkfree(buffer-\u003epages);\n+\tkfree(buffer);\n+\n+\tfirst = find_first_bit(vpr-\u003ebitmap, vpr-\u003enum_pages);\n+\tlast = find_last_bit(vpr-\u003ebitmap, vpr-\u003enum_pages);\n+}\n+\n+static int tegra_vpr_attach(struct dma_buf *buf,\n+\t\t\t    struct dma_buf_attachment *attachment)\n+{\n+\tstruct tegra_vpr_buffer *buffer = buf-\u003epriv;\n+\tstruct tegra_vpr_attachment *attach;\n+\tint err;\n+\n+\tattach = kzalloc_obj(*attach, GFP_KERNEL);\n+\tif (!attach)\n+\t\treturn -ENOMEM;\n+\n+\terr = sg_alloc_table_from_pages(\u0026attach-\u003esgt, buffer-\u003epages,\n+\t\t\t\t\tbuffer-\u003enum_pages, 0, buffer-\u003esize,\n+\t\t\t\t\tGFP_KERNEL);\n+\tif (err \u003c 0)\n+\t\tgoto free;\n+\n+\tattach-\u003edev = attach-\u003edev;\n+\tINIT_LIST_HEAD(\u0026attach-\u003elist);\n+\tattachment-\u003epriv = attach;\n+\n+\tmutex_lock(\u0026buffer-\u003elock);\n+\tlist_add(\u0026attach-\u003elist, \u0026buffer-\u003eattachments);\n+\tmutex_unlock(\u0026buffer-\u003elock);\n+\n+\treturn 0;\n+\n+free:\n+\tkfree(attach);\n+\treturn err;\n+}\n+\n+static void tegra_vpr_detach(struct dma_buf *buf,\n+\t\t\t     struct dma_buf_attachment *attachment)\n+{\n+\tstruct tegra_vpr_buffer *buffer = buf-\u003epriv;\n+\tstruct tegra_vpr_attachment *attach = attachment-\u003epriv;\n+\n+\tmutex_lock(\u0026buffer-\u003elock);\n+\tlist_del(\u0026attach-\u003elist);\n+\tmutex_unlock(\u0026buffer-\u003elock);\n+\n+\tsg_free_table(\u0026attach-\u003esgt);\n+\tkfree(attach);\n+}\n+\n+static struct sg_table *\n+tegra_vpr_map_dma_buf(struct dma_buf_attachment *attachment,\n+\t\t      enum dma_data_direction direction)\n+{\n+\tstruct tegra_vpr_attachment *attach = attachment-\u003epriv;\n+\tstruct sg_table *sgt = \u0026attach-\u003esgt;\n+\tint err;\n+\n+\terr = dma_map_sgtable(attachment-\u003edev, sgt, direction,\n+\t\t\t      DMA_ATTR_SKIP_CPU_SYNC);\n+\tif (err \u003c 0)\n+\t\treturn ERR_PTR(err);\n+\n+\treturn sgt;\n+}\n+\n+static void tegra_vpr_unmap_dma_buf(struct dma_buf_attachment *attachment,\n+\t\t\t\t    struct sg_table *sgt,\n+\t\t\t\t    enum dma_data_direction direction)\n+{\n+\tdma_unmap_sgtable(attachment-\u003edev, sgt, direction,\n+\t\t\t  DMA_ATTR_SKIP_CPU_SYNC);\n+}\n+\n+static void tegra_vpr_recycle(struct tegra_vpr *vpr)\n+{\n+\tDECLARE_BITMAP(dirty, vpr-\u003enum_chunks);\n+\tunsigned int i;\n+\tint err;\n+\n+\tbitmap_zero(dirty, vpr-\u003enum_chunks);\n+\n+\t/*\n+\t * Deactivate any unused chunks from the bottom...\n+\t */\n+\tfor (i = 0; i \u003c vpr-\u003enum_chunks; i++) {\n+\t\tstruct tegra_vpr_chunk *chunk = \u0026vpr-\u003echunks[i];\n+\n+\t\tif (!chunk-\u003eactive)\n+\t\t\tcontinue;\n+\n+\t\tif (chunk-\u003enum_buffers \u003e 0)\n+\t\t\tbreak;\n+\n+\t\terr = tegra_vpr_chunk_deactivate(chunk);\n+\t\tif (err \u003c 0)\n+\t\t\tpr_err(\"failed to deactivate chunk #%u\\n\", i);\n+\t\telse {\n+\t\t\tclear_bit(i, vpr-\u003eactive);\n+\t\t\tset_bit(i, dirty);\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * ... and the top.\n+\t */\n+\tfor (i = 0; i \u003c vpr-\u003enum_chunks; i++) {\n+\t\tunsigned int index = vpr-\u003enum_chunks - i - 1;\n+\t\tstruct tegra_vpr_chunk *chunk = \u0026vpr-\u003echunks[index];\n+\n+\t\tif (!chunk-\u003eactive)\n+\t\t\tcontinue;\n+\n+\t\tif (chunk-\u003enum_buffers \u003e 0)\n+\t\t\tbreak;\n+\n+\t\terr = tegra_vpr_chunk_deactivate(chunk);\n+\t\tif (err \u003c 0)\n+\t\t\tpr_err(\"failed to deactivate chunk #%u\\n\", index);\n+\t\telse {\n+\t\t\tclear_bit(i, vpr-\u003eactive);\n+\t\t\tset_bit(i, dirty);\n+\t\t}\n+\t}\n+\n+\tif (!bitmap_empty(dirty, vpr-\u003enum_chunks)) {\n+\t\terr = tegra_vpr_resize(vpr);\n+\t\tif (err \u003c 0) {\n+\t\t\tpr_err(\"failed to shrink VPR: %d\\n\", err);\n+\t\t\tgoto activate;\n+\t\t}\n+\t}\n+\n+\treturn;\n+\n+activate:\n+\tfor_each_set_bit(i, dirty, vpr-\u003enum_chunks) {\n+\t\terr = tegra_vpr_chunk_activate(\u0026vpr-\u003echunks[i]);\n+\t\tif (WARN_ON(err \u003c 0))\n+\t\t\tpr_err(\"failed to activate chunk #%u: %d\\n\", i, err);\n+\t}\n+}\n+\n+static void tegra_vpr_release(struct dma_buf *buf)\n+{\n+\tstruct tegra_vpr_buffer *buffer = buf-\u003epriv;\n+\tstruct tegra_vpr *vpr = buffer-\u003evpr;\n+\n+\tmutex_lock(\u0026vpr-\u003elock);\n+\n+\ttegra_vpr_buffer_release(buffer);\n+\n+\tif (vpr-\u003enum_chunks \u003e 0)\n+\t\ttegra_vpr_recycle(vpr);\n+\n+\tmutex_unlock(\u0026vpr-\u003elock);\n+}\n+\n+/*\n+ * Prohibit userspace mapping because the CPU cannot access this memory\n+ * anyway.\n+ */\n+static int tegra_vpr_begin_cpu_access(struct dma_buf *buf,\n+\t\t\t\t      enum dma_data_direction direction)\n+{\n+\treturn -EPERM;\n+}\n+\n+static int tegra_vpr_end_cpu_access(struct dma_buf *buf,\n+\t\t\t\t    enum dma_data_direction direction)\n+{\n+\treturn -EPERM;\n+}\n+\n+static int tegra_vpr_mmap(struct dma_buf *buf, struct vm_area_struct *vma)\n+{\n+\treturn -EPERM;\n+}\n+\n+static const struct dma_buf_ops tegra_vpr_buf_ops = {\n+\t.attach = tegra_vpr_attach,\n+\t.detach = tegra_vpr_detach,\n+\t.map_dma_buf = tegra_vpr_map_dma_buf,\n+\t.unmap_dma_buf = tegra_vpr_unmap_dma_buf,\n+\t.release = tegra_vpr_release,\n+\t.begin_cpu_access = tegra_vpr_begin_cpu_access,\n+\t.end_cpu_access = tegra_vpr_end_cpu_access,\n+\t.mmap = tegra_vpr_mmap,\n+};\n+\n+static struct dma_buf *tegra_vpr_allocate(struct dma_heap *heap,\n+\t\t\t\t\t  unsigned long len, u32 fd_flags,\n+\t\t\t\t\t  u64 heap_flags)\n+{\n+\tstruct tegra_vpr *vpr = dma_heap_get_drvdata(heap);\n+\tstruct tegra_vpr_buffer *buffer, *entry;\n+\tsize_t size = ALIGN(len, vpr-\u003ealign);\n+\tDEFINE_DMA_BUF_EXPORT_INFO(export);\n+\tstruct dma_buf *buf;\n+\n+\tmutex_lock(\u0026vpr-\u003elock);\n+\n+\tbuffer = tegra_vpr_buffer_allocate(vpr, size);\n+\tif (IS_ERR(buffer)) {\n+\t\tmutex_unlock(\u0026vpr-\u003elock);\n+\t\treturn ERR_CAST(buffer);\n+\t}\n+\n+\t/* insert in the correct order */\n+\tif (!list_empty(\u0026vpr-\u003ebuffers)) {\n+\t\tlist_for_each_entry(entry, \u0026vpr-\u003ebuffers, list) {\n+\t\t\tif (buffer-\u003epageno \u003c entry-\u003epageno) {\n+\t\t\t\tlist_add_tail(\u0026buffer-\u003elist, \u0026entry-\u003elist);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (list_empty(\u0026buffer-\u003elist))\n+\t\tlist_add_tail(\u0026buffer-\u003elist, \u0026vpr-\u003ebuffers);\n+\n+\tbuffer-\u003evpr = vpr;\n+\n+\t/*\n+\t * If a valid buffer was allocated, wrap it in a dma_buf\n+\t * and return it.\n+\t */\n+\texport.exp_name = dma_heap_get_name(heap);\n+\texport.ops = \u0026tegra_vpr_buf_ops;\n+\texport.size = buffer-\u003esize;\n+\texport.flags = fd_flags;\n+\texport.priv = buffer;\n+\n+\tbuf = dma_buf_export(\u0026export);\n+\tif (IS_ERR(buf))\n+\t\ttegra_vpr_buffer_release(buffer);\n+\n+\tmutex_unlock(\u0026vpr-\u003elock);\n+\treturn buf;\n+}\n+\n+static void tegra_vpr_debugfs_show_buffers(struct tegra_vpr *vpr,\n+\t\t\t\t\t   struct seq_file *s)\n+{\n+\tstruct tegra_vpr_buffer *buffer;\n+\tchar buf[16];\n+\n+\tlist_for_each_entry(buffer, \u0026vpr-\u003ebuffers, list) {\n+\t\tstring_get_size(buffer-\u003esize, 1, STRING_UNITS_2, buf,\n+\t\t\t\tsizeof(buf));\n+\t\tseq_printf(s, \"  %pap-%pap (%s)\\n\", \u0026buffer-\u003estart,\n+\t\t\t   \u0026buffer-\u003elimit, buf);\n+\n+\t}\n+}\n+\n+static void tegra_vpr_debugfs_show_chunks(struct tegra_vpr *vpr,\n+\t\t\t\t\t  struct seq_file *s)\n+{\n+\tstruct tegra_vpr_buffer *buffer;\n+\tunsigned int i;\n+\tchar buf[16];\n+\n+\tfor (i = 0; i \u003c vpr-\u003enum_chunks; i++) {\n+\t\tconst struct tegra_vpr_chunk *chunk = \u0026vpr-\u003echunks[i];\n+\n+\t\tstring_get_size(chunk-\u003esize, 1, STRING_UNITS_2, buf,\n+\t\t\t\tsizeof(buf));\n+\t\tseq_printf(s, \"  %pap-%pap (%s) (%s, %u buffers)\\n\",\n+\t\t\t   \u0026chunk-\u003estart, \u0026chunk-\u003elimit, buf,\n+\t\t\t   chunk-\u003eactive ? \"active\" : \"inactive\",\n+\t\t\t   chunk-\u003enum_buffers);\n+\t}\n+\n+\tlist_for_each_entry(buffer, \u0026vpr-\u003ebuffers, list) {\n+\t\tstring_get_size(buffer-\u003esize, 1, STRING_UNITS_2, buf,\n+\t\t\t\tsizeof(buf));\n+\t\tseq_printf(s, \"%pap-%pap (%s, chunks: %*pbl)\\n\",\n+\t\t\t   \u0026buffer-\u003estart, \u0026buffer-\u003elimit, buf,\n+\t\t\t   vpr-\u003enum_chunks, buffer-\u003echunks);\n+\t}\n+}\n+\n+static int tegra_vpr_debugfs_show(struct seq_file *s, struct dma_heap *heap)\n+{\n+\tstruct tegra_vpr *vpr = dma_heap_get_drvdata(heap);\n+\tphys_addr_t limit = vpr-\u003ebase + vpr-\u003esize;\n+\tchar buf[16];\n+\n+\tstring_get_size(vpr-\u003esize, 1, STRING_UNITS_2, buf, sizeof(buf));\n+\tseq_printf(s, \"%pap-%pap (%s)\\n\", \u0026vpr-\u003ebase, \u0026limit, buf);\n+\n+\tif (vpr-\u003enum_chunks == 0)\n+\t\ttegra_vpr_debugfs_show_buffers(vpr, s);\n+\telse\n+\t\ttegra_vpr_debugfs_show_chunks(vpr, s);\n+\n+\treturn 0;\n+}\n+\n+static const struct dma_heap_ops tegra_vpr_heap_ops = {\n+\t.allocate = tegra_vpr_allocate,\n+\t.show = tegra_vpr_debugfs_show,\n+};\n+\n+static int tegra_vpr_setup_chunks(struct tegra_vpr *vpr, const char *name)\n+{\n+\tphys_addr_t start, limit;\n+\tunsigned int order, i;\n+\tsize_t max_size;\n+\tint err;\n+\n+\t/* This seems a reasonable value, so hard-code this for now. */\n+\tvpr-\u003enum_chunks = 4;\n+\n+\tvpr-\u003echunks = kcalloc(vpr-\u003enum_chunks, sizeof(*vpr-\u003echunks),\n+\t\t\t      GFP_KERNEL);\n+\tif (!vpr-\u003echunks)\n+\t\treturn -ENOMEM;\n+\n+\tmax_size = PAGE_SIZE \u003c\u003c (get_order(vpr-\u003esize) - ilog2(vpr-\u003enum_chunks));\n+\torder = get_order(vpr-\u003ealign);\n+\n+\t/*\n+\t * Allocate CMA areas for VPR. All areas will be roughtly the same\n+\t * size, with the last area taking up the rest.\n+\t */\n+\tstart = vpr-\u003ebase;\n+\tlimit = vpr-\u003ebase + vpr-\u003esize;\n+\n+\tpr_debug(\"VPR: %pap-%pap (%lu pages, %u chunks, %lu MiB)\\n\", \u0026start,\n+\t\t \u0026limit, vpr-\u003enum_pages, vpr-\u003enum_chunks,\n+\t\t (unsigned long)vpr-\u003esize / 1024 / 1024);\n+\n+\tfor (i = 0; i \u003c vpr-\u003enum_chunks; i++) {\n+\t\tsize_t size = limit - start;\n+\t\tphys_addr_t end;\n+\n+\t\tsize = min_t(size_t, size, max_size);\n+\t\tend = start + size - 1;\n+\n+\t\terr = tegra_vpr_chunk_init(vpr, \u0026vpr-\u003echunks[i], start, size,\n+\t\t\t\t\t   order, name);\n+\t\tif (err \u003c 0) {\n+\t\t\tpr_err(\"failed to create VPR chunk: %d\\n\", err);\n+\t\t\tgoto free;\n+\t\t}\n+\n+\t\tpr_debug(\"  %2u: %pap-%pap (%lu MiB)\\n\", i, \u0026start, \u0026end,\n+\t\t\t size / 1024 / 1024);\n+\t\tstart += size;\n+\t}\n+\n+\tvpr-\u003efirst = ~0U;\n+\tvpr-\u003elast = 0U;\n+\n+\treturn 0;\n+\n+free:\n+\twhile (i--)\n+\t\ttegra_vpr_chunk_free(\u0026vpr-\u003echunks[i]);\n+\n+\tkfree(vpr-\u003echunks);\n+\treturn err;\n+}\n+\n+static void tegra_vpr_free_chunks(struct tegra_vpr *vpr)\n+{\n+\tunsigned int i;\n+\n+\tfor (i = 0; i \u003c vpr-\u003enum_chunks; i++)\n+\t\ttegra_vpr_chunk_free(\u0026vpr-\u003echunks[i]);\n+\n+\tkfree(vpr-\u003echunks);\n+}\n+\n+static int tegra_vpr_setup_static(struct tegra_vpr *vpr)\n+{\n+\tphys_addr_t start, limit;\n+\n+\tstart = vpr-\u003ebase;\n+\tlimit = vpr-\u003ebase + vpr-\u003esize;\n+\n+\tpr_debug(\"VPR: %pap-%pap (%lu pages, %lu MiB)\\n\", \u0026start, \u0026limit,\n+\t\t vpr-\u003enum_pages, (unsigned long)vpr-\u003esize / 1024 / 1024);\n+\n+\treturn 0;\n+}\n+\n+static int __init tegra_vpr_add_heap(struct reserved_mem *rmem,\n+\t\t\t\t     struct device_node *np)\n+{\n+\tstruct dma_heap_export_info info = {};\n+\tunsigned long first, last;\n+\tstruct dma_heap *heap;\n+\tstruct tegra_vpr *vpr;\n+\tint err;\n+\n+\tvpr = kzalloc_obj(*vpr, GFP_KERNEL);\n+\tif (!vpr)\n+\t\treturn -ENOMEM;\n+\n+\tINIT_LIST_HEAD(\u0026vpr-\u003ebuffers);\n+\tINIT_LIST_HEAD(\u0026vpr-\u003edevices);\n+\tvpr-\u003eresizable = !of_property_read_bool(np, \"no-map\");\n+\tvpr-\u003euse_freezer = true;\n+\tvpr-\u003edev_node = np;\n+\tvpr-\u003ealign = PAGE_SIZE;\n+\tvpr-\u003ebase = rmem-\u003ebase;\n+\tvpr-\u003esize = rmem-\u003esize;\n+\n+\t/* common setup */\n+\tvpr-\u003estart_page = phys_to_page(vpr-\u003ebase);\n+\tvpr-\u003enum_pages = vpr-\u003esize \u003e\u003e PAGE_SHIFT;\n+\n+\tvpr-\u003ebitmap = bitmap_zalloc(vpr-\u003enum_pages, GFP_KERNEL);\n+\tif (!vpr-\u003ebitmap) {\n+\t\terr = -ENOMEM;\n+\t\tgoto free;\n+\t}\n+\n+\tfirst = find_first_bit(vpr-\u003ebitmap, vpr-\u003enum_pages);\n+\tlast = find_last_bit(vpr-\u003ebitmap, vpr-\u003enum_pages);\n+\n+\tif (vpr-\u003eresizable)\n+\t\terr = tegra_vpr_setup_chunks(vpr, rmem-\u003ename);\n+\telse\n+\t\terr = tegra_vpr_setup_static(vpr);\n+\n+\tif (err \u003c 0)\n+\t\tgoto free;\n+\n+\tinfo.name = vpr-\u003edev_node-\u003ename;\n+\tinfo.ops = \u0026tegra_vpr_heap_ops;\n+\tinfo.priv = vpr;\n+\n+\theap = dma_heap_add(\u0026info);\n+\tif (IS_ERR(heap)) {\n+\t\terr = PTR_ERR(heap);\n+\t\tgoto cleanup;\n+\t}\n+\n+\trmem-\u003epriv = heap;\n+\n+\treturn 0;\n+\n+cleanup:\n+\tif (vpr-\u003eresizable)\n+\t\ttegra_vpr_free_chunks(vpr);\n+free:\n+\tbitmap_free(vpr-\u003ebitmap);\n+\tkfree(vpr);\n+\treturn err;\n+}\n+\n+static int __init tegra_vpr_init(void)\n+{\n+\tconst char *compatible = \"nvidia,tegra-video-protection-region\";\n+\tstruct device_node *parent;\n+\tstruct reserved_mem *rmem;\n+\tint err;\n+\n+\tparent = of_find_node_by_path(\"/reserved-memory\");\n+\tif (!parent)\n+\t\treturn 0;\n+\n+\tfor_each_child_of_node_scoped(parent, child) {\n+\t\tif (!of_device_is_compatible(child, compatible))\n+\t\t\tcontinue;\n+\n+\t\trmem = of_reserved_mem_lookup(child);\n+\t\tif (!rmem)\n+\t\t\tcontinue;\n+\n+\t\terr = tegra_vpr_add_heap(rmem, child);\n+\t\tif (err \u003c 0)\n+\t\t\tpr_err(\"failed to add VPR heap for %pOF: %d\\n\", child,\n+\t\t\t       err);\n+\n+\t\t/* only a single VPR heap is supported */\n+\t\tbreak;\n+\t}\n+\n+\treturn 0;\n+}\n+module_init(tegra_vpr_init);\n+\n+static int tegra_vpr_node_init(unsigned long offset, struct reserved_mem *rmem)\n+{\n+\treturn 0;\n+}\n+\n+static int tegra_vpr_device_init(struct reserved_mem *rmem, struct device *dev)\n+{\n+\tstruct dma_heap *heap = rmem-\u003epriv;\n+\tstruct tegra_vpr *vpr = dma_heap_get_drvdata(heap);\n+\tstruct tegra_vpr_device *node;\n+\n+\tif (!dev-\u003edriver-\u003epm-\u003efreeze || !dev-\u003edriver-\u003epm-\u003ethaw)\n+\t\treturn -EINVAL;\n+\n+\tnode = kzalloc_obj(*node, GFP_KERNEL);\n+\tif (!node)\n+\t\treturn -ENOMEM;\n+\n+\tINIT_LIST_HEAD(\u0026node-\u003enode);\n+\tnode-\u003edev = dev;\n+\n+\tlist_add_tail(\u0026node-\u003enode, \u0026vpr-\u003edevices);\n+\n+\treturn 0;\n+}\n+\n+static void tegra_vpr_device_release(struct reserved_mem *rmem,\n+\t\t\t\t     struct device *dev)\n+{\n+\tstruct dma_heap *heap = rmem-\u003epriv;\n+\tstruct tegra_vpr *vpr = dma_heap_get_drvdata(heap);\n+\tstruct tegra_vpr_device *node, *tmp;\n+\n+\tlist_for_each_entry_safe(node, tmp, \u0026vpr-\u003edevices, node) {\n+\t\tif (node-\u003edev == dev) {\n+\t\t\tlist_del(\u0026node-\u003enode);\n+\t\t\tkfree(node);\n+\t\t}\n+\t}\n+}\n+\n+static const struct reserved_mem_ops tegra_vpr_rmem_ops = {\n+\t.node_init = tegra_vpr_node_init,\n+\t.device_init = tegra_vpr_device_init,\n+\t.device_release = tegra_vpr_device_release,\n+};\n+\n+RESERVEDMEM_OF_DECLARE(tegra_vpr, \"nvidia,tegra-video-protection-region\",\n+\t\t       \u0026tegra_vpr_rmem_ops);\n+\n+MODULE_DESCRIPTION(\"NVIDIA Tegra Video-Protection-Region DMA-BUF heap driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/include/linux/bitmap.h b/include/linux/bitmap.h\nindex b007d54a9036a5..dcc81dc082edc9 100644\n--- a/include/linux/bitmap.h\n+++ b/include/linux/bitmap.h\n@@ -721,10 +721,10 @@ void bitmap_release_region(unsigned long *bitmap, unsigned int pos, int order)\n }\n \n /**\n- * bitmap_allocate_region - allocate bitmap region\n+ * bitmap_allocate - allocate bitmap region\n  *\t@bitmap: array of unsigned longs corresponding to the bitmap\n  *\t@pos: beginning of bit region to allocate\n- *\t@order: region size (log base 2 of number of bits) to allocate\n+ *\t@len: number of bits to allocate\n  *\n  * Allocate (set bits in) a specified region of a bitmap.\n  *\n@@ -732,16 +732,31 @@ void bitmap_release_region(unsigned long *bitmap, unsigned int pos, int order)\n  * free (not all bits were zero).\n  */\n static __always_inline\n-int bitmap_allocate_region(unsigned long *bitmap, unsigned int pos, int order)\n+int bitmap_allocate(unsigned long *bitmap, unsigned int pos, unsigned int len)\n {\n-\tunsigned int len = BIT(order);\n-\n \tif (find_next_bit(bitmap, pos + len, pos) \u003c pos + len)\n \t\treturn -EBUSY;\n \tbitmap_set(bitmap, pos, len);\n \treturn 0;\n }\n \n+/**\n+ * bitmap_allocate_region - allocate bitmap region\n+ *\t@bitmap: array of unsigned longs corresponding to the bitmap\n+ *\t@pos: beginning of bit region to allocate\n+ *\t@order: region size (log base 2 of number of bits) to allocate\n+ *\n+ * Allocate (set bits in) a specified region of a bitmap.\n+ *\n+ * Returns: 0 on success, or %-EBUSY if specified region wasn't\n+ * free (not all bits were zero).\n+ */\n+static __always_inline\n+int bitmap_allocate_region(unsigned long *bitmap, unsigned int pos, int order)\n+{\n+\treturn bitmap_allocate(bitmap, pos, BIT(order));\n+}\n+\n /**\n  * bitmap_find_free_region - find a contiguous aligned mem region\n  *\t@bitmap: array of unsigned longs corresponding to the bitmap\ndiff --git a/include/linux/cma.h b/include/linux/cma.h\nindex 8555d38a97b1a5..fb7a4923c3ba9a 100644\n--- a/include/linux/cma.h\n+++ b/include/linux/cma.h\n@@ -7,7 +7,7 @@\n #include \u003clinux/numa.h\u003e\n \n #ifdef CONFIG_CMA_AREAS\n-#define MAX_CMA_AREAS\tCONFIG_CMA_AREAS\n+#define MAX_EARLY_CMA_AREAS\tCONFIG_CMA_AREAS\n #endif\n \n #define CMA_MAX_NAME 64\n@@ -57,8 +57,14 @@ struct page *cma_alloc_frozen_compound(struct cma *cma, unsigned int order);\n bool cma_release_frozen(struct cma *cma, const struct page *pages,\n \t\tunsigned long count);\n \n+extern int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data);\n extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data);\n extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end);\n \n extern void cma_reserve_pages_on_error(struct cma *cma);\n+\n+extern struct cma *cma_create(phys_addr_t base, phys_addr_t size,\n+\t\t\t      unsigned int order_per_bit, const char *name);\n+extern void cma_free(struct cma *cma);\n+\n #endif\ndiff --git a/include/linux/dma-heap.h b/include/linux/dma-heap.h\nindex 648328a64b27ea..1c9bed1f4ddef9 100644\n--- a/include/linux/dma-heap.h\n+++ b/include/linux/dma-heap.h\n@@ -12,6 +12,7 @@\n #include \u003clinux/types.h\u003e\n \n struct dma_heap;\n+struct seq_file;\n \n /**\n  * struct dma_heap_ops - ops to operate on a given heap\n@@ -24,6 +25,7 @@ struct dma_heap_ops {\n \t\t\t\t    unsigned long len,\n \t\t\t\t    u32 fd_flags,\n \t\t\t\t    u64 heap_flags);\n+\tint (*show)(struct seq_file *s, struct dma_heap *heap);\n };\n \n /**\ndiff --git a/include/linux/set_memory.h b/include/linux/set_memory.h\nindex 3030d9245f5ac8..abcb11378fdbd8 100644\n--- a/include/linux/set_memory.h\n+++ b/include/linux/set_memory.h\n@@ -72,6 +72,17 @@ static inline int clear_mce_nospec(unsigned long pfn)\n }\n #endif\n \n+#ifndef CONFIG_ARM64\n+static inline int set_memory_device(unsigned long addr, int numpages)\n+{\n+\treturn 0;\n+}\n+static inline int set_memory_normal(unsigned long addr, int numpages)\n+{\n+\treturn 0;\n+}\n+#endif\n+\n #ifndef CONFIG_ARCH_HAS_MEM_ENCRYPT\n static inline int set_memory_encrypted(unsigned long addr, int numpages)\n {\ndiff --git a/include/trace/events/tegra_vpr.h b/include/trace/events/tegra_vpr.h\nnew file mode 100644\nindex 00000000000000..f8ceb17679fe0a\n--- /dev/null\n+++ b/include/trace/events/tegra_vpr.h\n@@ -0,0 +1,57 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+\n+#if !defined(_TRACE_TEGRA_VPR_H) || defined(TRACE_HEADER_MULTI_READ)\n+#define _TRACE_TEGRA_VPR_H\n+\n+#undef TRACE_SYSTEM\n+#define TRACE_SYSTEM tegra_vpr\n+\n+#include \u003clinux/tracepoint.h\u003e\n+\n+TRACE_EVENT(tegra_vpr_chunk_activate,\n+\tTP_PROTO(phys_addr_t start, phys_addr_t limit),\n+\tTP_ARGS(start, limit),\n+\tTP_STRUCT__entry(\n+\t\t__field(phys_addr_t, start)\n+\t\t__field(phys_addr_t, limit)\n+\t),\n+\tTP_fast_assign(\n+\t\t__entry-\u003estart = start;\n+\t\t__entry-\u003elimit = limit;\n+\t),\n+\tTP_printk(\"%pap-%pap\", \u0026__entry-\u003estart,\n+\t\t  \u0026__entry-\u003elimit)\n+);\n+\n+TRACE_EVENT(tegra_vpr_chunk_deactivate,\n+\tTP_PROTO(phys_addr_t start, phys_addr_t limit),\n+\tTP_ARGS(start, limit),\n+\tTP_STRUCT__entry(\n+\t\t__field(phys_addr_t, start)\n+\t\t__field(phys_addr_t, limit)\n+\t),\n+\tTP_fast_assign(\n+\t\t__entry-\u003estart = start;\n+\t\t__entry-\u003elimit = limit;\n+\t),\n+\tTP_printk(\"%pap-%pap\", \u0026__entry-\u003estart,\n+\t\t  \u0026__entry-\u003elimit)\n+);\n+\n+TRACE_EVENT(tegra_vpr_set,\n+\tTP_PROTO(phys_addr_t base, phys_addr_t size),\n+\tTP_ARGS(base, size),\n+\tTP_STRUCT__entry(\n+\t\t__field(phys_addr_t, start)\n+\t\t__field(phys_addr_t, limit)\n+\t),\n+\tTP_fast_assign(\n+\t\t__entry-\u003estart = base;\n+\t\t__entry-\u003elimit = base + size;\n+\t),\n+\tTP_printk(\"%pap-%pap\", \u0026__entry-\u003estart, \u0026__entry-\u003elimit)\n+);\n+\n+#endif /* _TRACE_TEGRA_VPR_H */\n+\n+#include \u003ctrace/define_trace.h\u003e\ndiff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c\nindex f754079a287d61..7975551f69b3d9 100644\n--- a/kernel/dma/contiguous.c\n+++ b/kernel/dma/contiguous.c\n@@ -52,7 +52,7 @@\n #define CMA_SIZE_MBYTES 0\n #endif\n \n-static struct cma *dma_contiguous_areas[MAX_CMA_AREAS];\n+static struct cma *dma_contiguous_areas[MAX_EARLY_CMA_AREAS];\n static unsigned int dma_contiguous_areas_num;\n \n static int dma_contiguous_insert_area(struct cma *cma)\ndiff --git a/mm/cma.c b/mm/cma.c\nindex a13ce4999b39f6..f989e2e98594fb 100644\n--- a/mm/cma.c\n+++ b/mm/cma.c\n@@ -34,7 +34,12 @@\n #include \"internal.h\"\n #include \"cma.h\"\n \n-struct cma cma_areas[MAX_CMA_AREAS];\n+static DEFINE_MUTEX(cma_lock);\n+\n+struct cma cma_early_areas[MAX_EARLY_CMA_AREAS];\n+unsigned int cma_early_area_count;\n+\n+static LIST_HEAD(cma_areas);\n unsigned int cma_area_count;\n \n phys_addr_t cma_get_base(const struct cma *cma)\n@@ -198,7 +203,6 @@ static void __init cma_activate_area(struct cma *cma)\n \t\t\t\tfree_reserved_page(pfn_to_page(pfn));\n \t\t}\n \t}\n-\ttotalcma_pages -= cma-\u003ecount;\n \tcma-\u003eavailable_count = cma-\u003ecount = 0;\n \tpr_err(\"CMA area %s could not be activated\\n\", cma-\u003ename);\n }\n@@ -207,8 +211,8 @@ static int __init cma_init_reserved_areas(void)\n {\n \tint i;\n \n-\tfor (i = 0; i \u003c cma_area_count; i++)\n-\t\tcma_activate_area(\u0026cma_areas[i]);\n+\tfor (i = 0; i \u003c cma_early_area_count; i++)\n+\t\tcma_activate_area(\u0026cma_early_areas[i]);\n \n \treturn 0;\n }\n@@ -219,41 +223,77 @@ void __init cma_reserve_pages_on_error(struct cma *cma)\n \tset_bit(CMA_RESERVE_PAGES_ON_ERROR, \u0026cma-\u003eflags);\n }\n \n+static void __init cma_init_area(struct cma *cma, const char *name,\n+\t\t\t\t phys_addr_t size, unsigned int order_per_bit)\n+{\n+\tif (name)\n+\t\tstrscpy(cma-\u003ename, name);\n+\telse\n+\t\tsnprintf(cma-\u003ename, CMA_MAX_NAME,  \"cma%d\\n\", cma_area_count);\n+\n+\tcma-\u003eavailable_count = cma-\u003ecount = size \u003e\u003e PAGE_SHIFT;\n+\tcma-\u003eorder_per_bit = order_per_bit;\n+\n+\tINIT_LIST_HEAD(\u0026cma-\u003enode);\n+}\n+\n static int __init cma_new_area(const char *name, phys_addr_t size,\n \t\t\t       unsigned int order_per_bit,\n \t\t\t       struct cma **res_cma)\n {\n \tstruct cma *cma;\n \n-\tif (cma_area_count == ARRAY_SIZE(cma_areas)) {\n+\tif (cma_early_area_count == ARRAY_SIZE(cma_early_areas)) {\n \t\tpr_err(\"Not enough slots for CMA reserved regions!\\n\");\n \t\treturn -ENOSPC;\n \t}\n \n+\tmutex_lock(\u0026cma_lock);\n+\n \t/*\n \t * Each reserved area must be initialised later, when more kernel\n \t * subsystems (like slab allocator) are available.\n \t */\n-\tcma = \u0026cma_areas[cma_area_count];\n-\tcma_area_count++;\n+\tcma = \u0026cma_early_areas[cma_early_area_count];\n+\tcma_early_area_count++;\n \n-\tif (name)\n-\t\tstrscpy(cma-\u003ename, name);\n-\telse\n-\t\tsnprintf(cma-\u003ename, CMA_MAX_NAME,  \"cma%d\\n\", cma_area_count);\n+\tcma_init_area(cma, name, size, order_per_bit);\n \n-\tcma-\u003eavailable_count = cma-\u003ecount = size \u003e\u003e PAGE_SHIFT;\n-\tcma-\u003eorder_per_bit = order_per_bit;\n-\t*res_cma = cma;\n \ttotalcma_pages += cma-\u003ecount;\n+\t*res_cma = cma;\n+\n+\tmutex_unlock(\u0026cma_lock);\n \n \treturn 0;\n }\n \n static void __init cma_drop_area(struct cma *cma)\n {\n+\tmutex_lock(\u0026cma_lock);\n \ttotalcma_pages -= cma-\u003ecount;\n-\tcma_area_count--;\n+\tcma_early_area_count--;\n+\tmutex_unlock(\u0026cma_lock);\n+}\n+\n+static int __init cma_check_memory(phys_addr_t base, phys_addr_t size)\n+{\n+\tif (!size || !memblock_is_region_reserved(base, size))\n+\t\treturn -EINVAL;\n+\n+\t/*\n+\t * CMA uses CMA_MIN_ALIGNMENT_BYTES as alignment requirement which\n+\t * needs pageblock_order to be initialized. Let's enforce it.\n+\t */\n+\tif (!pageblock_order) {\n+\t\tpr_err(\"pageblock_order not yet initialized. Called during early boot?\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* ensure minimal alignment required by mm core */\n+\tif (!IS_ALIGNED(base | size, CMA_MIN_ALIGNMENT_BYTES))\n+\t\treturn -EINVAL;\n+\n+\treturn 0;\n }\n \n /**\n@@ -276,22 +316,9 @@ int __init cma_init_reserved_mem(phys_addr_t base, phys_addr_t size,\n \tstruct cma *cma;\n \tint ret;\n \n-\t/* Sanity checks */\n-\tif (!size || !memblock_is_region_reserved(base, size))\n-\t\treturn -EINVAL;\n-\n-\t/*\n-\t * CMA uses CMA_MIN_ALIGNMENT_BYTES as alignment requirement which\n-\t * needs pageblock_order to be initialized. Let's enforce it.\n-\t */\n-\tif (!pageblock_order) {\n-\t\tpr_err(\"pageblock_order not yet initialized. Called during early boot?\\n\");\n-\t\treturn -EINVAL;\n-\t}\n-\n-\t/* ensure minimal alignment required by mm core */\n-\tif (!IS_ALIGNED(base | size, CMA_MIN_ALIGNMENT_BYTES))\n-\t\treturn -EINVAL;\n+\tret = cma_check_memory(base, size);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n \n \tret = cma_new_area(name, size, order_per_bit, \u0026cma);\n \tif (ret != 0)\n@@ -444,7 +471,7 @@ static int __init __cma_declare_contiguous_nid(phys_addr_t *basep,\n \tpr_debug(\"%s(size %pa, base %pa, limit %pa alignment %pa)\\n\",\n \t\t__func__, \u0026size, \u0026base, \u0026limit, \u0026alignment);\n \n-\tif (cma_area_count == ARRAY_SIZE(cma_areas)) {\n+\tif (cma_early_area_count == ARRAY_SIZE(cma_early_areas)) {\n \t\tpr_err(\"Not enough slots for CMA reserved regions!\\n\");\n \t\treturn -ENOSPC;\n \t}\n@@ -1051,12 +1078,12 @@ bool cma_release_frozen(struct cma *cma, const struct page *pages,\n \treturn true;\n }\n \n-int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\n+int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data)\n {\n \tint i;\n \n-\tfor (i = 0; i \u003c cma_area_count; i++) {\n-\t\tint ret = it(\u0026cma_areas[i], data);\n+\tfor (i = 0; i \u003c cma_early_area_count; i++) {\n+\t\tint ret = it(\u0026cma_early_areas[i], data);\n \n \t\tif (ret)\n \t\t\treturn ret;\n@@ -1065,6 +1092,25 @@ int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\n \treturn 0;\n }\n \n+int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\n+{\n+\tstruct cma *cma;\n+\n+\tmutex_lock(\u0026cma_lock);\n+\n+\tlist_for_each_entry(cma, \u0026cma_areas, node) {\n+\t\tint ret = it(cma, data);\n+\n+\t\tif (ret) {\n+\t\t\tmutex_unlock(\u0026cma_lock);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tmutex_unlock(\u0026cma_lock);\n+\treturn 0;\n+}\n+\n bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end)\n {\n \tint r;\n@@ -1147,3 +1193,74 @@ void __init *cma_reserve_early(struct cma *cma, unsigned long size)\n \n \treturn ret;\n }\n+\n+struct cma *__init cma_create(phys_addr_t base, phys_addr_t size,\n+\t\t\t      unsigned int order_per_bit, const char *name)\n+{\n+\tstruct cma *cma;\n+\tint ret;\n+\n+\tret = cma_check_memory(base, size);\n+\tif (ret \u003c 0)\n+\t\treturn ERR_PTR(ret);\n+\n+\tcma = kzalloc_obj(*cma, GFP_KERNEL);\n+\tif (!cma)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tcma_init_area(cma, name, size, order_per_bit);\n+\tcma-\u003eranges[0].base_pfn = PFN_DOWN(base);\n+\tcma-\u003eranges[0].early_pfn = PFN_DOWN(base);\n+\tcma-\u003eranges[0].count = cma-\u003ecount;\n+\tcma-\u003enranges = 1;\n+\n+\tcma_activate_area(cma);\n+\n+\tmutex_lock(\u0026cma_lock);\n+\tlist_add_tail(\u0026cma-\u003enode, \u0026cma_areas);\n+\ttotalcma_pages += cma-\u003ecount;\n+\tcma_area_count++;\n+\tmutex_unlock(\u0026cma_lock);\n+\n+\treturn cma;\n+}\n+\n+void cma_free(struct cma *cma)\n+{\n+\tunsigned int i;\n+\n+\t/*\n+\t * Safety check to prevent a CMA with active allocations from being\n+\t * released.\n+\t */\n+\tfor (i = 0; i \u003c cma-\u003enranges; i++) {\n+\t\tunsigned long nbits = cma_bitmap_maxno(cma, \u0026cma-\u003eranges[i]);\n+\n+\t\tif (!bitmap_empty(cma-\u003eranges[i].bitmap, nbits)) {\n+\t\t\tWARN(1, \"%s: range %u not empty\\n\", cma-\u003ename, i);\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\n+\t/* free reserved pages and the bitmap */\n+\tfor (i = 0; i \u003c cma-\u003enranges; i++) {\n+\t\tstruct cma_memrange *cmr = \u0026cma-\u003eranges[i];\n+\t\tunsigned long end_pfn, pfn;\n+\n+\t\tend_pfn = cmr-\u003ebase_pfn + cmr-\u003ecount;\n+\t\tfor (pfn = cmr-\u003ebase_pfn; pfn \u003c end_pfn; pfn++)\n+\t\t\tfree_reserved_page(pfn_to_page(pfn));\n+\n+\t\tbitmap_free(cmr-\u003ebitmap);\n+\t}\n+\n+\tmutex_destroy(\u0026cma-\u003ealloc_mutex);\n+\n+\tmutex_lock(\u0026cma_lock);\n+\ttotalcma_pages -= cma-\u003ecount;\n+\tlist_del(\u0026cma-\u003enode);\n+\tcma_area_count--;\n+\tmutex_unlock(\u0026cma_lock);\n+\n+\tkfree(cma);\n+}\ndiff --git a/mm/cma.h b/mm/cma.h\nindex c70180c36559c2..ae4db9819e38db 100644\n--- a/mm/cma.h\n+++ b/mm/cma.h\n@@ -41,6 +41,7 @@ struct cma {\n \tunsigned long\tavailable_count;\n \tunsigned int order_per_bit; /* Order of pages represented by one bit */\n \tspinlock_t\tlock;\n+\tstruct list_head node;\n \tstruct mutex alloc_mutex;\n #ifdef CONFIG_CMA_DEBUGFS\n \tstruct hlist_head mem_head;\n@@ -71,8 +72,8 @@ enum cma_flags {\n \tCMA_ACTIVATED,\n };\n \n-extern struct cma cma_areas[MAX_CMA_AREAS];\n-extern unsigned int cma_area_count;\n+extern struct cma cma_early_areas[MAX_EARLY_CMA_AREAS];\n+extern unsigned int cma_early_area_count;\n \n static inline unsigned long cma_bitmap_maxno(struct cma *cma,\n \t\tstruct cma_memrange *cmr)\n"
}

2/1 2026/07/01 23:35 agent patch-evaluator
3m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new DMA-BUF heap for NVIDIA Tegra Video-Protection-Region (VPR). The driver (tegra-vpr.c) only initializes if a specific device tree node ('nvidia,tegra-video-protection-region') is present, which is not the case in standard virtualized environments like QEMU. Furthermore, the driver relies on Tegra-specific SMC calls to a secure monitor. The changes to core CMA logic (mm/cma.c) are either executed only during early boot or are dead code (e.g., cma_for_each_area has no callers). Therefore, the functional changes are impossible to reach and fuzz in a virtualized environment.",
  "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 b30d69576a1da6241030cb4edb32e0197943e1b9
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Jul 1 23:35:04 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml
index 7200095ef19e3c..1e27a731ad9ad2 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml
@@ -67,6 +67,14 @@ properties:
       - const: dma-mem # read
       - const: write
 
+  memory-region:
+    items:
+      - description: reference to the video protection memory region
+
+  memory-region-names:
+    items:
+      - const: protected
+
   dma-coherent: true
 
 additionalProperties: false
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra186-dc.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra186-dc.yaml
index ce4589466a186b..881bfbf4764dfa 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra186-dc.yaml
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra186-dc.yaml
@@ -57,6 +57,16 @@ properties:
       - const: dma-mem # read-0
       - const: read-1
 
+  memory-region:
+    minItems: 1
+    maxItems: 2
+
+  memory-region-names:
+    items:
+      enum: [ framebuffer, protected ]
+    minItems: 1
+    maxItems: 2
+
   nvidia,outputs:
     description: A list of phandles of outputs that this display
       controller can drive.
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml
index 69be95afd56277..a012644eeb7d7a 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml
@@ -65,7 +65,15 @@ properties:
     items:
       - description: phandle to the core power domain
 
-  memory-region: true
+  memory-region:
+    minItems: 1
+    maxItems: 2
+
+  memory-region-names:
+    items:
+      enum: [ framebuffer, protected ]
+    minItems: 1
+    maxitems: 2
 
   nvidia,head:
     $ref: /schemas/types.yaml#/definitions/uint32
diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
index 3563378a01af47..f45be30835a8b3 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
@@ -96,6 +96,13 @@ properties:
     items:
       - description: phandle to the HEG or core power domain
 
+  memory-region:
+    maxItems: 1
+
+  memory-region-names:
+    items:
+      - const: protected
+
 required:
   - compatible
   - interrupts
diff --git a/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml b/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml
index 0b7561c8b9bb2c..72313f776dbd8f 100644
--- a/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml
+++ b/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml
@@ -60,6 +60,14 @@ properties:
       - const: dma-mem
       - const: write
 
+  memory-region:
+    items:
+      - description: reference to the video protection memory region
+
+  memory-region-names:
+    items:
+      - const: protected
+
   nvidia,memory-controller:
     $ref: /schemas/types.yaml#/definitions/phandle
     description:
diff --git a/Documentation/devicetree/bindings/reserved-memory/nvidia,tegra-video-protection-region.yaml b/Documentation/devicetree/bindings/reserved-memory/nvidia,tegra-video-protection-region.yaml
new file mode 100644
index 00000000000000..1c524bae9ce320
--- /dev/null
+++ b/Documentation/devicetree/bindings/reserved-memory/nvidia,tegra-video-protection-region.yaml
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/reserved-memory/nvidia,tegra-video-protection-region.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra Video Protection Region (VPR)
+
+maintainers:
+  - Thierry Reding <thierry.reding@gmail.com>
+  - Jon Hunter <jonathanh@nvidia.com>
+
+description: |
+  NVIDIA Tegra chips have long supported a mechanism to protect a single,
+  contiguous memory region from non-secure memory accesses. Typically this
+  region is used for decoding and playback of DRM protected content. Various
+  devices, such as the display controller and multimedia engines (video
+  decoder) can access this region in a secure way. Access from the CPU is
+  generally forbidden.
+
+  Two variants exist for VPR: one is fixed in both the base address and size,
+  while the other is resizable. Fixed VPR can be described by just a "reg"
+  property specifying the base address and size, whereas the resizable VPR
+  is defined by a size/alignment pair of properties. For resizable VPR the
+  memory is reusable by the rest of the system when it's unused for VPR and
+  therefore the "reusable" property must be specified along with it. For a
+  fixed VPR, the memory is permanently protected, and therefore it's not
+  reusable and must also be marked as "no-map" to prevent any (including
+  speculative) accesses to it.
+
+allOf:
+  - $ref: reserved-memory.yaml
+
+properties:
+  compatible:
+    const: nvidia,tegra-video-protection-region
+
+dependencies:
+  size: [alignment, reusable]
+  alignment: [size, reusable]
+  reusable: [alignment, size]
+
+  reg: [no-map]
+  no-map: [reg]
+
+unevaluatedProperties: false
+
+oneOf:
+  - required:
+      - compatible
+      - reg
+
+  - required:
+      - compatible
+      - size
+
+examples:
+  - |
+    /* resizable VPR */
+    protected {
+      compatible = "nvidia,tegra-video-protection-region";
+
+      size = <0x0 0x70000000>;
+      alignment = <0x0 0x100000>;
+      reusable;
+    };
+
+  - |
+    /* fixed VPR */
+    protected@2a8000000 {
+      compatible = "nvidia,tegra-video-protection-region";
+
+      /* fixed VPR */
+      reg = <0x2 0xa8000000 0x0 0x70000000>;
+      no-map;
+    };
diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
index f9bc53b60f9974..934952ab210275 100644
--- a/arch/arm/mm/dma-mapping.c
+++ b/arch/arm/mm/dma-mapping.c
@@ -254,7 +254,7 @@ struct dma_contig_early_reserve {
 	unsigned long size;
 };
 
-static struct dma_contig_early_reserve dma_mmu_remap[MAX_CMA_AREAS] __initdata;
+static struct dma_contig_early_reserve dma_mmu_remap[MAX_EARLY_CMA_AREAS] __initdata;
 
 static int dma_mmu_remap_num __initdata;
 
diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
index 8e0c51e496e20d..38637e8e6fc92c 100644
--- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
@@ -29,6 +29,45 @@ aliases {
 		i2c8 = &dp_aux_ch3_i2c;
 	};
 
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		vpr: protected {
+			compatible = "nvidia,tegra-video-protection-region";
+			status = "disabled";
+
+			/*
+			 * Two variants exist for this. For fixed VPR, the
+			 * firmware is supposed to update the "reg" property
+			 * with the fixed memory region configured as VPR.
+			 *
+			 * For resizable VPR we don't care about the exact
+			 * address and instead want a reserved region to be
+			 * allocated with a certain size and alignment at
+			 * boot time.
+			 *
+			 * The below assumes resizable VPR by default. If the
+			 * firmwares sets up fixed VPR, it is responsible for
+			 * adding the missing "reg" property, removing any of
+			 * the unused properties, as well as adding a unit-
+			 * address matching the "reg" property.
+			 */
+
+			/* fixed VPR */
+			/*
+			reg = <0x0 0x0 0x0 0x0>;
+			no-map;
+			*/
+
+			/* resizable VPR */
+			size = <0x0 0x70000000>;
+			alignment = <0x0 0x100000>;
+			reusable;
+		};
+	};
+
 	bus@0 {
 		compatible = "simple-bus";
 
@@ -4440,6 +4479,9 @@ vic@15340000 {
 				interconnect-names = "dma-mem", "write";
 				iommus = <&smmu_niso1 TEGRA234_SID_VIC>;
 				dma-coherent;
+
+				memory-region = <&vpr>;
+				memory-region-names = "protected";
 			};
 
 			nvdec@15480000 {
@@ -4458,6 +4500,9 @@ nvdec@15480000 {
 				iommus = <&smmu_niso1 TEGRA234_SID_NVDEC>;
 				dma-coherent;
 
+				memory-region = <&vpr>;
+				memory-region-names = "protected";
+
 				nvidia,memory-controller = <&mc>;
 
 				/*
diff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
index 2d8e7e37830ff2..cc93c6822a7397 100644
--- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
@@ -23,6 +23,39 @@ shmem_bpmp: shmem@86070000 {
 			reg = <0x0 0x86070000 0x0 0x2000>;
 			no-map;
 		};
+
+		vpr: protected {
+			compatible = "nvidia,tegra-video-protection-region";
+			status = "disabled";
+
+			/*
+			 * Two variants exist for this. For fixed VPR, the
+			 * firmware is supposed to update the "reg" property
+			 * with the fixed memory region configured as VPR.
+			 *
+			 * For resizable VPR we don't care about the exact
+			 * address and instead want a reserved region to be
+			 * allocated with a certain size and alignment at
+			 * boot time.
+			 *
+			 * The below assumes resizable VPR by default. If the
+			 * firmwares sets up fixed VPR, it is responsible for
+			 * adding the missing "reg" property, removing any of
+			 * the unused properties, as well as adding a unit-
+			 * address matching the "reg" property.
+			 */
+
+			/* fixed VPR */
+			/*
+			reg = <0x0 0x0 0x0 0x0>;
+			no-map;
+			*/
+
+			/* resizable VPR */
+			size = <0x0 0x70000000>;
+			alignment = <0x0 0x100000>;
+			reusable;
+		};
 	};
 
 	/* SYSTEM MMIO */
diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h
index 90f61b17275e1b..6a7030609789bb 100644
--- a/arch/arm64/include/asm/set_memory.h
+++ b/arch/arm64/include/asm/set_memory.h
@@ -10,6 +10,8 @@ bool can_set_direct_map(void);
 #define can_set_direct_map can_set_direct_map
 
 int set_memory_valid(unsigned long addr, int numpages, int enable);
+int set_memory_device(unsigned long addr, int numpages);
+int set_memory_normal(unsigned long addr, int numpages);
 
 int set_direct_map_invalid_noflush(struct page *page);
 int set_direct_map_default_noflush(struct page *page);
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index bbe98ac9ad8c67..871b59a6c9eaf3 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -251,6 +251,22 @@ int set_memory_valid(unsigned long addr, int numpages, int enable)
 					__pgprot(PTE_PRESENT_VALID_KERNEL));
 }
 
+int set_memory_device(unsigned long addr, int numpages)
+{
+	return __change_memory_common(addr, PAGE_SIZE * numpages,
+				__pgprot(PROT_DEVICE_nGnRnE),
+				__pgprot(PROT_NORMAL));
+}
+EXPORT_SYMBOL_GPL(set_memory_device);
+
+int set_memory_normal(unsigned long addr, int numpages)
+{
+	return __change_memory_common(addr, PAGE_SIZE * numpages,
+				__pgprot(PROT_NORMAL),
+				__pgprot(PROT_DEVICE_nGnRnE));
+}
+EXPORT_SYMBOL_GPL(set_memory_normal);
+
 int set_direct_map_invalid_noflush(struct page *page)
 {
 	pgprot_t clear_mask = __pgprot(PTE_PRESENT_VALID_KERNEL);
diff --git a/arch/s390/mm/init.c b/arch/s390/mm/init.c
index f07168a0d3dd34..f8f78f1434eafb 100644
--- a/arch/s390/mm/init.c
+++ b/arch/s390/mm/init.c
@@ -241,7 +241,7 @@ static int s390_cma_mem_notifier(struct notifier_block *nb,
 	mem_data.start = arg->start_pfn << PAGE_SHIFT;
 	mem_data.end = mem_data.start + (arg->nr_pages << PAGE_SHIFT);
 	if (action == MEM_GOING_OFFLINE)
-		rc = cma_for_each_area(s390_cma_check_range, &mem_data);
+		rc = cma_for_each_early_area(s390_cma_check_range, &mem_data);
 	return notifier_from_errno(rc);
 }
 
diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c
index a76bf3f8b071a3..1ceb6ee8c05a21 100644
--- a/drivers/dma-buf/dma-heap.c
+++ b/drivers/dma-buf/dma-heap.c
@@ -7,6 +7,7 @@
  */
 
 #include <linux/cdev.h>
+#include <linux/debugfs.h>
 #include <linux/device.h>
 #include <linux/dma-buf.h>
 #include <linux/dma-heap.h>
@@ -224,6 +225,46 @@ const char *dma_heap_get_name(struct dma_heap *heap)
 }
 EXPORT_SYMBOL_NS_GPL(dma_heap_get_name, "DMA_BUF_HEAP");
 
+#ifdef CONFIG_DEBUG_FS
+static int dma_heap_debug_show(struct seq_file *s, void *unused)
+{
+	struct dma_heap *heap = s->private;
+	int err = 0;
+
+	if (heap->ops && heap->ops->show)
+		err = heap->ops->show(s, heap);
+
+	return err;
+}
+DEFINE_SHOW_ATTRIBUTE(dma_heap_debug);
+
+static struct dentry *dma_heap_debugfs_dir;
+
+static void dma_heap_init_debugfs(void)
+{
+	struct dentry *dir;
+
+	dir = debugfs_create_dir("dma_heap", NULL);
+	if (IS_ERR(dir))
+		return;
+
+	dma_heap_debugfs_dir = dir;
+}
+
+static void dma_heap_exit_debugfs(void)
+{
+	debugfs_remove_recursive(dma_heap_debugfs_dir);
+}
+#else
+static void dma_heap_init_debugfs(void)
+{
+}
+
+static void dma_heap_exit_debugfs(void)
+{
+}
+#endif
+
 /**
  * dma_heap_add - adds a heap to dmabuf heaps
  * @exp_info: information needed to register this heap
@@ -298,6 +339,13 @@ struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info)
 
 	/* Add heap to the list */
 	list_add(&heap->list, &heap_list);
+
+#ifdef CONFIG_DEBUG_FS
+	if (heap->ops && heap->ops->show)
+		debugfs_create_file(heap->name, 0444, dma_heap_debugfs_dir,
+				    heap, &dma_heap_debug_fops);
+#endif
+
 	mutex_unlock(&heap_list_lock);
 
 	return heap;
@@ -334,6 +382,14 @@ static int dma_heap_init(void)
 	}
 	dma_heap_class->devnode = dma_heap_devnode;
 
+	dma_heap_init_debugfs();
+
 	return 0;
 }
 subsys_initcall(dma_heap_init);
+
+static void __exit dma_heap_exit(void)
+{
+	dma_heap_exit_debugfs();
+}
+__exitcall(dma_heap_exit);
diff --git a/drivers/dma-buf/heaps/Kconfig b/drivers/dma-buf/heaps/Kconfig
index bb729e91545c40..dd6035598d0288 100644
--- a/drivers/dma-buf/heaps/Kconfig
+++ b/drivers/dma-buf/heaps/Kconfig
@@ -20,3 +20,10 @@ config DMABUF_HEAPS_CMA
 	  Choose this option to enable dma-buf CMA heap. This heap is backed
 	  by the Contiguous Memory Allocator (CMA). If your system has these
 	  regions, you should say Y here.
+
+config DMABUF_HEAPS_TEGRA_VPR
+	bool "NVIDIA Tegra Video-Protected-Region DMA-BUF Heap"
+	depends on DMABUF_HEAPS && DMA_CMA
+	help
+	  Choose this option to enable Video-Protected-Region (VPR) support on
+	  a range of NVIDIA Tegra devices.
diff --git a/drivers/dma-buf/heaps/Makefile b/drivers/dma-buf/heaps/Makefile
index 974467791032ff..265b77a7b88998 100644
--- a/drivers/dma-buf/heaps/Makefile
+++ b/drivers/dma-buf/heaps/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_DMABUF_HEAPS_SYSTEM)	+= system_heap.o
 obj-$(CONFIG_DMABUF_HEAPS_CMA)		+= cma_heap.o
+obj-$(CONFIG_DMABUF_HEAPS_TEGRA_VPR)	+= tegra-vpr.o
diff --git a/drivers/dma-buf/heaps/tegra-vpr.c b/drivers/dma-buf/heaps/tegra-vpr.c
new file mode 100644
index 00000000000000..918cf66ec222c9
--- /dev/null
+++ b/drivers/dma-buf/heaps/tegra-vpr.c
@@ -0,0 +1,1242 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DMA-BUF restricted heap exporter for NVIDIA Video-Protection-Region (VPR)
+ *
+ * Copyright (C) 2024-2026 NVIDIA Corporation
+ */
+
+#define pr_fmt(fmt) "tegra-vpr: " fmt
+
+#include <linux/arm-smccc.h>
+#include <linux/cma.h>
+#include <linux/debugfs.h>
+#include <linux/dma-buf.h>
+#include <linux/dma-heap.h>
+#include <linux/find.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/set_memory.h>
+
+#include <linux/freezer.h>
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/tegra_vpr.h>
+
+#define TEGRA_VPR_MAX_CHUNKS 64
+
+struct tegra_vpr;
+
+struct tegra_vpr_device {
+	struct list_head node;
+	struct device *dev;
+};
+
+struct tegra_vpr_chunk {
+	phys_addr_t start;
+	phys_addr_t limit;
+	size_t size;
+
+	struct tegra_vpr *vpr;
+	struct cma *cma;
+	bool active;
+
+	struct page *start_page;
+	unsigned int offset;
+	unsigned long virt;
+	pgoff_t num_pages;
+
+	unsigned int num_buffers;
+};
+
+struct tegra_vpr {
+	struct device_node *dev_node;
+	unsigned long align;
+	phys_addr_t base;
+	phys_addr_t size;
+	bool use_freezer;
+	bool resizable;
+
+	struct list_head buffers;
+	struct page *start_page;
+	unsigned long *bitmap;
+	pgoff_t num_pages;
+
+	/* resizable VPR */
+	DECLARE_BITMAP(active, TEGRA_VPR_MAX_CHUNKS);
+	struct tegra_vpr_chunk *chunks;
+	unsigned int num_chunks;
+
+	unsigned int first;
+	unsigned int last;
+
+	struct list_head devices;
+	struct mutex lock;
+};
+
+struct tegra_vpr_buffer {
+	struct list_head attachments;
+	struct tegra_vpr *vpr;
+	struct list_head list;
+	struct mutex lock;
+
+	struct page *start_page;
+	struct page **pages;
+	pgoff_t num_pages;
+	phys_addr_t start;
+	phys_addr_t limit;
+	size_t size;
+	int pageno;
+	int order;
+
+	DECLARE_BITMAP(chunks, TEGRA_VPR_MAX_CHUNKS);
+};
+
+struct tegra_vpr_attachment {
+	struct device *dev;
+	struct sg_table sgt;
+	struct list_head list;
+};
+
+#define ARM_SMCCC_TE_FUNC_PROGRAM_VPR 0x3
+
+#define ARM_SMCCC_VENDOR_SIP_TE_PROGRAM_VPR_FUNC_ID		\
+	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL,			\
+			   ARM_SMCCC_SMC_32,			\
+			   ARM_SMCCC_OWNER_SIP,			\
+			   ARM_SMCCC_TE_FUNC_PROGRAM_VPR)
+
+static int tegra_vpr_set(phys_addr_t base, phys_addr_t size)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(ARM_SMCCC_VENDOR_SIP_TE_PROGRAM_VPR_FUNC_ID, base, size,
+		      0, 0, 0, 0, 0, &res);
+
+	return res.a0;
+}
+
+static int tegra_vpr_get_extents(struct tegra_vpr *vpr, phys_addr_t *base,
+				 phys_addr_t *size)
+{
+	phys_addr_t start = ~0, limit = 0;
+	unsigned int i;
+
+	for (i = 0; i < vpr->num_chunks; i++) {
+		struct tegra_vpr_chunk *chunk = &vpr->chunks[i];
+
+		if (chunk->active) {
+			if (chunk->start < start)
+				start = chunk->start;
+
+			if (chunk->limit > limit)
+				limit = chunk->limit;
+		}
+	}
+
+	if (limit > start) {
+		*size = limit - start;
+		*base = start;
+	} else {
+		*base = *size = 0;
+	}
+
+	return 0;
+}
+
+static int tegra_vpr_resize(struct tegra_vpr *vpr)
+{
+	struct tegra_vpr_device *node;
+	phys_addr_t base, size;
+	int err, status = 0;
+
+	err = tegra_vpr_get_extents(vpr, &base, &size);
+	if (err < 0) {
+		pr_err("%s(): failed to get VPR extents: %d\n", __func__, err);
+		return err;
+	}
+
+	if (vpr->use_freezer) {
+		err = freeze_processes();
+		if (err < 0) {
+			pr_err("%s(): failed to freeze processes: %d\n",
+			       __func__, err);
+			return err;
+		}
+	}
+
+	list_for_each_entry(node, &vpr->devices, node) {
+		err = pm_generic_freeze(node->dev);
+		if (err < 0) {
+			pr_err("failed to runtime suspend %s: %d\n",
+			       dev_name(node->dev), err);
+			goto thaw;
+		}
+	}
+
+	trace_tegra_vpr_set(base, size);
+
+	err = tegra_vpr_set(base, size);
+	if (err < 0) {
+		pr_err("failed to secure VPR: %d\n", err);
+		status = err;
+	}
+
+thaw:
+	list_for_each_entry_continue_reverse(node, &vpr->devices, node) {
+		err = pm_generic_thaw(node->dev);
+		if (err < 0) {
+			pr_err("failed to runtime resume %s\n",
+			       dev_name(node->dev));
+			continue;
+		}
+	}
+
+	if (vpr->use_freezer)
+		thaw_processes();
+
+	return status;
+}
+
+static int __init tegra_vpr_chunk_init(struct tegra_vpr *vpr,
+				       struct tegra_vpr_chunk *chunk,
+				       phys_addr_t start, size_t size,
+				       unsigned int order, const char *name)
+{
+	chunk->start = start;
+	chunk->limit = start + size;
+	chunk->size = size;
+	chunk->vpr = vpr;
+
+	chunk->cma = cma_create(start, size, order, name);
+	if (IS_ERR(chunk->cma)) {
+		pr_err("cma_create() failed: %ld\n", PTR_ERR(chunk->cma));
+		return PTR_ERR(chunk->cma);
+	}
+
+	chunk->offset = (start - vpr->base) >> PAGE_SHIFT;
+	chunk->num_pages = size >> PAGE_SHIFT;
+	chunk->num_buffers = 0;
+
+	/* CMA area is not reserved yet */
+	chunk->start_page = NULL;
+	chunk->virt = 0;
+
+	return 0;
+}
+
+static void tegra_vpr_chunk_free(struct tegra_vpr_chunk *chunk)
+{
+	cma_free(chunk->cma);
+}
+
+static inline bool tegra_vpr_chunk_is_last(const struct tegra_vpr_chunk *chunk)
+{
+	phys_addr_t limit = chunk->vpr->base + chunk->vpr->size;
+
+	return chunk->limit == limit;
+}
+
+static inline bool tegra_vpr_chunk_is_leaf(const struct tegra_vpr_chunk *chunk)
+{
+	const struct tegra_vpr_chunk *next = chunk + 1;
+
+	if (tegra_vpr_chunk_is_last(chunk))
+		return true;
+
+	return !next->active;
+}
+
+static int tegra_vpr_chunk_activate(struct tegra_vpr_chunk *chunk)
+{
+	unsigned long align = get_order(chunk->vpr->align);
+	int err;
+
+	trace_tegra_vpr_chunk_activate(chunk->start, chunk->limit);
+
+	chunk->start_page = cma_alloc(chunk->cma, chunk->num_pages, align,
+				      false);
+	if (!chunk->start_page) {
+		err = -ENOMEM;
+		goto fail;
+	}
+
+	chunk->virt = (unsigned long)page_to_virt(chunk->start_page);
+
+	err = set_memory_device(chunk->virt, chunk->num_pages);
+	if (err < 0)
+		goto free;
+
+	chunk->active = true;
+
+	return 0;
+
+free:
+	cma_release(chunk->cma, chunk->start_page, chunk->num_pages);
+fail:
+	chunk->start_page = NULL;
+	chunk->virt = 0;
+	return err;
+}
+
+static int tegra_vpr_chunk_deactivate(struct tegra_vpr_chunk *chunk)
+{
+	int err;
+
+	if (!chunk->active)
+		return 0;
+
+	/* do not deactivate if there are buffers left in this chunk */
+	if (WARN_ON(chunk->num_buffers > 0))
+		return -EBUSY;
+
+	trace_tegra_vpr_chunk_deactivate(chunk->start, chunk->limit);
+
+	err = set_memory_normal(chunk->virt, chunk->num_pages);
+	if (err < 0)
+		return err;
+
+	chunk->active = false;
+
+	cma_release(chunk->cma, chunk->start_page, chunk->num_pages);
+	chunk->start_page = NULL;
+	chunk->virt = 0;
+
+	return 0;
+}
+
+static bool tegra_vpr_chunk_overlaps(struct tegra_vpr_chunk *chunk,
+				     unsigned int start, unsigned int limit)
+{
+	unsigned int first = chunk->offset;
+	unsigned int last = chunk->offset + chunk->num_pages - 1;
+
+	if (last < start || first >= limit)
+		return false;
+
+	return true;
+}
+
+static int tegra_vpr_activate_chunks(struct tegra_vpr *vpr,
+				     struct tegra_vpr_buffer *buffer)
+{
+	DECLARE_BITMAP(dirty, vpr->num_chunks);
+	unsigned int i, bottom, top;
+	int err = 0, ret;
+
+	bitmap_zero(dirty, vpr->num_chunks);
+
+	/* activate any inactive chunks that overlap this buffer */
+	for_each_set_bit(i, buffer->chunks, vpr->num_chunks) {
+		struct tegra_vpr_chunk *chunk = &vpr->chunks[i];
+
+		if (chunk->active)
+			continue;
+
+		err = tegra_vpr_chunk_activate(chunk);
+		if (err < 0)
+			goto deactivate;
+
+		set_bit(i, vpr->active);
+		set_bit(i, dirty);
+	}
+
+	/*
+	 * Activating chunks above may have created holes, but since the VPR
+	 * can only ever be a single contiguous region, make sure to activate
+	 * any missing chunks.
+	 */
+	for_each_clear_bitrange(bottom, top, vpr->active, vpr->num_chunks) {
+		/* inactive chunks at the bottom or the top are harmless */
+		if (bottom == 0 || top == vpr->num_chunks)
+			continue;
+
+		for (i = bottom; i < top; i++) {
+			struct tegra_vpr_chunk *chunk = &vpr->chunks[i];
+
+			err = tegra_vpr_chunk_activate(chunk);
+			if (err < 0)
+				goto deactivate;
+
+			set_bit(i, vpr->active);
+			set_bit(i, dirty);
+		}
+	}
+
+	/* if any chunks have been activated, VPR needs to be resized */
+	if (!bitmap_empty(dirty, vpr->num_chunks)) {
+		err = tegra_vpr_resize(vpr);
+		if (err < 0) {
+			pr_err("failed to grow VPR: %d\n", err);
+			goto deactivate;
+		}
+	}
+
+	/* increment buffer count for each chunk */
+	for_each_set_bit(i, buffer->chunks, vpr->num_chunks)
+		vpr->chunks[i].num_buffers++;
+
+	return 0;
+
+deactivate:
+	/* deactivate any of the previously inactive chunks on failure */
+	for_each_set_bit(i, dirty, vpr->num_chunks) {
+		ret = tegra_vpr_chunk_deactivate(&vpr->chunks[i]);
+		if (ret < 0)
+			WARN(1, "failed to deactivate chunk #%u: %d\n", i, ret);
+
+		clear_bit(i, vpr->active);
+	}
+
+	return err;
+}
+
+/*
+ * Retrieve the range of pages within the activate region of the VPR.
+ */
+static bool tegra_vpr_get_active_range(struct tegra_vpr *vpr,
+				       unsigned int *first,
+				       unsigned int *last)
+{
+	unsigned long i, j;
+
+	i = find_first_bit(vpr->active, vpr->num_chunks);
+	if (i >= vpr->num_chunks)
+		return false;
+
+	j = find_last_bit(vpr->active, vpr->num_chunks);
+	if (j >= vpr->num_chunks)
+		return false;
+
+	*first = vpr->chunks[i].offset;
+	*last = vpr->chunks[j].offset + vpr->chunks[j].num_pages;
+
+	return true;
+}
+
+/*
+ * Try to find and allocate a free region within a specific page range.
+ * Returns the page number if successful, -ENOSPC otherwise.
+ *
+ * This function mimics bitmap_find_free_region() but restricts the search
+ * to a specific range to enable allocation within individual chunks.
+ */
+static int tegra_vpr_find_free_region_in_range(struct tegra_vpr *vpr,
+					       unsigned int start_page,
+					       unsigned int end_page,
+					       unsigned int num_pages,
+					       unsigned int align)
+{
+	unsigned int pos, next = ALIGN(start_page, align);
+
+	/* Scan through aligned positions, trying to allocate at each one */
+	for (pos = next; pos + num_pages <= end_page; pos = next) {
+		next = find_next_bit(vpr->bitmap, pos + num_pages, pos);
+
+		if (next >= pos + num_pages) {
+			bitmap_set(vpr->bitmap, pos, num_pages);
+			return pos;
+		}
+
+		next = find_next_zero_bit(vpr->bitmap, vpr->num_pages, next);
+		next = ALIGN(next, align);
+	}
+
+	return -ENOSPC;
+}
+
+static int tegra_vpr_find_free_region(struct tegra_vpr *vpr,
+				      unsigned int num_pages,
+				      unsigned long align)
+{
+	return tegra_vpr_find_free_region_in_range(vpr, 0, vpr->num_pages - 1,
+						   num_pages, align);
+}
+
+static int tegra_vpr_find_free_region_clustered(struct tegra_vpr *vpr,
+						unsigned int num_pages,
+						unsigned int align)
+{
+	unsigned int target, first, last;
+	int pageno;
+
+	/*
+	 * If there are no allocations, abort the clustered allocation scheme
+	 * and use the generic allocation scheme instead.
+	 */
+	if (vpr->first > vpr->last)
+		return -ENOSPC;
+
+	/*
+	 * First, try to allocate within the currently allocated region. This
+	 * keeps allocations tightly packed and minimizes the VPR size needed.
+	 */
+	pageno = tegra_vpr_find_free_region_in_range(vpr, vpr->first,
+						     vpr->last + 1, num_pages,
+						     align);
+	if (pageno >= 0)
+		return pageno;
+
+	/*
+	 * If not enough free space exists within the currently allocated
+	 * region, check to see if the allocation fits anywhere within the
+	 * active region, avoiding the need to resize the VPR.
+	 */
+	if (tegra_vpr_get_active_range(vpr, &first, &last)) {
+		pageno = tegra_vpr_find_free_region_in_range(vpr, first, last,
+							     num_pages, align);
+		if (pageno >= 0)
+			return pageno;
+	}
+
+	/*
+	 * If not enough free space exists within the currently active region,
+	 * try to allocate adjacent to it to grow it contiguously and ensure
+	 * optimal packing.
+	 */
+
+	/*
+	 * Calculate where the allocation should start to end right at the
+	 * first allocated page, with proper alignment.
+	 */
+	if (vpr->first >= num_pages) {
+		target = ALIGN_DOWN(vpr->first - num_pages, align);
+
+		if (!bitmap_allocate(vpr->bitmap, target, num_pages))
+			return target;
+	}
+
+	/* Try after the last allocation */
+	target = ALIGN(vpr->last + 1, align);
+
+	if (target + num_pages <= vpr->num_pages &&
+	    !bitmap_allocate(vpr->bitmap, target, num_pages))
+		return target;
+
+	/*
+	 * Couldn't allocate at the ideal adjacent position, search for any
+	 * available space before the first allocated page.
+	 */
+	pageno = tegra_vpr_find_free_region_in_range(vpr, 0, vpr->first,
+						     num_pages, align);
+	if (pageno >= 0)
+		return pageno;
+
+	/*
+	 * Couldn't allocate at the ideal adjacent position, search
+	 * for any available space after the last allocated page.
+	 */
+	pageno = tegra_vpr_find_free_region_in_range(vpr, vpr->last + 1,
+						     vpr->num_pages, num_pages,
+						     align);
+	if (pageno >= 0)
+		return pageno;
+
+	return -ENOSPC;
+}
+
+/*
+ * Find a free region, preferring locations near existing allocations to
+ * minimize VPR fragmentation. The allocation strategy is to first allocate
+ * within or adjacent to the existing region to keep allocations clustered.
+ * Otherwise fall back to a generic allocation using the first available
+ * space.
+ *
+ * This approach focuses on page-level allocation first, then the chunk
+ * system determines which chunks need to be activated based on where the
+ * pages ended up.
+ */
+static int tegra_vpr_allocate_region(struct tegra_vpr *vpr,
+				     unsigned int num_pages,
+				     unsigned int align)
+{
+	int pageno;
+
+	/*
+	 * For non-resizable VPR (no chunks), use simple first-fit allocation.
+	 * Clustering optimization is only beneficial for resizable VPR where
+	 * keeping allocations together minimizes the active VPR size.
+	 */
+	if (vpr->num_chunks == 0)
+		return tegra_vpr_find_free_region(vpr, num_pages, align);
+
+	/*
+	 * Check if there are any existing allocations in the bitmap. If so,
+	 * try to allocate near them to minimize fragmentation.
+	 */
+	pageno = tegra_vpr_find_free_region_clustered(vpr, num_pages, align);
+	if (pageno >= 0)
+		return pageno;
+
+	/*
+	 * If there are no existing allocations, or no space adjacent to them,
+	 * fall back to the first available space anywhere in the VPR.
+	 */
+	pageno = tegra_vpr_find_free_region(vpr, num_pages, align);
+	if (pageno >= 0)
+		return pageno;
+
+	return -ENOSPC;
+}
+
+static struct tegra_vpr_buffer *
+tegra_vpr_buffer_allocate(struct tegra_vpr *vpr, size_t size)
+{
+	unsigned int num_pages = size >> PAGE_SHIFT;
+	unsigned int order = get_order(size);
+	struct tegra_vpr_buffer *buffer;
+	unsigned long first, last;
+	int pageno, err;
+	pgoff_t i;
+
+	/*
+	 * "order" defines the alignment and size, so this may result in
+	 * fragmented memory depending on the allocation patterns. However,
+	 * since this is used primarily for video frames, it is expected that
+	 * a number of buffers of the same size will be allocated, so
+	 * fragmentation should be negligible.
+	 */
+	pageno = tegra_vpr_allocate_region(vpr, num_pages, 1);
+	if (pageno < 0)
+		return ERR_PTR(pageno);
+
+	first = find_first_bit(vpr->bitmap, vpr->num_pages);
+	last = find_last_bit(vpr->bitmap, vpr->num_pages);
+
+	buffer = kzalloc_obj(*buffer, GFP_KERNEL);
+	if (!buffer) {
+		err = -ENOMEM;
+		goto release;
+	}
+
+	INIT_LIST_HEAD(&buffer->attachments);
+	INIT_LIST_HEAD(&buffer->list);
+	mutex_init(&buffer->lock);
+	buffer->start = vpr->base + (pageno << PAGE_SHIFT);
+	buffer->limit = buffer->start + size;
+	buffer->size = size;
+	buffer->num_pages = num_pages;
+	buffer->pageno = pageno;
+	buffer->order = order;
+
+	buffer->pages = kmalloc_array(buffer->num_pages,
+				      sizeof(*buffer->pages),
+				      GFP_KERNEL);
+	if (!buffer->pages) {
+		err = -ENOMEM;
+		goto free;
+	}
+
+	/* track which chunks this buffer overlaps */
+	if (vpr->num_chunks > 0) {
+		unsigned int limit = buffer->pageno + buffer->num_pages, i;
+
+		for (i = 0; i < vpr->num_chunks; i++) {
+			struct tegra_vpr_chunk *chunk = &vpr->chunks[i];
+
+			if (tegra_vpr_chunk_overlaps(chunk, pageno, limit))
+				set_bit(i, buffer->chunks);
+		}
+
+		/* activate chunks if necessary */
+		err = tegra_vpr_activate_chunks(vpr, buffer);
+		if (err < 0)
+			goto free;
+
+		/* track first and last allocated pages */
+		if (buffer->pageno < vpr->first)
+			vpr->first = buffer->pageno;
+
+		if (limit - 1 > vpr->last)
+			vpr->last = limit - 1;
+	}
+
+	for (i = 0; i < buffer->num_pages; i++)
+		buffer->pages[i] = &vpr->start_page[pageno + i];
+
+	return buffer;
+
+free:
+	kfree(buffer->pages);
+	kfree(buffer);
+release:
+	bitmap_release_region(vpr->bitmap, pageno, order);
+	return ERR_PTR(err);
+}
+
+static void tegra_vpr_buffer_release(struct tegra_vpr_buffer *buffer)
+{
+	struct tegra_vpr *vpr = buffer->vpr;
+	struct tegra_vpr_buffer *entry;
+	unsigned long first, last;
+	unsigned int i;
+
+	/*
+	 * Decrement buffer count for each overlapping chunk. Note that chunks
+	 * are not deactivated here yet, that's done in tegra_vpr_recycle()
+	 * instead.
+	 */
+	for_each_set_bit(i, buffer->chunks, vpr->num_chunks) {
+		if (!WARN_ON(vpr->chunks[i].num_buffers == 0))
+			vpr->chunks[i].num_buffers--;
+	}
+
+	/* track first and last allocated pages */
+	if (list_is_first(&buffer->list, &vpr->buffers) &&
+	    list_is_last(&buffer->list, &vpr->buffers)) {
+		/* if there are no remaining buffers after this, reset */
+		vpr->first = ~0U;
+		vpr->last = 0U;
+	} else if (list_is_first(&buffer->list, &vpr->buffers)) {
+		entry = list_next_entry(buffer, list);
+		vpr->first = entry->pageno;
+	} else if (list_is_last(&buffer->list, &vpr->buffers)) {
+		entry = list_prev_entry(buffer, list);
+		vpr->last = entry->pageno + entry->num_pages - 1;
+	}
+
+	bitmap_release_region(vpr->bitmap, buffer->pageno, buffer->order);
+	list_del(&buffer->list);
+	kfree(buffer->pages);
+	kfree(buffer);
+
+	first = find_first_bit(vpr->bitmap, vpr->num_pages);
+	last = find_last_bit(vpr->bitmap, vpr->num_pages);
+}
+
+static int tegra_vpr_attach(struct dma_buf *buf,
+			    struct dma_buf_attachment *attachment)
+{
+	struct tegra_vpr_buffer *buffer = buf->priv;
+	struct tegra_vpr_attachment *attach;
+	int err;
+
+	attach = kzalloc_obj(*attach, GFP_KERNEL);
+	if (!attach)
+		return -ENOMEM;
+
+	err = sg_alloc_table_from_pages(&attach->sgt, buffer->pages,
+					buffer->num_pages, 0, buffer->size,
+					GFP_KERNEL);
+	if (err < 0)
+		goto free;
+
+	attach->dev = attach->dev;
+	INIT_LIST_HEAD(&attach->list);
+	attachment->priv = attach;
+
+	mutex_lock(&buffer->lock);
+	list_add(&attach->list, &buffer->attachments);
+	mutex_unlock(&buffer->lock);
+
+	return 0;
+
+free:
+	kfree(attach);
+	return err;
+}
+
+static void tegra_vpr_detach(struct dma_buf *buf,
+			     struct dma_buf_attachment *attachment)
+{
+	struct tegra_vpr_buffer *buffer = buf->priv;
+	struct tegra_vpr_attachment *attach = attachment->priv;
+
+	mutex_lock(&buffer->lock);
+	list_del(&attach->list);
+	mutex_unlock(&buffer->lock);
+
+	sg_free_table(&attach->sgt);
+	kfree(attach);
+}
+
+static struct sg_table *
+tegra_vpr_map_dma_buf(struct dma_buf_attachment *attachment,
+		      enum dma_data_direction direction)
+{
+	struct tegra_vpr_attachment *attach = attachment->priv;
+	struct sg_table *sgt = &attach->sgt;
+	int err;
+
+	err = dma_map_sgtable(attachment->dev, sgt, direction,
+			      DMA_ATTR_SKIP_CPU_SYNC);
+	if (err < 0)
+		return ERR_PTR(err);
+
+	return sgt;
+}
+
+static void tegra_vpr_unmap_dma_buf(struct dma_buf_attachment *attachment,
+				    struct sg_table *sgt,
+				    enum dma_data_direction direction)
+{
+	dma_unmap_sgtable(attachment->dev, sgt, direction,
+			  DMA_ATTR_SKIP_CPU_SYNC);
+}
+
+static void tegra_vpr_recycle(struct tegra_vpr *vpr)
+{
+	DECLARE_BITMAP(dirty, vpr->num_chunks);
+	unsigned int i;
+	int err;
+
+	bitmap_zero(dirty, vpr->num_chunks);
+
+	/*
+	 * Deactivate any unused chunks from the bottom...
+	 */
+	for (i = 0; i < vpr->num_chunks; i++) {
+		struct tegra_vpr_chunk *chunk = &vpr->chunks[i];
+
+		if (!chunk->active)
+			continue;
+
+		if (chunk->num_buffers > 0)
+			break;
+
+		err = tegra_vpr_chunk_deactivate(chunk);
+		if (err < 0)
+			pr_err("failed to deactivate chunk #%u\n", i);
+		else {
+			clear_bit(i, vpr->active);
+			set_bit(i, dirty);
+		}
+	}
+
+	/*
+	 * ... and the top.
+	 */
+	for (i = 0; i < vpr->num_chunks; i++) {
+		unsigned int index = vpr->num_chunks - i - 1;
+		struct tegra_vpr_chunk *chunk = &vpr->chunks[index];
+
+		if (!chunk->active)
+			continue;
+
+		if (chunk->num_buffers > 0)
+			break;
+
+		err = tegra_vpr_chunk_deactivate(chunk);
+		if (err < 0)
+			pr_err("failed to deactivate chunk #%u\n", index);
+		else {
+			clear_bit(i, vpr->active);
+			set_bit(i, dirty);
+		}
+	}
+
+	if (!bitmap_empty(dirty, vpr->num_chunks)) {
+		err = tegra_vpr_resize(vpr);
+		if (err < 0) {
+			pr_err("failed to shrink VPR: %d\n", err);
+			goto activate;
+		}
+	}
+
+	return;
+
+activate:
+	for_each_set_bit(i, dirty, vpr->num_chunks) {
+		err = tegra_vpr_chunk_activate(&vpr->chunks[i]);
+		if (WARN_ON(err < 0))
+			pr_err("failed to activate chunk #%u: %d\n", i, err);
+	}
+}
+
+static void tegra_vpr_release(struct dma_buf *buf)
+{
+	struct tegra_vpr_buffer *buffer = buf->priv;
+	struct tegra_vpr *vpr = buffer->vpr;
+
+	mutex_lock(&vpr->lock);
+
+	tegra_vpr_buffer_release(buffer);
+
+	if (vpr->num_chunks > 0)
+		tegra_vpr_recycle(vpr);
+
+	mutex_unlock(&vpr->lock);
+}
+
+/*
+ * Prohibit userspace mapping because the CPU cannot access this memory
+ * anyway.
+ */
+static int tegra_vpr_begin_cpu_access(struct dma_buf *buf,
+				      enum dma_data_direction direction)
+{
+	return -EPERM;
+}
+
+static int tegra_vpr_end_cpu_access(struct dma_buf *buf,
+				    enum dma_data_direction direction)
+{
+	return -EPERM;
+}
+
+static int tegra_vpr_mmap(struct dma_buf *buf, struct vm_area_struct *vma)
+{
+	return -EPERM;
+}
+
+static const struct dma_buf_ops tegra_vpr_buf_ops = {
+	.attach = tegra_vpr_attach,
+	.detach = tegra_vpr_detach,
+	.map_dma_buf = tegra_vpr_map_dma_buf,
+	.unmap_dma_buf = tegra_vpr_unmap_dma_buf,
+	.release = tegra_vpr_release,
+	.begin_cpu_access = tegra_vpr_begin_cpu_access,
+	.end_cpu_access = tegra_vpr_end_cpu_access,
+	.mmap = tegra_vpr_mmap,
+};
+
+static struct dma_buf *tegra_vpr_allocate(struct dma_heap *heap,
+					  unsigned long len, u32 fd_flags,
+					  u64 heap_flags)
+{
+	struct tegra_vpr *vpr = dma_heap_get_drvdata(heap);
+	struct tegra_vpr_buffer *buffer, *entry;
+	size_t size = ALIGN(len, vpr->align);
+	DEFINE_DMA_BUF_EXPORT_INFO(export);
+	struct dma_buf *buf;
+
+	mutex_lock(&vpr->lock);
+
+	buffer = tegra_vpr_buffer_allocate(vpr, size);
+	if (IS_ERR(buffer)) {
+		mutex_unlock(&vpr->lock);
+		return ERR_CAST(buffer);
+	}
+
+	/* insert in the correct order */
+	if (!list_empty(&vpr->buffers)) {
+		list_for_each_entry(entry, &vpr->buffers, list) {
+			if (buffer->pageno < entry->pageno) {
+				list_add_tail(&buffer->list, &entry->list);
+				break;
+			}
+		}
+	}
+
+	if (list_empty(&buffer->list))
+		list_add_tail(&buffer->list, &vpr->buffers);
+
+	buffer->vpr = vpr;
+
+	/*
+	 * If a valid buffer was allocated, wrap it in a dma_buf
+	 * and return it.
+	 */
+	export.exp_name = dma_heap_get_name(heap);
+	export.ops = &tegra_vpr_buf_ops;
+	export.size = buffer->size;
+	export.flags = fd_flags;
+	export.priv = buffer;
+
+	buf = dma_buf_export(&export);
+	if (IS_ERR(buf))
+		tegra_vpr_buffer_release(buffer);
+
+	mutex_unlock(&vpr->lock);
+	return buf;
+}
+
+static void tegra_vpr_debugfs_show_buffers(struct tegra_vpr *vpr,
+					   struct seq_file *s)
+{
+	struct tegra_vpr_buffer *buffer;
+	char buf[16];
+
+	list_for_each_entry(buffer, &vpr->buffers, list) {
+		string_get_size(buffer->size, 1, STRING_UNITS_2, buf,
+				sizeof(buf));
+		seq_printf(s, "  %pap-%pap (%s)\n", &buffer->start,
+			   &buffer->limit, buf);
+
+	}
+}
+
+static void tegra_vpr_debugfs_show_chunks(struct tegra_vpr *vpr,
+					  struct seq_file *s)
+{
+	struct tegra_vpr_buffer *buffer;
+	unsigned int i;
+	char buf[16];
+
+	for (i = 0; i < vpr->num_chunks; i++) {
+		const struct tegra_vpr_chunk *chunk = &vpr->chunks[i];
+
+		string_get_size(chunk->size, 1, STRING_UNITS_2, buf,
+				sizeof(buf));
+		seq_printf(s, "  %pap-%pap (%s) (%s, %u buffers)\n",
+			   &chunk->start, &chunk->limit, buf,
+			   chunk->active ? "active" : "inactive",
+			   chunk->num_buffers);
+	}
+
+	list_for_each_entry(buffer, &vpr->buffers, list) {
+		string_get_size(buffer->size, 1, STRING_UNITS_2, buf,
+				sizeof(buf));
+		seq_printf(s, "%pap-%pap (%s, chunks: %*pbl)\n",
+			   &buffer->start, &buffer->limit, buf,
+			   vpr->num_chunks, buffer->chunks);
+	}
+}
+
+static int tegra_vpr_debugfs_show(struct seq_file *s, struct dma_heap *heap)
+{
+	struct tegra_vpr *vpr = dma_heap_get_drvdata(heap);
+	phys_addr_t limit = vpr->base + vpr->size;
+	char buf[16];
+
+	string_get_size(vpr->size, 1, STRING_UNITS_2, buf, sizeof(buf));
+	seq_printf(s, "%pap-%pap (%s)\n", &vpr->base, &limit, buf);
+
+	if (vpr->num_chunks == 0)
+		tegra_vpr_debugfs_show_buffers(vpr, s);
+	else
+		tegra_vpr_debugfs_show_chunks(vpr, s);
+
+	return 0;
+}
+
+static const struct dma_heap_ops tegra_vpr_heap_ops = {
+	.allocate = tegra_vpr_allocate,
+	.show = tegra_vpr_debugfs_show,
+};
+
+static int tegra_vpr_setup_chunks(struct tegra_vpr *vpr, const char *name)
+{
+	phys_addr_t start, limit;
+	unsigned int order, i;
+	size_t max_size;
+	int err;
+
+	/* This seems a reasonable value, so hard-code this for now. */
+	vpr->num_chunks = 4;
+
+	vpr->chunks = kcalloc(vpr->num_chunks, sizeof(*vpr->chunks),
+			      GFP_KERNEL);
+	if (!vpr->chunks)
+		return -ENOMEM;
+
+	max_size = PAGE_SIZE << (get_order(vpr->size) - ilog2(vpr->num_chunks));
+	order = get_order(vpr->align);
+
+	/*
+	 * Allocate CMA areas for VPR. All areas will be roughtly the same
+	 * size, with the last area taking up the rest.
+	 */
+	start = vpr->base;
+	limit = vpr->base + vpr->size;
+
+	pr_debug("VPR: %pap-%pap (%lu pages, %u chunks, %lu MiB)\n", &start,
+		 &limit, vpr->num_pages, vpr->num_chunks,
+		 (unsigned long)vpr->size / 1024 / 1024);
+
+	for (i = 0; i < vpr->num_chunks; i++) {
+		size_t size = limit - start;
+		phys_addr_t end;
+
+		size = min_t(size_t, size, max_size);
+		end = start + size - 1;
+
+		err = tegra_vpr_chunk_init(vpr, &vpr->chunks[i], start, size,
+					   order, name);
+		if (err < 0) {
+			pr_err("failed to create VPR chunk: %d\n", err);
+			goto free;
+		}
+
+		pr_debug("  %2u: %pap-%pap (%lu MiB)\n", i, &start, &end,
+			 size / 1024 / 1024);
+		start += size;
+	}
+
+	vpr->first = ~0U;
+	vpr->last = 0U;
+
+	return 0;
+
+free:
+	while (i--)
+		tegra_vpr_chunk_free(&vpr->chunks[i]);
+
+	kfree(vpr->chunks);
+	return err;
+}
+
+static void tegra_vpr_free_chunks(struct tegra_vpr *vpr)
+{
+	unsigned int i;
+
+	for (i = 0; i < vpr->num_chunks; i++)
+		tegra_vpr_chunk_free(&vpr->chunks[i]);
+
+	kfree(vpr->chunks);
+}
+
+static int tegra_vpr_setup_static(struct tegra_vpr *vpr)
+{
+	phys_addr_t start, limit;
+
+	start = vpr->base;
+	limit = vpr->base + vpr->size;
+
+	pr_debug("VPR: %pap-%pap (%lu pages, %lu MiB)\n", &start, &limit,
+		 vpr->num_pages, (unsigned long)vpr->size / 1024 / 1024);
+
+	return 0;
+}
+
+static int __init tegra_vpr_add_heap(struct reserved_mem *rmem,
+				     struct device_node *np)
+{
+	struct dma_heap_export_info info = {};
+	unsigned long first, last;
+	struct dma_heap *heap;
+	struct tegra_vpr *vpr;
+	int err;
+
+	vpr = kzalloc_obj(*vpr, GFP_KERNEL);
+	if (!vpr)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&vpr->buffers);
+	INIT_LIST_HEAD(&vpr->devices);
+	vpr->resizable = !of_property_read_bool(np, "no-map");
+	vpr->use_freezer = true;
+	vpr->dev_node = np;
+	vpr->align = PAGE_SIZE;
+	vpr->base = rmem->base;
+	vpr->size = rmem->size;
+
+	/* common setup */
+	vpr->start_page = phys_to_page(vpr->base);
+	vpr->num_pages = vpr->size >> PAGE_SHIFT;
+
+	vpr->bitmap = bitmap_zalloc(vpr->num_pages, GFP_KERNEL);
+	if (!vpr->bitmap) {
+		err = -ENOMEM;
+		goto free;
+	}
+
+	first = find_first_bit(vpr->bitmap, vpr->num_pages);
+	last = find_last_bit(vpr->bitmap, vpr->num_pages);
+
+	if (vpr->resizable)
+		err = tegra_vpr_setup_chunks(vpr, rmem->name);
+	else
+		err = tegra_vpr_setup_static(vpr);
+
+	if (err < 0)
+		goto free;
+
+	info.name = vpr->dev_node->name;
+	info.ops = &tegra_vpr_heap_ops;
+	info.priv = vpr;
+
+	heap = dma_heap_add(&info);
+	if (IS_ERR(heap)) {
+		err = PTR_ERR(heap);
+		goto cleanup;
+	}
+
+	rmem->priv = heap;
+
+	return 0;
+
+cleanup:
+	if (vpr->resizable)
+		tegra_vpr_free_chunks(vpr);
+free:
+	bitmap_free(vpr->bitmap);
+	kfree(vpr);
+	return err;
+}
+
+static int __init tegra_vpr_init(void)
+{
+	const char *compatible = "nvidia,tegra-video-protection-region";
+	struct device_node *parent;
+	struct reserved_mem *rmem;
+	int err;
+
+	parent = of_find_node_by_path("/reserved-memory");
+	if (!parent)
+		return 0;
+
+	for_each_child_of_node_scoped(parent, child) {
+		if (!of_device_is_compatible(child, compatible))
+			continue;
+
+		rmem = of_reserved_mem_lookup(child);
+		if (!rmem)
+			continue;
+
+		err = tegra_vpr_add_heap(rmem, child);
+		if (err < 0)
+			pr_err("failed to add VPR heap for %pOF: %d\n", child,
+			       err);
+
+		/* only a single VPR heap is supported */
+		break;
+	}
+
+	return 0;
+}
+module_init(tegra_vpr_init);
+
+static int tegra_vpr_node_init(unsigned long offset, struct reserved_mem *rmem)
+{
+	return 0;
+}
+
+static int tegra_vpr_device_init(struct reserved_mem *rmem, struct device *dev)
+{
+	struct dma_heap *heap = rmem->priv;
+	struct tegra_vpr *vpr = dma_heap_get_drvdata(heap);
+	struct tegra_vpr_device *node;
+
+	if (!dev->driver->pm->freeze || !dev->driver->pm->thaw)
+		return -EINVAL;
+
+	node = kzalloc_obj(*node, GFP_KERNEL);
+	if (!node)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&node->node);
+	node->dev = dev;
+
+	list_add_tail(&node->node, &vpr->devices);
+
+	return 0;
+}
+
+static void tegra_vpr_device_release(struct reserved_mem *rmem,
+				     struct device *dev)
+{
+	struct dma_heap *heap = rmem->priv;
+	struct tegra_vpr *vpr = dma_heap_get_drvdata(heap);
+	struct tegra_vpr_device *node, *tmp;
+
+	list_for_each_entry_safe(node, tmp, &vpr->devices, node) {
+		if (node->dev == dev) {
+			list_del(&node->node);
+			kfree(node);
+		}
+	}
+}
+
+static const struct reserved_mem_ops tegra_vpr_rmem_ops = {
+	.node_init = tegra_vpr_node_init,
+	.device_init = tegra_vpr_device_init,
+	.device_release = tegra_vpr_device_release,
+};
+
+RESERVEDMEM_OF_DECLARE(tegra_vpr, "nvidia,tegra-video-protection-region",
+		       &tegra_vpr_rmem_ops);
+
+MODULE_DESCRIPTION("NVIDIA Tegra Video-Protection-Region DMA-BUF heap driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/bitmap.h b/include/linux/bitmap.h
index b007d54a9036a5..dcc81dc082edc9 100644
--- a/include/linux/bitmap.h
+++ b/include/linux/bitmap.h
@@ -721,10 +721,10 @@ void bitmap_release_region(unsigned long *bitmap, unsigned int pos, int order)
 }
 
 /**
- * bitmap_allocate_region - allocate bitmap region
+ * bitmap_allocate - allocate bitmap region
  *	@bitmap: array of unsigned longs corresponding to the bitmap
  *	@pos: beginning of bit region to allocate
- *	@order: region size (log base 2 of number of bits) to allocate
+ *	@len: number of bits to allocate
  *
  * Allocate (set bits in) a specified region of a bitmap.
  *
@@ -732,16 +732,31 @@ void bitmap_release_region(unsigned long *bitmap, unsigned int pos, int order)
  * free (not all bits were zero).
  */
 static __always_inline
-int bitmap_allocate_region(unsigned long *bitmap, unsigned int pos, int order)
+int bitmap_allocate(unsigned long *bitmap, unsigned int pos, unsigned int len)
 {
-	unsigned int len = BIT(order);
-
 	if (find_next_bit(bitmap, pos + len, pos) < pos + len)
 		return -EBUSY;
 	bitmap_set(bitmap, pos, len);
 	return 0;
 }
 
+/**
+ * bitmap_allocate_region - allocate bitmap region
+ *	@bitmap: array of unsigned longs corresponding to the bitmap
+ *	@pos: beginning of bit region to allocate
+ *	@order: region size (log base 2 of number of bits) to allocate
+ *
+ * Allocate (set bits in) a specified region of a bitmap.
+ *
+ * Returns: 0 on success, or %-EBUSY if specified region wasn't
+ * free (not all bits were zero).
+ */
+static __always_inline
+int bitmap_allocate_region(unsigned long *bitmap, unsigned int pos, int order)
+{
+	return bitmap_allocate(bitmap, pos, BIT(order));
+}
+
 /**
  * bitmap_find_free_region - find a contiguous aligned mem region
  *	@bitmap: array of unsigned longs corresponding to the bitmap
diff --git a/include/linux/cma.h b/include/linux/cma.h
index 8555d38a97b1a5..fb7a4923c3ba9a 100644
--- a/include/linux/cma.h
+++ b/include/linux/cma.h
@@ -7,7 +7,7 @@
 #include <linux/numa.h>
 
 #ifdef CONFIG_CMA_AREAS
-#define MAX_CMA_AREAS	CONFIG_CMA_AREAS
+#define MAX_EARLY_CMA_AREAS	CONFIG_CMA_AREAS
 #endif
 
 #define CMA_MAX_NAME 64
@@ -57,8 +57,14 @@ struct page *cma_alloc_frozen_compound(struct cma *cma, unsigned int order);
 bool cma_release_frozen(struct cma *cma, const struct page *pages,
 		unsigned long count);
 
+extern int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data);
 extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data);
 extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end);
 
 extern void cma_reserve_pages_on_error(struct cma *cma);
+
+extern struct cma *cma_create(phys_addr_t base, phys_addr_t size,
+			      unsigned int order_per_bit, const char *name);
+extern void cma_free(struct cma *cma);
+
 #endif
diff --git a/include/linux/dma-heap.h b/include/linux/dma-heap.h
index 648328a64b27ea..1c9bed1f4ddef9 100644
--- a/include/linux/dma-heap.h
+++ b/include/linux/dma-heap.h
@@ -12,6 +12,7 @@
 #include <linux/types.h>
 
 struct dma_heap;
+struct seq_file;
 
 /**
  * struct dma_heap_ops - ops to operate on a given heap
@@ -24,6 +25,7 @@ struct dma_heap_ops {
 				    unsigned long len,
 				    u32 fd_flags,
 				    u64 heap_flags);
+	int (*show)(struct seq_file *s, struct dma_heap *heap);
 };
 
 /**
diff --git a/include/linux/set_memory.h b/include/linux/set_memory.h
index 3030d9245f5ac8..abcb11378fdbd8 100644
--- a/include/linux/set_memory.h
+++ b/include/linux/set_memory.h
@@ -72,6 +72,17 @@ static inline int clear_mce_nospec(unsigned long pfn)
 }
 #endif
 
+#ifndef CONFIG_ARM64
+static inline int set_memory_device(unsigned long addr, int numpages)
+{
+	return 0;
+}
+static inline int set_memory_normal(unsigned long addr, int numpages)
+{
+	return 0;
+}
+#endif
+
 #ifndef CONFIG_ARCH_HAS_MEM_ENCRYPT
 static inline int set_memory_encrypted(unsigned long addr, int numpages)
 {
diff --git a/include/trace/events/tegra_vpr.h b/include/trace/events/tegra_vpr.h
new file mode 100644
index 00000000000000..f8ceb17679fe0a
--- /dev/null
+++ b/include/trace/events/tegra_vpr.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#if !defined(_TRACE_TEGRA_VPR_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_TEGRA_VPR_H
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM tegra_vpr
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(tegra_vpr_chunk_activate,
+	TP_PROTO(phys_addr_t start, phys_addr_t limit),
+	TP_ARGS(start, limit),
+	TP_STRUCT__entry(
+		__field(phys_addr_t, start)
+		__field(phys_addr_t, limit)
+	),
+	TP_fast_assign(
+		__entry->start = start;
+		__entry->limit = limit;
+	),
+	TP_printk("%pap-%pap", &__entry->start,
+		  &__entry->limit)
+);
+
+TRACE_EVENT(tegra_vpr_chunk_deactivate,
+	TP_PROTO(phys_addr_t start, phys_addr_t limit),
+	TP_ARGS(start, limit),
+	TP_STRUCT__entry(
+		__field(phys_addr_t, start)
+		__field(phys_addr_t, limit)
+	),
+	TP_fast_assign(
+		__entry->start = start;
+		__entry->limit = limit;
+	),
+	TP_printk("%pap-%pap", &__entry->start,
+		  &__entry->limit)
+);
+
+TRACE_EVENT(tegra_vpr_set,
+	TP_PROTO(phys_addr_t base, phys_addr_t size),
+	TP_ARGS(base, size),
+	TP_STRUCT__entry(
+		__field(phys_addr_t, start)
+		__field(phys_addr_t, limit)
+	),
+	TP_fast_assign(
+		__entry->start = base;
+		__entry->limit = base + size;
+	),
+	TP_printk("%pap-%pap", &__entry->start, &__entry->limit)
+);
+
+#endif /* _TRACE_TEGRA_VPR_H */
+
+#include <trace/define_trace.h>
diff --git a/kernel/dma/contiguous.c b/kernel/dma/contiguous.c
index f754079a287d61..7975551f69b3d9 100644
--- a/kernel/dma/contiguous.c
+++ b/kernel/dma/contiguous.c
@@ -52,7 +52,7 @@
 #define CMA_SIZE_MBYTES 0
 #endif
 
-static struct cma *dma_contiguous_areas[MAX_CMA_AREAS];
+static struct cma *dma_contiguous_areas[MAX_EARLY_CMA_AREAS];
 static unsigned int dma_contiguous_areas_num;
 
 static int dma_contiguous_insert_area(struct cma *cma)
diff --git a/mm/cma.c b/mm/cma.c
index a13ce4999b39f6..f989e2e98594fb 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -34,7 +34,12 @@
 #include "internal.h"
 #include "cma.h"
 
-struct cma cma_areas[MAX_CMA_AREAS];
+static DEFINE_MUTEX(cma_lock);
+
+struct cma cma_early_areas[MAX_EARLY_CMA_AREAS];
+unsigned int cma_early_area_count;
+
+static LIST_HEAD(cma_areas);
 unsigned int cma_area_count;
 
 phys_addr_t cma_get_base(const struct cma *cma)
@@ -198,7 +203,6 @@ static void __init cma_activate_area(struct cma *cma)
 				free_reserved_page(pfn_to_page(pfn));
 		}
 	}
-	totalcma_pages -= cma->count;
 	cma->available_count = cma->count = 0;
 	pr_err("CMA area %s could not be activated\n", cma->name);
 }
@@ -207,8 +211,8 @@ static int __init cma_init_reserved_areas(void)
 {
 	int i;
 
-	for (i = 0; i < cma_area_count; i++)
-		cma_activate_area(&cma_areas[i]);
+	for (i = 0; i < cma_early_area_count; i++)
+		cma_activate_area(&cma_early_areas[i]);
 
 	return 0;
 }
@@ -219,41 +223,77 @@ void __init cma_reserve_pages_on_error(struct cma *cma)
 	set_bit(CMA_RESERVE_PAGES_ON_ERROR, &cma->flags);
 }
 
+static void __init cma_init_area(struct cma *cma, const char *name,
+				 phys_addr_t size, unsigned int order_per_bit)
+{
+	if (name)
+		strscpy(cma->name, name);
+	else
+		snprintf(cma->name, CMA_MAX_NAME,  "cma%d\n", cma_area_count);
+
+	cma->available_count = cma->count = size >> PAGE_SHIFT;
+	cma->order_per_bit = order_per_bit;
+
+	INIT_LIST_HEAD(&cma->node);
+}
+
 static int __init cma_new_area(const char *name, phys_addr_t size,
 			       unsigned int order_per_bit,
 			       struct cma **res_cma)
 {
 	struct cma *cma;
 
-	if (cma_area_count == ARRAY_SIZE(cma_areas)) {
+	if (cma_early_area_count == ARRAY_SIZE(cma_early_areas)) {
 		pr_err("Not enough slots for CMA reserved regions!\n");
 		return -ENOSPC;
 	}
 
+	mutex_lock(&cma_lock);
+
 	/*
 	 * Each reserved area must be initialised later, when more kernel
 	 * subsystems (like slab allocator) are available.
 	 */
-	cma = &cma_areas[cma_area_count];
-	cma_area_count++;
+	cma = &cma_early_areas[cma_early_area_count];
+	cma_early_area_count++;
 
-	if (name)
-		strscpy(cma->name, name);
-	else
-		snprintf(cma->name, CMA_MAX_NAME,  "cma%d\n", cma_area_count);
+	cma_init_area(cma, name, size, order_per_bit);
 
-	cma->available_count = cma->count = size >> PAGE_SHIFT;
-	cma->order_per_bit = order_per_bit;
-	*res_cma = cma;
 	totalcma_pages += cma->count;
+	*res_cma = cma;
+
+	mutex_unlock(&cma_lock);
 
 	return 0;
 }
 
 static void __init cma_drop_area(struct cma *cma)
 {
+	mutex_lock(&cma_lock);
 	totalcma_pages -= cma->count;
-	cma_area_count--;
+	cma_early_area_count--;
+	mutex_unlock(&cma_lock);
+}
+
+static int __init cma_check_memory(phys_addr_t base, phys_addr_t size)
+{
+	if (!size || !memblock_is_region_reserved(base, size))
+		return -EINVAL;
+
+	/*
+	 * CMA uses CMA_MIN_ALIGNMENT_BYTES as alignment requirement which
+	 * needs pageblock_order to be initialized. Let's enforce it.
+	 */
+	if (!pageblock_order) {
+		pr_err("pageblock_order not yet initialized. Called during early boot?\n");
+		return -EINVAL;
+	}
+
+	/* ensure minimal alignment required by mm core */
+	if (!IS_ALIGNED(base | size, CMA_MIN_ALIGNMENT_BYTES))
+		return -EINVAL;
+
+	return 0;
 }
 
 /**
@@ -276,22 +316,9 @@ int __init cma_init_reserved_mem(phys_addr_t base, phys_addr_t size,
 	struct cma *cma;
 	int ret;
 
-	/* Sanity checks */
-	if (!size || !memblock_is_region_reserved(base, size))
-		return -EINVAL;
-
-	/*
-	 * CMA uses CMA_MIN_ALIGNMENT_BYTES as alignment requirement which
-	 * needs pageblock_order to be initialized. Let's enforce it.
-	 */
-	if (!pageblock_order) {
-		pr_err("pageblock_order not yet initialized. Called during early boot?\n");
-		return -EINVAL;
-	}
-
-	/* ensure minimal alignment required by mm core */
-	if (!IS_ALIGNED(base | size, CMA_MIN_ALIGNMENT_BYTES))
-		return -EINVAL;
+	ret = cma_check_memory(base, size);
+	if (ret < 0)
+		return ret;
 
 	ret = cma_new_area(name, size, order_per_bit, &cma);
 	if (ret != 0)
@@ -444,7 +471,7 @@ static int __init __cma_declare_contiguous_nid(phys_addr_t *basep,
 	pr_debug("%s(size %pa, base %pa, limit %pa alignment %pa)\n",
 		__func__, &size, &base, &limit, &alignment);
 
-	if (cma_area_count == ARRAY_SIZE(cma_areas)) {
+	if (cma_early_area_count == ARRAY_SIZE(cma_early_areas)) {
 		pr_err("Not enough slots for CMA reserved regions!\n");
 		return -ENOSPC;
 	}
@@ -1051,12 +1078,12 @@ bool cma_release_frozen(struct cma *cma, const struct page *pages,
 	return true;
 }
 
-int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)
+int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data)
 {
 	int i;
 
-	for (i = 0; i < cma_area_count; i++) {
-		int ret = it(&cma_areas[i], data);
+	for (i = 0; i < cma_early_area_count; i++) {
+		int ret = it(&cma_early_areas[i], data);
 
 		if (ret)
 			return ret;
@@ -1065,6 +1092,25 @@ int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)
 	return 0;
 }
 
+int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)
+{
+	struct cma *cma;
+
+	mutex_lock(&cma_lock);
+
+	list_for_each_entry(cma, &cma_areas, node) {
+		int ret = it(cma, data);
+
+		if (ret) {
+			mutex_unlock(&cma_lock);
+			return ret;
+		}
+	}
+
+	mutex_unlock(&cma_lock);
+	return 0;
+}
+
 bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end)
 {
 	int r;
@@ -1147,3 +1193,74 @@ void __init *cma_reserve_early(struct cma *cma, unsigned long size)
 
 	return ret;
 }
+
+struct cma *__init cma_create(phys_addr_t base, phys_addr_t size,
+			      unsigned int order_per_bit, const char *name)
+{
+	struct cma *cma;
+	int ret;
+
+	ret = cma_check_memory(base, size);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	cma = kzalloc_obj(*cma, GFP_KERNEL);
+	if (!cma)
+		return ERR_PTR(-ENOMEM);
+
+	cma_init_area(cma, name, size, order_per_bit);
+	cma->ranges[0].base_pfn = PFN_DOWN(base);
+	cma->ranges[0].early_pfn = PFN_DOWN(base);
+	cma->ranges[0].count = cma->count;
+	cma->nranges = 1;
+
+	cma_activate_area(cma);
+
+	mutex_lock(&cma_lock);
+	list_add_tail(&cma->node, &cma_areas);
+	totalcma_pages += cma->count;
+	cma_area_count++;
+	mutex_unlock(&cma_lock);
+
+	return cma;
+}
+
+void cma_free(struct cma *cma)
+{
+	unsigned int i;
+
+	/*
+	 * Safety check to prevent a CMA with active allocations from being
+	 * released.
+	 */
+	for (i = 0; i < cma->nranges; i++) {
+		unsigned long nbits = cma_bitmap_maxno(cma, &cma->ranges[i]);
+
+		if (!bitmap_empty(cma->ranges[i].bitmap, nbits)) {
+			WARN(1, "%s: range %u not empty\n", cma->name, i);
+			return;
+		}
+	}
+
+	/* free reserved pages and the bitmap */
+	for (i = 0; i < cma->nranges; i++) {
+		struct cma_memrange *cmr = &cma->ranges[i];
+		unsigned long end_pfn, pfn;
+
+		end_pfn = cmr->base_pfn + cmr->count;
+		for (pfn = cmr->base_pfn; pfn < end_pfn; pfn++)
+			free_reserved_page(pfn_to_page(pfn));
+
+		bitmap_free(cmr->bitmap);
+	}
+
+	mutex_destroy(&cma->alloc_mutex);
+
+	mutex_lock(&cma_lock);
+	totalcma_pages -= cma->count;
+	list_del(&cma->node);
+	cma_area_count--;
+	mutex_unlock(&cma_lock);
+
+	kfree(cma);
+}
diff --git a/mm/cma.h b/mm/cma.h
index c70180c36559c2..ae4db9819e38db 100644
--- a/mm/cma.h
+++ b/mm/cma.h
@@ -41,6 +41,7 @@ struct cma {
 	unsigned long	available_count;
 	unsigned int order_per_bit; /* Order of pages represented by one bit */
 	spinlock_t	lock;
+	struct list_head node;
 	struct mutex alloc_mutex;
 #ifdef CONFIG_CMA_DEBUGFS
 	struct hlist_head mem_head;
@@ -71,8 +72,8 @@ enum cma_flags {
 	CMA_ACTIVATED,
 };
 
-extern struct cma cma_areas[MAX_CMA_AREAS];
-extern unsigned int cma_area_count;
+extern struct cma cma_early_areas[MAX_EARLY_CMA_AREAS];
+extern unsigned int cma_early_area_count;
 
 static inline unsigned long cma_bitmap_maxno(struct cma *cma,
 		struct cma_memrange *cmr)

3/2 2026/07/01 23:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26296
							output: 34
							thoughts: 2222
						

Thoughts:
**Initial Assessment of the DMA-BUF and CMA Changes**

Okay, so I'm looking at a patch introducing a DMA-BUF heap for NVIDIA Tegra Video-Protection-Region (VPR). The primary file is `tegra-vpr.c`, and it's guarded by `CONFIG_DMABUF_HEAPS_TEGRA_VPR`. This is platform-specific, but the changes to CMA (`mm/cma.c` and `include/linux/cma.h`) are concerning because they appear more broadly impactful. It seems they are introducing a dynamic CMA (Contiguous Memory Allocator) management. Let's dig deeper.

First, the VPR heap.  The code in `tegra_vpr_init` suggests this heap is initialized via a device tree node with the compatible string "nvidia,tegra-video-protection-region." It's a `module_init`, so it'll run early.  Crucially, it searches `/reserved-memory`. If no DT node is present, the heap *isn't* initialized.  This is a problem for standard virtualization and fuzzing because we don't usually have such a device tree node in QEMU.  While syzkaller can use overlays, I don't think it is set up to automatically add this node, and manually adding a DT overlay is outside the scope of usual automated fuzzing setups.

The CMA changes, though, look significant. The patch is renaming `cma_areas` to `cma_early_areas` and creating a dynamic list `cma_areas`.  It's introducing a mutex (`cma_lock`) and changing `cma_for_each_area` to iterate over this list. It also adds `cma_create` and `cma_free`. This is core CMA code! I need to understand this.  `cma_for_each_area` is used in a lot of places –  `mm/cma_debug.c`, `kernel/dma/contiguous.c`, `arch/s390/mm/init.c`, which makes me think these changes affect everything using CMA. This is definitely worth attention for fuzzing.

The refactoring of `bitmap_allocate_region` in `include/linux/bitmap.h` to use `bitmap_allocate` is less of a concern. It's a refactoring, and it's probably only used in the new heap code.

Now I need to clarify the CMA initialization. The patch introduces `cma_early_areas`, but, based on the diff, it seems that the CMA areas created early (using `cma_early_areas`) don't get added to the `cma_areas` list for later use. It seems this oversight is very very problematic!

Let's look more closely at the code...  Ah, I see a mistake in my initial assessment: `cma_new_area` actually populates `cma_early_areas`, not `cma_areas`. Now I need to see how the early areas are converted to list entries.  I need to check how the initial CMA regions in `cma_early_areas` get added to `cma_areas` to be actually usable. The initialization logic must be responsible for this, and it looks like a bug is present in the patch. There is no call to add the `cma_early_areas` to `cma_areas`. This requires immediate attention!



4/2 2026/07/01 23:35 tool grepper
0m Args:
{
  "Expression": "list_add.*cma_areas",
  "PathPrefix": "mm/cma.c"
}

Results:
{
  "Output": "mm/cma.c=1197=struct cma *__init cma_create(phys_addr_t base, phys_addr_t size,\n--\nmm/cma.c-1219-\tmutex_lock(\u0026cma_lock);\nmm/cma.c:1220:\tlist_add_tail(\u0026cma-\u003enode, \u0026cma_areas);\nmm/cma.c-1221-\ttotalcma_pages += cma-\u003ecount;\n"
}

5/2 2026/07/01 23:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29428
							output: 30
							thoughts: 63
						

Thoughts:
Okay, let me think this through.

**Area Management Conundrum**

Right, so `cma_create` *definitely* adds to `cma_areas`. That's understood. But a nagging question keeps popping up: what about `cma_early_areas`? Are those early-stage areas ever actually *migrated* over to the main `cma_areas` pool? It feels like something is missing. I need to figure out the relationship between those two lists.

I'm thinking my best bet is to dive into the implementation of `cma_init_area` or `cma_activate_area`. Either of those functions seems like the logical place where an area might transition, or where a connection would be made, if it exists, between these two area lists. I'll need to scrutinize their code to understand this flow. Gotta get to the bottom of this area management!



6/2 2026/07/01 23:35 tool grepper
0m Args:
{
  "Expression": "cma_areas",
  "PathPrefix": "mm/cma.c"
}

Results:
{
  "Output": "mm/cma.c=40=unsigned int cma_early_area_count;\nmm/cma.c-41-\nmm/cma.c:42:static LIST_HEAD(cma_areas);\nmm/cma.c-43-unsigned int cma_area_count;\n--\nmm/cma.c=1095=int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\n--\nmm/cma.c-1100-\nmm/cma.c:1101:\tlist_for_each_entry(cma, \u0026cma_areas, node) {\nmm/cma.c-1102-\t\tint ret = it(cma, data);\n--\nmm/cma.c=1197=struct cma *__init cma_create(phys_addr_t base, phys_addr_t size,\n--\nmm/cma.c-1219-\tmutex_lock(\u0026cma_lock);\nmm/cma.c:1220:\tlist_add_tail(\u0026cma-\u003enode, \u0026cma_areas);\nmm/cma.c-1221-\ttotalcma_pages += cma-\u003ecount;\n"
}

7/2 2026/07/01 23:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30040
							output: 34
							thoughts: 110
						

Thoughts:
**Analyzing CMA Area Iteration**

Okay, hold on a second. If I'm understanding this correctly, the `cma_early_areas` list *never* gets merged into the main `cma_areas` list. That means the `cma_for_each_area` macro, the one we're using to iterate, would *only* hit the CMAs that were created dynamically using `cma_create`.

But... isn't `cma_for_each_area` also crucial for `dma_contiguous_areas`? And I could have sworn there was an interaction there. Ah! Wait, I think I'm remembering this wrong, wasn't `dma_contiguous.c` modified to use `cma_early_areas` directly? That would make sense. Let me just double check `kernel/dma/contiguous.c` to be completely certain. I need to make sure I have this straight.



8/2 2026/07/01 23:35 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "cma_for_each",
  "PathPrefix": "kernel/dma/contiguous.c"
}

Results:
{
  "error": "no matches"
}

9/2 2026/07/01 23:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30402
							output: 22
							thoughts: 43
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating `cma_for_each_area` Usage**

Alright, so I'm looking at `kernel/dma/contiguous.c` and immediately noticing it doesn't seem to be leveraging `cma_for_each_area`. That's a bit surprising, given its likely purpose related to CMA (Contiguous Memory Allocator). Hmm, I need to understand why. Perhaps it's implementing its own iteration logic, or maybe it's using a completely different method. It's time to dig deeper and see what *does* utilize `cma_for_each_area`. Understanding the existing call sites will give me crucial context. This should help to understand the design decisions and potential rationale behind the approach employed by `contiguous.c`. Let's see what's going on elsewhere in the codebase.



10/2 2026/07/01 23:35 tool grepper
0m Args:
{
  "Expression": "cma_for_each_area"
}

Results:
{
  "Output": "include/linux/cma.h=60=extern int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h:61:extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h-62-extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end);\n--\nmm/cma.c=1081=int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data)\n--\nmm/cma.c-1094-\nmm/cma.c:1095:int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\nmm/cma.c-1096-{\n"
}

11/2 2026/07/01 23:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30915
							output: 30
							thoughts: 42
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating `cma_for_each_area`**

Hmm, let's see... I'm looking at `cma_for_each_area`. Right, so it's defined solely within `mm/cma.c` and declared in `include/linux/cma.h`. That's the obvious starting point. Now the real question is: is this function *actually* utilized outside of those files? Or is it purely a local helper function within the CMA subsystem itself? It's pretty important I figure this out. If it's used elsewhere, that implies some sort of broader dependency or integration that I need to understand better. But if it is only used locally, it gives me some additional context that I can infer.



12/2 2026/07/01 23:35 tool grepper
0m Args:
{
  "Expression": "cma_for_each_area",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/cma.c=1081=int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data)\n--\nmm/cma.c-1094-\nmm/cma.c:1095:int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\nmm/cma.c-1096-{\n"
}

13/2 2026/07/01 23:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31294
							output: 22
							thoughts: 26
						

Thoughts:
Okay, here's my take on those thoughts, framed as an expert's internal monologue:

**Investigating a CMA Debug/Sysfs Issue**

Right, so I need to dig into the CMA code again. It's a common area for memory management problems to surface. My gut feeling is this is probably a debugging or sysfs-related issue given the context. Where to start? Let's not waste any time. I think the two most likely places to find the root cause of this are either `mm/cma_debug.c` or `mm/cma_sysfs.c`. I need to figure out which one is more relevant, or perhaps, check both.



14/2 2026/07/01 23:35 tool grepper
0m Args:
{
  "Expression": "cma_for_each_area"
}

Results:
{
  "Output": "include/linux/cma.h=60=extern int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h:61:extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h-62-extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end);\n--\nmm/cma.c=1081=int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data)\n--\nmm/cma.c-1094-\nmm/cma.c:1095:int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\nmm/cma.c-1096-{\n"
}

15/2 2026/07/01 23:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31739
							output: 24
							thoughts: 37
						

Thoughts:
**A Moment of Kernel Confusion**

Okay, hold on a second. I was just reviewing the CMA code, and I could have sworn `cma_for_each_area` was supposed to be a crucial function, but now I'm thinking, where is it actually *used*? This is strange, I'm drawing a blank. It's almost like it's a forgotten function that someone left in and never hooked up...

Alright, let's step back and double-check, my memory isn't always perfect. Maybe I'm just misremembering the exact name. Maybe it's a related function that's being used. Let's not jump to any wild conclusions, not yet at least.

A likely alternative is to check `cma_for_each_early_area`. I'll verify if that's the one I'm looking for.



16/2 2026/07/01 23:35 tool grepper
0m Args:
{
  "Expression": "cma_for_each_early_area"
}

Results:
{
  "Output": "arch/s390/mm/init.c=233=static int s390_cma_mem_notifier(struct notifier_block *nb,\n--\narch/s390/mm/init.c-243-\tif (action == MEM_GOING_OFFLINE)\narch/s390/mm/init.c:244:\t\trc = cma_for_each_early_area(s390_cma_check_range, \u0026mem_data);\narch/s390/mm/init.c-245-\treturn notifier_from_errno(rc);\n--\ninclude/linux/cma.h=57=bool cma_release_frozen(struct cma *cma, const struct page *pages,\n--\ninclude/linux/cma.h-59-\ninclude/linux/cma.h:60:extern int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h-61-extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data);\n--\nmm/cma.c=1067=bool cma_release_frozen(struct cma *cma, const struct page *pages,\n--\nmm/cma.c-1080-\nmm/cma.c:1081:int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data)\nmm/cma.c-1082-{\n"
}

17/2 2026/07/01 23:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32396
							output: 32
							thoughts: 87
						

Thoughts:
Okay, here's my quick take on this.

**Investigating a CMA Patch**

Right, so I'm digging into this patch. Looks like they've swapped out `cma_for_each_area` for `cma_for_each_early_area` in `arch/s390/mm/init.c`. Interesting. The initial call was a standard `cma_for_each_area` call, but now it's been replaced with the "early" version. I need to understand why. Makes sense, assuming this is happening during early boot.

But hold on a second... What's the context? What about other CMA initialization functions? Specifically, I'm thinking about `cma_debugfs_init` and `cma_sysfs_init` because I know those are related and need to be in sync. I need to see what's happening with those.

Let me take a quick look at `mm/cma_debug.c` to see if those other CMA initialization calls have been touched, and to get a better handle on the overall CMA initialization sequence. I suspect there's a reason for this change, and I want to understand the full picture.



18/2 2026/07/01 23:35 tool grepper
0m Args:
{
  "Expression": "cma_areas",
  "PathPrefix": "mm/cma_debug.c"
}

Results:
{
  "Output": "mm/cma_debug.c=200=static int __init cma_debugfs_init(void)\n--\nmm/cma_debug.c-207-\tfor (i = 0; i \u003c cma_area_count; i++)\nmm/cma_debug.c:208:\t\tif (test_bit(CMA_ACTIVATED, \u0026cma_areas[i].flags))\nmm/cma_debug.c:209:\t\t\tcma_debugfs_add_one(\u0026cma_areas[i], cma_debugfs_root);\nmm/cma_debug.c-210-\n"
}

19/2 2026/07/01 23:35 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32942
							output: 32
							thoughts: 264
						

Thoughts:
**Analyzing the CMA Area Change**

Okay, hold on a second. It just hit me: `mm/cma_debug.c` directly references `cma_areas`! But, this patch *completely* changes `cma_areas` to a `LIST_HEAD`. My immediate thought is, "This *has* to cause a compile failure." If `mm/cma_debug.c` was expecting an array like the original code, and now it's getting a linked list head, trying to use array indexing like `cma_areas[i]` is going to be a fatal syntax error.

Let's quickly re-examine that patch snippet:

```c
-struct cma cma_areas[MAX_CMA_AREAS];
+static DEFINE_MUTEX(cma_lock);
+
+struct cma cma_early_areas[MAX_EARLY_CMA_AREAS];
+unsigned int cma_early_area_count;
+
+static LIST_HEAD(cma_areas);
 unsigned int cma_area_count;
```

Yup, it's clear as day. If `cma_areas` is now a `LIST_HEAD`, the direct indexing like `cma_areas[i]` is definitely invalid in `mm/cma_debug.c`. So, the question isn't *if* it fails, but *how* it's handled. We need to check if `mm/cma_debug.c` has been touched anywhere else in the tree to account for this change, or if this is a glaring omission. The likelihood is that this patch is incomplete and will result in compile errors.



20/2 2026/07/01 23:36 tool grepper
0m Args:
{
  "Expression": "cma_areas",
  "PathPrefix": "mm/cma_debug.c"
}

Results:
{
  "Output": "mm/cma_debug.c=200=static int __init cma_debugfs_init(void)\n--\nmm/cma_debug.c-207-\tfor (i = 0; i \u003c cma_area_count; i++)\nmm/cma_debug.c:208:\t\tif (test_bit(CMA_ACTIVATED, \u0026cma_areas[i].flags))\nmm/cma_debug.c:209:\t\t\tcma_debugfs_add_one(\u0026cma_areas[i], cma_debugfs_root);\nmm/cma_debug.c-210-\n"
}

21/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 33771
							output: 33
							thoughts: 39
						

Thoughts:
Okay, let's see...

**Checking CMA Area Management**

Hold on a second, I thought we'd cleaned up the `cma_areas` array in the context of CMA, but here it is again, still lurking in `mm/cma_debug.c`. That's unexpected, and potentially problematic. It means I need to re-evaluate where this global array is being used. Perhaps I missed something in my earlier review. Let me quickly cross-reference that with the sysfs interaction, so I'll check `mm/cma_sysfs.c` now to make sure I don't introduce a regression in CMA sysfs interface.



22/2 2026/07/01 23:36 tool grepper
0m Args:
{
  "Expression": "cma_areas",
  "PathPrefix": "mm/cma_sysfs.c"
}

Results:
{
  "Output": "mm/cma_sysfs.c=108=static int __init cma_sysfs_init(void)\n--\nmm/cma_sysfs.c-119-\tfor (i = 0; i \u003c cma_area_count; i++) {\nmm/cma_sysfs.c:120:\t\tcma = \u0026cma_areas[i];\nmm/cma_sysfs.c-121-\t\tif (!test_bit(CMA_ACTIVATED, \u0026cma-\u003eflags))\n--\nmm/cma_sysfs.c-142-\twhile (--i \u003e= 0) {\nmm/cma_sysfs.c:143:\t\tcma = \u0026cma_areas[i];\nmm/cma_sysfs.c-144-\t\tif (cma-\u003ecma_kobj)\n"
}

23/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34203
							output: 32
							thoughts: 56
						

Thoughts:
**Realizing the Indexing Issue**

Okay, hold on a minute. I'm taking a look at this code, and something's not quite right. If `cma_areas` is defined as a `LIST_HEAD`, then directly indexing it with something like `cma_areas[i]` is definitely going to throw a compilation error. That's just not how you interact with a linked list. You can't treat it like a regular array in that sense. I have to access the list elements through the defined list manipulation functions like `list_for_each_entry`. I need to figure out how it’s meant to be iterated on.

Let me just quickly verify my understanding. It's best to confirm how it's defined and how it's meant to be used. I will quickly check if `cma_areas` is indeed exported in `include/linux/cma.h`. That's the first thing to confirm and rule out that assumption.



24/2 2026/07/01 23:36 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "cma_areas",
  "PathPrefix": "include/linux/cma.h"
}

Results:
{
  "error": "no matches"
}

25/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34514
							output: 30
							thoughts: 233
						

Thoughts:
**Investigating a CMA Header Change**

Okay, hold on a second. I see a modification to `include/linux/cma.h`. Specifically, the global variables `cma_areas` and `cma_area_count` are no longer exported. They've been replaced with `cma_early_areas` and `cma_early_area_count`. This is interesting. My first thought is about how this impacts the rest of the CMA code. Since `cma_areas` is no longer a public symbol in the include file, `mm/cma_debug.c` and `mm/cma_sysfs.c` are going to have a problem. Those files in `mm/` will choke on those missing symbols if they're including `include/linux/cma.h`. Wait… are they? Maybe they include a local `cma.h`? Let me quickly verify that. I need to check `mm/cma.h` to see how those symbols are now handled. This feels like it could break some existing code!



26/2 2026/07/01 23:36 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "cma_areas",
  "PathPrefix": "mm/cma.h"
}

Results:
{
  "error": "no matches"
}

27/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35021
							output: 34
							thoughts: 154
						

Thoughts:
**Investigating the CMA Changes**

Okay, so the diff shows that `mm/cma.h` has been altered. Specifically, it looks like `cma_areas` has been removed and replaced with `cma_early_areas`. Interesting. So, `cma_areas` is *no longer* declared in the header. That’s a significant change.

My immediate question is: how does `mm/cma_debug.c` still compile? I need to check whether that file is now using `cma_early_areas` or whether it's somehow accessing a variable that has now gone away. I'll need to examine that file's usage of the CMA structures to understand the implications of this refactoring. This feels like it might impact some debugging, or even affect the CMA initialization path.



28/2 2026/07/01 23:36 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "cma_early_areas",
  "PathPrefix": "mm/cma_debug.c"
}

Results:
{
  "error": "no matches"
}

29/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35398
							output: 32
							thoughts: 195
						

Thoughts:
**Investigating a Potential CMA Compilation Dependency**

Okay, so I just ran a `grep` for `cma_areas` within `mm/cma_debug.c`, and it popped up in a few places:

```
mm/cma_debug.c-207-	for (i = 0; i < cma_area_count; i++)
mm/cma_debug.c:208:		if (test_bit(CMA_ACTIVATED, &cma_areas[i].flags))
mm/cma_debug.c:209:			cma_debugfs_add_one(&cma_areas[i], cma_debugfs_root);
```

Now, that's interesting. If `cma_areas` isn't properly declared in either `mm/cma.h` or within the standard include `include/linux/cma.h`, then we are going to have a *major* compilation problem with `mm/cma_debug.c`. The compiler is going to choke on an undeclared symbol. I need to verify that declaration to ensure this doesn't bite us down the line. We need to prevent any nasty surprises. Let's dig in and verify that.



30/2 2026/07/01 23:36 tool grepper
0m Args:
{
  "Expression": "cma_areas",
  "PathPrefix": "mm/cma_debug.c"
}

Results:
{
  "Output": "mm/cma_debug.c=200=static int __init cma_debugfs_init(void)\n--\nmm/cma_debug.c-207-\tfor (i = 0; i \u003c cma_area_count; i++)\nmm/cma_debug.c:208:\t\tif (test_bit(CMA_ACTIVATED, \u0026cma_areas[i].flags))\nmm/cma_debug.c:209:\t\t\tcma_debugfs_add_one(\u0026cma_areas[i], cma_debugfs_root);\nmm/cma_debug.c-210-\n"
}

31/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 36069
							output: 26
							thoughts: 53
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, as if they were my own:

**Investigating a Potentially Obsolete Variable Access**

Alright, so I see a reference here – specifically `cma_areas[i]`. It's a pretty standard array indexing operation, nothing inherently suspicious. However, a red flag immediately goes up. I'm used to seeing the declaration for `cma_areas` right here, but I don't see anything. The context is `mm/cma_debug.c`, so I'd *expect* to find its declaration within this file or, at the very least, an `extern` declaration referencing it.

It's got me wondering: is this a stale reference? Has the `cma_areas` variable been removed or renamed somewhere upstream during a refactor? Or maybe this code is relying on an implicit declaration or a declaration defined in a header file I'm not immediately seeing? This is starting to smell like a potential bug, either a compile-time error waiting to happen or a subtle runtime issue.

Let me quickly double-check the included headers and global scope, just to confirm that this isn't just a simple oversight on my part. I need to be sure before I start to make any assumptions. This could lead to a hard-to-debug memory corruption bug.



32/2 2026/07/01 23:36 tool grepper
0m Args:
{
  "Expression": "cma_areas",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/cma.c=40=unsigned int cma_early_area_count;\nmm/cma.c-41-\nmm/cma.c:42:static LIST_HEAD(cma_areas);\nmm/cma.c-43-unsigned int cma_area_count;\n--\nmm/cma.c=1095=int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\n--\nmm/cma.c-1100-\nmm/cma.c:1101:\tlist_for_each_entry(cma, \u0026cma_areas, node) {\nmm/cma.c-1102-\t\tint ret = it(cma, data);\n--\nmm/cma.c=1197=struct cma *__init cma_create(phys_addr_t base, phys_addr_t size,\n--\nmm/cma.c-1219-\tmutex_lock(\u0026cma_lock);\nmm/cma.c:1220:\tlist_add_tail(\u0026cma-\u003enode, \u0026cma_areas);\nmm/cma.c-1221-\ttotalcma_pages += cma-\u003ecount;\n--\nmm/cma_debug.c=200=static int __init cma_debugfs_init(void)\n--\nmm/cma_debug.c-207-\tfor (i = 0; i \u003c cma_area_count; i++)\nmm/cma_debug.c:208:\t\tif (test_bit(CMA_ACTIVATED, \u0026cma_areas[i].flags))\nmm/cma_debug.c:209:\t\t\tcma_debugfs_add_one(\u0026cma_areas[i], cma_debugfs_root);\nmm/cma_debug.c-210-\n--\nmm/cma_sysfs.c=108=static int __init cma_sysfs_init(void)\n--\nmm/cma_sysfs.c-119-\tfor (i = 0; i \u003c cma_area_count; i++) {\nmm/cma_sysfs.c:120:\t\tcma = \u0026cma_areas[i];\nmm/cma_sysfs.c-121-\t\tif (!test_bit(CMA_ACTIVATED, \u0026cma-\u003eflags))\n--\nmm/cma_sysfs.c-142-\twhile (--i \u003e= 0) {\nmm/cma_sysfs.c:143:\t\tcma = \u0026cma_areas[i];\nmm/cma_sysfs.c-144-\t\tif (cma-\u003ecma_kobj)\n"
}

33/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37126
							output: 32
							thoughts: 1005
						

Thoughts:
**Analyzing CMA Changes for Syzkaller Compatibility**

Okay, so I'm looking at this patch and the first thing that jumps out is the use of `cma_areas` in `mm/cma_debug.c` and `mm/cma_sysfs.c`.  I immediately think "wait, how does that even compile?"  The patch must be incomplete or simply broken, but let's break it down further.  With `CONFIG_CMA_DEBUGFS` and `CONFIG_CMA_SYSFS` enabled, it *should* fail. However, what about `cma_early_areas`?

Let me just quickly confirm that `cma_debugfs_init` hasn't been changed in the patch; nope, it hasn't. So, enabling either `CONFIG_CMA_DEBUGFS` or `CONFIG_CMA_SYSFS` will definitely break the build. The patch changes `cma_areas` to `static LIST_HEAD(cma_areas);` in `mm/cma.c`, and I see now that `cma_area_count` is no longer exported.  Looking at `mm/cma.h`, I see that the patch also renames `cma_areas` and `cma_area_count` to `cma_early_areas` and `cma_early_area_count` respectively. This means `mm/cma_debug.c` will also fail to compile on `cma_area_count` as well.

Therefore, `CONFIG_CMA_DEBUGFS` and `CONFIG_CMA_SYSFS` must be disabled for this to compile, or the patch is indeed broken. If the patch is broken, there's a good chance syzkaller won't be able to compile the kernel. My job is to determine if this is worth fuzzing and to provide `EnableConfigs`. If I provide `CMA_DEBUGFS`, it's going to fail.  I should be more productive; I should focus on `DMABUF_HEAPS_TEGRA_VPR` and `DMA_CMA`.

Let's quickly confirm those dependencies in `drivers/dma-buf/heaps/Kconfig`: Okay, so `config DMABUF_HEAPS_TEGRA_VPR` is the config to enable, and it depends on `DMABUF_HEAPS && DMA_CMA`.

Now, wait, `tegra-vpr.c` uses `cma_create` and `cma_free` - is that code reachable?  It registers a DMA-BUF heap.  It calls `dma_heap_add` but only if the DT node `nvidia,tegra-video-protection-region` is present. And syzkaller doesn't add that dynamically, so no heap.  So syzkaller can only fuzz dma-buf heaps if they're created.

Let me see if there's any other way to reach `tegra-vpr.c`. Nope, `tegra_vpr_init` is the only entry point. Now, what about the changes to `mm/cma.c`?  Those are reachable because CMA is used by many things. But, hold on... `cma_create` and `cma_free` are only called by `tegra-vpr.c`. So the changes to `cma_for_each_area` and `cma_new_area` are reachable.

But wait, if `cma_areas` is now a list and `cma_early_areas` is an array, what happens to the early areas?  `cma_new_area` adds to `cma_early_areas`. And then `cma_init_reserved_areas` calls `cma_activate_area` for each `cma_early_areas`, but it NEVER adds them to the `cma_areas` list! So `cma_areas` doesn't include the early allocated areas. Now I need to check `cma_activate_area` in `mm/cma.c`.



34/2 2026/07/01 23:36 tool grepper
0m Args:
{
  "Expression": "cma_activate_area",
  "PathPrefix": "mm/cma.c"
}

Results:
{
  "Output": "mm/cma.c=108=bool cma_validate_zones(struct cma *cma)\n--\nmm/cma.c-145-\nmm/cma.c:146:static void __init cma_activate_area(struct cma *cma)\nmm/cma.c-147-{\n--\nmm/cma.c=210=static int __init cma_init_reserved_areas(void)\n--\nmm/cma.c-214-\tfor (i = 0; i \u003c cma_early_area_count; i++)\nmm/cma.c:215:\t\tcma_activate_area(\u0026cma_early_areas[i]);\nmm/cma.c-216-\n--\nmm/cma.c=1197=struct cma *__init cma_create(phys_addr_t base, phys_addr_t size,\n--\nmm/cma.c-1216-\nmm/cma.c:1217:\tcma_activate_area(cma);\nmm/cma.c-1218-\n"
}

35/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39346
							output: 34
							thoughts: 146
						

Thoughts:
**My Immediate Concerns Regarding CMA Initialization**

Okay, hold on a second. I'm just looking over this CMA code, and something's not sitting right with me. I'm seeing that `cma_activate_area` *doesn't* actually modify the `cma_areas` list. That’s a red flag. So, that means `cma_early_areas` are *never* incorporated into `cma_areas`! This has some pretty significant implications.

If that's true, then `cma_for_each_area` is only going to iterate over CMAs created explicitly by `cma_create`. That’s a major limitation, especially considering how this function is used. But… wait, wasn't `cma_for_each_area` used within `dma_contiguous_areas`? And wasn’t there some change? Yes! It just came to me! It looks like `dma_contiguous.c` was updated to use `cma_early_areas` directly.

Okay, I need to verify that. I'm going to go straight to `kernel/dma/contiguous.c` and make absolutely sure I'm not missing anything. I need to be certain that the logic is correct and that this doesn't introduce any unforeseen issues in memory allocation, especially during early boot. I need to fully understand how these CMA lists interact and what the ramifications are.



36/2 2026/07/01 23:36 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "cma_early_areas",
  "PathPrefix": "kernel/dma/contiguous.c"
}

Results:
{
  "error": "no matches"
}

37/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39847
							output: 38
							thoughts: 49
						

Thoughts:
**Investigating the `cma_early_areas` Absence in `contiguous.c`**

Okay, so I just ran a `grep` for `cma_early_areas` within `kernel/dma/contiguous.c`, expecting to find some context around early CMA allocation, and... nothing. That's a bit odd. I was sure that term, or something related to it, would be present, given the subject matter.

Alright, time to dig deeper. If the symbol isn't directly referenced in that file, then I need to understand what's been *changed* there recently. I need to see if the implementation logic has been refactored, moved elsewhere, or possibly replaced by a new system. Perhaps it's under a different name, or the functionality is now handled by a completely different subsystem. Let's see what the change history in `kernel/dma/contiguous.c` reveals. That will give me a direction to follow.



38/2 2026/07/01 23:36 tool grepper
0m Args:
{
  "Expression": "MAX_EARLY_CMA_AREAS",
  "PathPrefix": "kernel/dma/contiguous.c"
}

Results:
{
  "Output": "kernel/dma/contiguous.c-54-\nkernel/dma/contiguous.c:55:static struct cma *dma_contiguous_areas[MAX_EARLY_CMA_AREAS];\nkernel/dma/contiguous.c-56-static unsigned int dma_contiguous_areas_num;\n"
}

39/2 2026/07/01 23:36 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40225
							output: 22
							thoughts: 137
						

Thoughts:
**Investigating a Recent Kernel Change**

Okay, so `kernel/dma/contiguous.c` just had a subtle change – `MAX_CMA_AREAS` is now `MAX_EARLY_CMA_AREAS`. Interesting. It's still using the `dma_contiguous_areas` array, which makes sense. I was curious to understand the impact of the change.

Wait, looking closer, `cma_for_each_area` isn't even *used* within `kernel/dma/contiguous.c`! I had assumed a relationship there, but that clearly isn't the case. Okay, where *is* `cma_for_each_area` actually used then? I'm pretty sure I already grepped the code, but let me confirm, it's ONLY in `include/linux/cma.h` and `mm/cma.c`. So it's a CMA-specific thing, not a general contiguous memory allocation tool.

That's a relief, it's not affecting a broader section of the kernel than initially thought. Still, I should double check to ensure there's no subtle cross-dependencies. I need to know if any other drivers actually leverage that `cma_for_each_area` macro in a potentially problematic way.



40/2 2026/07/01 23:36 tool grepper
0m Args:
{
  "Expression": "cma_for_each_area"
}

Results:
{
  "Output": "include/linux/cma.h=60=extern int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h:61:extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h-62-extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end);\n--\nmm/cma.c=1081=int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data)\n--\nmm/cma.c-1094-\nmm/cma.c:1095:int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\nmm/cma.c-1096-{\n"
}

41/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40917
							output: 30
							thoughts: 67
						

Thoughts:
Here's my summary, as the expert:

**Investigating the `cma_for_each_area` Function**

Okay, so I'm digging into this `cma_for_each_area` function. My initial impression is... it's practically orphaned. I can't find any other use of this function within the current codebase. That's a red flag. Is it just dead code, taking up space and potentially causing confusion? Or perhaps it's lurking somewhere, being used by an external, out-of-tree module? I need to verify its purpose.

Wait a second, let me check related functions, just to be thorough. What about `cma_release_frozen`? That one hasn't been touched, has it? And what about `cma_intersects`? Are they linked? I need to consider their context before I make any judgements on the overall health of this section of the code. This lack of use raises concerns about maintenance and potential future issues. I need to figure out where it's being used, and if not, why it was never removed.



42/2 2026/07/01 23:37 tool grepper
0m Args:
{
  "Expression": "cma_intersects",
  "PathPrefix": "mm/cma.c"
}

Results:
{
  "Output": "mm/cma.c=1095=int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\n--\nmm/cma.c-1113-\nmm/cma.c:1114:bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end)\nmm/cma.c-1115-{\n"
}

43/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41365
							output: 18
							thoughts: 10
						

Thoughts:
Okay, here's my attempt at summarizing those thoughts from a first-person, expert perspective:

**Investigating `cma_intersects` Usage**

Alright, let's get down to brass tacks. My initial thought is focused on a specific function, `cma_intersects`. I'm immediately questioning its usage. *Is* this function actually being called anywhere in the codebase? It's crucial for understanding its relevance, potential impact, and even if it can be safely removed or refactored. We all know how codebases grow, and unused functions are just technical debt in disguise. I need to figure out where this function lives within the overall architecture. Am I going to have to run a code search, or can I quickly determine this through some means within our IDE? It's a quick analysis I need to perform immediately. If not used, I'm thinking whether I can remove it, simplify the architecture, and reduce the chance of errors. So, let's determine the answer quickly.



44/2 2026/07/01 23:37 tool grepper
0m Args:
{
  "Expression": "cma_intersects"
}

Results:
{
  "Output": "arch/s390/mm/init.c=221=static int s390_cma_check_range(struct cma *cma, void *data)\n--\narch/s390/mm/init.c-226-\narch/s390/mm/init.c:227:\tif (cma_intersects(cma, mem_data-\u003estart, mem_data-\u003eend))\narch/s390/mm/init.c-228-\t\treturn -EBUSY;\n--\ninclude/linux/cma.h=61=extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h:62:extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end);\ninclude/linux/cma.h-63-\n--\nmm/cma.c=1095=int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\n--\nmm/cma.c-1113-\nmm/cma.c:1114:bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end)\nmm/cma.c-1115-{\n"
}

45/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41944
							output: 36
							thoughts: 346
						

Thoughts:
**CMA Initialization and Debugging**

Okay, so I'm looking at the CMA (Contiguous Memory Allocator) initialization and how it interacts with the s390 architecture. I know that `arch/s390/mm/init.c` is the place to start. But hold on, I remember that file utilizes `cma_for_each_early_area`. Let's take a closer look at that `s390_cma_mem_notifier` function. I see it uses `cma_for_each_early_area` to iterate over the `cma_early_areas` list. Within the loop, it calls `s390_cma_check_range`, which I'm pretty sure calls `cma_intersects`. That all checks out, so far, so good. No red flags there.

Now, I need to consider `cma_debugfs_init` and `cma_sysfs_init`. I need to ensure they're still located where I expect them to be, in `mm/cma_debug.c` and `mm/cma_sysfs.c`, respectively. I'll take a quick look at `mm/cma_debug.c` to make sure it compiles.



46/2 2026/07/01 23:37 tool read-file
0m Args:
{
  "File": "mm/cma_debug.c",
  "FirstLine": 190,
  "LineCount": 30
}

Results:
{
  "Contents": " 190:\t\t\t\t\t\u0026cmr-\u003edfs_bitmap);\n 191:\t\t}\n 192:\t\n 193:\t\t/*\n 194:\t\t * Backward compatible symlinks to range 0 for base_pfn and bitmap.\n 195:\t\t */\n 196:\t\tdebugfs_create_symlink(\"base_pfn\", tmp, \"ranges/0/base_pfn\");\n 197:\t\tdebugfs_create_symlink(\"bitmap\", tmp, \"ranges/0/bitmap\");\n 198:\t}\n 199:\t\n 200:\tstatic int __init cma_debugfs_init(void)\n 201:\t{\n 202:\t\tstruct dentry *cma_debugfs_root;\n 203:\t\tint i;\n 204:\t\n 205:\t\tcma_debugfs_root = debugfs_create_dir(\"cma\", NULL);\n 206:\t\n 207:\t\tfor (i = 0; i \u003c cma_area_count; i++)\n 208:\t\t\tif (test_bit(CMA_ACTIVATED, \u0026cma_areas[i].flags))\n 209:\t\t\t\tcma_debugfs_add_one(\u0026cma_areas[i], cma_debugfs_root);\n 210:\t\n 211:\t\treturn 0;\n 212:\t}\n 213:\tlate_initcall(cma_debugfs_init);\n"
}

47/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 42990
							output: 33
							thoughts: 52
						

Thoughts:
Okay, here's the summary, assuming I'm the expert and considering the potential implications:

**CMA Area Mystery**

Hmm, hold on a second. I'm seeing `cma_areas[i]` being referenced here. That's a little unusual. The issue immediately jumps out: I don't see `cma_areas` declared anywhere obvious within this `mm/cma_debug.c` file. This raises a red flag; how is this variable getting its definition? This could be a sneaky global, which is never ideal, but the lack of an explicit declaration is more likely a missing include or a declaration elsewhere.

Alright, before I start digging for obscure compiler flags or header file gymnastics, the logical first step is to check `mm/cma.h`. That's where I'd expect any related structures or variables to be declared, especially if we're dealing with CMA (Contiguous Memory Allocator) functionality. Time to see what's in that header and confirm or debunk my suspicions. Let's see if that declaration is there...



48/2 2026/07/01 23:37 tool read-file
0m Args:
{
  "File": "mm/cma.h",
  "FirstLine": 1,
  "LineCount": 100
}

Results:
{
  "Contents": "   1:\t/* SPDX-License-Identifier: GPL-2.0 */\n   2:\t#ifndef __MM_CMA_H__\n   3:\t#define __MM_CMA_H__\n   4:\t\n   5:\t#include \u003clinux/debugfs.h\u003e\n   6:\t#include \u003clinux/kobject.h\u003e\n   7:\t\n   8:\tstruct cma_kobject {\n   9:\t\tstruct kobject kobj;\n  10:\t\tstruct cma *cma;\n  11:\t};\n  12:\t\n  13:\t/*\n  14:\t * Multi-range support. This can be useful if the size of the allocation\n  15:\t * is not expected to be larger than the alignment (like with hugetlb_cma),\n  16:\t * and the total amount of memory requested, while smaller than the total\n  17:\t * amount of memory available, is large enough that it doesn't fit in a\n  18:\t * single physical memory range because of memory holes.\n  19:\t *\n  20:\t * Fields:\n  21:\t *   @base_pfn: physical address of range\n  22:\t *   @early_pfn: first PFN not reserved through cma_reserve_early\n  23:\t *   @count: size of range\n  24:\t *   @bitmap: bitmap of allocated (1 \u003c\u003c order_per_bit)-sized chunks.\n  25:\t */\n  26:\tstruct cma_memrange {\n  27:\t\tunsigned long base_pfn;\n  28:\t\tunsigned long count;\n  29:\t\tunion {\n  30:\t\t\tunsigned long early_pfn;\n  31:\t\t\tunsigned long *bitmap;\n  32:\t\t};\n  33:\t#ifdef CONFIG_CMA_DEBUGFS\n  34:\t\tstruct debugfs_u32_array dfs_bitmap;\n  35:\t#endif\n  36:\t};\n  37:\t#define CMA_MAX_RANGES 8\n  38:\t\n  39:\tstruct cma {\n  40:\t\tunsigned long   count;\n  41:\t\tunsigned long\tavailable_count;\n  42:\t\tunsigned int order_per_bit; /* Order of pages represented by one bit */\n  43:\t\tspinlock_t\tlock;\n  44:\t\tstruct list_head node;\n  45:\t\tstruct mutex alloc_mutex;\n  46:\t#ifdef CONFIG_CMA_DEBUGFS\n  47:\t\tstruct hlist_head mem_head;\n  48:\t\tspinlock_t mem_head_lock;\n  49:\t#endif\n  50:\t\tchar name[CMA_MAX_NAME];\n  51:\t\tint nranges;\n  52:\t\tstruct cma_memrange ranges[CMA_MAX_RANGES];\n  53:\t#ifdef CONFIG_CMA_SYSFS\n  54:\t\t/* the number of CMA page successful allocations */\n  55:\t\tatomic64_t nr_pages_succeeded;\n  56:\t\t/* the number of CMA page allocation failures */\n  57:\t\tatomic64_t nr_pages_failed;\n  58:\t\t/* the number of CMA page released */\n  59:\t\tatomic64_t nr_pages_released;\n  60:\t\t/* kobject requires dynamic object */\n  61:\t\tstruct cma_kobject *cma_kobj;\n  62:\t#endif\n  63:\t\tunsigned long flags;\n  64:\t\t/* NUMA node (NUMA_NO_NODE if unspecified) */\n  65:\t\tint nid;\n  66:\t};\n  67:\t\n  68:\tenum cma_flags {\n  69:\t\tCMA_RESERVE_PAGES_ON_ERROR,\n  70:\t\tCMA_ZONES_VALID,\n  71:\t\tCMA_ZONES_INVALID,\n  72:\t\tCMA_ACTIVATED,\n  73:\t};\n  74:\t\n  75:\textern struct cma cma_early_areas[MAX_EARLY_CMA_AREAS];\n  76:\textern unsigned int cma_early_area_count;\n  77:\t\n  78:\tstatic inline unsigned long cma_bitmap_maxno(struct cma *cma,\n  79:\t\t\tstruct cma_memrange *cmr)\n  80:\t{\n  81:\t\treturn cmr-\u003ecount \u003e\u003e cma-\u003eorder_per_bit;\n  82:\t}\n  83:\t\n  84:\t#ifdef CONFIG_CMA_SYSFS\n  85:\tvoid cma_sysfs_account_success_pages(struct cma *cma, unsigned long nr_pages);\n  86:\tvoid cma_sysfs_account_fail_pages(struct cma *cma, unsigned long nr_pages);\n  87:\tvoid cma_sysfs_account_release_pages(struct cma *cma, unsigned long nr_pages);\n  88:\t#else\n  89:\tstatic inline void cma_sysfs_account_success_pages(struct cma *cma,\n  90:\t\t\t\t\t\t\t   unsigned long nr_pages) {};\n  91:\tstatic inline void cma_sysfs_account_fail_pages(struct cma *cma,\n  92:\t\t\t\t\t\t\tunsigned long nr_pages) {};\n  93:\tstatic inline void cma_sysfs_account_release_pages(struct cma *cma,\n  94:\t\t\t\t\t\t\t   unsigned long nr_pages) {};\n  95:\t#endif\n  96:\t#endif\n"
}

49/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 44697
							output: 32
							thoughts: 207
						

Thoughts:
**Debugging a Kernel Patch**

Okay, hold on a second. I'm looking at this patch and immediately see a problem. It seems like the definitions for `cma_areas` and `cma_area_count` are no longer in `mm/cma.h`. That's a huge red flag! It means if `CONFIG_CMA_DEBUGFS` or `CONFIG_CMA_SYSFS` are enabled, `mm/cma_debug.c` and `mm/cma_sysfs.c` are going to completely fail to compile. This patch looks broken.

But wait, I need to take a deep breath and think this through. This is from syzkaller, right? Syzkaller tests applied patches, doesn't it? If the tests passed, the code must have compiled. So, maybe, just maybe, the syzkaller configuration disables `CONFIG_CMA_DEBUGFS` and `CONFIG_CMA_SYSFS`? Or perhaps this patch I received is only a diff and the complete source code contains changes to those files.

Ah, I just got this note: "The changes have ALREADY been applied and committed as the HEAD commit in your workspace." That's good to know, but still, this discrepancy is troubling. Let's not assume anything. The best thing to do is quickly double-check my local workspace. I need to verify if `mm/cma_debug.c` still includes that variable. I need to be sure before I jump to any conclusions. Let's see...



50/2 2026/07/01 23:37 tool grepper
0m Error:
You are repeating the same tool call with the exact same arguments. Please synthesize the information you already have instead of repeating queries.

Args:
{
  "Expression": "cma_areas",
  "PathPrefix": "mm/cma_debug.c"
}

Results:
{
  "error": "You are repeating the same tool call with the exact same arguments. Please synthesize the information you already have instead of repeating queries."
}

51/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 45328
							output: 31
							thoughts: 772
						

Thoughts:
**Analyzing the Patch for Fuzzing Potential**

Okay, so I've quickly confirmed the grep result points to a problem in `mm/cma_debug.c`, specifically referencing `cma_areas[i]`. Right, so this patch seems to be introducing or modifying kernel memory management functions, so fuzzing is certainly warranted. I *must* avoid enabling `CONFIG_CMA_DEBUGFS` and `CONFIG_CMA_SYSFS` when testing this. Luckily, syzkaller has a config for this, so I will rely on that, and if it fails to build, that will be reported.

My job here is to generate the configuration for fuzzing this patch. I need to make sure `WorthFuzzing` is set to `true`, and provide `EnableConfigs` and `FocusSymbols`. The patch adds `CONFIG_DMABUF_HEAPS_TEGRA_VPR`, and obviously, I have to include that in `EnableConfigs`. It depends on `DMA_CMA` and `DMABUF_HEAPS` as well, so I need to add those to `EnableConfigs` as well.

Now, let's consider the changes in `mm/cma.c`. This is where it gets interesting. I need to figure out which functions are worth focusing on. `cma_create` and `cma_free` seem tied to `tegra-vpr` initialization, which requires a DT node. Since we probably won't have the appropriate DT node in a standard syzkaller setup, these functions are likely unreachable. Let me verify. Yes, the path to `cma_create` is through `tegra_vpr_chunk_init`, `tegra_vpr_setup_chunks`, `tegra_vpr_add_heap`, and finally `tegra_vpr_init`, all clearly tied to the Tegra VPR hardware. This makes them *not* a good target.

What about `cma_new_area`, `cma_init_reserved_areas`, and `cma_drop_area`? These are called during boot, but syzkaller usually focuses on syscalls, so early boot code is not what it usually focuses on.

Is there any syscall-reachable code changed in `mm/cma.c`? `cma_for_each_area` is changed to iterate over a list. However, it looks like it isn't used anywhere! Wait, it IS exported. Could some module use it? Nope, a grep returned no users. Okay, how about `cma_for_each_early_area`? Ah, it's used in `arch/s390/mm/init.c`. That's not a common architecture for syzkaller.

So, the changes in `mm/cma.c` appear to be mostly unreachable or dead code, except for early boot initialization. Wait, wait, there's `bitmap_allocate` added to `include/linux/bitmap.h`. It is used by `bitmap_allocate_region`, and `bitmap_allocate_region` is in turn used by `cma_alloc` in `mm/cma.c`. Okay, let's take a look at `cma_alloc` more closely.



52/2 2026/07/01 23:37 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "bitmap_allocate_region",
  "PathPrefix": "mm/cma.c"
}

Results:
{
  "error": "no matches"
}

53/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 46847
							output: 28
							thoughts: 37
						

Thoughts:
Here's my take on the thought process:

**Diving into CMA Allocation Details**

Okay, so `cma_alloc` is using `bitmap_find_free_region`... That's interesting. I hadn't immediately connected those dots. It immediately makes me think that the allocation strategy is based on a bitmap, which is pretty standard, but it's always good to refresh my memory on the specifics of the CMA implementation. Time to crack open the source code. Let's head over to `mm/cma.c` and search specifically for the `bitmap_` functions. I want to see exactly how they are integrated and how the CMA allocator leverages the bitmap to manage its contiguous memory regions. This should shed light on the allocation algorithm, fragmentation handling, and any potential optimizations implemented within the CMA framework. I suspect I might find some subtle details about how it handles physical address ranges, or perhaps some clever tricks for coalescing free blocks. Let's see what we find.



54/2 2026/07/01 23:37 tool grepper
0m Args:
{
  "Expression": "bitmap_",
  "PathPrefix": "mm/cma.c"
}

Results:
{
  "Output": "mm/cma.c=60=EXPORT_SYMBOL_GPL(cma_get_name);\nmm/cma.c-61-\nmm/cma.c:62:static unsigned long cma_bitmap_aligned_mask(const struct cma *cma,\nmm/cma.c-63-\t\t\t\t\t     unsigned int align_order)\n--\nmm/cma.c-73- */\nmm/cma.c:74:static unsigned long cma_bitmap_aligned_offset(const struct cma *cma,\nmm/cma.c-75-\t\t\t\t\t       const struct cma_memrange *cmr,\n--\nmm/cma.c-81-\nmm/cma.c:82:static unsigned long cma_bitmap_pages_to_bits(const struct cma *cma,\nmm/cma.c-83-\t\t\t\t\t      unsigned long pages)\n--\nmm/cma.c=88=static void cma_clear_bitmap(struct cma *cma, const struct cma_memrange *cmr,\n--\nmm/cma.c-90-{\nmm/cma.c:91:\tunsigned long bitmap_no, bitmap_count;\nmm/cma.c-92-\tunsigned long flags;\nmm/cma.c-93-\nmm/cma.c:94:\tbitmap_no = (pfn - cmr-\u003ebase_pfn) \u003e\u003e cma-\u003eorder_per_bit;\nmm/cma.c:95:\tbitmap_count = cma_bitmap_pages_to_bits(cma, count);\nmm/cma.c-96-\nmm/cma.c-97-\tspin_lock_irqsave(\u0026cma-\u003elock, flags);\nmm/cma.c:98:\tbitmap_clear(cmr-\u003ebitmap, bitmap_no, bitmap_count);\nmm/cma.c-99-\tcma-\u003eavailable_count += count;\n--\nmm/cma.c=146=static void __init cma_activate_area(struct cma *cma)\n--\nmm/cma.c-150-\tstruct cma_memrange *cmr;\nmm/cma.c:151:\tunsigned long bitmap_count, count;\nmm/cma.c-152-\n--\nmm/cma.c-155-\t\tearly_pfn[allocrange] = cmr-\u003eearly_pfn;\nmm/cma.c:156:\t\tcmr-\u003ebitmap = bitmap_zalloc(cma_bitmap_maxno(cma, cmr),\nmm/cma.c-157-\t\t\t\t\t    GFP_KERNEL);\n--\nmm/cma.c-168-\t\t\tcount = early_pfn[r] - cmr-\u003ebase_pfn;\nmm/cma.c:169:\t\t\tbitmap_count = cma_bitmap_pages_to_bits(cma, count);\nmm/cma.c:170:\t\t\tbitmap_set(cmr-\u003ebitmap, 0, bitmap_count);\nmm/cma.c-171-\t\t}\n--\nmm/cma.c-191-\tfor (r = 0; r \u003c allocrange; r++)\nmm/cma.c:192:\t\tbitmap_free(cma-\u003eranges[r].bitmap);\nmm/cma.c-193-\n--\nmm/cma.c=784=static void cma_debug_show_areas(struct cma *cma)\n--\nmm/cma.c-796-\nmm/cma.c:797:\t\tnbits = cma_bitmap_maxno(cma, cmr);\nmm/cma.c-798-\n--\nmm/cma.c=811=static int cma_range_alloc(struct cma *cma, struct cma_memrange *cmr,\n--\nmm/cma.c-814-{\nmm/cma.c:815:\tunsigned long bitmap_maxno, bitmap_no, bitmap_count;\nmm/cma.c-816-\tunsigned long start, pfn, mask, offset;\n--\nmm/cma.c-819-\nmm/cma.c:820:\tmask = cma_bitmap_aligned_mask(cma, align);\nmm/cma.c:821:\toffset = cma_bitmap_aligned_offset(cma, cmr, align);\nmm/cma.c:822:\tbitmap_maxno = cma_bitmap_maxno(cma, cmr);\nmm/cma.c:823:\tbitmap_count = cma_bitmap_pages_to_bits(cma, count);\nmm/cma.c-824-\nmm/cma.c:825:\tif (bitmap_count \u003e bitmap_maxno)\nmm/cma.c-826-\t\tgoto out;\nmm/cma.c-827-\nmm/cma.c:828:\tfor (start = 0; ; start = bitmap_no + mask + 1) {\nmm/cma.c-829-\t\tspin_lock_irq(\u0026cma-\u003elock);\n--\nmm/cma.c-837-\t\t}\nmm/cma.c:838:\t\tbitmap_no = bitmap_find_next_zero_area_off(cmr-\u003ebitmap,\nmm/cma.c:839:\t\t\t\tbitmap_maxno, start, bitmap_count, mask,\nmm/cma.c-840-\t\t\t\toffset);\nmm/cma.c:841:\t\tif (bitmap_no \u003e= bitmap_maxno) {\nmm/cma.c-842-\t\t\tspin_unlock_irq(\u0026cma-\u003elock);\n--\nmm/cma.c-845-\nmm/cma.c:846:\t\tpfn = cmr-\u003ebase_pfn + (bitmap_no \u003c\u003c cma-\u003eorder_per_bit);\nmm/cma.c-847-\t\tpage = pfn_to_page(pfn);\n--\nmm/cma.c-860-\nmm/cma.c:861:\t\tbitmap_set(cmr-\u003ebitmap, bitmap_no, bitmap_count);\nmm/cma.c-862-\t\tcma-\u003eavailable_count -= count;\n--\nmm/cma.c=1228=void cma_free(struct cma *cma)\n--\nmm/cma.c-1236-\tfor (i = 0; i \u003c cma-\u003enranges; i++) {\nmm/cma.c:1237:\t\tunsigned long nbits = cma_bitmap_maxno(cma, \u0026cma-\u003eranges[i]);\nmm/cma.c-1238-\nmm/cma.c:1239:\t\tif (!bitmap_empty(cma-\u003eranges[i].bitmap, nbits)) {\nmm/cma.c-1240-\t\t\tWARN(1, \"%s: range %u not empty\\n\", cma-\u003ename, i);\n--\nmm/cma.c-1253-\nmm/cma.c:1254:\t\tbitmap_free(cmr-\u003ebitmap);\nmm/cma.c-1255-\t}\n"
}

55/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 48940
							output: 40
							thoughts: 53
						

Thoughts:
**Investigating Bitmap Allocation in CMA Context**

Okay, so it seems `bitmap_allocate` isn't actually employed within the `mm/cma.c` file itself. That's a little unexpected, I'd assumed it would be central to the Contiguous Memory Allocator implementation. So, the obvious next step is to figure out *where* this function *is* used. Let's trace it down. Let's see... a quick grep through the code... ah, here we go, let's take a look at the `drivers/dma-buf/heaps/tegra-vpr.c` file. Hopefully, this will shed some light on the allocation strategy and its interaction with the bitmap allocation mechanisms.



56/2 2026/07/01 23:37 tool grepper
0m Args:
{
  "Expression": "bitmap_allocate",
  "PathPrefix": "drivers/dma-buf/heaps/tegra-vpr.c"
}

Results:
{
  "Output": "drivers/dma-buf/heaps/tegra-vpr.c=457=static int tegra_vpr_find_free_region_clustered(struct tegra_vpr *vpr,\n--\ndrivers/dma-buf/heaps/tegra-vpr.c-505-\ndrivers/dma-buf/heaps/tegra-vpr.c:506:\t\tif (!bitmap_allocate(vpr-\u003ebitmap, target, num_pages))\ndrivers/dma-buf/heaps/tegra-vpr.c-507-\t\t\treturn target;\n--\ndrivers/dma-buf/heaps/tegra-vpr.c-513-\tif (target + num_pages \u003c= vpr-\u003enum_pages \u0026\u0026\ndrivers/dma-buf/heaps/tegra-vpr.c:514:\t    !bitmap_allocate(vpr-\u003ebitmap, target, num_pages))\ndrivers/dma-buf/heaps/tegra-vpr.c-515-\t\treturn target;\n"
}

57/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 49454
							output: 34
							thoughts: 915
						

Thoughts:
**Fuzzing Analysis: Tegra VPR Driver and CMA Refactoring**

Okay, let's break this down. First, I see that `bitmap_allocate` is indeed used in `tegra-vpr.c`. The core changes are in three areas: the `tegra-vpr.c` driver, CMA (Contiguous Memory Allocator) management (`mm/cma.c`), and some page attribute adjustments (`arch/arm64/mm/pageattr.c`).

The `tegra-vpr.c` driver itself is conditional, only initializing if its Device Tree node is present. Since syzkaller doesn't inject DT nodes, that code path is essentially unreachable in a syzkaller context. However, the CMA changes in `mm/cma.c` – specifically, refactoring `cma_areas` to a list and early boot initialization – *are* reached during boot. But I need to determine if any of these changes are relevant to fuzzing.

My initial thought was that `cma_create` and `cma_free` were the key areas, but those are *only* called by `tegra-vpr.c`, so again, uninteresting from a fuzzing perspective. The refactoring of `cma_areas` and the early boot init, while executed, aren't fuzzed directly by syzkaller which primarily targets syscalls.

I need to confirm if any *syscall*-reachable code has changed. `cma_alloc` and `cma_release` seem untouched, so that's a dead end. Okay, `cma_for_each_area` is modified, but that's not used in any syscall path either, except for a couple of niche cases in `arch/s390/mm/init.c` and `mm/cma_debug.c`, which are not relevant. This function is not called from a syscall-reachable path.

So, is this patch worth fuzzing? The new `tegra-vpr.c` driver itself is off-limits; without a DT node, syzkaller is going nowhere with that. The question now becomes: can syzkaller be configured to use DT overlays? It's not default behavior. But even if it could, given that syzkaller usually runs on generic QEMU (`virt` machine), and `tegra-vpr` requires a specific DT node, it's virtually impossible to reach in this setup. The instructions say to consider QEMU as a virtualized environment where this is impossible, so it is impossible to reach.

However, there's also the CMA refactoring. Are the changes in `mm/cma.c` reachable? Yes, `cma_new_area` and `cma_init_reserved_areas` are executed during boot, but not through syscalls, which means no fuzzing. But what about `cma_for_each_area`, which is exported? I have to look for callers, so I'll grep the code. No luck, the code is dead and unreachable. Okay, I was wrong, `cma_create` and `cma_free` are exported, which means it should be used in some code. Let's dig deeper into `mm/cma.c` to see how it works. I need more information to evaluate these.



58/2 2026/07/01 23:37 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*cma_create",
  "PathPrefix": "mm/cma.c"
}

Results:
{
  "error": "no matches"
}

59/2 2026/07/01 23:37 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 51137
							output: 30
							thoughts: 25
						

Thoughts:
Here's my take on those thoughts:

**A Quick Panic, Followed by a Need for Investigation**

Okay, hold on a second. I was *certain* that `cma_create` was part of the standard API. Why isn't it exporting? That's definitely not what I expected. This is a bit unsettling; I need to figure out what's going on immediately.

Right, time to dive in. Let's trace back the definition of `cma_create`. I need to understand why it's not being exposed where I thought it would be. Is it a naming issue? A scope problem? Or has something changed in the library I'm working with? This requires a deep dive into the source code to understand what is happening here.



60/2 2026/07/01 23:37 tool grepper
0m Args:
{
  "Expression": "cma_create",
  "PathPrefix": "mm/cma.c"
}

Results:
{
  "Output": "mm/cma.c=1157=void __init *cma_reserve_early(struct cma *cma, unsigned long size)\n--\nmm/cma.c-1196-\nmm/cma.c:1197:struct cma *__init cma_create(phys_addr_t base, phys_addr_t size,\nmm/cma.c-1198-\t\t\t      unsigned int order_per_bit, const char *name)\n"
}

61/2 2026/07/01 23:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 51484
							output: 41
							thoughts: 105
						

Thoughts:
**Analyzing the CMA Memory Allocation Initialization**

Okay, hold on a second. I'm just looking through this code and something's pinging my radar. I see that `cma_create` is flagged as `__init`, which is usually a pretty strong indicator that it's intended to be called only during the boot process. But here's where it gets interesting... I'm tracing the calls, and it looks like `tegra-vpr.c` invokes `cma_create` from `tegra_vpr_setup_chunks`. That function is, in turn, called by `tegra_vpr_add_heap`, which is *also* called by `tegra_vpr_init`. And *that* one is definitely marked `__init`. So, the entire chain is tied to the early initialization stages, confirming my initial hunch about `cma_create` being a boot-time function.

But wait a minute... I also see `tegra-vpr.c` calling `cma_free` as well. That is a function that usually is called during the shutdown of a system, not just during the boot process. I need to investigate that and see what that's all about.



62/2 2026/07/01 23:38 tool grepper
0m Args:
{
  "Expression": "cma_free",
  "PathPrefix": "drivers/dma-buf/heaps/tegra-vpr.c"
}

Results:
{
  "Output": "drivers/dma-buf/heaps/tegra-vpr.c=229=static void tegra_vpr_chunk_free(struct tegra_vpr_chunk *chunk)\ndrivers/dma-buf/heaps/tegra-vpr.c-230-{\ndrivers/dma-buf/heaps/tegra-vpr.c:231:\tcma_free(chunk-\u003ecma);\ndrivers/dma-buf/heaps/tegra-vpr.c-232-}\n"
}

63/2 2026/07/01 23:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 52030
							output: 30
							thoughts: 94
						

Thoughts:
Okay, let me think this through.

**Analyzing Memory Management in the NVIDIA Tegra VPR Driver**

Alright, so I'm looking at how the Tegra VPR driver handles memory. The function `tegra_vpr_chunk_free` is definitely central here. It's called in two places: once during the initial setup process, specifically within `tegra_vpr_setup_chunks` if something goes wrong. Then, it's called from `tegra_vpr_free_chunks`, and that function is called from `tegra_vpr_add_heap` in the error handling path. The initial impression is that these are all happening during the module's initialization phase, everything being linked with the `__init` macro.

But here’s the kicker - hang on, why isn’t `cma_free` marked with `__init`? That seems strange. It implies that `cma_free` might be called later, not just during the module's initialization, which means it should be scrutinized carefully for potential memory leaks or corruption issues that may arise from non-`__init` calls. This requires a deeper dive into the control flow and use cases of `cma_free`, or rather how memory that's been freed using `cma_free` is being managed in relation to these calls that use `__init`. This warrants further investigation.



64/2 2026/07/01 23:38 tool grepper
0m Args:
{
  "Expression": "cma_free",
  "PathPrefix": "mm/cma.c"
}

Results:
{
  "Output": "mm/cma.c=1197=struct cma *__init cma_create(phys_addr_t base, phys_addr_t size,\n--\nmm/cma.c-1227-\nmm/cma.c:1228:void cma_free(struct cma *cma)\nmm/cma.c-1229-{\n"
}

65/2 2026/07/01 23:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 52555
							output: 37
							thoughts: 394
						

Thoughts:
**My Analysis of CMA and DMA-BUF Heap Interactions**

Okay, let me break this down. From what I'm seeing, `cma_create` and `cma_free` are primarily used during the boot process or module initialization. The `tegra-vpr.c` driver is built-in. I see that `DMABUF_HEAPS_TEGRA_VPR` is a boolean, so it's statically linked. That means all the CMA allocation and deallocation code within `tegra-vpr.c` happens early during boot.

Now, the DMA-BUF heap operations are where it gets interesting: `tegra_vpr_allocate`, `tegra_vpr_buffer_allocate`, `tegra_vpr_buffer_release`, `tegra_vpr_attach`, `tegra_vpr_map_dma_buf`, all of these are accessible through DMA-BUF heap ioctls. But, and this is crucial, the heap itself only gets registered if its corresponding Device Tree (DT) node exists. Since QEMU's `virt` machine setup *doesn't* have that DT node, the heap will *not* be registered. Therefore, all that DMA-BUF heap code in `tegra-vpr.c` is effectively unreachable in virtualized QEMU environments. It's a dead end, practically speaking.

Looking at the CMA changes in `mm/cma.c`, these are executed during boot, but they don't seem to introduce any new system call functionality. Unless `cma_for_each_area` was exposed through a syscall, which it isn't.

Finally, in `arch/arm64/mm/pageattr.c`, there are the new `set_memory_device` and `set_memory_normal` functions. This might need more investigation depending on the overall goals, but it does seem related to the memory attributes being set at runtime.



66/2 2026/07/01 23:38 tool grepper
0m Args:
{
  "Expression": "set_memory_device",
  "PathPrefix": "arch/arm64/mm/pageattr.c"
}

Results:
{
  "Output": "arch/arm64/mm/pageattr.c=242=int set_memory_valid(unsigned long addr, int numpages, int enable)\n--\narch/arm64/mm/pageattr.c-253-\narch/arm64/mm/pageattr.c:254:int set_memory_device(unsigned long addr, int numpages)\narch/arm64/mm/pageattr.c-255-{\n--\narch/arm64/mm/pageattr.c-259-}\narch/arm64/mm/pageattr.c:260:EXPORT_SYMBOL_GPL(set_memory_device);\narch/arm64/mm/pageattr.c-261-\n"
}

67/2 2026/07/01 23:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 53595
							output: 19
							thoughts: 6
						

Thoughts:
Okay, here's the summary, formatted as requested:

**Considering Context and Potential Application**

I'm pondering the nature of these "thoughts" – are they truly unique in this context, or are they utilized, in some way, in other fields or disciplines? I'm asking this as a seasoned professional who can rapidly identify patterns and extrapolate from existing knowledge. Perhaps a similar concept, or a variation of it, is employed in other processes, models, or theoretical frameworks. It's a key question when evaluating novelty. I need to understand if these thoughts are truly original, or if their utility lies in an adaptation or synthesis of existing concepts. The answer profoundly impacts their significance and applicability.



68/2 2026/07/01 23:38 tool grepper
0m Args:
{
  "Expression": "set_memory_device"
}

Results:
{
  "Output": "arch/arm64/include/asm/set_memory.h=12=int set_memory_valid(unsigned long addr, int numpages, int enable);\narch/arm64/include/asm/set_memory.h:13:int set_memory_device(unsigned long addr, int numpages);\narch/arm64/include/asm/set_memory.h-14-int set_memory_normal(unsigned long addr, int numpages);\n--\narch/arm64/mm/pageattr.c=242=int set_memory_valid(unsigned long addr, int numpages, int enable)\n--\narch/arm64/mm/pageattr.c-253-\narch/arm64/mm/pageattr.c:254:int set_memory_device(unsigned long addr, int numpages)\narch/arm64/mm/pageattr.c-255-{\n--\narch/arm64/mm/pageattr.c-259-}\narch/arm64/mm/pageattr.c:260:EXPORT_SYMBOL_GPL(set_memory_device);\narch/arm64/mm/pageattr.c-261-\n--\ndrivers/dma-buf/heaps/tegra-vpr.c=251=static int tegra_vpr_chunk_activate(struct tegra_vpr_chunk *chunk)\n--\ndrivers/dma-buf/heaps/tegra-vpr.c-266-\ndrivers/dma-buf/heaps/tegra-vpr.c:267:\terr = set_memory_device(chunk-\u003evirt, chunk-\u003enum_pages);\ndrivers/dma-buf/heaps/tegra-vpr.c-268-\tif (err \u003c 0)\n--\ninclude/linux/set_memory.h=69=static inline int clear_mce_nospec(unsigned long pfn)\n--\ninclude/linux/set_memory.h-75-#ifndef CONFIG_ARM64\ninclude/linux/set_memory.h:76:static inline int set_memory_device(unsigned long addr, int numpages)\ninclude/linux/set_memory.h-77-{\n"
}

69/2 2026/07/01 23:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 54306
							output: 22
							thoughts: 1538
						

Thoughts:
**Analysis of the Patch: Unreachable Code in Virtualized Environments**

Okay, let's break this down. My initial assessment is that this patch introduces code paths that are inherently unreachable under typical virtualized conditions, specifically QEMU. It appears to focus around the `tegra-vpr` driver, and that is a key point. This driver's initialization is dependent on the presence of a specific Device Tree (DT) node, `"nvidia,tegra-video-protection-region"`. Given that Syzkaller, in its standard configuration, doesn't inject or dynamically load these DT nodes, that alone makes the driver's core functionality inaccessible.

I checked if Syzkaller offers any mechanisms for DT injection, just to be sure. I know it has `syz_init_net_socket` and `syz_mount_image`, but these are unrelated. I also know that Syzkaller doesn't handle DT overlays unless there's a specific setup using `CONFIG_OF_OVERLAY` and a user-space interface, which isn't standard and which Syzkaller doesn't generically describe. Therefore, no way to inject the necessary DT node.

Even if, by some incredible chance, that DT node were present, the driver utilizes `arm_smccc_smc` to communicate with a secure monitor. In a QEMU environment, this call would likely trap to EL3 or EL2, depending on the QEMU configuration. If QEMU's EL3 implementation doesn't handle this particular Secure Monitor Call (SMC), it will return an error code, likely `SMCCC_RET_NOT_SUPPORTED` (-1). This would make the driver function fail before it even got to the `tegra_vpr_resize` function that the code attempts to modify.

I know the instructions say to return `WorthFuzzing=false` if the patch contains unreachable code paths, specifically if it’s hardware-specific and requires a DT node for a device (NVIDIA Tegra VPR) that isn’t available in QEMU, and also relies on Tegra-specific SMCs. That seems to apply here. The patch falls squarely under that category: a driver dependent on a DT node for a device not in QEMU, along with its specific hardware calls.

I also need to consider the changes to `mm/cma.c`, which are:
1.  `cma_areas` array changed to `cma_early_areas` array.
2.  `cma_areas` list introduced.
3.  `cma_new_area` adds to `cma_early_areas`.
4.  `cma_init_reserved_areas` iterates over `cma_early_areas`.
5.  `cma_create` and `cma_free` added (unreachable).
6.  `cma_for_each_area` changed to iterate over `cma_areas` list (unreachable, no callers).
7.  `cma_for_each_early_area` added and used in `arch/s390/mm/init.c`.

I need to make sure none of these changes introduce reachable functionality in a way that should be fuzzed. The early boot CMA initialization is reachable, but I know that code is executed once during boot and won't be fuzzed by Syzkaller. `cma_for_each_area`, in theory, would be the only part of this that’s syscall-reachable but, I know it has no callers. Therefore, there's no functional change in `mm/cma.c` that’s reachable.

Therefore, the whole patch is not worth fuzzing. I'll return `WorthFuzzing=false`. The reasoning is that the `tegra-vpr` driver requires a specific DT node that won't exist in a virtualized QEMU setup and uses Tegra-specific SMC calls. The changes to core CMA logic are not reachable. The patch is unreachable.

I should also make sure no other methods can reach the function `dma_heap_add` inside `tegra-vpr.c`. I will read through the code now.

I see that `dma_heap_add` will be called from `tegra_vpr_add_heap`, which is called inside `tegra_vpr_init`. The function requires the `/reserved-memory` node with the exact string. This also is not present in QEMU.

I'll finalize this reasoning now.



70/2 2026/07/01 23:38 tool grepper
0m Args:
{
  "Expression": "cma_for_each_area"
}

Results:
{
  "Output": "include/linux/cma.h=60=extern int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h:61:extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data);\ninclude/linux/cma.h-62-extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end);\n--\nmm/cma.c=1081=int cma_for_each_early_area(int (*it)(struct cma *cma, void *data), void *data)\n--\nmm/cma.c-1094-\nmm/cma.c:1095:int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)\nmm/cma.c-1096-{\n"
}

71/2 2026/07/01 23:38 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 57099
							output: 178
							thoughts: 23
						

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

**Final Decision: No Further Fuzzing**

Alright, that's the end of that investigation. We've reached a point where further exploration with fuzzing isn't going to be beneficial, at least based on what we've seen so far. There's no value in taking this any further. So, I will proceed to call `set-results` and ensure that `WorthFuzzing` is set to `false`. That should signal that we're concluding this iteration, and the system can move on to other tasks or evaluations without wasting cycles on a dead end.



72/2 2026/07/01 23:38 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new DMA-BUF heap for NVIDIA Tegra Video-Protection-Region (VPR). The driver (tegra-vpr.c) only initializes if a specific device tree node ('nvidia,tegra-video-protection-region') is present, which is not the case in standard virtualized environments like QEMU. Furthermore, the driver relies on Tegra-specific SMC calls to a secure monitor. The changes to core CMA logic (mm/cma.c) are either executed only during early boot or are dead code (e.g., cma_for_each_area has no callers). Therefore, the functional changes are impossible to reach and fuzz in a virtualized environment.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces a new DMA-BUF heap for NVIDIA Tegra Video-Protection-Region (VPR). The driver (tegra-vpr.c) only initializes if a specific device tree node ('nvidia,tegra-video-protection-region') is present, which is not the case in standard virtualized environments like QEMU. Furthermore, the driver relies on Tegra-specific SMC calls to a secure monitor. The changes to core CMA logic (mm/cma.c) are either executed only during early boot or are dead code (e.g., cma_for_each_area has no callers). Therefore, the functional changes are impossible to reach and fuzz in a virtualized environment.",
  "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)