Linux s390 Architecture development
 help / color / mirror / Atom feed
* [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR
@ 2026-08-07 15:54 Thierry Reding
  2026-08-07 15:54 ` [PATCH v4 01/10] dt-bindings: reserved-memory: Document " Thierry Reding
                   ` (9 more replies)
  0 siblings, 10 replies; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

This series adds support for the video protection region (VPR) used on
Tegra SoC devices. It's a special region of memory that is protected
from accesses by the CPU and used to store DRM protected content (both
decrypted stream data as well as decoded video frames).

Patches 1 through 3 add DT binding documentation for the VPR and add the
VPR to the list of memory-region items for display, host1x and NVDEC.

Patch 4 adds bitmap_allocate(), which is like bitmap_allocate_region()
but works on sizes that are not a power of two.

Patch 5 introduces new APIs needed by the Tegra VPR implementation that
allow CMA areas to be dynamically created at runtime rather than using
the fixed, system-wide list. This is used in this driver specifically
because it can use an arbitrary number of these areas (though they are
currently limited to 4).

Patch 6 adds some infrastructure for DMA heap implementations to provide
information through debugfs.

The Tegra VPR implementation is added in patch 7. See its commit message
for more details about the specifics of this implementation.

Finally, patches 8-10 add the VPR placeholder node on Tegra234 and
Tegra264 and hook it up to the host1x node so that it can make use of
this region.

Changes in v4:
- Link to v3: https://patch.msgid.link/20260701-tegra-vpr-v3-0-d80f7b871bb4@nvidia.com
- fully remove from linear map while chunks are active
- address checkpatch.pl and Sashiko comments
- improve error handling
- remove freezer support

Changes in v3:
- Link to v2: https://patch.msgid.link/20260122161009.3865888-1-thierry.reding@kernel.org
- introduce set_memory_device() and set_memory_normal()
- rename VPR nodes to "protected"
- add Tegra264 placeholder nodes

Changes in v2:
- Link to v1: https://patch.msgid.link/20250902154630.4032984-1-thierry.reding@gmail.com
- Tegra VPR implementation is now more optimized to reduce the number of
  (very slow) resize operations, and allows cross-chunk allocations
- dynamic CMA areas are now trackd separately from static ones, but the
  global number of CMA pages accounts for all areas

Thierry

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Thierry Reding (10):
      dt-bindings: reserved-memory: Document Tegra VPR
      dt-bindings: display: tegra: Document memory regions
      dt-bindings: gpu: host1x: Document memory-regions for NVDEC
      bitmap: Add bitmap_allocate() function
      mm/cma: Allow dynamically creating CMA areas
      dma-buf: heaps: Add debugfs support
      dma-buf: heaps: Add support for Tegra VPR
      arm64: tegra: Add VPR placeholder node on Tegra234
      arm64: tegra: Hook up VPR to host1x
      arm64: tegra: Add VPR placeholder node on Tegra264

 .../display/tegra/nvidia,tegra124-vic.yaml         |    8 +
 .../bindings/display/tegra/nvidia,tegra186-dc.yaml |   10 +
 .../bindings/display/tegra/nvidia,tegra20-dc.yaml  |   10 +-
 .../display/tegra/nvidia,tegra20-host1x.yaml       |    7 +
 .../bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml |    8 +
 .../nvidia,tegra-video-protection-region.yaml      |   75 ++
 arch/arm/mm/dma-mapping.c                          |    2 +-
 arch/arm64/boot/dts/nvidia/tegra234.dtsi           |   45 +
 arch/arm64/boot/dts/nvidia/tegra264.dtsi           |   33 +
 arch/s390/mm/init.c                                |    2 +-
 drivers/dma-buf/dma-heap.c                         |   56 +
 drivers/dma-buf/heaps/Kconfig                      |   12 +
 drivers/dma-buf/heaps/Makefile                     |    1 +
 drivers/dma-buf/heaps/tegra-vpr.c                  | 1368 ++++++++++++++++++++
 include/linux/bitmap.h                             |   25 +-
 include/linux/cma.h                                |    8 +-
 include/linux/dma-heap.h                           |    2 +
 include/trace/events/tegra_vpr.h                   |   57 +
 kernel/dma/contiguous.c                            |    2 +-
 mm/cma.c                                           |  221 +++-
 mm/cma.h                                           |    5 +-
 mm/cma_debug.c                                     |    6 +-
 mm/cma_sysfs.c                                     |    6 +-
 mm/mm_init.c                                       |    4 +-
 24 files changed, 1912 insertions(+), 61 deletions(-)
---
base-commit: a6cebf4799ccfd3ea08e45abe5360414fd3822e1
change-id: 20260507-tegra-vpr-cd4bc2509c4c

Best regards,
--  
Thierry Reding <treding@nvidia.com>


^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH v4 01/10] dt-bindings: reserved-memory: Document Tegra VPR
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:08   ` sashiko-bot
  2026-08-07 15:54 ` [PATCH v4 02/10] dt-bindings: display: tegra: Document memory regions Thierry Reding
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

The Video Protection Region (VPR) found on NVIDIA Tegra chips is a
region of memory that is protected from CPU accesses. It is used to
decode and play back DRM protected content.

It is a standard reserved memory region that can exist in two forms:
static VPR where the base address and size are fixed (uses the "reg"
property to describe the memory) and a resizable VPR where only the
size is known upfront and the OS can allocate it wherever it can be
accomodated.

Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v4:
- move oneOf into allOf for a bit more tidiness
- fix example "reg" property

