diff --git a/include/libcamera/internal/sysfs.h b/include/libcamera/internal/sysfs.h
index 72f436205d8d30e8..ef8ec55909d85bf9 100644
--- a/include/libcamera/internal/sysfs.h
+++ b/include/libcamera/internal/sysfs.h
@@ -15,6 +15,8 @@ namespace sysfs {
 
 std::string charDevPath(const std::string &devicePath);
 
+int firmwareId(const std::string &path, std::string *id);
+
 } /* namespace sysfs */
 
 } /* namespace libcamera */
diff --git a/src/libcamera/sysfs.cpp b/src/libcamera/sysfs.cpp
index 3b2920663e9c3bcc..98be4df9d38e6fa8 100644
--- a/src/libcamera/sysfs.cpp
+++ b/src/libcamera/sysfs.cpp
@@ -7,9 +7,11 @@
 
 #include "libcamera/internal/sysfs.h"
 
+#include <fstream>
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 
+#include "libcamera/internal/file.h"
 #include "libcamera/internal/log.h"
 
 /**
@@ -43,6 +45,61 @@ std::string charDevPath(const std::string &devicePath)
 	return dev.str();
 }
 
+/**
+ * \brief Try to read a device firmware ID from sysfs
+ * \param[in] path Path in sysfs to search
+ * \param[out] id Location to store ID if found
+ *
+ * A systems firmware description is recorded differently in sysfs depending if
+ * the system uses OF or ACPI. Add a helper to abstract this, allowing users not
+ * to 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 -EINVAL Error when looking up firmware ID
+ * \retval -ENODEV No firmware ID available for \a path
+ */
+int firmwareId(const std::string &path, std::string *id)
+{
+	ASSERT(id);
+
+	/* ID lookup for OF-based systems */
+	File ofFile(path + "/of_node");
+	if (ofFile.exists()) {
+		char *ofPath = realpath(ofFile.fileName().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;
+	}
+
+	/* ID lookup for ACPI-based systems */
+	File acpiFile(path + "/firmware_node/path");
+	if (acpiFile.exists()) {
+		std::ifstream file(acpiFile.fileName());
+		if (!file.is_open())
+			return -EINVAL;
+
+		std::getline(file, *id);
+		file.close();
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
 } /* namespace sysfs */
 
 } /* namespace libcamera */
