diff --git a/include/libcamera/internal/utils.h b/include/libcamera/internal/utils.h
index 45cd6f120c51586b..69977340e2593db6 100644
--- a/include/libcamera/internal/utils.h
+++ b/include/libcamera/internal/utils.h
@@ -200,6 +200,8 @@ details::StringSplitter split(const std::string &str, const std::string &delim);
 std::string libcameraBuildPath();
 std::string libcameraSourcePath();
 
+int tryLookupFirmwareID(const std::string &devPath, std::string *id);
+
 constexpr unsigned int alignDown(unsigned int value, unsigned int alignment)
 {
 	return value / alignment * alignment;
diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp
index 615df46ac142a2a9..07ebce61f230e5f0 100644
--- a/src/libcamera/utils.cpp
+++ b/src/libcamera/utils.cpp
@@ -9,6 +9,7 @@
 
 #include <dlfcn.h>
 #include <elf.h>
+#include <fstream>
 #include <iomanip>
 #include <limits.h>
 #include <link.h>
@@ -444,6 +445,66 @@ std::string libcameraSourcePath()
 	return path + "/";
 }
 
+/**
+ * \brief Try to read a device firmware ID from sysfs
+ * \param[in] devPath Path in sysfs to search
+ * \param[out] id Location to store ID if found
+ *
+ * The ID recorded in the devices firmware description is recorded differently
+ * in sysfs depending on if the devices uses OF or ACPI. This helper abstracts
+ * this away, allowing callers to not care which of the two are used.
+ *
+ * For OF-based systems the ID is the full path of the device in the device tree
+ * description. For ACPI-based systems the ID is the ACPI firmware nodes path.
+ * Both ID sources are guaranteed to be unique and persistent as long as the
+ * firmware of the system is not changed.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -ENOMEM \a id is nullptr
+ * \retval -EINVAL Error when looking up firmware ID
+ * \retval -ENODEV No firmware ID aviable for device
+ */
+int tryLookupFirmwareID(const std::string &devPath, std::string *id)
+{
+	struct stat statbuf;
+	std::string path;
+
+	if (!id)
+		return -EINVAL;
+
+	/* Try to lookup ID as if the system where OF-based. */
+	path = devPath + "/of_node";
+	if (stat(path.c_str(), &statbuf) == 0) {
+		char *ofPath = realpath(path.c_str(), nullptr);
+		if (!ofPath)
+			return -EINVAL;
+
+		*id = ofPath;
+		free(ofPath);
+
+		static const std::string dropStr = "/sys/firmware/devicetree/";
+		if (id->find(dropStr) == 0)
+			id->erase(0, dropStr.length());
+
+		return 0;
+	}
+
+	/* Try to lookup ID as if the system where ACPI-based. */
+	path = devPath + "/firmware_node/path";
+	if (stat(path.c_str(), &statbuf) == 0) {
+		std::ifstream file(path.c_str());
+		if (!file.is_open())
+			return -EINVAL;
+
+		std::getline(file, *id);
+		file.close();
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
 /**
  * \fn alignDown(unsigned int value, unsigned int alignment)
  * \brief Align \a value down to \a alignment
