rpi-hevc-dec: `memory-region` property is ignored; the decoder's internal buffers always come from the default CMA pool

Offen
#7,743 4 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Anfängerfreundlichkeit
55/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
c, linux
Bereich
embedded-iot

Rechercherichtung

Start with drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c, hevc_d.h, and hevc_d_h265.c, then read the proposed diff and compare it with the existing s5p_mfc_alloc_memdev() pattern. Check how internal buffers are allocated and freed, keeping the vb2 frame and bitstream buffers on the platform device. Done means the optional memory-region supplies internal buffers with per-buffer fallback and matching frees, the Coeff log reports the requested size, and the described decode and allocation checks pass.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

Describe the bug

rpi-hevc-dec (the BCM2711 stateless HEVC decoder) ignores a memory-region property on its device-tree node: hevc_d_probe() never calls of_reserved_mem_device_init*(). The decoder's ~45 MiB of private working buffers (PU / coeff / cmd FIFO / colmv, allocated at STREAMON) therefore always come from the default CMA pool. On BCM2711 that pool must stay below 1 GiB (the HVS scans out from the first 1 GiB), and userspace may already have filled it by then.

Effect: a V4L2 stateless client (GStreamer's v4l2slh265dec) pre-sizes its capture pool from the stream's declared level before the decoder starts. For footage whose level undersells its real size, e.g. 3840x2880 10-bit HEVC declaring level 5.2 (action-camera footage), that pool alone reaches ~440 MiB of the default 512 MiB CMA pool, and STREAMON fails with -ENOMEM.

A second, small bug in the same file: the failure message Failed to alloc %#zx Coeff%d buffer prints pu_size (a quarter of the size actually requested) instead of coeff_size.

Both are still present on the current rpi-6.18.y head (31a348853888): the driver has no memory-region / of_reserved_mem handling and the message still prints pu_size.

Steps to reproduce the behaviour
  1. Raspberry Pi 4 (BCM2711), Raspberry Pi OS, rpi-hevc-dec as shipped, default CMA pool (512 MiB).
  2. Decode a 3840x2880 10-bit HEVC clip (level 5.2) through the stateless decoder, for example
    gst-launch-1.0 filesrc location=clip.mp4 ! qtdemux ! h265parse ! v4l2slh265dec ! fakesink
    -> VIDIOC_STREAMON(SINK) failed: Cannot allocate memory (dmesg under "Logs").
  3. For the memory-region part: reserve a pool and point the decoder node at it with this overlay
    (dtc -@ -I dts -O dtb), enable the cma:cma_alloc_* tracepoints and decode again. The region is
    reserved and present in the device tree, but every allocation of the decoder still comes from linux,cma.
/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2711";

    fragment@0 {
        target-path = "/reserved-memory";
        __overlay__ {
            hevc_cma: hevc_cma@0 {
                compatible = "shared-dma-pool";
                reusable;
                size = <0x8000000>;
            };
        };
    };

    fragment@1 {
        target-path = "/scb/codec@7eb10000";
        __overlay__ {
            memory-region = <&hevc_cma>;
        };
    };
};
Device (s)

Raspberry Pi 4 Mod. B

System
/etc/rpi-issue:   Raspberry Pi reference 2026-09-15 (pi-gen 2c235fa703cacb65e0fe0b2ab2fcb23d44dfd268, stage2)
vcgencmd version: Sep  7 2026 10:23:43 / version 465206a286cc1a7b13c2e86bc199f74dde7a8309 (clean) (release) (start)
uname:            Linux 6.18.50+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.18.50-1+rpt1 (2026-09-11) aarch64
board:            Raspberry Pi 4 Model B Rev 1.5

The stock rpi-hevc-dec of this kernel matches rpi-6.18.y commit f2bc178ed43d. The measurements below were taken with the stock module and with the patched one on the same kernel.

Logs

Stock module, default CMA pool, 3840x2880 10-bit HEVC (level 5.2):

cma: __cma_alloc: linux,cma: alloc failed, req-size: 3072 pages, ret: -16
rpi-hevc-dec feb00000.codec: Failed to alloc 0x300000 Coeff0 buffer
rpi-hevc-dec feb00000.codec: hevc_d_start_streaming: qtype=10: FAIL
ERROR: from element .../v4l2slh265dec0: Could not enable the decoder driver.
VIDIOC_STREAMON(SINK) failed: Cannot allocate memory