Changes in v3:
- add examples for fixed and resizable VPR
---
 .../nvidia,tegra-video-protection-region.yaml      | 75 ++++++++++++++++++++++
 1 file changed, 75 insertions(+)

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 000000000000..862bfd391378
--- /dev/null
+++ b/Documentation/devicetree/bindings/reserved-memory/nvidia,tegra-video-protection-region.yaml
@@ -0,0 +1,75 @@
+# 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
+  - oneOf:
+      - required:
+          - compatible
+          - reg
+
+      - required:
+          - compatible
+          - size
+
+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
+
+examples:
+  - |
+    /* resizable VPR */
+    protected {
+      compatible = "nvidia,tegra-video-protection-region";
+
+      size = <0x0 0x70000000>;
+      alignment = <0x0 0x100000>;
+      reusable;
+    };
+
+  - |
+    /* fixed VPR */
+    protected@a8000000 {
+      compatible = "nvidia,tegra-video-protection-region";
+
+      /* fixed VPR */
+      reg = <0xa8000000 0x70000000>;
+      no-map;
+    };

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v4 02/10] dt-bindings: display: tegra: Document memory regions
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
  2026-08-07 15:54 ` [PATCH v4 01/10] dt-bindings: reserved-memory: Document " Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:05   ` sashiko-bot
  2026-08-07 15:54 ` [PATCH v4 03/10] dt-bindings: gpu: host1x: Document memory-regions for NVDEC Thierry Reding
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

Add the memory-region and memory-region-names properties to the bindings
for the display controllers and the host1x engine found on various Tegra
generations. These memory regions are used to access firmware-provided
framebuffer memory as well as the video protection region.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v4:
- typofix

Changes in v3:
- document properties for VIC
---
 .../devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml |  8 ++++++++
 .../devicetree/bindings/display/tegra/nvidia,tegra186-dc.yaml  | 10 ++++++++++
 .../devicetree/bindings/display/tegra/nvidia,tegra20-dc.yaml   | 10 +++++++++-
 .../bindings/display/tegra/nvidia,tegra20-host1x.yaml          |  7 +++++++
 4 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml
index bdf981781bd5..fdd3fe9e2547 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra124-vic.yaml
@@ -68,6 +68,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 ce4589466a18..881bfbf4764d 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 69be95afd562..3bf06dcd6198 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 8312b7699cbe..420a1fe5ee80 100644
--- a/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
+++ b/Documentation/devicetree/bindings/display/tegra/nvidia,tegra20-host1x.yaml
@@ -98,6 +98,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

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v4 03/10] dt-bindings: gpu: host1x: Document memory-regions for NVDEC
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
  2026-08-07 15:54 ` [PATCH v4 01/10] dt-bindings: reserved-memory: Document " Thierry Reding
  2026-08-07 15:54 ` [PATCH v4 02/10] dt-bindings: display: tegra: Document memory regions Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:05   ` sashiko-bot
  2026-08-07 15:54 ` [PATCH v4 04/10] bitmap: Add bitmap_allocate() function Thierry Reding
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

The video protection region is a reserved memory region that can be used
for secure video playback. NVDEC can access this region to decode images
into securely.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 .../devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml     | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml b/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml
index 4eb325cfd296..bcaaabca945d 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:

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v4 04/10] bitmap: Add bitmap_allocate() function
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
                   ` (2 preceding siblings ...)
  2026-08-07 15:54 ` [PATCH v4 03/10] dt-bindings: gpu: host1x: Document memory-regions for NVDEC Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:11   ` sashiko-bot
  2026-08-07 15:54 ` [PATCH v4 05/10] mm/cma: Allow dynamically creating CMA areas Thierry Reding
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

This is similar to bitmap_allocate_region() but allows allocation of
non-power of two pages/bits.

While at it, reimplement bitmap_allocate_region() in terms of this new
helper to remove a sliver of code duplication.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 include/linux/bitmap.h | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/include/linux/bitmap.h b/include/linux/bitmap.h
index 7df1573a409c..c2ab02985b5f 100644
--- a/include/linux/bitmap.h
+++ b/include/linux/bitmap.h
@@ -716,10 +716,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.
  *
@@ -727,16 +727,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

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v4 05/10] mm/cma: Allow dynamically creating CMA areas
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
                   ` (3 preceding siblings ...)
  2026-08-07 15:54 ` [PATCH v4 04/10] bitmap: Add bitmap_allocate() function Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:15   ` sashiko-bot
  2026-08-07 16:16   ` David Hildenbrand (Arm)
  2026-08-07 15:54 ` [PATCH v4 06/10] dma-buf: heaps: Add debugfs support Thierry Reding
                   ` (4 subsequent siblings)
  9 siblings, 2 replies; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

There is no technical reason why there should be a limited number of CMA
regions, so extract some code into helpers and use them to create extra
functions (cma_create() and cma_free()) that allow creating and freeing,
respectively, CMA regions dynamically at runtime.

The static array of CMA areas cannot be replaced by dynamically created
areas because for many of them, allocation must not fail and some cases
may need to initialize them before the slab allocator is even available.
To account for this, keep these "early" areas in a separate list and
track the dynamic areas in a separate list.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v4:
- remove __init annotations to allow cma_create() after early boot
- make sure CMA area names are unique

Changes in v3:
- rebase on top of recent linux-next, update kernel/dma/contiguous.c
- use kzalloc_obj() instead of kzalloc() with sizeof()

Changes in v2:
- rename fixed number of CMA areas to reflect their main use
- account for pages in dynamically allocated regions
---
 arch/arm/mm/dma-mapping.c |   2 +-
 arch/s390/mm/init.c       |   2 +-
 include/linux/cma.h       |   8 +-
 kernel/dma/contiguous.c   |   2 +-
 mm/cma.c                  | 221 +++++++++++++++++++++++++++++++++++++---------
 mm/cma.h                  |   5 +-
 mm/cma_debug.c            |   6 +-
 mm/cma_sysfs.c            |   6 +-
 mm/mm_init.c              |   4 +-
 9 files changed, 201 insertions(+), 55 deletions(-)

diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
index 7761099dde9e..99bd7655e673 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/s390/mm/init.c b/arch/s390/mm/init.c
index be7e009e7b59..ccd48d611129 100644
--- a/arch/s390/mm/init.c
+++ b/arch/s390/mm/init.c
@@ -256,7 +256,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/include/linux/cma.h b/include/linux/cma.h
index 8555d38a97b1..fb7a4923c3ba 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/kernel/dma/contiguous.c b/kernel/dma/contiguous.c
index f754079a287d..7975551f69b3 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 a7929c758df1..8d8fe7c82ae5 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -35,7 +35,12 @@
 #include "cma.h"
 #include "mm_init.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)
@@ -138,10 +143,10 @@ bool cma_validate_zones(struct cma *cma)
 	return true;
 }
 
-static void __init cma_activate_area(struct cma *cma)
+static int cma_activate_area(struct cma *cma)
 {
 	unsigned long pfn, end_pfn, early_pfn[CMA_MAX_RANGES];
-	int allocrange, r;
+	int allocrange, r, err;
 	struct cma_memrange *cmr;
 	unsigned long bitmap_count, count;
 
@@ -150,12 +155,16 @@ static void __init cma_activate_area(struct cma *cma)
 		early_pfn[allocrange] = cmr->early_pfn;
 		cmr->bitmap = bitmap_zalloc(cma_bitmap_maxno(cma, cmr),
 					    GFP_KERNEL);
-		if (!cmr->bitmap)
+		if (!cmr->bitmap) {
+			err = -ENOMEM;
 			goto cleanup;
+		}
 	}
 
-	if (!cma_validate_zones(cma))
+	if (!cma_validate_zones(cma)) {
+		err = -EINVAL;
 		goto cleanup;
+	}
 
 	for (r = 0; r < cma->nranges; r++) {
 		cmr = &cma->ranges[r];
@@ -182,7 +191,7 @@ static void __init cma_activate_area(struct cma *cma)
 #endif
 	set_bit(CMA_ACTIVATED, &cma->flags);
 
-	return;
+	return 0;
 
 cleanup:
 	for (r = 0; r < allocrange; r++)
@@ -198,19 +207,24 @@ static void __init cma_activate_area(struct cma *cma)
 			end_pfn = cmr->base_pfn + cmr->count;
 			for (pfn = start_pfn; pfn < end_pfn; pfn++)
 				free_reserved_page(pfn_to_page(pfn));
+
+			/* reset these so we don't later confuse cma_free() */
+			cmr->bitmap = NULL;
+			cmr->count = 0;
 		}
 	}
-	totalcma_pages -= cma->count;
 	cma->available_count = cma->count = 0;
 	pr_err("CMA area %s could not be activated\n", cma->name);
+
+	return err;
 }
 
 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;
 }
@@ -221,41 +235,85 @@ void __init cma_reserve_pages_on_error(struct cma *cma)
 	set_bit(CMA_RESERVE_PAGES_ON_ERROR, &cma->flags);
 }
 
+static void cma_init_area(struct cma *cma, const char *name,
+			  phys_addr_t size, unsigned int order_per_bit,
+			  bool dynamic)
+{
+	unsigned int index;
+
+	if (dynamic)
+		index = MAX_EARLY_CMA_AREAS + cma_area_count;
+	else
+		index = cma_early_area_count;
+
+	if (name)
+		strscpy(cma->name, name);
+	else
+		snprintf(cma->name, CMA_MAX_NAME,  "cma%d\n", index);
+
+	cma->count = size >> PAGE_SHIFT;
+	cma->available_count = cma->count;
+	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_init_area(cma, name, size, order_per_bit, false);
 
-	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;
-	*res_cma = cma;
 	totalcma_pages += cma->count;
+	cma_early_area_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 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;
 }
 
 /**
@@ -278,22 +336,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)
@@ -446,7 +491,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;
 	}
@@ -1053,12 +1098,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;
@@ -1067,6 +1112,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;
@@ -1149,3 +1213,78 @@ void __init *cma_reserve_early(struct cma *cma, unsigned long size)
 
 	return ret;
 }
+
+struct cma *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);
+
+	mutex_lock(&cma_lock);
+
+	cma_init_area(cma, name, size, order_per_bit, true);
+	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);
+
+	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 (nbits && !bitmap_empty(cma->ranges[i].bitmap, nbits)) {
+			WARN(1, "%s: range %u not empty\n", cma->name, i);
+			return;
+		}
+	}
+
+	mutex_lock(&cma_lock);
+
+	/* 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);
+
+	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 c70180c36559..ae4db9819e38 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)
diff --git a/mm/cma_debug.c b/mm/cma_debug.c
index 523ba4a0f9f7..548b0157e7b5 100644
--- a/mm/cma_debug.c
+++ b/mm/cma_debug.c
@@ -204,9 +204,9 @@ static int __init cma_debugfs_init(void)
 
 	cma_debugfs_root = debugfs_create_dir("cma", NULL);
 
-	for (i = 0; i < cma_area_count; i++)
-		if (test_bit(CMA_ACTIVATED, &cma_areas[i].flags))
-			cma_debugfs_add_one(&cma_areas[i], cma_debugfs_root);
+	for (i = 0; i < cma_early_area_count; i++)
+		if (test_bit(CMA_ACTIVATED, &cma_early_areas[i].flags))
+			cma_debugfs_add_one(&cma_early_areas[i], cma_debugfs_root);
 
 	return 0;
 }
diff --git a/mm/cma_sysfs.c b/mm/cma_sysfs.c
index d5bf792c6245..15319c7b8227 100644
--- a/mm/cma_sysfs.c
+++ b/mm/cma_sysfs.c
@@ -116,8 +116,8 @@ static int __init cma_sysfs_init(void)
 	if (!cma_kobj_root)
 		return -ENOMEM;
 
-	for (i = 0; i < cma_area_count; i++) {
-		cma = &cma_areas[i];
+	for (i = 0; i < cma_early_area_count; i++) {
+		cma = &cma_early_areas[i];
 		if (!test_bit(CMA_ACTIVATED, &cma->flags))
 			continue;
 
@@ -140,7 +140,7 @@ static int __init cma_sysfs_init(void)
 	return 0;
 out:
 	while (--i >= 0) {
-		cma = &cma_areas[i];
+		cma = &cma_early_areas[i];
 		if (cma->cma_kobj)
 			kobject_put(&cma->cma_kobj->kobj);
 	}
diff --git a/mm/mm_init.c b/mm/mm_init.c
index e5aa20a9b898..796eafb57752 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -2156,7 +2156,7 @@ bool __init deferred_grow_zone(struct zone *zone, unsigned int order)
 #endif /* CONFIG_DEFERRED_STRUCT_PAGE_INIT */
 
 #ifdef CONFIG_CMA
-void __init init_cma_reserved_pageblock(struct page *page)
+void init_cma_reserved_pageblock(struct page *page)
 {
 	unsigned i = pageblock_nr_pages;
 	struct page *p = page;
@@ -2178,7 +2178,7 @@ void __init init_cma_reserved_pageblock(struct page *page)
 /*
  * Similar to above, but only set the migrate type and stats.
  */
-void __init init_cma_pageblock(struct page *page)
+void init_cma_pageblock(struct page *page)
 {
 	init_pageblock_migratetype(page, MIGRATE_CMA, false);
 	adjust_managed_page_count(page, pageblock_nr_pages);

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v4 06/10] dma-buf: heaps: Add debugfs support
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
                   ` (4 preceding siblings ...)
  2026-08-07 15:54 ` [PATCH v4 05/10] mm/cma: Allow dynamically creating CMA areas Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:19   ` sashiko-bot
  2026-08-07 15:54 ` [PATCH v4 07/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

Add a callback to struct dma_heap_ops that heap providers can implement
to show information about the state of the heap in debugfs. A top-level
directory named "dma_heap" is created in debugfs and individual files
will be named after the heaps.

Reviewed-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 drivers/dma-buf/dma-heap.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/dma-heap.h   |  2 ++
 2 files changed, 58 insertions(+)

diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c
index 3937dd41bb0f..389cddd07c8d 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>
@@ -225,6 +226,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
@@ -299,6 +340,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;
@@ -335,6 +383,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/include/linux/dma-heap.h b/include/linux/dma-heap.h
index 648328a64b27..1c9bed1f4dde 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);
 };
 
 /**

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v4 07/10] dma-buf: heaps: Add support for Tegra VPR
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
                   ` (5 preceding siblings ...)
  2026-08-07 15:54 ` [PATCH v4 06/10] dma-buf: heaps: Add debugfs support Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:12   ` sashiko-bot
  2026-08-07 15:54 ` [PATCH v4 08/10] arm64: tegra: Add VPR placeholder node on Tegra234 Thierry Reding
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

NVIDIA Tegra SoCs commonly define a Video-Protection-Region, which is a
region of memory dedicated to content-protected video decode and
playback. This memory cannot be accessed by the CPU and only certain
hardware devices have access to it.

Expose the VPR as a DMA heap so that applications and drivers can
allocate buffers from this region for use-cases that require this kind
of protected memory.

VPR has a few very critical peculiarities. First, it must be a single
contiguous region of memory (there is a single pair of registers that
set the base address and size of the region), which is configured by
calling back into the secure monitor. The memory region also needs to
quite large for some use-cases because it needs to fit multiple video
frames (8K video should be supported), so VPR sizes of ~2 GiB are
expected. However, some devices cannot afford to reserve this amount
of memory for a particular use-case, and therefore the VPR must be
resizable.

Unfortunately, resizing the VPR is slightly tricky because the GPU found
on Tegra SoCs must be in reset during the VPR resize operation. This is
currently implemented by freezing all userspace processes and calling
invoking the GPU's freeze() implementation, resizing and the thawing the
GPU and userspace processes. This is quite heavy-handed, so eventually
it might be better to implement thawing/freezing in the GPU driver in
such a way that they block accesses to the GPU so that the VPR resize
operation can happen without suspending all userspace.

In order to balance the memory usage versus the amount of resizing that
needs to happen, the VPR is divided into multiple chunks. Each chunk is
implemented as a CMA area that is completely allocated on first use to
guarantee the contiguity of the VPR. Once all buffers from a chunk have
been freed, the CMA area is deallocated and the memory returned to the
system.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v4:
- address Sashiko and checkpatch comments
- fully remove from linear map while chunks are allocated
- improve error handling
- remove freezer support

Changes in v3:
- use set_memory_device() and set_memory_normal() helpers
- use kzalloc_obj() instead of kzalloc() with sizeof()

Changes in v2:
- cluster allocations to reduce the number of resize operations
- support cross-chunk allocation
---
 drivers/dma-buf/heaps/Kconfig     |   12 +
 drivers/dma-buf/heaps/Makefile    |    1 +
 drivers/dma-buf/heaps/tegra-vpr.c | 1368 +++++++++++++++++++++++++++++++++++++
 include/trace/events/tegra_vpr.h  |   57 ++
 4 files changed, 1438 insertions(+)

diff --git a/drivers/dma-buf/heaps/Kconfig b/drivers/dma-buf/heaps/Kconfig
index bb729e91545c..8909330bfaa2 100644
--- a/drivers/dma-buf/heaps/Kconfig
+++ b/drivers/dma-buf/heaps/Kconfig
@@ -20,3 +20,15 @@ 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. Access to VPR memory is limited to
+	  a subset of hardware engines and specifically disallowed from the
+	  CPU. The region can be fixed, in which case no linear mapping exists
+	  for the memory, or it can be resizable on systems that want to reuse
+	  the memory for other uses when content-protected video is not played
+	  back.
diff --git a/drivers/dma-buf/heaps/Makefile b/drivers/dma-buf/heaps/Makefile
index 974467791032..265b77a7b889 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 000000000000..b56dfa5c7a0a
--- /dev/null
+++ b/drivers/dma-buf/heaps/tegra-vpr.c
@@ -0,0 +1,1368 @@
+// 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/memory.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>
+
+#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 invalid;
+	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;
+	int nid;
+
+	struct list_head buffers;
+	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;
+	struct page *start_page;
+	bool resizable;
+
+	unsigned int first;
+	unsigned int last;
+
+	struct list_head devices;
+
+	/**
+	 * @lock: Protects concurrent access to the allocation bitmap, as well
+	 * as the buffers and devices lists.
+	 */
+	struct mutex lock;
+};
+
+struct tegra_vpr_buffer {
+	struct list_head attachments;
+	struct tegra_vpr *vpr;
+	struct list_head list;
+
+	/**
+	 * @lock: Protects concurrent access to the list of attachments.
+	 */
+	struct mutex lock;
+
+	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;
+	}
+
+	list_for_each_entry(node, &vpr->devices, node) {
+		err = pm_generic_freeze(node->dev);
+		if (err < 0) {
+			pr_err("failed to freeze %s: %d\n",
+			       dev_name(node->dev), err);
+			status = 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 thaw %s: %d\n",
+			       dev_name(node->dev), err);
+			continue;
+		}
+	}
+
+	return status;
+}
+
+static int 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_alloc(struct tegra_vpr_chunk *chunk)
+{
+	unsigned long align = get_order(chunk->vpr->align);
+
+	chunk->start_page = cma_alloc(chunk->cma, chunk->num_pages, align,
+				      false);
+	if (!chunk->start_page)
+		return -ENOMEM;
+
+	chunk->virt = (unsigned long)page_to_virt(chunk->start_page);
+
+	return 0;
+}
+
+static int tegra_vpr_chunk_activate(struct tegra_vpr_chunk *chunk)
+{
+	unsigned int num_errors = 0;
+	int err, status = 0;
+	pgoff_t i;
+
+	trace_tegra_vpr_chunk_activate(chunk->start, chunk->limit);
+
+	for (i = 0; i < chunk->num_pages; i++) {
+		err = set_direct_map_invalid_noflush(chunk->start_page + i);
+		if (err)
+			goto restore;
+	}
+
+	flush_tlb_kernel_range(chunk->virt, chunk->virt + chunk->size);
+	chunk->invalid = false;
+	chunk->active = true;
+
+	return 0;
+
+restore:
+	status = err;
+
+	while (i--) {
+		err = set_direct_map_default_noflush(chunk->start_page + i);
+		if (err)
+			num_errors++;
+	}
+
+	flush_tlb_kernel_range(chunk->virt, chunk->virt + chunk->size);
+	chunk->invalid = num_errors > 0;
+
+	return status;
+}
+
+static int tegra_vpr_chunk_deactivate(struct tegra_vpr_chunk *chunk)
+{
+	unsigned int num_errors = 0;
+	int err, status = 0;
+	pgoff_t i;
+
+	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);
+
+	for (i = 0; i < chunk->num_pages; i++) {
+		err = set_direct_map_default_noflush(chunk->start_page + i);
+		if (err)
+			goto restore;
+	}
+
+	flush_tlb_kernel_range(chunk->virt, chunk->virt + chunk->size);
+	chunk->invalid = false;
+	chunk->active = false;
+
+	return 0;
+
+restore:
+	status = err;
+
+	while (i--) {
+		err = set_direct_map_invalid_noflush(chunk->start_page + i);
+		if (err)
+			num_errors++;
+	}
+
+	flush_tlb_kernel_range(chunk->virt, chunk->virt + chunk->size);
+	chunk->invalid = num_errors > 0;
+
+	return status;
+}
+
+static void tegra_vpr_chunk_release(struct tegra_vpr_chunk *chunk)
+{
+	if (!WARN_ON(chunk->active || chunk->invalid)) {
+		cma_release(chunk->cma, chunk->start_page, chunk->num_pages);
+		chunk->start_page = NULL;
+		chunk->virt = 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_alloc(chunk);
+		if (err < 0)
+			goto deactivate;
+
+		err = tegra_vpr_chunk_activate(chunk);
+		if (err < 0) {
+			tegra_vpr_chunk_release(chunk);
+			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_alloc(chunk);
+			if (err < 0)
+				goto deactivate;
+
+			err = tegra_vpr_chunk_activate(chunk);
+			if (err < 0) {
+				tegra_vpr_chunk_release(chunk);
+				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) {
+		struct tegra_vpr_chunk *chunk = &vpr->chunks[i];
+
+		ret = tegra_vpr_chunk_deactivate(chunk);
+		if (WARN_ON(ret < 0)) {
+			pr_err("failed to deactivate chunk #%u: %d\n", i, ret);
+		} else {
+			tegra_vpr_chunk_release(chunk);
+			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->resizable)
+		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;
+
+	/*
+	 * Quick sanity check that we're not trying to allocate a buffer that
+	 * has no chance of fitting into the VPR.
+	 */
+	if (size > vpr->size)
+		return ERR_PTR(-EINVAL);
+
+	/*
+	 * "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;
+
+	/* track which chunks this buffer overlaps */
+	if (vpr->resizable) {
+		unsigned int limit = buffer->pageno + buffer->num_pages;
+		pgoff_t i;
+
+		/*
+		 * Memory is backed by struct page, so track which ones we
+		 * use.
+		 */
+		buffer->pages = kvmalloc_array(buffer->num_pages,
+					       sizeof(*buffer->pages),
+					       GFP_KERNEL);
+		if (!buffer->pages) {
+			err = -ENOMEM;
+			goto free;
+		}
+
+		for (i = 0; i < buffer->num_pages; i++)
+			buffer->pages[i] = &vpr->start_page[pageno + 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) {
+			kfree(buffer->pages);
+			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;
+	}
+
+	return buffer;
+
+free:
+	kfree(buffer);
+release:
+	bitmap_clear(vpr->bitmap, pageno, num_pages);
+	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_clear(vpr->bitmap, buffer->pageno, buffer->num_pages);
+	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;
+
+	/*
+	 * For resizable VPR, the memory is backed by struct page, so we can
+	 * use the convenient helper to create the SG table.
+	 */
+	if (buffer->pages) {
+		err = sg_alloc_table_from_pages(&attach->sgt, buffer->pages,
+						buffer->num_pages, 0,
+						buffer->size, GFP_KERNEL);
+		if (err < 0)
+			goto free;
+	} else {
+		if (sg_alloc_table(&attach->sgt, 1, GFP_KERNEL)) {
+			err = -ENOMEM;
+			goto free;
+		}
+
+		sg_set_page(attach->sgt.sgl, NULL, buffer->size, 0);
+		sg_dma_address(attach->sgt.sgl) = buffer->start;
+		sg_dma_len(attach->sgt.sgl) = buffer->size;
+	}
+
+	attach->dev = attachment->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;
+
+	if (!vpr->resizable)
+		return;
+
+	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: %d\n", i, err);
+			goto activate;
+		} 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: %d\n", index,
+			       err);
+			goto activate;
+		} else {
+			clear_bit(index, vpr->active);
+			set_bit(index, 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;
+		}
+	}
+
+	/* release the CMA memory associated with deactivated chunks */
+	for_each_set_bit(i, dirty, vpr->num_chunks)
+		tegra_vpr_chunk_release(&vpr->chunks[i]);
+
+	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);
+
+		/*
+		 * This may not be fully activated at this point, but we need
+		 * to keep track of it anyway to make sure the CMA region can
+		 * eventually be released. The WARN_ON above tells us when it
+		 * happens: here be dragons.
+		 */
+		set_bit(i, vpr->active);
+	}
+}
+
+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);
+	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);
+		tegra_vpr_recycle(vpr);
+	}
+
+	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];
+
+	mutex_lock(&vpr->lock);
+
+	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);
+	}
+
+	mutex_unlock(&vpr->lock);
+}
+
+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->resizable)
+		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;
+
+	/* Memory is backed by struct page, so track the first one. */
+	vpr->start_page = phys_to_page(vpr->base);
+
+	/* This seems a reasonable value, so hard-code it 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 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);
+	mutex_init(&vpr->lock);
+
+	vpr->resizable = !of_property_read_bool(np, "no-map");
+	vpr->dev_node = of_node_get(np);
+	vpr->align = PAGE_SIZE;
+	vpr->base = rmem->base;
+	vpr->size = rmem->size;
+	vpr->num_pages = vpr->size >> PAGE_SHIFT;
+	vpr->nid = of_node_to_nid(np);
+
+	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 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;
+	}
+
+	of_node_put(parent);
+	return 0;
+}
+module_init(tegra_vpr_init);
+
+static int tegra_vpr_node_init(unsigned long offset, struct reserved_mem *rmem)
+{
+	if (!IS_ALIGNED(rmem->base, SZ_1M)) {
+		pr_err("%s: base is not aligned to 1 MiB\n", rmem->name);
+		return -EINVAL;
+	}
+
+	if (!IS_ALIGNED(rmem->size, SZ_1M)) {
+		pr_err("%s: size is not aligned to 1 MiB\n", rmem->name);
+		return -EINVAL;
+	}
+
+	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);
+	const struct dev_pm_ops *pm = dev->driver->pm;
+	struct tegra_vpr_device *node;
+
+	if (!rmem->priv)
+		return -EPROBE_DEFER;
+
+	if (!pm || !pm->freeze || !pm->thaw)
+		return -EINVAL;
+
+	node = kzalloc_obj(*node, GFP_KERNEL);
+	if (!node)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&node->node);
+	node->dev = dev;
+
+	mutex_lock(&vpr->lock);
+	list_add_tail(&node->node, &vpr->devices);
+	mutex_unlock(&vpr->lock);
+
+	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;
+
+	mutex_lock(&vpr->lock);
+
+	list_for_each_entry_safe(node, tmp, &vpr->devices, node) {
+		if (node->dev == dev) {
+			list_del(&node->node);
+			kfree(node);
+		}
+	}
+
+	mutex_unlock(&vpr->lock);
+}
+
+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/trace/events/tegra_vpr.h b/include/trace/events/tegra_vpr.h
new file mode 100644
index 000000000000..f8ceb17679fe
--- /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>

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v4 08/10] arm64: tegra: Add VPR placeholder node on Tegra234
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
                   ` (6 preceding siblings ...)
  2026-08-07 15:54 ` [PATCH v4 07/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:06   ` sashiko-bot
  2026-08-07 15:54 ` [PATCH v4 09/10] arm64: tegra: Hook up VPR to host1x Thierry Reding
  2026-08-07 15:54 ` [PATCH v4 10/10] arm64: tegra: Add VPR placeholder node on Tegra264 Thierry Reding
  9 siblings, 1 reply; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

