diff --git a/Documentation/runtime_configuration.rst b/Documentation/runtime_configuration.rst
index a71b715a17245fc5fea405165323a607827a9ecb..79c6e4026adcdbb974f7398419b37f246717b9a9 100644
--- a/Documentation/runtime_configuration.rst
+++ b/Documentation/runtime_configuration.rst
@@ -33,6 +33,9 @@ file structure:
 ::
 
   configuration:
+    dma_buf_allocator:
+      provider_priority:
+        - ... # dma-buf provider name: cma, system, or udmabuf
     ipa:
       force_isolation: # true/false
       config_paths:
@@ -62,6 +65,11 @@ Configuration file example
    ---
    version: 1
    configuration:
+     dma_buf_allocator:
+       provider_priority:
+         - udmabuf
+         - cma
+         - system
      ipa:
        config_paths:
          - /home/user/.libcamera/share/ipa
@@ -150,6 +158,25 @@ LIBCAMERA_SOFTISP_MODE, software_isp.mode
 
    Example value: ``gpu``
 
+dma_buf_allocator.provider_priority
+   Define an ordered list of dma-buf providers to try when libcamera
+   allocates buffers internally (for example for the software ISP). The
+   first requested provider that is available on the system is used. Valid
+   provider names are ``cma`` (CMA dma-heap), ``system`` (system dma-heap)
+   and ``udmabuf`` (memfd + /dev/udmabuf). Providers accepted by a component
+   but not listed here are tried after the listed ones, in libcamera's
+   built-in order.
+
+   Example value:
+
+   ::
+
+      dma_buf_allocator:
+        provider_priority:
+          - udmabuf
+          - cma
+          - system
+
 pipelines.simple.supported_devices.driver, pipelines.simple.supported_devices.software_isp
    Override whether software ISP is enabled for the given driver.
 
