diff --git a/src/libcamera/include/utils.h b/src/libcamera/include/utils.h
index 9405977..bc96e79 100644
--- a/src/libcamera/include/utils.h
+++ b/src/libcamera/include/utils.h
@@ -143,6 +143,10 @@ private:
 
 details::StringSplitter split(const std::string &str, const std::string &delim);
 
+bool isLibcameraInstalled();
+
+std::string libcameraPath();
+
 } /* namespace utils */
 
 } /* namespace libcamera */
diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
index 238433d..0bd280c 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -9,9 +9,6 @@
 
 #include <algorithm>
 #include <dirent.h>
-#include <dlfcn.h>
-#include <elf.h>
-#include <link.h>
 #include <string.h>
 #include <sys/types.h>
 
@@ -27,35 +24,6 @@
  * \brief Image Processing Algorithm module manager
  */
 
-static bool isLibcameraInstalled()
-{
-	/* musl doesn't declare _DYNAMIC in link.h, declare it manually. */
-	extern ElfW(Dyn) _DYNAMIC[];
-
-	/*
-	 * DT_RUNPATH (DT_RPATH when the linker uses old dtags) is removed on
-	 * install.
-	 */
-	for (const ElfW(Dyn) *dyn = _DYNAMIC; dyn->d_tag != DT_NULL; ++dyn) {
-		if (dyn->d_tag == DT_RUNPATH || dyn->d_tag == DT_RPATH)
-			return false;
-	}
-
-	return true;
-}
-
-static std::string libcameraPath()
-{
-	Dl_info info;
-
-	/* Look up our own symbol. */
-	int ret = dladdr(reinterpret_cast<void *>(libcameraPath), &info);
-	if (ret == 0)
-		return nullptr;
-
-	return info.dli_fname;
-}
-
 namespace libcamera {
 
 LOG_DEFINE_CATEGORY(IPAManager)
@@ -151,8 +119,8 @@ IPAManager::IPAManager()
 	 * path for the IPA from that point. We need to recurse one level of
 	 * sub-directories to match the build tree.
 	 */
-	if (!isLibcameraInstalled()) {
-		std::string ipaBuildPath = utils::dirname(libcameraPath()) + "/../ipa";
+	if (!utils::isLibcameraInstalled()) {
+		std::string ipaBuildPath = utils::dirname(utils::libcameraPath()) + "/../ipa";
 		constexpr int maxDepth = 1;
 
 		LOG(IPAManager, Info)
diff --git a/src/libcamera/utils.cpp b/src/libcamera/utils.cpp
index f566e88..7e118fa 100644
--- a/src/libcamera/utils.cpp
+++ b/src/libcamera/utils.cpp
@@ -7,7 +7,10 @@
 
 #include "utils.h"
 
+#include <dlfcn.h>
+#include <elf.h>
 #include <iomanip>
+#include <link.h>
 #include <sstream>
 #include <stdlib.h>
 #include <string.h>
@@ -18,6 +21,9 @@
  * \brief Miscellaneous utility functions
  */
 
+/* musl doesn't declare _DYNAMIC in link.h, declare it manually. */
+extern ElfW(Dyn) _DYNAMIC[];
+
 namespace libcamera {
 
 namespace utils {
@@ -310,6 +316,49 @@ details::StringSplitter split(const std::string &str, const std::string &delim)
 	return details::StringSplitter(str, delim);
 }
 
+/**
+ * \brief Check if libcamera is installed or not
+ *
+ * Utilise the build_rpath dynamic tag which is stripped out by meson at
+ * install time to determine at runtime if the library currently executing
+ * has been installed or not.
+ *
+ * \return True if libcamera is installed, false otherwise
+ */
+bool isLibcameraInstalled()
+{
+	/*
+	 * DT_RUNPATH (DT_RPATH when the linker uses old dtags) is removed on
+	 * install.
+	 */
+	for (const ElfW(Dyn) *dyn = _DYNAMIC; dyn->d_tag != DT_NULL; ++dyn) {
+		if (dyn->d_tag == DT_RUNPATH || dyn->d_tag == DT_RPATH)
+			return false;
+	}
+
+	return true;
+}
+
+/**
+ * \brief Identify the libcamera.so path
+ *
+ * This function locates the running libcamera.so and returns its full path,
+ * including the file name.
+ *
+ * \return A string stating the path
+ */
+std::string libcameraPath()
+{
+	Dl_info info;
+
+	/* Look up our own symbol. */
+	int ret = dladdr(reinterpret_cast<void *>(libcameraPath), &info);
+	if (ret == 0)
+		return nullptr;
+
+	return info.dli_fname;
+}
+
 } /* namespace utils */
 
 } /* namespace libcamera */
