/* 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 "libcamera/internal/device_enumerator_sysfs.h" #include "libcamera/internal/device_enumerator_udev.h" #include #include "libcamera/internal/log.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::create() { std::unique_ptr enumerator; #ifdef HAVE_LIBUDEV enumerator = std::make_unique(); 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(); if (!enumerator->init()) return enumerator; return nullptr; } DeviceEnumerator::~DeviceEnumerator() { for (std::shared_ptr 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 DeviceEnumerator::createDevice(const std::string &deviceNode) { std::unique_ptr media = std::make_unique(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 method 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 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 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(media.get()); } /** * \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 DeviceEnumerator::search(const DeviceMatch &dm) { for (std::shared_ptr &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 */