This node contains two sets of properties, one for the case where the
VPR is resizable (in which case the VPR region will be dynamically
allocated at boot time) and another case where the VPR is fixed in size
and initialized by early firmware.

The firmware running on the device is responsible for updating the node
with the real physical address for the fixed VPR case and remove the
properties needed only for resizable VPR. Similarly, if the VPR is
resizable, the firmware should remove the "reg" property since it is no
longer needed.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v3:
- comment out fixed VPR properties, assume resizable by default
- rename node to "protected"
---
 arch/arm64/boot/dts/nvidia/tegra234.dtsi | 39 ++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
index 5e29316a4d75..6c4739efc8a5 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";
 

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v4 09/10] arm64: tegra: Hook up VPR to host1x
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
                   ` (7 preceding siblings ...)
  2026-08-07 15:54 ` [PATCH v4 08/10] arm64: tegra: Add VPR placeholder node on Tegra234 Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:16   ` sashiko-bot
  2026-08-07 15:54 ` [PATCH v4 10/10] arm64: tegra: Add VPR placeholder node on Tegra264 Thierry Reding
  9 siblings, 1 reply; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

The host1x needs access to the VPR region, so make sure to reference it
via the memory-region property.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 arch/arm64/boot/dts/nvidia/tegra234.dtsi | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
index 6c4739efc8a5..c6a5ced069e7 100644
--- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
@@ -4479,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 {
@@ -4497,6 +4500,9 @@ nvdec@15480000 {
 				iommus = <&smmu_niso1 TEGRA234_SID_NVDEC>;
 				dma-coherent;
 
+				memory-region = <&vpr>;
+				memory-region-names = "protected";
+
 				nvidia,memory-controller = <&mc>;
 
 				/*

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [PATCH v4 10/10] arm64: tegra: Add VPR placeholder node on Tegra264
  2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
                   ` (8 preceding siblings ...)
  2026-08-07 15:54 ` [PATCH v4 09/10] arm64: tegra: Hook up VPR to host1x Thierry Reding
@ 2026-08-07 15:54 ` Thierry Reding
  2026-08-07 16:09   ` sashiko-bot
  9 siblings, 1 reply; 22+ messages in thread
From: Thierry Reding @ 2026-08-07 15:54 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Thierry Reding,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Marek Szyprowski, Robin Murphy,
	Sumit Semwal, Benjamin Gaignard, Brian Starkey, John Stultz,
	T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

From: Thierry Reding <treding@nvidia.com>

This node contains two sets of properties, one for the case where the
VPR is resizable (in which case the VPR region will be dynamically
allocated at boot time) and another case where the VPR is fixed in size
and initialized by early firmware.

The firmware running on the device is responsible for updating the node
with the real physical address for the fixed VPR case and remove the
properties needed only for resizable VPR. Similarly, if the VPR is
resizable, the firmware should remove the "reg" property since it is no
longer needed.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
Changes in v3:
- comment out fixed VPR properties, assume resizable by default
- rename node to "protected"
---
 arch/arm64/boot/dts/nvidia/tegra264.dtsi | 33 ++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
index 31bd29df8e46..b3a8ea64aa37 100644
--- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
@@ -24,6 +24,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 */

-- 
2.54.0


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 02/10] dt-bindings: display: tegra: Document memory regions
  2026-08-07 15:54 ` [PATCH v4 02/10] dt-bindings: display: tegra: Document memory regions Thierry Reding
@ 2026-08-07 16:05   ` sashiko-bot
  0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:05 UTC (permalink / raw)
  To: Thierry Reding
  Cc: devicetree, linux-s390, Vasily Gorbik, Heiko Carstens, dri-devel,
	Alexander Gordeev, media-ci, robh, conor+dt

> From: Thierry Reding <treding@nvidia.com>
> 
> Add the memory-region and memory-region-names properties to the bindings
> for the display controllers and the host1x engine found on various Tegra
> generations. These memory regions are used to access firmware-provided
> framebuffer memory as well as the video protection region.
> 
> Signed-off-by: Thierry Reding <treding@nvidia.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=2


^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 03/10] dt-bindings: gpu: host1x: Document memory-regions for NVDEC
  2026-08-07 15:54 ` [PATCH v4 03/10] dt-bindings: gpu: host1x: Document memory-regions for NVDEC Thierry Reding
@ 2026-08-07 16:05   ` sashiko-bot
  0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:05 UTC (permalink / raw)
  To: Thierry Reding
  Cc: linux-s390, conor+dt, devicetree, Vasily Gorbik, dri-devel,
	media-ci, Heiko Carstens, robh, Alexander Gordeev

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `memory-region` and `memory-region-names` properties were added to `nvidia,tegra234-nvdec.yaml` to support the Video Protection Region (VPR) for secure video decoding, but they were omitted from the related `nvidia,tegra210-nvdec.yaml` binding which covers earlier generations of the same hardware family.
--