Wrong size in the message: with the CMA pool hogged until STREAMON fails, a failed 4096-page (0x1000000) request is reported by the stock module as Failed to alloc 0x400000 Coeff0 buffer (it prints pu_size); with the patch it prints Failed to alloc 0x1000000 Coeff2 buffer.

Where the decoder's allocations come from (128 MiB reserved region attached to the decoder node, cma:cma_alloc_* tracepoints):

module internal buffers (16-, 1024- and 4096-page allocations) frame buffers
stock linux,cma (the region is reserved and present in the DT, but not used) linux,cma
stock + patch below the reserved region (hevc_cma@0) linux,cma
Additional context

Proposed change: attach the memory-region (if present) to a child "memdev" device, the same pattern as s5p_mfc_alloc_memdev(), and use it in preference to the default pool only for the decoder's own internal buffers. vb2 frame/bitstream buffers stay on the platform device's default CMA, since those may need to be reachable by the display. Each internal buffer records the device it came from, so a too-small or missing region falls back per buffer to the default pool and frees go to the matching pool. It also fixes the Coeff%d log size. With no memory-region present nothing changes.

Measured with the patch: decode speed is the same (48.9 vs 48.8 fps on that clip, GStreamer v4l2slh265dec), output is bit-exact against software decode (avdec_h265) over repeated runs, and module load/unload is clean.

A config-only workaround exists (raising the default CMA pool), so this is not urgent; the dedicated pool is the cleaner fix.

The patch below applies cleanly to rpi-6.18.y at 31a348853888; it does not apply to rpi-6.19.y without rebasing (the context changed), which I have not looked into. It is offered as-is, not as a pull request and without a sign-off: feel free to take it, adapt it or write a different fix.

Patch (diff against rpi-6.18.y)
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c
index 392fd40..023e3cc 100644
--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c
@@ -14,6 +14,10 @@
 #include <linux/dma-mapping.h>
 #include <linux/platform_device.h>
 #include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/slab.h>
 
 #include <media/v4l2-device.h>
 #include <media/v4l2-ioctl.h>
@@ -198,6 +202,81 @@ static const struct media_device_ops hevc_d_m2m_media_ops = {
 	.req_queue	= hevc_d_media_req_queue,
 };
 
+/*
+ * Child device used to allocate the decoder's internal working buffers from a
+ * dedicated reserved-memory region, if the DT node has a "memory-region".
+ *
+ * The region is deliberately not attached to the platform device itself:
+ * that would also move all vb2 (frame) buffers into it, and frame buffers
+ * may have to be scanned out by a display controller with tighter DMA
+ * addressing limits than the decoder (e.g. the BCM2711 HVS can only reach
+ * the first 1GB, whereas the decoder can address the whole of RAM).
+ *
+ * Same pattern as s5p_mfc_alloc_memdev().
+ */
+struct hevc_d_memdev {
+	struct device dev;
+	u64 dma_mask;
+};
+
+static void hevc_d_memdev_release(struct device *dev)
+{
+	struct hevc_d_memdev *md = container_of(dev, struct hevc_d_memdev, dev);
+
+	of_reserved_mem_device_release(dev);
+	kfree(md);
+}
+
+static struct device *hevc_d_memdev_create(struct device *parent)
+{
+	struct hevc_d_memdev *md;
+	struct device *child;
+	int ret;
+
+	if (!of_property_present(parent->of_node, "memory-region"))
+		return NULL;
+
+	md = kzalloc(sizeof(*md), GFP_KERNEL);
+	if (!md)
+		return ERR_PTR(-ENOMEM);
+
+	child = &md->dev;
+	device_initialize(child);
+	child->parent = parent;
+	child->release = hevc_d_memdev_release;
+	md->dma_mask = dma_get_mask(parent);
+	child->dma_mask = &md->dma_mask;
+	child->coherent_dma_mask = parent->coherent_dma_mask;
+
+	ret = dev_set_name(child, "%s:mem", dev_name(parent));
+	if (ret)
+		goto err_put;
+
+	/* Inherit the decoder node's DMA configuration (ranges, coherency) */
+	ret = of_dma_configure(child, parent->of_node, true);
+	if (ret)
+		goto err_put;
+	ret = dma_set_mask_and_coherent(child, dma_get_mask(parent));
+	if (ret)
+		goto err_put;
+
+	ret = device_add(child);
+	if (ret)
+		goto err_put;
+
+	ret = of_reserved_mem_device_init_by_idx(child, parent->of_node, 0);
+	if (ret) {
+		device_del(child);
+		goto err_put;
+	}
+
+	return child;
+
+err_put:
+	put_device(child);
+	return ERR_PTR(ret);
+}
+
 static int hevc_d_probe(struct platform_device *pdev)
 {
 	struct hevc_d_dev *dev;
@@ -240,13 +319,21 @@ static int hevc_d_probe(struct platform_device *pdev)
 		goto err_v4l2;
 	}
 