diff --git a/include/libcamera/internal/dma_buf_allocator.h b/include/libcamera/internal/dma_buf_allocator.h
index 13600915a066d61c6f0dac2bfc7ec21d7fef33ef..07fd903170c92dc83b1b05d77aa261f6ac614c01 100644
--- a/include/libcamera/internal/dma_buf_allocator.h
+++ b/include/libcamera/internal/dma_buf_allocator.h
@@ -18,6 +18,7 @@
 
 namespace libcamera {
 
+class CameraManager;
 class FrameBuffer;
 
 class DmaBufAllocator
@@ -31,7 +32,8 @@ public:
 
 	using DmaBufAllocatorFlags = Flags<DmaBufAllocatorFlag>;
 
-	DmaBufAllocator(DmaBufAllocatorFlags flags = DmaBufAllocatorFlag::CmaHeap);
+	DmaBufAllocator(const CameraManager &cm,
+			DmaBufAllocatorFlags type = DmaBufAllocatorFlag::CmaHeap);
 	~DmaBufAllocator();
 	bool isValid() const { return providerHandle_.isValid(); }
 	UniqueFD alloc(const char *name, std::size_t size);
diff --git a/src/libcamera/dma_buf_allocator.cpp b/src/libcamera/dma_buf_allocator.cpp
index c673d23ec761e62b746f8984dfbdd0b01a858a38..9d96f06014eaeed38f4dda19fc4a1a4586ea488b 100644
--- a/src/libcamera/dma_buf_allocator.cpp
+++ b/src/libcamera/dma_buf_allocator.cpp
@@ -8,13 +8,16 @@
 
 #include "libcamera/internal/dma_buf_allocator.h"
 
+#include <algorithm>
 #include <array>
 #include <fcntl.h>
+#include <optional>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <vector>
 
 #include <linux/dma-buf.h>
 #include <linux/dma-heap.h>
@@ -26,6 +29,8 @@
 
 #include <libcamera/framebuffer.h>
 
+#include "libcamera/internal/camera_manager.h"
+
 /**
  * \file dma_buf_allocator.cpp
  * \brief dma-buf allocator
@@ -51,6 +56,26 @@ static constexpr std::array<DmaBufAllocatorInfo, 4> providerInfos = { {
 	{ DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf, "/dev/udmabuf" },
 } };
 
+/* Built-in provider priority, used when no other order is specified. */
+static constexpr std::array<DmaBufAllocator::DmaBufAllocatorFlag, 3>
+	defaultProviderPriority = { {
+		DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap,
+		DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap,
+		DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf,
+	} };
+
+static std::optional<DmaBufAllocator::DmaBufAllocatorFlag>
+providerFromName(const std::string &name)
+{
+	if (name == "cma")
+		return DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap;
+	if (name == "system")
+		return DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap;
+	if (name == "udmabuf")
+		return DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf;
+	return {};
+}
+
 LOG_DEFINE_CATEGORY(DmaBufAllocator)
 
 /**
@@ -83,6 +108,7 @@ LOG_DEFINE_CATEGORY(DmaBufAllocator)
 
 /**
  * \brief Construct a DmaBufAllocator of a given type
+ * \param[in] cm The camera manager, used to access the global configuration
  * \param[in] type The type(s) of the dma-buf providers to allocate from
  *
  * The dma-buf provider type is selected with the \a type parameter, which
@@ -90,29 +116,67 @@ LOG_DEFINE_CATEGORY(DmaBufAllocator)
  * the constructed DmaBufAllocator instance is invalid as indicated by
  * the isValid() function.
  *
- * Multiple types can be selected by combining type flags, in which case
- * the constructed DmaBufAllocator will match one of the types. If multiple
- * requested types can work on the system, which provider is used is undefined.
+ * Multiple types can be selected by combining type flags. In that case the
+ * provider to use is chosen by priority: the providers listed in the
+ * `dma_buf_allocator.provider_priority` global configuration option (valid
+ * names are "cma", "system" and "udmabuf") take precedence, followed by
+ * libcamera's built-in order (CMA heap, system heap, then udmabuf). The first
+ * provider that is both requested and accessible is used.
  */
-DmaBufAllocator::DmaBufAllocator(DmaBufAllocatorFlags type)
+DmaBufAllocator::DmaBufAllocator(const CameraManager &cm, DmaBufAllocatorFlags type)
 {
-	for (const auto &info : providerInfos) {
-		if (!(type & info.type))
-			continue;
+	std::vector<DmaBufAllocatorFlag> priority;
+
+	/*
+	 * The global configuration can override the provider priority;
+	 * providers not listed are appended in the built-in order, so no
+	 * acceptable provider is ever dropped.
+	 */
+	const GlobalConfiguration &configuration = cm._d()->configuration();
+	const std::vector<std::string> providerPriority =
+		configuration.listOption({ "dma_buf_allocator", "provider_priority" })
+			.value_or(std::vector<std::string>{});
+
+	for (const std::string &name : providerPriority) {
+		auto flag = providerFromName(name);
+		if (flag)
+			priority.push_back(*flag);
+		else
+			LOG(DmaBufAllocator, Warning)
+				<< "Ignoring unknown dma-buf provider \""
+				<< name << "\"";
+	}
 
-		int ret = ::open(info.deviceNodeName, O_RDONLY | O_CLOEXEC, 0);
-		if (ret < 0) {
-			ret = errno;
-			LOG(DmaBufAllocator, Debug)
-				<< "Failed to open " << info.deviceNodeName << ": "
-				<< strerror(ret);
+	for (DmaBufAllocatorFlag flag : defaultProviderPriority) {
+		if (std::find(priority.begin(), priority.end(), flag) == priority.end())
+			priority.push_back(flag);
+	}
+
+	for (DmaBufAllocatorFlag flag : priority) {
+		if (!(type & flag))
 			continue;
+
+		for (const auto &info : providerInfos) {
+			if (info.type != flag)
+				continue;
+
+			int ret = ::open(info.deviceNodeName, O_RDONLY | O_CLOEXEC, 0);
+			if (ret < 0) {
+				ret = errno;
+				LOG(DmaBufAllocator, Debug)
+					<< "Failed to open " << info.deviceNodeName
+					<< ": " << strerror(ret);
+				continue;
+			}
+
+			LOG(DmaBufAllocator, Debug) << "Using " << info.deviceNodeName;
+			providerHandle_ = UniqueFD(ret);
+			type_ = info.type;
+			break;
 		}
 
-		LOG(DmaBufAllocator, Debug) << "Using " << info.deviceNodeName;
-		providerHandle_ = UniqueFD(ret);
-		type_ = info.type;
-		break;
+		if (providerHandle_.isValid())
+			break;
 	}
 
 	if (!providerHandle_.isValid())
diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
index 200e3b58724a9f307922aa9574cd92b5da637378..4ea0f89f700838c4f225fb1c1fac6d9e157fcfb5 100644
--- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp
+++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
@@ -39,7 +39,7 @@ class Vc4CameraData final : public RPi::CameraData
 {
 public:
 	Vc4CameraData(PipelineHandler *pipe)
-		: RPi::CameraData(pipe)
+		: RPi::CameraData(pipe), dmaHeap_(*pipe->cameraManager())
 	{
 	}
 
diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
index e25dd85a7391b61bcea50e71e6264500d1e8a5b3..b8677e69a74d7fd8106022cc0c91ea8595917637 100644
--- a/src/libcamera/pipeline/virtual/virtual.cpp
+++ b/src/libcamera/pipeline/virtual/virtual.cpp
@@ -238,9 +238,10 @@ bool PipelineHandlerVirtual::created_ = false;
 
 PipelineHandlerVirtual::PipelineHandlerVirtual(CameraManager *manager)
 	: PipelineHandler(manager),
-	  dmaBufAllocator_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap |
-			   DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |
-			   DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
+	  dmaBufAllocator_(*manager,
+			   DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap |
+				   DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |
+				   DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
 {
 }
 
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index f8efd24424a3981fd3eca03d2fcf27723480d08c..e146a0911d9db4a61e7fa54dd643d4e308458757 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -24,6 +24,7 @@
 #include <libcamera/stream.h>
 
 #include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/camera_manager.h"
 #include "libcamera/internal/framebuffer.h"
 #include "libcamera/internal/software_isp/debayer_params.h"
 
@@ -81,9 +82,10 @@ LOG_DEFINE_CATEGORY(SoftwareIsp)
 SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor,
 			 ControlInfoMap *ipaControls)
 	: ispWorkerThread_("SWIspWorker"),
-	  dmaHeap_(DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap |
-		   DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |
-		   DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
+	  dmaHeap_(*pipe->cameraManager(),
+		   DmaBufAllocator::DmaBufAllocatorFlag::CmaHeap |
+			   DmaBufAllocator::DmaBufAllocatorFlag::SystemHeap |
+			   DmaBufAllocator::DmaBufAllocatorFlag::UDmaBuf)
 {
 	if (!dmaHeap_.isValid()) {
 		LOG(SoftwareIsp, Error) << "Failed to create DmaBufAllocator object";