--- Patch [3]: [PATCH v4 03/10] dt-bindings: gpu: host1x: Document memory-regions for NVDEC ---
commit 97ab5bd4094807d50abd7cf4f4dbbc00d0abb716
Author: Thierry Reding <treding@nvidia.com>

dt-bindings: gpu: host1x: Document memory-regions for NVDEC

This commit adds memory-region and memory-region-names properties to the
Tegra234 NVDEC binding. This allows device trees to define a reserved video
protection region for secure video playback.

> diff --git a/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml b/Documentation/devicetree/bindings/gpu/host1x/nvidia,tegra234-nvdec.yaml
> index 4eb325cfd2961..bcaaabca945dc 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
> +

[Severity: Medium]
Since earlier generations of the hardware share this family, should these
properties also be added to nvidia,tegra210-nvdec.yaml?

Because nvidia,tegra210-nvdec.yaml specifies additionalProperties: false, if
older devices (like those using nvidia,tegra186-nvdec) configure the NVDEC
block to use the Video Protection Region by adding the memory-region property,
they will fail device tree schema validation.

>    nvidia,memory-controller:
>      $ref: /schemas/types.yaml#/definitions/phandle
>      description:

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=3

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 08/10] arm64: tegra: Add VPR placeholder node on Tegra234
  2026-08-07 15:54 ` [PATCH v4 08/10] arm64: tegra: Add VPR placeholder node on Tegra234 Thierry Reding
@ 2026-08-07 16:06   ` sashiko-bot
  0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:06 UTC (permalink / raw)
  To: Thierry Reding
  Cc: robh, devicetree, conor+dt, Alexander Gordeev, linux-s390,
	Heiko Carstens, Vasily Gorbik, dri-devel, media-ci

