[libcamera-devel,1/1] libcamera: enumeration: Generalize DeviceMatch
diff mbox series

Message ID 20230302005415.1789544-2-dev@flowerpot.me
State New
Headers show
Series
  • Generalized DeviceMatch
Related show

Commit Message

Sophie Friedrich March 2, 2023, 12:54 a.m. UTC
Currently `DeviceMatch` is very specialized to handle `MediaDevice`s, which
has strong dependencies to V4L2 internals.

This patch enables `DeviceMatch` to match using key/value pairs instead
of a simple list of strings and introduces a `DeviceMatchEntityInterface` to
abstract away the likes of `MediaEntity` in order to search for a
device.

Signed-off-by: Sophie Friedrich <dev@flowerpot.me>
---
 .../libcamera/internal/device_enumerator.h    |  16 +--
 include/libcamera/internal/device_match.h     |  46 +++++++
 include/libcamera/internal/media_device.h     |   1 +
 include/libcamera/internal/media_object.h     |   7 +-
 include/libcamera/internal/meson.build        |   1 +
 src/libcamera/device_enumerator.cpp           |  73 ----------
 src/libcamera/device_match.cpp                | 126 ++++++++++++++++++
 src/libcamera/meson.build                     |   1 +
 8 files changed, 183 insertions(+), 88 deletions(-)
 create mode 100644 include/libcamera/internal/device_match.h
 create mode 100644 src/libcamera/device_match.cpp

Comments

Jacopo Mondi May 15, 2023, 9:43 a.m. UTC | #1
Hi Sophie,

 a few drive-by cosmetic comments

On Thu, Mar 02, 2023 at 01:54:15AM +0100, Sophie Friedrich via libcamera-devel wrote:
> Currently `DeviceMatch` is very specialized to handle `MediaDevice`s, which
> has strong dependencies to V4L2 internals.
>
> This patch enables `DeviceMatch` to match using key/value pairs instead
> of a simple list of strings and introduces a `DeviceMatchEntityInterface` to
> abstract away the likes of `MediaEntity` in order to search for a
> device.
>
> Signed-off-by: Sophie Friedrich <dev@flowerpot.me>
> ---
>  .../libcamera/internal/device_enumerator.h    |  16 +--
>  include/libcamera/internal/device_match.h     |  46 +++++++
>  include/libcamera/internal/media_device.h     |   1 +
>  include/libcamera/internal/media_object.h     |   7 +-
>  include/libcamera/internal/meson.build        |   1 +
>  src/libcamera/device_enumerator.cpp           |  73 ----------
>  src/libcamera/device_match.cpp                | 126 ++++++++++++++++++
>  src/libcamera/meson.build                     |   1 +
>  8 files changed, 183 insertions(+), 88 deletions(-)
>  create mode 100644 include/libcamera/internal/device_match.h
>  create mode 100644 src/libcamera/device_match.cpp
>
> diff --git a/include/libcamera/internal/device_enumerator.h b/include/libcamera/internal/device_enumerator.h
> index 72ec9a60..259a9e46 100644
> --- a/include/libcamera/internal/device_enumerator.h
> +++ b/include/libcamera/internal/device_enumerator.h
> @@ -13,24 +13,12 @@
>
>  #include <libcamera/base/signal.h>
>
> +#include "libcamera/internal/device_match.h"
> +

You could forward-declare the DeviceMatch class in the header and move
the inclusion directive to the cpp file to reduce the compile-time
dependencies and improve the build times

