diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h
index 8331898c..09419766 100644
--- a/include/libcamera/camera_manager.h
+++ b/include/libcamera/camera_manager.h
@@ -7,8 +7,10 @@
 #ifndef __LIBCAMERA_CAMERA_MANAGER_H__
 #define __LIBCAMERA_CAMERA_MANAGER_H__
 
+#include <map>
 #include <memory>
 #include <string>
+#include <sys/types.h>
 #include <vector>
 
 #include <libcamera/object.h>
@@ -33,8 +35,9 @@ public:
 
 	const std::vector<std::shared_ptr<Camera>> &cameras() const { return cameras_; }
 	std::shared_ptr<Camera> get(const std::string &name);
+	std::shared_ptr<Camera> get(dev_t devnum);
 
-	void addCamera(std::shared_ptr<Camera> camera);
+	void addCamera(std::shared_ptr<Camera> camera, dev_t devnum);
 	void removeCamera(Camera *camera);
 
 	static const std::string &version() { return version_; }
@@ -46,6 +49,7 @@ private:
 	std::unique_ptr<DeviceEnumerator> enumerator_;
 	std::vector<std::shared_ptr<PipelineHandler>> pipes_;
 	std::vector<std::shared_ptr<Camera>> cameras_;
+	std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
 
 	static const std::string version_;
 	static CameraManager *self_;
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index 7c6f72bb..88f8112c 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -180,15 +180,42 @@ std::shared_ptr<Camera> CameraManager::get(const std::string &name)
 	return nullptr;
 }
 
+/**
+ * \brief Retrieve a camera based on device number
+ * \param[in] devnum Device number of camera to get
+ *
+ * This method is meant solely for the use of the V4L2 compatibility
+ * layer, to map device nodes to Camera instances. Applications shall
+ * not use it and shall instead retrieve cameras by name.
+ *
+ * Before calling this function the caller is responsible for ensuring that
+ * the camera manager is running.
+ *
+ * \return Shared pointer to Camera object, which is empty if the camera is
+ * not found
+ */
+std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
+{
+	auto iter = camerasByDevnum_.find(devnum);
+	if (iter == camerasByDevnum_.end())
+		return nullptr;
+
+	return iter->second.lock();
+}
+
 /**
  * \brief Add a camera to the camera manager
  * \param[in] camera The camera to be added
+ * \param[in] devnum The device number to associate with \a camera
  *
  * This function is called by pipeline handlers to register the cameras they
  * handle with the camera manager. Registered cameras are immediately made
  * available to the system.
+ *
+ * \a devnum is used by the V4L2 compatibility layer to map V4L2 device nodes
+ * to Camera instances.
  */
-void CameraManager::addCamera(std::shared_ptr<Camera> camera)
+void CameraManager::addCamera(std::shared_ptr<Camera> camera, dev_t devnum)
 {
 	for (std::shared_ptr<Camera> c : cameras_) {
 		if (c->name() == camera->name()) {
@@ -200,6 +227,11 @@ void CameraManager::addCamera(std::shared_ptr<Camera> camera)
 	}
 
 	cameras_.push_back(std::move(camera));
+
+	if (devnum) {
+		unsigned int index = cameras_.size() - 1;
+		camerasByDevnum_[devnum] = cameras_[index];
+	}
 }
 
 /**
@@ -212,15 +244,24 @@ void CameraManager::addCamera(std::shared_ptr<Camera> camera)
  */
 void CameraManager::removeCamera(Camera *camera)
 {
-	for (auto iter = cameras_.begin(); iter != cameras_.end(); ++iter) {
-		if (iter->get() == camera) {
-			LOG(Camera, Debug)
-				<< "Unregistering camera '"
-				<< camera->name() << "'";
-			cameras_.erase(iter);
-			return;
-		}
-	}
+	auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
+				   [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
+					   return p.second.lock().get() == camera;
+				   });
+	if (iter_d != camerasByDevnum_.end())
+		camerasByDevnum_.erase(iter_d);
+
+	auto iter = std::find_if(cameras_.begin(), cameras_.end(),
+				 [camera](std::shared_ptr<Camera> &c) {
+					 return c.get() == camera;
+				 });
+	if (iter == cameras_.end())
+		return;
+
+	LOG(Camera, Debug)
+		<< "Unregistering camera '" << camera->name() << "'";
+
+	cameras_.erase(iter);
 }
 
 /**
diff --git a/src/libcamera/include/pipeline_handler.h b/src/libcamera/include/pipeline_handler.h
index f3622631..067baef5 100644
--- a/src/libcamera/include/pipeline_handler.h
+++ b/src/libcamera/include/pipeline_handler.h
@@ -12,6 +12,7 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <sys/sysmacros.h>
 #include <vector>
 
 #include <ipa/ipa_interface.h>
@@ -86,7 +87,7 @@ public:
 
 protected:
 	void registerCamera(std::shared_ptr<Camera> camera,
-			    std::unique_ptr<CameraData> data);
+			    std::unique_ptr<CameraData> data, dev_t devnum = 0);
 	void hotplugMediaDevice(MediaDevice *media);
 
 	virtual int queueRequestDevice(Camera *camera, Request *request) = 0;
diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp
index 5badf31c..698dd525 100644
--- a/src/libcamera/pipeline_handler.cpp
+++ b/src/libcamera/pipeline_handler.cpp
@@ -7,6 +7,8 @@
 
 #include "pipeline_handler.h"
 
+#include <sys/sysmacros.h>
+
 #include <libcamera/buffer.h>
 #include <libcamera/camera.h>
 #include <libcamera/camera_manager.h>
@@ -438,19 +440,26 @@ void PipelineHandler::completeRequest(Camera *camera, Request *request)
  * \brief Register a camera to the camera manager and pipeline handler
  * \param[in] camera The camera to be added
  * \param[in] data Pipeline-specific data for the camera
+ * \param[in] devnum Device number of the camera (optional)
  *
  * This method is called by pipeline handlers to register the cameras they
  * handle with the camera manager. It associates the pipeline-specific \a data
  * with the camera, for later retrieval with cameraData(). Ownership of \a data
  * is transferred to the PipelineHandler.
+ *
+ * \a devnum is the device number (as returned by makedev) that the \a camera
+ * is to be associated with. This is for the V4L2 compatibility layer to map
+ * device nodes to Camera instances based on the device number
+ * registered by this method in \a devnum.
  */
 void PipelineHandler::registerCamera(std::shared_ptr<Camera> camera,
-				     std::unique_ptr<CameraData> data)
+				     std::unique_ptr<CameraData> data,
+				     dev_t devnum)
 {
 	data->camera_ = camera.get();
 	cameraData_[camera.get()] = std::move(data);
 	cameras_.push_back(camera);
-	manager_->addCamera(std::move(camera));
+	manager_->addCamera(std::move(camera), devnum);
 }
 
 /**