> From: Thierry Reding <treding@nvidia.com>
> 
> This node contains two sets of properties, one for the case where the
> VPR is resizable (in which case the VPR region will be dynamically
> allocated at boot time) and another case where the VPR is fixed in size
> and initialized by early firmware.
> 
> The firmware running on the device is responsible for updating the node
> with the real physical address for the fixed VPR case and remove the
> properties needed only for resizable VPR. Similarly, if the VPR is
> resizable, the firmware should remove the "reg" property since it is no
> longer needed.
> 
> Signed-off-by: Thierry Reding <treding@nvidia.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=8


^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 01/10] dt-bindings: reserved-memory: Document Tegra VPR
  2026-08-07 15:54 ` [PATCH v4 01/10] dt-bindings: reserved-memory: Document " Thierry Reding
@ 2026-08-07 16:08   ` sashiko-bot
  0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:08 UTC (permalink / raw)
  To: Thierry Reding
  Cc: conor+dt, robh, Alexander Gordeev, media-ci, dri-devel,
	Vasily Gorbik, devicetree, Heiko Carstens, linux-s390

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The dt-schema examples for the VPR omit the parent `reserved-memory` node context, causing 64-bit multi-cell values to be incorrectly parsed against 1-cell defaults.
--

--- Patch [1]: [PATCH v4 01/10] dt-bindings: reserved-memory: Document Tegra VPR ---
commit 8113c8e6ae5fc4a5f07530aed37855cc00e7b6cc
Author: Thierry Reding <treding@nvidia.com>

dt-bindings: reserved-memory: Document Tegra VPR

This commit adds device tree binding documentation for the Video
Protection Region (VPR) on NVIDIA Tegra chips. It supports both fixed
and resizable memory regions for DRM protected content playback.

> 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 0000000000000..862bfd3913786
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/reserved-memory/nvidia,tegra-video-protection-region.yaml

[ ... ]

> +examples:
> +  - |
> +    /* resizable VPR */
> +    protected {
> +      compatible = "nvidia,tegra-video-protection-region";
> +
> +      size = <0x0 0x70000000>;
> +      alignment = <0x0 0x100000>;

[Severity: Low]
Will this example fail semantic validation during make dt_binding_check?

Because #size-cells and #address-cells default to <1> at the root level,
omitting the parent reserved-memory node wrapper causes these 64-bit
multi-cell values to be parsed against 1-cell defaults. The size and
alignment properties here provide 2 cells and will be evaluated as arrays
of two 32-bit values rather than single 64-bit values.

> +      reusable;
> +    };

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=1

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 10/10] arm64: tegra: Add VPR placeholder node on Tegra264
  2026-08-07 15:54 ` [PATCH v4 10/10] arm64: tegra: Add VPR placeholder node on Tegra264 Thierry Reding
@ 2026-08-07 16:09   ` sashiko-bot
  0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:09 UTC (permalink / raw)
  To: Thierry Reding
  Cc: conor+dt, linux-s390, robh, Alexander Gordeev, Heiko Carstens,
	devicetree, Vasily Gorbik, media-ci, dri-devel

> From: Thierry Reding <treding@nvidia.com>
> 
> This node contains two sets of properties, one for the case where the
> VPR is resizable (in which case the VPR region will be dynamically
> allocated at boot time) and another case where the VPR is fixed in size
> and initialized by early firmware.
> 
> The firmware running on the device is responsible for updating the node
> with the real physical address for the fixed VPR case and remove the
> properties needed only for resizable VPR. Similarly, if the VPR is
> resizable, the firmware should remove the "reg" property since it is no
> longer needed.
> 
> Signed-off-by: Thierry Reding <treding@nvidia.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=10


^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 04/10] bitmap: Add bitmap_allocate() function
  2026-08-07 15:54 ` [PATCH v4 04/10] bitmap: Add bitmap_allocate() function Thierry Reding
@ 2026-08-07 16:11   ` sashiko-bot
  0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:11 UTC (permalink / raw)
  To: Thierry Reding
  Cc: linux-s390, devicetree, Heiko Carstens, media-ci, Vasily Gorbik,
	dri-devel, conor+dt, robh, Alexander Gordeev

> From: Thierry Reding <treding@nvidia.com>
> 
> This is similar to bitmap_allocate_region() but allows allocation of
> non-power of two pages/bits.
> 
> While at it, reimplement bitmap_allocate_region() in terms of this new
> helper to remove a sliver of code duplication.
> 
> Signed-off-by: Thierry Reding <treding@nvidia.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=4


^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 07/10] dma-buf: heaps: Add support for Tegra VPR
  2026-08-07 15:54 ` [PATCH v4 07/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
@ 2026-08-07 16:12   ` sashiko-bot
  0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:12 UTC (permalink / raw)
  To: Thierry Reding
  Cc: devicetree, Heiko Carstens, media-ci, linux-s390,
	Alexander Gordeev, Vasily Gorbik, robh, conor+dt, dri-devel

Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
- [Critical] `tegra_vpr_map_dma_buf()` unconditionally calls `dma_map_sgtable()` on an SG table initialized with a NULL page for static VPRs.
- [High] Ad-hoc invocation of system PM callbacks (`pm_generic_freeze` / `pm_generic_thaw`) without `device_lock()` and freezing userspace risks severe data races and hardware state corruption.
- [High] Memory allocated with `kvmalloc_array()` is incorrectly freed using `kfree()`, which will cause a kernel panic if the allocation fell back to vmalloc.
- [High] `tegra_vpr_setup_chunks()` can calculate a chunk size of 0 for the final chunk, causing initialization to fail.
- [High] Calling `dma_heap_get_drvdata(heap)` before checking if `rmem->priv` is NULL causes a NULL pointer dereference.
- [High] If `tegra_vpr_chunk_activate` fails during the `tegra_vpr_recycle` error recovery path, it causes a CMA memory leak and invalid chunk state.
- [High] Potential NULL pointer dereference in `tegra_vpr_device_init()` if the device has no bound driver.
- [Medium] Unprotected iteration over the `vpr->buffers` list in `tegra_vpr_debugfs_show_chunks()` causes data races.
- [Medium] An off-by-one error in `tegra_vpr_find_free_region()` prevents allocations from using the last page of the VPR.
- [Medium] The device node reference acquired via `of_node_get()` is leaked on the error paths of `tegra_vpr_add_heap()`.
- [Low] The `dirty` bitmap is declared using a runtime variable, creating a Variable Length Array (VLA).
--