+	dev->mem_dev = hevc_d_memdev_create(dev->dev);
+	if (IS_ERR(dev->mem_dev)) {
+		dev_warn(dev->dev,
+			 "Failed to set up memory-region (%pe); using default CMA\n",
+			 dev->mem_dev);
+		dev->mem_dev = NULL;
+	}
+
 	dev->m2m_dev = v4l2_m2m_init(&hevc_d_m2m_ops);
 	if (IS_ERR(dev->m2m_dev)) {
 		v4l2_err(&dev->v4l2_dev,
 			 "Failed to initialize V4L2 M2M device\n");
 		ret = PTR_ERR(dev->m2m_dev);
 
-		goto err_v4l2;
+		goto err_memdev;
 	}
 
 	dev->mdev.dev = &pdev->dev;
@@ -292,6 +379,9 @@ static int hevc_d_probe(struct platform_device *pdev)
 err_m2m:
 	media_device_cleanup(&dev->mdev);
 	v4l2_m2m_release(dev->m2m_dev);
+err_memdev:
+	if (dev->mem_dev)
+		device_unregister(dev->mem_dev);
 err_v4l2:
 	mutex_destroy(&dev->dev_mutex);
 	v4l2_device_unregister(&dev->v4l2_dev);
@@ -314,6 +404,9 @@ static void hevc_d_remove(struct platform_device *pdev)
 	mutex_destroy(&dev->dev_mutex);
 	v4l2_device_unregister(&dev->v4l2_dev);
 
+	if (dev->mem_dev)
+		device_unregister(dev->mem_dev);
+
 	hevc_d_hw_remove(dev);
 }
 
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h
index 6d40d91..e624a83 100644
--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h
@@ -88,6 +88,8 @@ struct hevc_d_hwbuf {
 	u8 *ptr;
 	dma_addr_t addr;
 	unsigned long attrs;
+	/* Device the buffer was allocated against (and must be freed via) */
+	struct device *alloc_dev;
 };
 
 struct hevc_d_dev;
