blob: d12580505303af25893d1ffbf6454fc4c60f6548 [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2018, Google Inc.
*
* device_enumerator.cpp - Enumeration and matching
*/
#include "libcamera/internal/device_enumerator.h"
#include <string.h>
#include <libcamera/base/log.h>
#include "libcamera/internal/device_enumerator_sysfs.h"
#include "libcamera/internal/device_enumerator_udev.h"
#include "libcamera/internal/media_device.h"
/**
* \file device_enumerator.h
* \brief Enumeration and matching of media devices
*
* The purpose of device enumeration and matching is to find media devices in
* the system and map them to pipeline handlers.
*
* At the core of the enumeration is the DeviceEnumerator class, responsible
* for enumerating all media devices in the system. It handles all interactions
* with the operating system in a platform-specific way. For each media device
* found an instance of MediaDevice is created to store information about the
* device gathered from the kernel through the Media Controller API.
*
* The DeviceEnumerator can enumerate all or specific media devices in the
* system. When a new media device is added the enumerator creates a
* corresponding MediaDevice instance.
*
* The enumerator supports searching among enumerated devices based on criteria
* expressed in a DeviceMatch object.
*/
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
*
* The DeviceEnumerator class is responsible for all interactions with the
* operating system related to media devices. It enumerates all media devices
* in the system, and for each device found creates an instance of the
* MediaDevice class and stores it internally. The list of media devices can
* then be searched using DeviceMatch search patterns.
*
* The enumerator also associates media device entities with device node paths.
*/
/**
* \brief Create a new device enumerator matching the systems capabilities
*
* Depending on how the operating system handles device detection, hot-plug
* notification and device node lookup, different device enumerator
* implementations may be needed. This function creates the best enumerator for
* the operating system based on the available resources. Not all different
* enumerator types are guaranteed to support all features.
*
* \return A pointer to the newly created device enumerator on success, or
* nullptr if an error occurs
*/
std::unique_ptr<DeviceEnumerator> DeviceEnumerator::create()
{
std::unique_ptr<DeviceEnumerator> enumerator;
#ifdef HAVE_LIBUDEV
enumerator = std::make_unique<DeviceEnumeratorUdev>();
if (!enumerator->init())
return enumerator;
#endif
/*
* Either udev is not available or udev initialization failed. Fall back
* on the sysfs enumerator.
*/
enumerator = std::make_unique<DeviceEnumeratorSysfs>();
if (!enumerator->init())
return enumerator;
return nullptr;
}
DeviceEnumerator::~DeviceEnumerator()
{
for (std::shared_ptr<MediaDevice> media : devices_) {
if (media->busy())
LOG(DeviceEnumerator, Error)
<< "Removing media device " << media->deviceNode()
<< " while still in use";
}
}
/**
* \fn DeviceEnumerator::init()
* \brief Initialize the enumerator
* \return 0 on success or a negative error code otherwise
* \retval -EBUSY the enumerator has already been initialized
* \retval -ENODEV the enumerator can't enumerate devices
*/
/**
* \fn DeviceEnumerator::enumerate()
* \brief Enumerate all media devices in the system
*
* This function finds and add all media devices in the system to the
* enumerator. It shall be implemented by all subclasses of DeviceEnumerator
* using system-specific methods.
*
* Individual media devices that can't be properly enumerated shall be skipped
* with a warning message logged, without returning an error. Only errors that
* prevent enumeration altogether shall be fatal.
*
* \context This function is \threadbound.
*
* \return 0 on success or a negative error code otherwise
*/
/**
* \brief Create a media device instance
* \param[in] deviceNode path to the media device to create
*
* Create a media device for the \a deviceNode, open it, and populate its
* media graph. The device enumerator shall then populate the media device by
* associating device nodes with entities using MediaEntity::setDeviceNode().
* This process is specific to each device enumerator, and the device enumerator
* shall ensure that device nodes are ready to be used (for instance, if
* applicable, by waiting for device nodes to be created and access permissions
* to be set by the system). Once done, it shall add the media device to the
* system with addDevice().
*
* \return Created media device instance on success, or nullptr otherwise
*/
std::unique_ptr<MediaDevice> DeviceEnumerator::createDevice(const std::string &deviceNode)
{
std::unique_ptr<MediaDevice> media = std::make_unique<MediaDevice>(deviceNode);
int ret = media->populate();
if (ret < 0) {
LOG(DeviceEnumerator, Info)
<< "Unable to populate media device " << deviceNode
<< " (" << strerror(-ret) << "), skipping";
return nullptr;
}
LOG(DeviceEnumerator, Debug)
<< "New media device \"" << media->driver()
<< "\" created from " << deviceNode;
return media;
}
/**
* \var DeviceEnumerator::devicesAdded
* \brief Notify of new media devices being found
*
* This signal is emitted when the device enumerator finds new media devices in
* the system. It may be emitted for every newly detected device, or once for
* multiple devices, at the discretion of the device enumerator. Not all device
* enumerator types may support dynamic detection of new devices.
*/
/**
* \brief Add a media device to the enumerator
* \param[in] media media device instance to add
*
* Store the media device in the internal list for later matching with
* pipeline handlers. \a media shall be created with createDevice() first.
* This function shall be called after all members of the entities of the
* media graph have been confirmed to be initialized.
*/
void DeviceEnumerator::addDevice(std::unique_ptr<MediaDevice> media)
{
LOG(DeviceEnumerator, Debug)
<< "Added device " << media->deviceNode() << ": " << media->driver();
devices_.push_back(std::move(media));
/* \todo To batch multiple additions, emit with a small delay here. */
devicesAdded.emit();
}
/**
* \brief Remove a media device from the enumerator
* \param[in] deviceNode Path to the media device to remove
*
* Remove the media device identified by \a deviceNode previously added to the
* enumerator with addDevice(). The media device's MediaDevice::disconnected
* signal is emitted.
*/
void DeviceEnumerator::removeDevice(const std::string &deviceNode)
{
std::shared_ptr<MediaDevice> media;
for (auto iter = devices_.begin(); iter != devices_.end(); ++iter) {
if ((*iter)->deviceNode() == deviceNode) {
media = std::move(*iter);
devices_.erase(iter);
break;
}
}
if (!media) {
LOG(DeviceEnumerator, Warning)
<< "Media device for node " << deviceNode
<< " not found";
return;
}
LOG(DeviceEnumerator, Debug)
<< "Media device for node " << deviceNode << " removed.";
media->disconnected.emit();
}
/**
* \brief Search available media devices for a pattern match
* \param[in] dm Search pattern
*
* Search in the enumerated media devices that are not already in use for a
* match described in \a dm. If a match is found and the caller intends to use
* it the caller is responsible for acquiring the MediaDevice object and
* releasing it when done with it.
*
* \return pointer to the matching MediaDevice, or nullptr if no match is found
*/
std::shared_ptr<MediaDevice> DeviceEnumerator::search(const DeviceMatch &dm)
{
for (std::shared_ptr<MediaDevice> &media : devices_) {
if (media->busy())
continue;
if (dm.match(media.get())) {
LOG(DeviceEnumerator, Debug)
<< "Successful match for media device \""
<< media->driver() << "\"";
return media;
}
}
return nullptr;
}
} /* namespace libcamera */