--- Patch [7]: [PATCH v4 07/10] dma-buf: heaps: Add support for Tegra VPR ---
commit c6dee57f094afce846e231b53f10aa5c8c4898a7
Author: Thierry Reding <treding@nvidia.com>

dma-buf: heaps: Add support for Tegra VPR

NVIDIA Tegra SoCs commonly define a Video-Protection-Region, which is a
region of memory dedicated to content-protected video decode and
playback. Expose the VPR as a DMA heap so that applications and drivers
can allocate buffers from this region.

> diff --git a/drivers/dma-buf/heaps/tegra-vpr.c b/drivers/dma-buf/heaps/tegra-vpr.c
> new file mode 100644
> index 0000000000000..b56dfa5c7a0a0
> --- /dev/null
> +++ b/drivers/dma-buf/heaps/tegra-vpr.c
[ ... ]
> @@ -171,6 +171,11 @@ static int tegra_vpr_resize(struct tegra_vpr *vpr)
>  	}
>  
>  	list_for_each_entry(node, &vpr->devices, node) {
>  		err = pm_generic_freeze(node->dev);

[Severity: High]
Does this code introduce a data race or risk hardware corruption by invoking
pm_generic_freeze() directly from an ioctl context? These callbacks are
normally executed during system-wide suspend and expect device_lock() to be
held and userspace to be frozen.

>  		if (err < 0) {
>  			pr_err("failed to freeze %s: %d\n",
>  			       dev_name(node->dev), err);
[ ... ]
> @@ -368,6 +368,7 @@ static int tegra_vpr_activate_chunks(struct tegra_vpr *vpr,
>  				     struct tegra_vpr_buffer *buffer)
>  {
>  	DECLARE_BITMAP(dirty, vpr->num_chunks);

[Severity: Low]
Does this introduce a variable length array (VLA)? vpr->num_chunks is not a
compile-time constant, so this expands to a dynamic allocation on the stack,
which might trigger build errors with -Wvla.

>  	unsigned int i, bottom, top;
>  	int err = 0, ret;
[ ... ]
> @@ -503,8 +503,8 @@ 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);

[Severity: Medium]
Does this code leak the last page of the VPR due to an off-by-one boundary
check? tegra_vpr_find_free_region_in_range() loops while
pos + num_pages <= end_page, meaning passing vpr->num_pages - 1 as end_page
prevents allocations from using the final page.

>  }
[ ... ]
> @@ -668,9 +668,9 @@ tegra_vpr_buffer_allocate(struct tegra_vpr *vpr, size_t size)
>  		/*
>  		 * Memory is backed by struct page, so track which ones we
>  		 * use.
>  		 */
>  		buffer->pages = kvmalloc_array(buffer->num_pages,
>  					       sizeof(*buffer->pages),
>  					       GFP_KERNEL);

[Severity: High]
Does this memory leak or cause a panic if the allocation falls back to
vmalloc? buffer->pages is later freed using kfree(), which will fail for
vmalloc addresses.

>  		if (!buffer->pages) {
>  			err = -ENOMEM;
[ ... ]
> @@ -746,8 +746,8 @@ static void tegra_vpr_buffer_release(struct tegra_vpr_buffer *buffer)
>  
>  	bitmap_clear(vpr->bitmap, buffer->pageno, buffer->num_pages);
>  	list_del(&buffer->list);
>  	kfree(buffer->pages);

[Severity: High]
Is it possible for this code to panic by calling kfree() on a vmalloc
pointer? buffer->pages was allocated using kvmalloc_array().

>  	kfree(buffer);
[ ... ]
> @@ -776,9 +776,9 @@ static int tegra_vpr_attach(struct dma_buf *buf,
>  		if (sg_alloc_table(&attach->sgt, 1, GFP_KERNEL)) {
>  			err = -ENOMEM;
>  			goto free;
>  		}
>  
>  		sg_set_page(attach->sgt.sgl, NULL, buffer->size, 0);

[Severity: Critical]
Can this code cause a panic when an importer maps the buffer? For static
VPRs, the SG table is initialized with a NULL page here, but later passed to
dma_map_sgtable() which attempts to dereference it.

>  		sg_dma_address(attach->sgt.sgl) = buffer->start;
>  		sg_dma_len(attach->sgt.sgl) = buffer->size;
>  	}
[ ... ]
> @@ -806,8 +806,8 @@ tegra_vpr_map_dma_buf(struct dma_buf_attachment *attachment,
>  	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);

[Severity: Critical]
Does this code trigger a panic for static VPR buffers? The sgt can be
populated with a NULL page by tegra_vpr_attach(), causing dma_map_sgtable()
to dereference it.

>  	if (err < 0)
>  		return ERR_PTR(err);
[ ... ]
> @@ -826,6 +826,7 @@ static void tegra_vpr_unmap_dma_buf(struct dma_buf_attachment *attachment,
>  static void tegra_vpr_recycle(struct tegra_vpr *vpr)
>  {
>  	DECLARE_BITMAP(dirty, vpr->num_chunks);

[Severity: Low]
Does this introduce a variable length array (VLA)? vpr->num_chunks is not a
compile-time constant, so this expands to a dynamic stack allocation which
might trigger build errors with -Wvla.

>  	unsigned int i;
>  	int err;
[ ... ]
> @@ -890,7 +890,7 @@ static void tegra_vpr_recycle(struct tegra_vpr *vpr)
>  		/*
>  		 * This may not be fully activated at this point, but we need
>  		 * to keep track of it anyway to make sure the CMA region can
>  		 * eventually be released. The WARN_ON above tells us when it
>  		 * happens: here be dragons.
>  		 */
>  		set_bit(i, vpr->active);

[Severity: High]
Does this code create an inconsistent chunk state and leak CMA memory? If
tegra_vpr_chunk_activate() fails on the error path above, the chunk's active
flag remains false, but this blindly sets it as active in the VPR bitmap.
Subsequent allocations may see the chunk as inactive, allocating new memory
and leaking the old CMA allocation.

>  	}
>  }
[ ... ]
> @@ -1023,8 +1023,8 @@ static void tegra_vpr_debugfs_show_chunks(struct tegra_vpr *vpr,
>  			   chunk->active ? "active" : "inactive",
>  			   chunk->num_buffers);
>  	}
>  
>  	list_for_each_entry(buffer, &vpr->buffers, list) {

[Severity: Medium]
Does this unprotected traversal of vpr->buffers cause data races? This loop
iterates over the list without holding mutex_lock(&vpr->lock), which can race
against concurrent buffer allocation or release operations.

>  		string_get_size(buffer->size, 1, STRING_UNITS_2, buf,
>  				sizeof(buf));
[ ... ]
> @@ -1079,9 +1079,9 @@ static int tegra_vpr_setup_chunks(struct tegra_vpr *vpr, const char *name)
>  	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);

[Severity: High]
Can this code fail during initialization if the calculated size is 0? If
vpr->size is not a power of 2, earlier chunks could consume all available
space. For the final chunk, size would evaluate to 0, which gets passed to
tegra_vpr_chunk_init() and cma_create(), failing the allocation.

>  		if (err < 0) {
>  			pr_err("failed to create VPR chunk: %d\n", err);
[ ... ]
> @@ -1134,6 +1134,7 @@ static int tegra_vpr_add_heap(struct reserved_mem *rmem,
>  	INIT_LIST_HEAD(&vpr->devices);
>  	mutex_init(&vpr->lock);
>  
>  	vpr->resizable = !of_property_read_bool(np, "no-map");
>  	vpr->dev_node = of_node_get(np);

[Severity: Medium]
Does this code leak the device node reference on error paths? If subsequent
operations like bitmap_zalloc() or dma_heap_add() fail, the function jumps to
the free label but does not call of_node_put(vpr->dev_node).

>  	vpr->align = PAGE_SIZE;
>  	vpr->base = rmem->base;
[ ... ]
> @@ -1215,9 +1215,9 @@ static int tegra_vpr_node_init(unsigned long offset, struct reserved_mem *rmem)
>  
>  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);
>  	const struct dev_pm_ops *pm = dev->driver->pm;

[Severity: High]
Can these lines cause a kernel panic due to NULL pointer dereferences?
dma_heap_get_drvdata() dereferences the heap pointer before checking if
rmem->priv (the heap) is NULL. Furthermore, dev->driver is dereferenced
without checking if a driver is actually bound to the device.

>  	struct tegra_vpr_device *node;
>  
>  	if (!rmem->priv)
>  		return -EPROBE_DEFER;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=7

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 05/10] mm/cma: Allow dynamically creating CMA areas
  2026-08-07 15:54 ` [PATCH v4 05/10] mm/cma: Allow dynamically creating CMA areas Thierry Reding
@ 2026-08-07 16:15   ` sashiko-bot
  2026-08-07 16:16   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:15 UTC (permalink / raw)
  To: Thierry Reding
  Cc: Vasily Gorbik, dri-devel, media-ci, devicetree, Alexander Gordeev,
	linux-s390, robh, conor+dt, Heiko Carstens

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] cma_free() double-frees pages into the buddy allocator, causing memory corruption.
- [High] cma_create() ignores the return value of cma_activate_area(), publishing broken and partially-initialized CMA areas.
- [High] cma_create() relies on memblock_is_region_reserved() at runtime, which is discarded after early boot.
- [High] cma_for_each_area() only iterates over dynamic areas, breaking APIs relying on scanning all CMA areas.
- [High] cma_free() checks if the CMA area is empty without holding the allocation mutex, leading to a TOCTOU race with concurrent allocations.
- [Medium] totalcma_pages is permanently inflated if an early CMA area fails to activate.
- [Medium] cma_create() fails to initialize cma->nid, defaulting it to 0 and potentially breaking zone validation on NUMA systems.
- [Low] Dynamically created CMA areas are missing from sysfs and debugfs interfaces.
--