>  namespace libcamera {
>
>  class MediaDevice;
>
> -class DeviceMatch
> -{
> -public:
> -	DeviceMatch(const std::string &driver);
> -
> -	void add(const std::string &entity);
> -
> -	bool match(const MediaDevice *device) const;
> -
> -private:
> -	std::string driver_;
> -	std::vector<std::string> entities_;
> -};
> -
>  class DeviceEnumerator
>  {
>  public:
> diff --git a/include/libcamera/internal/device_match.h b/include/libcamera/internal/device_match.h
> new file mode 100644
> index 00000000..e4563d3c
> --- /dev/null
> +++ b/include/libcamera/internal/device_match.h
> @@ -0,0 +1,46 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2023, Sophie Friedrich
> + *
> + * device_match.h - API to match devices
> + */
> +
> +#pragma once
> +
> +#include <string>
> +#include <vector>
> +
> +#include <libcamera/base/class.h>
> +
> +

Multiple blank lines

> +namespace libcamera {
> +
> +class MediaDevice;
> +
> +class DeviceMatchEntityInterface
> +{
> +public:
> +	virtual const std::string &match_key() const = 0;
> +	virtual const std::string &match_value() const = 0;

libcamera uses camelCase and not snake_case

> +	virtual ~DeviceMatchEntityInterface() = default;
> +
> +	static const std::string MATCH_ALL_KEY;
> +};
> +
> +class DeviceMatch
> +{
> +public:
> +	DeviceMatch(const std::string &driver);
> +
> +	void add(const std::string &entity);
> +	void add(const std::string &key, const std::string &value);
> +
> +	bool match(const MediaDevice *device) const;
> +
> +private:
> +	std::string driver_;
> +	std::vector<std::pair<std::string, std::string>> map_;

I'm a bit skeptical about this mapping, see below

> +};
> +
> +

multiple blank lines

> +}
> \ No newline at end of file
> diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h
> index eb8cfde4..b10bb2eb 100644
> --- a/include/libcamera/internal/media_device.h
> +++ b/include/libcamera/internal/media_device.h
> @@ -19,6 +19,7 @@
>  #include <libcamera/base/unique_fd.h>
>
>  #include "libcamera/internal/media_object.h"
> +#include "libcamera/internal/device_match.h"

Alphabetical order please

>
>  namespace libcamera {
>
> diff --git a/include/libcamera/internal/media_object.h b/include/libcamera/internal/media_object.h
> index b1572968..d07ed698 100644
> --- a/include/libcamera/internal/media_object.h
> +++ b/include/libcamera/internal/media_object.h
> @@ -14,6 +14,8 @@
>
>  #include <libcamera/base/class.h>
>
> +#include "libcamera/internal/device_match.h"
> +
>  namespace libcamera {
>
>  class MediaDevice;
> @@ -85,7 +87,7 @@ private:
>  	std::vector<MediaLink *> links_;
>  };
>
> -class MediaEntity : public MediaObject
> +class MediaEntity : public MediaObject, public DeviceMatchEntityInterface
>  {
>  public:
>  	enum class Type {
> @@ -111,6 +113,9 @@ public:
>
>  	int setDeviceNode(const std::string &deviceNode);
>
> +	const std::string &match_key() const{ return DeviceMatchEntityInterface::MATCH_ALL_KEY; }
> +	const std::string &match_value() const { return name_; }
> +

This interface is designed to match the requirement of matching on a
key/value pair. I wonder if we could find a pattern to delegate
matching to the DeviceMatchEntityInterface derived classes. If you
have code already working for your usb camera I would like to see how
it gets to look like

>  private:
>  	LIBCAMERA_DISABLE_COPY_AND_MOVE(MediaEntity)
>
> diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
> index d7508805..1e3c15ed 100644
> --- a/include/libcamera/internal/meson.build
> +++ b/include/libcamera/internal/meson.build
> @@ -21,6 +21,7 @@ libcamera_internal_headers = files([
>      'control_validator.h',
>      'converter.h',
>      'delayed_controls.h',
> +    'device_match.h',
>      'device_enumerator.h',
>      'device_enumerator_sysfs.h',
>      'device_enumerator_udev.h',
> diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp
> index f2e055de..49afd783 100644
> --- a/src/libcamera/device_enumerator.cpp
> +++ b/src/libcamera/device_enumerator.cpp
> @@ -40,79 +40,6 @@ namespace libcamera {
>
>  LOG_DEFINE_CATEGORY(DeviceEnumerator)
>
> -/**
> - * \class DeviceMatch
> - * \brief Description of a media device search pattern
> - *
> - * The DeviceMatch class describes a media device using properties from the
> - * Media Controller struct media_device_info, entity names in the media graph
> - * or other properties that can be used to identify a media device.
> - *
> - * The description is meant to be filled by pipeline managers and passed to a
> - * device enumerator to find matching media devices.
> - *
> - * A DeviceMatch is created with a specific Linux device driver in mind,
> - * therefore the name of the driver is a required property. One or more Entity
> - * names can be added as match criteria.
> - *
> - * Pipeline handlers are recommended to add entities to DeviceMatch as
> - * appropriare to ensure that the media device they need can be uniquely
> - * identified. This is useful when the corresponding kernel driver can produce
> - * different graphs, for instance as a result of different driver versions or
> - * hardware configurations, and not all those graphs are suitable for a pipeline
> - * handler.
> - */
> -
> -/**
> - * \brief Construct a media device search pattern
> - * \param[in] driver The Linux device driver name that created the media device
> - */
> -DeviceMatch::DeviceMatch(const std::string &driver)
> -	: driver_(driver)
> -{
> -}
> -
> -/**
> - * \brief Add a media entity name to the search pattern
> - * \param[in] entity The name of the entity in the media graph
> - */
> -void DeviceMatch::add(const std::string &entity)
> -{
> -	entities_.push_back(entity);
> -}
> -
> -/**
> - * \brief Compare a search pattern with a media device
> - * \param[in] device The media device
> - *
> - * Matching is performed on the Linux device driver name and entity names from
> - * the media graph. A match is found if both the driver name matches and the
> - * media device contains all the entities listed in the search pattern.
> - *
> - * \return true if the media device matches the search pattern, false otherwise
> - */
> -bool DeviceMatch::match(const MediaDevice *device) const
> -{
> -	if (driver_ != device->driver())
> -		return false;
> -
> -	for (const std::string &name : entities_) {
> -		bool found = false;
> -
> -		for (const MediaEntity *entity : device->entities()) {
> -			if (name == entity->name()) {
> -				found = true;
> -				break;
> -			}
> -		}
> -
> -		if (!found)
> -			return false;
> -	}
> -
> -	return true;
> -}
> -
>  /**
>   * \class DeviceEnumerator
>   * \brief Enumerate, store and search media devices
> diff --git a/src/libcamera/device_match.cpp b/src/libcamera/device_match.cpp
> new file mode 100644
> index 00000000..0c205f93
> --- /dev/null
> +++ b/src/libcamera/device_match.cpp
> @@ -0,0 +1,126 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2023, Sophie Friedrich
> + *
> + * device_match.cpp - API to match devices
> + */
> +
> +#include "libcamera/internal/device_match.h"
> +
> +#include <string.h>
> +
> +#include <libcamera/base/log.h>
> +
> +#include "libcamera/internal/media_device.h"
> +
> +/**
> + * \file device_match.h
> + * \brief Matching of media devices
> + *
> + * Pipelines find compatible devices by matching against known strings or
> + * key/value pairs during device enumeration. MediaDevice are responsible for
> + * finding these entities during their population phase.
> + */
> +
> +namespace libcamera {
> +
> +LOG_DEFINE_CATEGORY(DeviceMatch)
> +
> +/**
> + * \interface DeviceMatchInterface
> + * \brief Interface which enables matching using DeviceMatch
> + *
> + *
> + *

Multiple blank lines

> + */
> +const std::string DeviceMatchEntityInterface::MATCH_ALL_KEY = "*";
> +
> +/**
> + * \class DeviceMatch
> + * \brief Description of a media device search pattern
> + *
> + * The DeviceMatch class describes a media device using properties from the
> + * Media Controller struct media_device_info, entity names in the media graph
> + * or other properties that can be used to identify a media device.
> + *
> + * The description is meant to be filled by pipeline managers and passed to a
> + * device enumerator to find matching media devices.
> + *
> + * A DeviceMatch is created with a specific Linux device driver in mind,
> + * therefore the name of the driver is a required property. One or more Entity
> + * names can be added as match criteria.
> + *
> + * Pipeline handlers are recommended to add entities to DeviceMatch as
> + * appropriare to ensure that the media device they need can be uniquely
> + * identified. This is useful when the corresponding kernel driver can produce
> + * different graphs, for instance as a result of different driver versions or
> + * hardware configurations, and not all those graphs are suitable for a pipeline
> + * handler.
> + */
> +
> +/**
> + * \brief Construct a media device search pattern
> + * \param[in] driver The Linux device driver name that created the media device
> + */
> +DeviceMatch::DeviceMatch(const std::string &driver)
> +	: driver_(driver)
> +{
> +}
> +
> +/**
> + * \brief Add a media entity name to the search pattern
> + * \param[in] entity The name of the entity in the media graph
> + */
> +void DeviceMatch::add(const std::string &entity)
> +{
> +	add(DeviceMatchEntityInterface::MATCH_ALL_KEY, entity);
> +}
> +
> +/**
> + * \brief Add key / value pair to search pattern
> + * \param[in] key The key the value is associated to
> + * \param[in] value The value that should be searched for
> + *
> + * There is no uniqueness enforced to key/value. Mentioning a certain
> + * key multiple times (in best case with different values) allows matching
> + * multiple properties on the same key.

Is this just to accommodate the usage of "*" as a key or is a
requirement for your use case too ?

> + */
> +void DeviceMatch::add(const std::string &key, const std::string &value)
> +{
> +	map_.push_back(std::pair(key, value));
> +}
> +
> +/**
> + * \brief Compare a search pattern with a media device
> + * \param[in] device The media device
> + *
> + * Matching is performed on the Linux device driver name and entity names from
> + * the media graph. A match is found if both the driver name matches and the
> + * media device contains all the entities listed in the search pattern.
> + *
> + * \return true if the media device matches the search pattern, false otherwise
> + */
> +bool DeviceMatch::match(const MediaDevice *device) const
> +{
> +	if (driver_ != device->driver())
> +		return false;
> +
> +	for (const std::pair<std::string, std::string> &item : map_) {
> +		bool found = false;
> +
> +		for (const DeviceMatchEntityInterface *entity : device->entities()) {
> +			if (item.first == entity->match_key() &&
> +				item.second == entity->match_value()) {

Align to the open ( please

If we could if (entity.match(item)) this would be nicer as it would
abstract away the matching criteria from the DeviceMatch class and
delegate it to sub-classes. Let's ponder a bit if that's possible and
how to design it.

Thanks
  j

> +				found = true;
> +				break;
> +			}
> +		}
> +
> +		if (!found)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +}
> \ No newline at end of file
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index 9869bfe7..445eb8e9 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -15,6 +15,7 @@ libcamera_sources = files([
>      'control_validator.cpp',
>      'converter.cpp',
>      'delayed_controls.cpp',
> +    'device_match.cpp',
>      'device_enumerator.cpp',
>      'device_enumerator_sysfs.cpp',
>      'fence.cpp',
> --
> 2.34.1
>

Patch
diff mbox series

diff --git a/include/libcamera/internal/device_enumerator.h b/include/libcamera/internal/device_enumerator.h
index 72ec9a60..259a9e46 100644
--- a/include/libcamera/internal/device_enumerator.h
+++ b/include/libcamera/internal/device_enumerator.h
@@ -13,24 +13,12 @@ 
 
 #include <libcamera/base/signal.h>
 
+#include "libcamera/internal/device_match.h"
+
 namespace libcamera {
 
 class MediaDevice;
 
-class DeviceMatch
-{
-public:
-	DeviceMatch(const std::string &driver);
-
-	void add(const std::string &entity);
-
-	bool match(const MediaDevice *device) const;
-
-private:
-	std::string driver_;
-	std::vector<std::string> entities_;
-};
-
 class DeviceEnumerator
 {
 public:
diff --git a/include/libcamera/internal/device_match.h b/include/libcamera/internal/device_match.h
new file mode 100644
index 00000000..e4563d3c
--- /dev/null
+++ b/include/libcamera/internal/device_match.h
@@ -0,0 +1,46 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Sophie Friedrich
+ *
+ * device_match.h - API to match devices
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <libcamera/base/class.h>
+
+
+namespace libcamera {
+
+class MediaDevice;
+
+class DeviceMatchEntityInterface
+{
+public:
+	virtual const std::string &match_key() const = 0;
+	virtual const std::string &match_value() const = 0;
+	virtual ~DeviceMatchEntityInterface() = default;
+
+	static const std::string MATCH_ALL_KEY;
+};
+
+class DeviceMatch
+{
+public:
+	DeviceMatch(const std::string &driver);
+
+	void add(const std::string &entity);
+	void add(const std::string &key, const std::string &value);
+
+	bool match(const MediaDevice *device) const;
+
+private:
+	std::string driver_;
+	std::vector<std::pair<std::string, std::string>> map_;
+};
+
+
+}
\ No newline at end of file
diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h
index eb8cfde4..b10bb2eb 100644
--- a/include/libcamera/internal/media_device.h
+++ b/include/libcamera/internal/media_device.h
@@ -19,6 +19,7 @@ 
 #include <libcamera/base/unique_fd.h>
 
 #include "libcamera/internal/media_object.h"
+#include "libcamera/internal/device_match.h"
 
 namespace libcamera {
 
diff --git a/include/libcamera/internal/media_object.h b/include/libcamera/internal/media_object.h
index b1572968..d07ed698 100644
--- a/include/libcamera/internal/media_object.h
+++ b/include/libcamera/internal/media_object.h
@@ -14,6 +14,8 @@ 
 
 #include <libcamera/base/class.h>
 
+#include "libcamera/internal/device_match.h"
+
 namespace libcamera {
 
 class MediaDevice;
@@ -85,7 +87,7 @@  private:
 	std::vector<MediaLink *> links_;
 };
 
-class MediaEntity : public MediaObject
+class MediaEntity : public MediaObject, public DeviceMatchEntityInterface
 {
 public:
 	enum class Type {
@@ -111,6 +113,9 @@  public:
 
 	int setDeviceNode(const std::string &deviceNode);
 
+	const std::string &match_key() const{ return DeviceMatchEntityInterface::MATCH_ALL_KEY; }
+	const std::string &match_value() const { return name_; }
+
 private:
 	LIBCAMERA_DISABLE_COPY_AND_MOVE(MediaEntity)
 
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index d7508805..1e3c15ed 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -21,6 +21,7 @@  libcamera_internal_headers = files([
     'control_validator.h',
     'converter.h',
     'delayed_controls.h',
+    'device_match.h',
     'device_enumerator.h',
     'device_enumerator_sysfs.h',
     'device_enumerator_udev.h',
diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp
index f2e055de..49afd783 100644
--- a/src/libcamera/device_enumerator.cpp
+++ b/src/libcamera/device_enumerator.cpp
@@ -40,79 +40,6 @@  namespace libcamera {
 
 LOG_DEFINE_CATEGORY(DeviceEnumerator)
 
-/**
- * \class DeviceMatch
- * \brief Description of a media device search pattern
- *
- * The DeviceMatch class describes a media device using properties from the
- * Media Controller struct media_device_info, entity names in the media graph
- * or other properties that can be used to identify a media device.
- *
- * The description is meant to be filled by pipeline managers and passed to a
- * device enumerator to find matching media devices.
- *
- * A DeviceMatch is created with a specific Linux device driver in mind,
- * therefore the name of the driver is a required property. One or more Entity
- * names can be added as match criteria.
- *
- * Pipeline handlers are recommended to add entities to DeviceMatch as
- * appropriare to ensure that the media device they need can be uniquely
- * identified. This is useful when the corresponding kernel driver can produce
- * different graphs, for instance as a result of different driver versions or
- * hardware configurations, and not all those graphs are suitable for a pipeline
- * handler.
- */
-
-/**
- * \brief Construct a media device search pattern
- * \param[in] driver The Linux device driver name that created the media device
- */
-DeviceMatch::DeviceMatch(const std::string &driver)
-	: driver_(driver)
-{
-}
-
-/**
- * \brief Add a media entity name to the search pattern
- * \param[in] entity The name of the entity in the media graph
- */
-void DeviceMatch::add(const std::string &entity)
-{
-	entities_.push_back(entity);
-}
-
-/**
- * \brief Compare a search pattern with a media device
- * \param[in] device The media device
- *
- * Matching is performed on the Linux device driver name and entity names from
- * the media graph. A match is found if both the driver name matches and the
- * media device contains all the entities listed in the search pattern.
- *
- * \return true if the media device matches the search pattern, false otherwise
- */
-bool DeviceMatch::match(const MediaDevice *device) const
-{
-	if (driver_ != device->driver())
-		return false;
-
-	for (const std::string &name : entities_) {
-		bool found = false;
-
-		for (const MediaEntity *entity : device->entities()) {
-			if (name == entity->name()) {
-				found = true;
-				break;
-			}
-		}
-
-		if (!found)
-			return false;
-	}
-
-	return true;
-}
-
 /**
  * \class DeviceEnumerator
  * \brief Enumerate, store and search media devices
diff --git a/src/libcamera/device_match.cpp b/src/libcamera/device_match.cpp
new file mode 100644
index 00000000..0c205f93
--- /dev/null
+++ b/src/libcamera/device_match.cpp
@@ -0,0 +1,126 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Sophie Friedrich
+ *
+ * device_match.cpp - API to match devices
+ */
+
+#include "libcamera/internal/device_match.h"
+
+#include <string.h>
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/media_device.h"
+
+/**
+ * \file device_match.h
+ * \brief Matching of media devices
+ *
+ * Pipelines find compatible devices by matching against known strings or
+ * key/value pairs during device enumeration. MediaDevice are responsible for
+ * finding these entities during their population phase.
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(DeviceMatch)
+
+/**
+ * \interface DeviceMatchInterface
+ * \brief Interface which enables matching using DeviceMatch
+ *
+ *
+ *
+ */
+const std::string DeviceMatchEntityInterface::MATCH_ALL_KEY = "*";
+
+/**
+ * \class DeviceMatch
+ * \brief Description of a media device search pattern
+ *
+ * The DeviceMatch class describes a media device using properties from the
+ * Media Controller struct media_device_info, entity names in the media graph
+ * or other properties that can be used to identify a media device.
+ *
+ * The description is meant to be filled by pipeline managers and passed to a
+ * device enumerator to find matching media devices.
+ *
+ * A DeviceMatch is created with a specific Linux device driver in mind,
+ * therefore the name of the driver is a required property. One or more Entity
+ * names can be added as match criteria.
+ *
+ * Pipeline handlers are recommended to add entities to DeviceMatch as
+ * appropriare to ensure that the media device they need can be uniquely
+ * identified. This is useful when the corresponding kernel driver can produce
+ * different graphs, for instance as a result of different driver versions or
+ * hardware configurations, and not all those graphs are suitable for a pipeline
+ * handler.
+ */
+
+/**
+ * \brief Construct a media device search pattern
+ * \param[in] driver The Linux device driver name that created the media device
+ */
+DeviceMatch::DeviceMatch(const std::string &driver)
+	: driver_(driver)
+{
+}
+
+/**
+ * \brief Add a media entity name to the search pattern
+ * \param[in] entity The name of the entity in the media graph
+ */
+void DeviceMatch::add(const std::string &entity)
+{
+	add(DeviceMatchEntityInterface::MATCH_ALL_KEY, entity);
+}
+
+/**
+ * \brief Add key / value pair to search pattern
+ * \param[in] key The key the value is associated to
+ * \param[in] value The value that should be searched for
+ *
+ * There is no uniqueness enforced to key/value. Mentioning a certain
+ * key multiple times (in best case with different values) allows matching
+ * multiple properties on the same key.
+ */
+void DeviceMatch::add(const std::string &key, const std::string &value)
+{
+	map_.push_back(std::pair(key, value));
+}
+
+/**
+ * \brief Compare a search pattern with a media device
+ * \param[in] device The media device
+ *
+ * Matching is performed on the Linux device driver name and entity names from
+ * the media graph. A match is found if both the driver name matches and the
+ * media device contains all the entities listed in the search pattern.
+ *
+ * \return true if the media device matches the search pattern, false otherwise
+ */
+bool DeviceMatch::match(const MediaDevice *device) const
+{
+	if (driver_ != device->driver())
+		return false;
+
+	for (const std::pair<std::string, std::string> &item : map_) {
+		bool found = false;
+
+		for (const DeviceMatchEntityInterface *entity : device->entities()) {
+			if (item.first == entity->match_key() &&
+				item.second == entity->match_value()) {
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+			return false;
+	}
+
+	return true;
+}
+
+}
\ No newline at end of file
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 9869bfe7..445eb8e9 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -15,6 +15,7 @@  libcamera_sources = files([
     'control_validator.cpp',
     'converter.cpp',
     'delayed_controls.cpp',
+    'device_match.cpp',
     'device_enumerator.cpp',
     'device_enumerator_sysfs.cpp',
     'fence.cpp',