@@ -178,6 +180,15 @@ struct hevc_d_dev {
 	struct media_device	mdev;
 	struct platform_device	*pdev;
 	struct device		*dev;
+	/*
+	 * Optional child device bound to a dedicated reserved-memory region
+	 * (DT "memory-region"). If present, the decoder's internal working
+	 * buffers (PU, coeff, cmd FIFOs, colmv) are allocated from it in
+	 * preference to the default CMA pool. vb2 (frame/bitstream) buffers
+	 * always use dev, as they may need to be accessible to other devices
+	 * (e.g. the display) with tighter addressing limits.
+	 */
+	struct device		*mem_dev;
 	struct v4l2_m2m_dev	*m2m_dev;
 
 	/* Video device file (vfd) mutex */
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
index 8be35a9..5325c49 100644
--- a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
@@ -38,27 +38,59 @@ enum hevc_slice_type {
 
 enum hevc_layer { L0 = 0, L1 = 1 };
 
+/*
+ * Allocate hwbuf->size bytes for decoder-internal use.
+ *
+ * These buffers are only ever accessed by the decoder itself, so if a
+ * dedicated memory-region has been provided (dev->mem_dev) try that first,
+ * quietly, and only fall back to the default CMA pool (shared with vb2 frame
+ * buffers, the display, etc.) if that fails.
+ * Records the device used so the buffer can be freed against it.
+ */
+static void *hwbuf_dma_alloc(struct hevc_d_dev *const dev,
+			     struct hevc_d_hwbuf *const hwbuf)
+{
+	if (dev->mem_dev) {
+		hwbuf->alloc_dev = dev->mem_dev;
+		hwbuf->ptr = dma_alloc_attrs(dev->mem_dev, hwbuf->size,
+					     &hwbuf->addr,
+					     GFP_KERNEL | __GFP_NOWARN,
+					     hwbuf->attrs);
+		if (hwbuf->ptr)
+			return hwbuf->ptr;
+		hevc_d_dbg(1, &dev->v4l2_dev,
+			   "%s: %#zx not available from memory-region, using default pool\n",
+			   __func__, hwbuf->size);
+	}
+
+	hwbuf->alloc_dev = dev->dev;
+	hwbuf->ptr = dma_alloc_attrs(dev->dev, hwbuf->size, &hwbuf->addr,
+				     GFP_KERNEL, hwbuf->attrs);
+	if (!hwbuf->ptr)
+		hwbuf->alloc_dev = NULL;
+	return hwbuf->ptr;
+}
+
 static int hwbuf_alloc(struct hevc_d_dev *const dev, struct hevc_d_hwbuf *hwbuf,
 		       size_t size, unsigned long attrs)
 {
 	hwbuf->size = size;
 	hwbuf->attrs = attrs;
 	hwbuf->addr = 0;
-	hwbuf->ptr = dma_alloc_attrs(dev->dev, hwbuf->size, &hwbuf->addr,
-				     GFP_KERNEL, hwbuf->attrs);
-	return !hwbuf->ptr ? -ENOMEM : 0;
+	return !hwbuf_dma_alloc(dev, hwbuf) ? -ENOMEM : 0;
 }
 
 static void hwbuf_free(struct hevc_d_dev *const dev,
 		      struct hevc_d_hwbuf *const hwbuf)
 {
 	if (hwbuf->ptr)
-		dma_free_attrs(dev->dev, hwbuf->size, hwbuf->ptr, hwbuf->addr,
-			       hwbuf->attrs);
+		dma_free_attrs(hwbuf->alloc_dev, hwbuf->size, hwbuf->ptr,
+			       hwbuf->addr, hwbuf->attrs);
 	hwbuf->size = 0;
 	hwbuf->ptr = NULL;
 	hwbuf->addr = 0;
 	hwbuf->attrs = 0;
+	hwbuf->alloc_dev = NULL;
 }
 
 /* Realloc but do not copy
@@ -77,20 +109,16 @@ static int hwbuf_realloc_new(struct hevc_d_dev * const dev,
 		return 0;
 
 	if (hwbuf->ptr)
-		dma_free_attrs(dev->dev, hwbuf->size, hwbuf->ptr,
+		dma_free_attrs(hwbuf->alloc_dev, hwbuf->size, hwbuf->ptr,
 			       hwbuf->addr, hwbuf->attrs);
 
 	hwbuf->addr = 0;
 	hwbuf->size = size;
-	hwbuf->ptr = dma_alloc_attrs(dev->dev, hwbuf->size,
-				     &hwbuf->addr, GFP_KERNEL, hwbuf->attrs);
 
-	if (!hwbuf->ptr) {
+	if (!hwbuf_dma_alloc(dev, hwbuf)) {
 		hwbuf->addr = 0;
 		hwbuf->size = old_size;
-		hwbuf->ptr = dma_alloc_attrs(dev->dev, hwbuf->size,
-					     &hwbuf->addr, GFP_KERNEL, hwbuf->attrs);
-		if (!hwbuf->ptr) {
+		if (!hwbuf_dma_alloc(dev, hwbuf)) {
 			hwbuf->size = 0;
 			hwbuf->addr = 0;
 			hwbuf->attrs = 0;
@@ -2366,7 +2394,7 @@ int hevc_d_h265_start(struct hevc_d_ctx *ctx)
 				DMA_ATTR_NO_KERNEL_MAPPING)) {
 			v4l2_err(&dev->v4l2_dev,
 				 "Failed to alloc %#zx Coeff%d buffer\n",
-				 pu_size, i);
+				 coeff_size, i);
 			goto fail;
 		}
 	}
Vorherrschende Sprache
C
Sterne
13.3k
Forks
5.5k
Ø Merge
1 T. 4 Std.
Gemergte PRs (30 T.)
21

Entwicklungsumgebung

Dieses Projekt bietet weder Dev-Container noch Dockerfile noch Beitragsleitfaden – die Einrichtung liegt bei Ihnen. Beginnen Sie mit der README; die allgemeinen Schritte stehen in unserem Leitfaden für den ersten Beitrag.

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus raspberrypi/linux

Alle Issues in raspberrypi/linux

Ähnliche Issues

Weitere Issues zu C

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.