--- Patch [5]: [PATCH v4 05/10] mm/cma: Allow dynamically creating CMA areas ---
commit ef1094abdd1bc24a286e7eaebd2724d471aebbfd
Author: Thierry Reding <treding@nvidia.com>

mm/cma: Allow dynamically creating CMA areas

This patch introduces cma_create() and cma_free() to allow dynamically 
creating and freeing CMA regions at runtime. It separates static early areas 
from dynamically allocated ones into different tracking structures.

> diff --git a/mm/cma.c b/mm/cma.c
> index a7929c758df15..8d8fe7c82ae5b 100644
> --- a/mm/cma.c
> +++ b/mm/cma.c
[ ... ]
> @@ -198,19 +207,24 @@ static void __init cma_activate_area(struct cma *cma)
>  			end_pfn = cmr->base_pfn + cmr->count;
>  			for (pfn = start_pfn; pfn < end_pfn; pfn++)
>  				free_reserved_page(pfn_to_page(pfn));
> +
> +			/* reset these so we don't later confuse cma_free() */
> +			cmr->bitmap = NULL;
> +			cmr->count = 0;
>  		}
>  	}
> -	totalcma_pages -= cma->count;
>  	cma->available_count = cma->count = 0;
>  	pr_err("CMA area %s could not be activated\n", cma->name);
> +
> +	return err;
>  }

[Severity: Medium]
Does this removal leave totalcma_pages permanently inflated if an early boot
area fails to activate? Early areas still have their page count added during
cma_new_area() prior to activation.

[ ... ]
> @@ -221,41 +235,85 @@ void __init cma_reserve_pages_on_error(struct cma *cma)
[ ... ]
> +static int cma_check_memory(phys_addr_t base, phys_addr_t size)
> +{
> +	if (!size || !memblock_is_region_reserved(base, size))
> +		return -EINVAL;

[Severity: High]
Is it safe to call memblock_is_region_reserved() from cma_create() at runtime?
Since memblock_is_region_reserved() and memblock data structures are marked
__init_memblock, won't they be discarded after early boot on architectures
without CONFIG_ARCH_KEEP_MEMBLOCK, leading to a use-after-free?

[ ... ]
> @@ -1053,12 +1098,12 @@ bool cma_release_frozen(struct cma *cma, const struct page *pages,
[ ... ]
> +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) {

[Severity: High]
Since cma_areas only contains dynamically created regions, will callers of
cma_for_each_area() now silently skip all standard early-boot CMA areas?

[ ... ]
> @@ -1149,3 +1213,78 @@ void __init *cma_reserve_early(struct cma *cma, unsigned long size)
[ ... ]
> +struct cma *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);

[Severity: Medium]
Should cma->nid be initialized to NUMA_NO_NODE here? Since kzalloc_obj()
leaves it as 0, could this cause pfn_range_intersects_zones() to validate
against Node 0 instead of the correct node, potentially bypassing NUMA zone
overlap checks?

> +
> +	mutex_lock(&cma_lock);
> +
> +	cma_init_area(cma, name, size, order_per_bit, true);
> +	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);
> +
> +	list_add_tail(&cma->node, &cma_areas);

[Severity: High]
What happens if cma_activate_area() fails (e.g., due to an -ENOMEM during
bitmap allocation)? The error code is discarded, and the partially-initialized
cma object (with uninitialized mutexes and locks) is added to the global
cma_areas list. Could this cause crashes when the caller or other subsystems
try to use or free it?

> +	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 (nbits && !bitmap_empty(cma->ranges[i].bitmap, nbits)) {
> +			WARN(1, "%s: range %u not empty\n", cma->name, i);
> +			return;
> +		}
> +	}

[Severity: High]
Can a concurrent thread successfully allocate from this CMA area right after
the bitmap_empty() check completes? The check is performed outside of
cma->alloc_mutex, so a race could lead to destroying the mutex and freeing
the underlying memory while it is actively being allocated.

> +
> +	mutex_lock(&cma_lock);
> +
> +	/* 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));

[Severity: Critical]
Does this loop double-free pages into the buddy allocator? When
cma_activate_area() is initially called, it invokes
init_cma_reserved_pageblock() which clears the PageReserved flag and inserts
the memory into the buddy allocator. If cma_free() later iterates over these
pages and calls free_reserved_page(), won't that blindly call __free_pages()
on pages already managed by the buddy allocator, corrupting the free lists?

[ ... ]
> diff --git a/mm/cma_debug.c b/mm/cma_debug.c
> index 523ba4a0f9f76..548b0157e7b5a 100644
> --- a/mm/cma_debug.c
> +++ b/mm/cma_debug.c
> @@ -204,9 +204,9 @@ static int __init cma_debugfs_init(void)
>  
>  	cma_debugfs_root = debugfs_create_dir("cma", NULL);
>  
> -	for (i = 0; i < cma_area_count; i++)
> -		if (test_bit(CMA_ACTIVATED, &cma_areas[i].flags))
> -			cma_debugfs_add_one(&cma_areas[i], cma_debugfs_root);
> +	for (i = 0; i < cma_early_area_count; i++)
> +		if (test_bit(CMA_ACTIVATED, &cma_early_areas[i].flags))
> +			cma_debugfs_add_one(&cma_early_areas[i], cma_debugfs_root);

[Severity: Low]
Since sysfs and debugfs initialization now strictly iterate over
cma_early_areas during boot, are dynamically created CMA areas intentionally
excluded from these interfaces?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=5

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 05/10] mm/cma: Allow dynamically creating CMA areas
  2026-08-07 15:54 ` [PATCH v4 05/10] mm/cma: Allow dynamically creating CMA areas Thierry Reding
  2026-08-07 16:15   ` sashiko-bot
@ 2026-08-07 16:16   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 22+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-07 16:16 UTC (permalink / raw)
  To: Thierry Reding, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Hunter, David Airlie, Simona Vetter, Maarten Lankhorst,
	Maxime Ripard, Thomas Zimmermann, Sowjanya Komatineni,
	Luca Ceresoli, Mikko Perttunen, Yury Norov, Rasmus Villemoes,
	Russell King, Alexander Gordeev, Gerald Schaefer, Heiko Carstens,
	Vasily Gorbik, Christian Borntraeger, Sven Schnelle,
	Andrew Morton, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Marek Szyprowski,
	Robin Murphy, Sumit Semwal, Benjamin Gaignard, Brian Starkey,
	John Stultz, T.J. Mercier, Christian König, Steven Rostedt,
	Masami Hiramatsu, Mathieu Desnoyers, Catalin Marinas, Will Deacon,
	Chun Ng
  Cc: Thierry Reding, devicetree, linux-tegra, linux-kernel, dri-devel,
	linux-media, linux-arm-kernel, linux-s390, linux-mm, iommu,
	linaro-mm-sig, linux-trace-kernel, Thierry Reding

On 8/7/26 17:54, Thierry Reding wrote:
> From: Thierry Reding <treding@nvidia.com>
> 
> There is no technical reason why there should be a limited number of CMA
> regions, so extract some code into helpers and use them to create extra
> functions (cma_create() and cma_free()) that allow creating and freeing,
> respectively, CMA regions dynamically at runtime.
> 
> The static array of CMA areas cannot be replaced by dynamically created
> areas because for many of them, allocation must not fail and some cases
> may need to initialize them before the slab allocator is even available.
> To account for this, keep these "early" areas in a separate list and
> track the dynamic areas in a separate list.
> 
> Signed-off-by: Thierry Reding <treding@nvidia.com>
> ---

Sorry that I didn't get to reply earlier to

	https://lore.kernel.org/all/anS0nxAwCXGWZRl9@orome/

But I am still not convinced why we shouldn't just have a single array that
grows dynamically.

"
After replying to T.J. I think this static array is actually a nice
feature in a way. It allows us to more easily make a distinction between
the regular CMA regions created from reserved-memory regions that
represent classic carveouts, and the more versatile CMA areas that could
be used for any purpose.

Telling the two types apart could obviously also be solved using some
kind of flag stored with the CMA area, but at this point I don't really
see a reason why the two lists would need to be unified.
"

Really, cma_for_each_early_area() + cma_for_each_area() is just nasty.

I'd strongly vote to unify unless there is a very good reason to do it
separately.

-- 
Cheers,

David

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 09/10] arm64: tegra: Hook up VPR to host1x
  2026-08-07 15:54 ` [PATCH v4 09/10] arm64: tegra: Hook up VPR to host1x Thierry Reding
@ 2026-08-07 16:16   ` sashiko-bot
  0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:16 UTC (permalink / raw)
  To: Thierry Reding
  Cc: media-ci, Alexander Gordeev, conor+dt, Vasily Gorbik, dri-devel,
	devicetree, Heiko Carstens, robh, linux-s390

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Unconditional NULL pointer dereference in tegra_vpr_device_init on deferral and driverless paths.
- [High] VPR heap initialization fails for sizes in the lower half of an order due to 0-sized chunks.
- [Medium] Off-by-one error prevents allocating the final page of the VPR or a single max-sized buffer.
--

--- Patch [9]: [PATCH v4 09/10] arm64: tegra: Hook up VPR to host1x ---
commit 04120e646b04a85555bacc81f097911f705ea117
Author: Thierry Reding <treding@nvidia.com>

arm64: tegra: Hook up VPR to host1x

The host1x needs access to the VPR region, so make sure to reference it
via the memory-region property.

> diff --git a/arch/arm64/boot/dts/nvidia/tegra234.dtsi b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
> index 6c4739efc8a5d..c6a5ced069e79 100644
> --- a/arch/arm64/boot/dts/nvidia/tegra234.dtsi
> +++ b/arch/arm64/boot/dts/nvidia/tegra234.dtsi
> @@ -4479,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 {
> @@ -4497,6 +4500,9 @@ nvdec@15480000 {
>  				iommus = <&smmu_niso1 TEGRA234_SID_NVDEC>;
>  				dma-coherent;
>  
> +				memory-region = <&vpr>;
> +				memory-region-names = "protected";
> +
>  				nvidia,memory-controller = <&mc>;
>  
>  				/*

With the VPR region now hooked up, we noticed a few issues in the tegra-vpr
heap driver that might be triggered by this change.

[Severity: High]
Does this code risk a NULL pointer dereference on the deferral or driverless
paths?

drivers/dma-buf/heaps/tegra-vpr.c:tegra_vpr_device_init() {
	struct dma_heap *heap = rmem->priv;
	struct tegra_vpr *vpr = dma_heap_get_drvdata(heap);
	const struct dev_pm_ops *pm = dev->driver->pm;

	if (!rmem->priv)
		return -EPROBE_DEFER;
}

The heap pointer (rmem->priv) and dev->driver appear to be unconditionally
dereferenced before the check for !rmem->priv happens. Could this cause a
kernel panic if the VPR heap driver is not yet initialized or if dev->driver
is NULL?


[Severity: High]
Can the VPR heap initialization fail for sizes in the lower half of an order
due to 0-sized chunks?

drivers/dma-buf/heaps/tegra-vpr.c:tegra_vpr_setup_chunks() {
		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);
}

When max_size is artificially large due to power-of-2 rounding, size could
reach 0 in the final loop iterations. Would this lead to an invalid CMA
region creation when cma_create() is called with a size of 0?


[Severity: Medium]
Does this off-by-one error prevent allocating the final page of the VPR or a
single max-sized buffer?

drivers/dma-buf/heaps/tegra-vpr.c:tegra_vpr_find_free_region() {
	return tegra_vpr_find_free_region_in_range(vpr, 0, vpr->num_pages - 1,
						   num_pages, align);
}

The end_page parameter is given the inclusive upper bound (vpr->num_pages - 1).

drivers/dma-buf/heaps/tegra-vpr.c:tegra_vpr_find_free_region_in_range() {
	for (pos = next; pos + num_pages <= end_page; pos = next) {
}

However, the loop condition checks pos + num_pages <= end_page, meaning
end_page acts as an exclusive bound. Does this cause allocations that span
the final page to fail with -ENOSPC?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=9

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH v4 06/10] dma-buf: heaps: Add debugfs support
  2026-08-07 15:54 ` [PATCH v4 06/10] dma-buf: heaps: Add debugfs support Thierry Reding
@ 2026-08-07 16:19   ` sashiko-bot
  0 siblings, 0 replies; 22+ messages in thread
From: sashiko-bot @ 2026-08-07 16:19 UTC (permalink / raw)
  To: Thierry Reding
  Cc: dri-devel, devicetree, conor+dt, Alexander Gordeev, linux-s390,
	Vasily Gorbik, media-ci, robh, Heiko Carstens

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [Medium] If debugfs_create_dir() fails, dma_heap_debugfs_dir remains NULL, causing subsequent debugfs files to be erroneously created in the debugfs root directory.
- [Low] The newly added dma_heap_exit function performs partial cleanup by removing debugfs entries but leaks the character device region and class.

Pre-existing issues:
- [Critical] Missing provider module pinning and absence of a heap unregister function causes a Use-After-Free if a dynamically loaded heap provider fails module initialization.
--

--- Patch [6]: [PATCH v4 06/10] dma-buf: heaps: Add debugfs support ---
commit 1c3871b543fe35314ecde7429a59aac59e23b5b3
Author: Thierry Reding <treding@nvidia.com>

dma-buf: heaps: Add debugfs support
    
Add a callback to struct dma_heap_ops that heap providers can implement
to show information about the state of the heap in debugfs. A top-level
directory named "dma_heap" is created in debugfs and individual files
will be named after the heaps.

> diff --git a/drivers/dma-buf/dma-heap.c b/drivers/dma-buf/dma-heap.c
> index 3937dd41bb0f6..389cddd07c8d3 100644
> --- a/drivers/dma-buf/dma-heap.c
> +++ b/drivers/dma-buf/dma-heap.c

[ ... ]

> @@ -225,6 +226,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;
> +}

[Severity: Medium]
If debugfs_create_dir() fails and returns an error pointer, this check
returns early and leaves dma_heap_debugfs_dir as NULL.

Later, when dma_heap_add() attempts to create files, it will pass NULL
as the parent directory. Does this cause the debugfs files to be erroneously 
created in the debugfs root namespace?

If dma_heap_debugfs_dir was allowed to hold the error pointer instead,
debugfs_create_file() would safely abort child creation.

[ ... ]

> @@ -299,6 +340,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
> +

[Severity: Critical]
This is a pre-existing issue, but registering debugfs files and character 
devices without a corresponding unregister function seems to expose a 
use-after-free vulnerability. 

If a dynamically loaded heap provider module successfully registers its first 
heap via dma_heap_add() during module_init, but fails to register a second 
heap, the module loader will abort the load and free the module's memory.

Since there is no dma_heap_remove() function to undo the first registration, 
the first heap remains in heap_list and exposed via its debugfs file.

If a user later accesses this debugfs file (or the chardev), will the kernel
dereference the freed heap->ops->show pointer in dma_heap_debug_show()?

[ ... ]

> @@ -335,6 +383,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);

[Severity: Low]
The new dma_heap_exit() function only cleans up the debugfs entries. Does 
this leak the character device region and the device class allocated earlier 
in dma_heap_init()?

Even though the subsystem is currently built-in only, omitting the cleanup
might become a hazard if it is ever converted to a loadable module.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-tegra-vpr-v4-0-5510d16af89e@nvidia.com?part=6

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2026-08-07 16:19 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07 15:54 [PATCH v4 00/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
2026-08-07 15:54 ` [PATCH v4 01/10] dt-bindings: reserved-memory: Document " Thierry Reding
2026-08-07 16:08   ` sashiko-bot
2026-08-07 15:54 ` [PATCH v4 02/10] dt-bindings: display: tegra: Document memory regions Thierry Reding
2026-08-07 16:05   ` sashiko-bot
2026-08-07 15:54 ` [PATCH v4 03/10] dt-bindings: gpu: host1x: Document memory-regions for NVDEC Thierry Reding
2026-08-07 16:05   ` sashiko-bot
2026-08-07 15:54 ` [PATCH v4 04/10] bitmap: Add bitmap_allocate() function Thierry Reding
2026-08-07 16:11   ` sashiko-bot
2026-08-07 15:54 ` [PATCH v4 05/10] mm/cma: Allow dynamically creating CMA areas Thierry Reding
2026-08-07 16:15   ` sashiko-bot
2026-08-07 16:16   ` David Hildenbrand (Arm)
2026-08-07 15:54 ` [PATCH v4 06/10] dma-buf: heaps: Add debugfs support Thierry Reding
2026-08-07 16:19   ` sashiko-bot
2026-08-07 15:54 ` [PATCH v4 07/10] dma-buf: heaps: Add support for Tegra VPR Thierry Reding
2026-08-07 16:12   ` sashiko-bot
2026-08-07 15:54 ` [PATCH v4 08/10] arm64: tegra: Add VPR placeholder node on Tegra234 Thierry Reding
2026-08-07 16:06   ` sashiko-bot
2026-08-07 15:54 ` [PATCH v4 09/10] arm64: tegra: Hook up VPR to host1x Thierry Reding
2026-08-07 16:16   ` sashiko-bot
2026-08-07 15:54 ` [PATCH v4 10/10] arm64: tegra: Add VPR placeholder node on Tegra264 Thierry Reding
2026-08-07 16:09   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox