Provide a helper on the MediaDevice to return a list of all available entities which match a given function in the graph. As a drive by, also fix a whitespace error in the documentation of MediaDevice::setupLink. Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Reviewed-by: Umang Jain <umang.jain@ideasonboard.com> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>
855 lines
22 KiB
C++
855 lines
22 KiB
C++
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
/*
|
|
* Copyright (C) 2018, Google Inc.
|
|
*
|
|
* Media device handler
|
|
*/
|
|
|
|
#include "libcamera/internal/media_device.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdint.h>
|
|
#include <string>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <tuple>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
|
|
#include <linux/media.h>
|
|
|
|
#include <libcamera/base/log.h>
|
|
|
|
/**
|
|
* \file media_device.h
|
|
* \brief Provide a representation of a Linux kernel Media Controller device
|
|
* that exposes the full graph topology.
|
|
*/
|
|
|
|
namespace libcamera {
|
|
|
|
LOG_DEFINE_CATEGORY(MediaDevice)
|
|
|
|
/**
|
|
* \class MediaDevice
|
|
* \brief The MediaDevice represents a Media Controller device with its full
|
|
* graph of connected objects.
|
|
*
|
|
* A MediaDevice instance is associated with a media controller device node when
|
|
* created, and that association is kept for the lifetime of the MediaDevice
|
|
* instance.
|
|
*
|
|
* The instance is created with an empty media graph. Before performing any
|
|
* other operation, it must be populate by calling populate(). Instances of
|
|
* MediaEntity, MediaPad and MediaLink are created to model the media graph,
|
|
* and stored in a map indexed by object id.
|
|
*
|
|
* The graph is valid once successfully populated, as reported by the isValid()
|
|
* function. It can be queried to list all entities(), or entities can be
|
|
* looked up by name with getEntityByName(). The graph can be traversed from
|
|
* entity to entity through pads and links as exposed by the corresponding
|
|
* classes.
|
|
*
|
|
* Media devices can be claimed for exclusive use with acquire(), released with
|
|
* release() and tested with busy(). This mechanism is aimed at pipeline
|
|
* managers to claim media devices they support during enumeration.
|
|
*/
|
|
|
|
/**
|
|
* \brief Construct a MediaDevice
|
|
* \param[in] deviceNode The media device node path
|
|
*
|
|
* Once constructed the media device is invalid, and must be populated with
|
|
* populate() before the media graph can be queried.
|
|
*/
|
|
MediaDevice::MediaDevice(const std::string &deviceNode)
|
|
: deviceNode_(deviceNode), valid_(false), acquired_(false)
|
|
{
|
|
}
|
|
|
|
MediaDevice::~MediaDevice()
|
|
{
|
|
fd_.reset();
|
|
clear();
|
|
}
|
|
|
|
std::string MediaDevice::logPrefix() const
|
|
{
|
|
return deviceNode() + "[" + driver() + "]";
|
|
}
|
|
|
|
/**
|
|
* \brief Claim a device for exclusive use
|
|
*
|
|
* The device claiming mechanism offers simple media device access arbitration
|
|
* between multiple users. When the media device is created, it is available to
|
|
* all users. Users can query the media graph to determine whether they can
|
|
* support the device and, if they do, claim the device for exclusive use. Other
|
|
* users are then expected to skip over media devices in use as reported by the
|
|
* busy() function.
|
|
*
|
|
* Once claimed the device shall be released by its user when not needed anymore
|
|
* by calling the release() function. Acquiring the media device opens a file
|
|
* descriptor to the device which is kept open until release() is called.
|
|
*
|
|
* Exclusive access is only guaranteed if all users of the media device abide by
|
|
* the device claiming mechanism, as it isn't enforced by the media device
|
|
* itself.
|
|
*
|
|
* \return true if the device was successfully claimed, or false if it was
|
|
* already in use
|
|
* \sa release(), busy()
|
|
*/
|
|
bool MediaDevice::acquire()
|
|
{
|
|
if (acquired_)
|
|
return false;
|
|
|
|
if (open())
|
|
return false;
|
|
|
|
acquired_ = true;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Release a device previously claimed for exclusive use
|
|
* \sa acquire(), busy()
|
|
*/
|
|
void MediaDevice::release()
|
|
{
|
|
close();
|
|
acquired_ = false;
|
|
}
|
|
|
|
/**
|
|
* \brief Lock the device to prevent it from being used by other instances of
|
|
* libcamera
|
|
*
|
|
* Multiple instances of libcamera might be running on the same system, at the
|
|
* same time. To allow the different instances to coexist, system resources in
|
|
* the form of media devices must be accessible for enumerating the cameras
|
|
* they provide at all times, while still allowing an instance to lock a
|
|
* resource while it prepares to actively use a camera from the resource.
|
|
*
|
|
* This function shall not be called from a pipeline handler implementation
|
|
* directly, as the base PipelineHandler implementation handles this on the
|
|
* behalf of the specified implementation.
|
|
*
|
|
* \return True if the device could be locked, false otherwise
|
|
* \sa unlock()
|
|
*/
|
|
bool MediaDevice::lock()
|
|
{
|
|
if (!fd_.isValid())
|
|
return false;
|
|
|
|
if (lockf(fd_.get(), F_TLOCK, 0))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Unlock the device and free it for use for libcamera instances
|
|
*
|
|
* This function shall not be called from a pipeline handler implementation
|
|
* directly, as the base PipelineHandler implementation handles this on the
|
|
* behalf of the specified implementation.
|
|
*
|
|
* \sa lock()
|
|
*/
|
|
void MediaDevice::unlock()
|
|
{
|
|
if (!fd_.isValid())
|
|
return;
|
|
|
|
std::ignore = lockf(fd_.get(), F_ULOCK, 0);
|
|
}
|
|
|
|
/**
|
|
* \fn MediaDevice::busy()
|
|
* \brief Check if a device is in use
|
|
* \return true if the device has been claimed for exclusive use, or false if it
|
|
* is available
|
|
* \sa acquire(), release()
|
|
*/
|
|
|
|
/**
|
|
* \brief Populate the MediaDevice with device information and media objects
|
|
*
|
|
* This function retrieves the media device information and enumerates all
|
|
* media objects in the media device graph and creates their MediaObject
|
|
* representations. All entities, pads and links are stored as MediaEntity,
|
|
* MediaPad and MediaLink respectively, with cross-references between objects.
|
|
* Interfaces are not processed.
|
|
*
|
|
* Entities are stored in a separate list in the MediaDevice to ease lookup,
|
|
* while pads are accessible from the entity they belong to and links from the
|
|
* pads they connect.
|
|
*
|
|
* \return 0 on success or a negative error code otherwise
|
|
*/
|
|
int MediaDevice::populate()
|
|
{
|
|
struct media_v2_topology topology = {};
|
|
struct media_v2_entity *ents = nullptr;
|
|
struct media_v2_interface *interfaces = nullptr;
|
|
struct media_v2_link *links = nullptr;
|
|
struct media_v2_pad *pads = nullptr;
|
|
__u64 version = -1;
|
|
int ret;
|
|
|
|
clear();
|
|
|
|
ret = open();
|
|
if (ret)
|
|
return ret;
|
|
|
|
struct media_device_info info = {};
|
|
ret = ioctl(fd_.get(), MEDIA_IOC_DEVICE_INFO, &info);
|
|
if (ret) {
|
|
ret = -errno;
|
|
LOG(MediaDevice, Error)
|
|
<< "Failed to get media device info " << strerror(-ret);
|
|
goto done;
|
|
}
|
|
|
|
driver_ = info.driver;
|
|
model_ = info.model;
|
|
version_ = info.media_version;
|
|
hwRevision_ = info.hw_revision;
|
|
|
|
/*
|
|
* Keep calling G_TOPOLOGY until the version number stays stable.
|
|
*/
|
|
while (true) {
|
|
topology.topology_version = 0;
|
|
topology.ptr_entities = reinterpret_cast<uintptr_t>(ents);
|
|
topology.ptr_interfaces = reinterpret_cast<uintptr_t>(interfaces);
|
|
topology.ptr_links = reinterpret_cast<uintptr_t>(links);
|
|
topology.ptr_pads = reinterpret_cast<uintptr_t>(pads);
|
|
|
|
ret = ioctl(fd_.get(), MEDIA_IOC_G_TOPOLOGY, &topology);
|
|
if (ret < 0) {
|
|
ret = -errno;
|
|
LOG(MediaDevice, Error)
|
|
<< "Failed to enumerate topology: "
|
|
<< strerror(-ret);
|
|
goto done;
|
|
}
|
|
|
|
if (version == topology.topology_version)
|
|
break;
|
|
|
|
delete[] ents;
|
|
delete[] interfaces;
|
|
delete[] pads;
|
|
delete[] links;
|
|
|
|
ents = new struct media_v2_entity[topology.num_entities]();
|
|
interfaces = new struct media_v2_interface[topology.num_interfaces]();
|
|
links = new struct media_v2_link[topology.num_links]();
|
|
pads = new struct media_v2_pad[topology.num_pads]();
|
|
|
|
version = topology.topology_version;
|
|
}
|
|
|
|
/* Populate entities, pads and links. */
|
|
if (populateEntities(topology) &&
|
|
populatePads(topology) &&
|
|
populateLinks(topology))
|
|
valid_ = true;
|
|
|
|
ret = 0;
|
|
done:
|
|
close();
|
|
|
|
delete[] ents;
|
|
delete[] interfaces;
|
|
delete[] pads;
|
|
delete[] links;
|
|
|
|
if (!valid_) {
|
|
clear();
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* \fn MediaDevice::isValid()
|
|
* \brief Query whether the media graph has been populated and is valid
|
|
* \return true if the media graph is valid, false otherwise
|
|
*/
|
|
|
|
/**
|
|
* \fn MediaDevice::driver()
|
|
* \brief Retrieve the media device driver name
|
|
* \return The name of the kernel driver that handles the MediaDevice
|
|
*/
|
|
|
|
/**
|
|
* \fn MediaDevice::deviceNode()
|
|
* \brief Retrieve the media device node path
|
|
* \return The MediaDevice deviceNode path
|
|
*/
|
|
|
|
/**
|
|
* \fn MediaDevice::model()
|
|
* \brief Retrieve the media device model name
|
|
* \return The MediaDevice model name
|
|
*/
|
|
|
|
/**
|
|
* \fn MediaDevice::version()
|
|
* \brief Retrieve the media device API version
|
|
*
|
|
* The version is formatted with the KERNEL_VERSION() macro.
|
|
*
|
|
* \return The MediaDevice API version
|
|
*/
|
|
|
|
/**
|
|
* \fn MediaDevice::hwRevision()
|
|
* \brief Retrieve the media device hardware revision
|
|
*
|
|
* The hardware revision is in a driver-specific format.
|
|
*
|
|
* \return The MediaDevice hardware revision
|
|
*/
|
|
|
|
/**
|
|
* \fn MediaDevice::entities()
|
|
* \brief Retrieve the list of entities in the media graph
|
|
* \return The list of MediaEntities registered in the MediaDevice
|
|
*/
|
|
|
|
/**
|
|
* \brief Return the MediaEntity with name \a name
|
|
* \param[in] name The entity name
|
|
* \return The entity with \a name, or nullptr if no such entity is found
|
|
*/
|
|
MediaEntity *MediaDevice::getEntityByName(const std::string &name) const
|
|
{
|
|
for (MediaEntity *e : entities_)
|
|
if (e->name() == name)
|
|
return e;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* \brief Retrieve the MediaLink connecting two pads, identified by entity
|
|
* names and pad indexes
|
|
* \param[in] sourceName The source entity name
|
|
* \param[in] sourceIdx The index of the source pad
|
|
* \param[in] sinkName The sink entity name
|
|
* \param[in] sinkIdx The index of the sink pad
|
|
*
|
|
* Find the link that connects the pads at index \a sourceIdx of the source
|
|
* entity with name \a sourceName, to the pad at index \a sinkIdx of the
|
|
* sink entity with name \a sinkName, if any.
|
|
*
|
|
* \sa link(const MediaEntity *source, unsigned int sourceIdx,
|
|
* const MediaEntity *sink, unsigned int sinkIdx)
|
|
* \sa link(const MediaPad *source, const MediaPad *sink)
|
|
*
|
|
* \return The link that connects the two pads, or nullptr if no such a link
|
|
* exists
|
|
*/
|
|
MediaLink *MediaDevice::link(const std::string &sourceName, unsigned int sourceIdx,
|
|
const std::string &sinkName, unsigned int sinkIdx)
|
|
{
|
|
const MediaEntity *source = getEntityByName(sourceName);
|
|
const MediaEntity *sink = getEntityByName(sinkName);
|
|
if (!source || !sink)
|
|
return nullptr;
|
|
|
|
return link(source, sourceIdx, sink, sinkIdx);
|
|
}
|
|
|
|
/**
|
|
* \brief Retrieve the MediaLink connecting two pads, identified by the
|
|
* entities they belong to and pad indexes
|
|
* \param[in] source The source entity
|
|
* \param[in] sourceIdx The index of the source pad
|
|
* \param[in] sink The sink entity
|
|
* \param[in] sinkIdx The index of the sink pad
|
|
*
|
|
* Find the link that connects the pads at index \a sourceIdx of the source
|
|
* entity \a source, to the pad at index \a sinkIdx of the sink entity \a
|
|
* sink, if any.
|
|
*
|
|
* \sa link(const std::string &sourceName, unsigned int sourceIdx,
|
|
* const std::string &sinkName, unsigned int sinkIdx)
|
|
* \sa link(const MediaPad *source, const MediaPad *sink)
|
|
*
|
|
* \return The link that connects the two pads, or nullptr if no such a link
|
|
* exists
|
|
*/
|
|
MediaLink *MediaDevice::link(const MediaEntity *source, unsigned int sourceIdx,
|
|
const MediaEntity *sink, unsigned int sinkIdx)
|
|
{
|
|
|
|
const MediaPad *sourcePad = source->getPadByIndex(sourceIdx);
|
|
const MediaPad *sinkPad = sink->getPadByIndex(sinkIdx);
|
|
if (!sourcePad || !sinkPad)
|
|
return nullptr;
|
|
|
|
return link(sourcePad, sinkPad);
|
|
}
|
|
|
|
/**
|
|
* \brief Retrieve the MediaLink that connects two pads
|
|
* \param[in] source The source pad
|
|
* \param[in] sink The sink pad
|
|
*
|
|
* \sa link(const std::string &sourceName, unsigned int sourceIdx,
|
|
* const std::string &sinkName, unsigned int sinkIdx)
|
|
* \sa link(const MediaEntity *source, unsigned int sourceIdx,
|
|
* const MediaEntity *sink, unsigned int sinkIdx)
|
|
*
|
|
* \return The link that connects the two pads, or nullptr if no such a link
|
|
* exists
|
|
*/
|
|
MediaLink *MediaDevice::link(const MediaPad *source, const MediaPad *sink)
|
|
{
|
|
for (MediaLink *link : source->links()) {
|
|
if (link->sink()->id() == sink->id())
|
|
return link;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* \brief Disable all links in the media device
|
|
*
|
|
* Disable all the media device links, clearing the MEDIA_LNK_FL_ENABLED flag
|
|
* on links which are not flagged as IMMUTABLE.
|
|
*
|
|
* \return 0 on success or a negative error code otherwise
|
|
*/
|
|
int MediaDevice::disableLinks()
|
|
{
|
|
for (MediaEntity *entity : entities_) {
|
|
for (MediaPad *pad : entity->pads()) {
|
|
if (!(pad->flags() & MEDIA_PAD_FL_SOURCE))
|
|
continue;
|
|
|
|
for (MediaLink *link : pad->links()) {
|
|
if (link->flags() & MEDIA_LNK_FL_IMMUTABLE)
|
|
continue;
|
|
|
|
int ret = link->setEnabled(false);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \var MediaDevice::disconnected
|
|
* \brief Signal emitted when the media device is disconnected from the system
|
|
*
|
|
* This signal is emitted when the device enumerator detects that the media
|
|
* device has been removed from the system. For hot-pluggable devices this is
|
|
* usually caused by physical device disconnection, but can also result from
|
|
* driver unloading for most devices. The media device is passed as a parameter.
|
|
*/
|
|
|
|
/**
|
|
* \brief Open the media device
|
|
*
|
|
* \return 0 on success or a negative error code otherwise
|
|
* \retval -EBUSY Media device already open
|
|
* \sa close()
|
|
*/
|
|
int MediaDevice::open()
|
|
{
|
|
if (fd_.isValid()) {
|
|
LOG(MediaDevice, Error) << "MediaDevice already open";
|
|
return -EBUSY;
|
|
}
|
|
|
|
fd_ = UniqueFD(::open(deviceNode_.c_str(), O_RDWR | O_CLOEXEC));
|
|
if (!fd_.isValid()) {
|
|
int ret = -errno;
|
|
LOG(MediaDevice, Error)
|
|
<< "Failed to open media device at "
|
|
<< deviceNode_ << ": " << strerror(-ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Close the media device
|
|
*
|
|
* This function closes the media device node. It does not invalidate the media
|
|
* graph and all cached media objects remain valid and can be accessed normally.
|
|
* Once closed no operation interacting with the media device node can be
|
|
* performed until the device is opened again.
|
|
*
|
|
* Closing an already closed device is allowed and will not perform any
|
|
* operation.
|
|
*
|
|
* \sa open()
|
|
*/
|
|
void MediaDevice::close()
|
|
{
|
|
fd_.reset();
|
|
}
|
|
|
|
/**
|
|
* \var MediaDevice::objects_
|
|
* \brief Global map of media objects (entities, pads, links) keyed by their
|
|
* object id.
|
|
*/
|
|
|
|
/**
|
|
* \brief Retrieve the media graph object specified by \a id
|
|
* \return The graph object, or nullptr if no object with \a id is found
|
|
*/
|
|
MediaObject *MediaDevice::object(unsigned int id)
|
|
{
|
|
auto it = objects_.find(id);
|
|
return (it == objects_.end()) ? nullptr : it->second;
|
|
}
|
|
|
|
/**
|
|
* \brief Add a media object to the media graph
|
|
*
|
|
* If the \a object has a unique id it is added to the media graph, and its
|
|
* lifetime will be managed by the media device. Otherwise the object isn't
|
|
* added to the graph and the caller must delete it.
|
|
*
|
|
* \return true if the object was successfully added to the graph and false
|
|
* otherwise
|
|
*/
|
|
bool MediaDevice::addObject(MediaObject *object)
|
|
{
|
|
|
|
if (objects_.find(object->id()) != objects_.end()) {
|
|
LOG(MediaDevice, Error)
|
|
<< "Element with id " << object->id()
|
|
<< " already enumerated.";
|
|
return false;
|
|
}
|
|
|
|
objects_[object->id()] = object;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Delete all graph objects in the media device
|
|
*
|
|
* Clear the media graph and delete all the objects it contains. After this
|
|
* function returns any previously obtained pointer to a media graph object
|
|
* becomes invalid.
|
|
*
|
|
* The media device graph state is reset to invalid when the graph is cleared.
|
|
*
|
|
* \sa isValid()
|
|
*/
|
|
void MediaDevice::clear()
|
|
{
|
|
for (auto const &o : objects_)
|
|
delete o.second;
|
|
|
|
objects_.clear();
|
|
entities_.clear();
|
|
valid_ = false;
|
|
}
|
|
|
|
/**
|
|
* \var MediaDevice::entities_
|
|
* \brief Global list of media entities in the media graph
|
|
*/
|
|
|
|
/**
|
|
* \brief Find the interface associated with an entity
|
|
* \param[in] topology The media topology as returned by MEDIA_IOC_G_TOPOLOGY
|
|
* \param[in] entityId The entity id
|
|
* \return A pointer to the interface if found, or nullptr otherwise
|
|
*/
|
|
struct media_v2_interface *MediaDevice::findInterface(const struct media_v2_topology &topology,
|
|
unsigned int entityId)
|
|
{
|
|
struct media_v2_link *links = reinterpret_cast<struct media_v2_link *>
|
|
(topology.ptr_links);
|
|
unsigned int ifaceId = 0;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < topology.num_links; ++i) {
|
|
/* Search for the interface to entity link. */
|
|
if (links[i].sink_id != entityId)
|
|
continue;
|
|
|
|
if ((links[i].flags & MEDIA_LNK_FL_LINK_TYPE) !=
|
|
MEDIA_LNK_FL_INTERFACE_LINK)
|
|
continue;
|
|
|
|
ifaceId = links[i].source_id;
|
|
break;
|
|
}
|
|
if (i == topology.num_links)
|
|
return nullptr;
|
|
|
|
struct media_v2_interface *ifaces = reinterpret_cast<struct media_v2_interface *>
|
|
(topology.ptr_interfaces);
|
|
for (i = 0; i < topology.num_interfaces; ++i) {
|
|
if (ifaces[i].id == ifaceId)
|
|
return &ifaces[i];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/*
|
|
* For each entity in the media graph create a MediaEntity and store a
|
|
* reference in the media device objects map and entities list.
|
|
*/
|
|
bool MediaDevice::populateEntities(const struct media_v2_topology &topology)
|
|
{
|
|
struct media_v2_entity *mediaEntities = reinterpret_cast<struct media_v2_entity *>
|
|
(topology.ptr_entities);
|
|
|
|
for (unsigned int i = 0; i < topology.num_entities; ++i) {
|
|
struct media_v2_entity *ent = &mediaEntities[i];
|
|
|
|
/*
|
|
* The media_v2_entity structure was missing the flag field before
|
|
* v4.19.
|
|
*/
|
|
if (!MEDIA_V2_ENTITY_HAS_FLAGS(version_))
|
|
fixupEntityFlags(ent);
|
|
|
|
/*
|
|
* Find the interface linked to this entity to get the device
|
|
* node major and minor numbers.
|
|
*/
|
|
struct media_v2_interface *iface =
|
|
findInterface(topology, ent->id);
|
|
MediaEntity *entity = new MediaEntity(this, ent, iface);
|
|
|
|
if (!addObject(entity)) {
|
|
delete entity;
|
|
return false;
|
|
}
|
|
|
|
entities_.push_back(entity);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MediaDevice::populatePads(const struct media_v2_topology &topology)
|
|
{
|
|
struct media_v2_pad *mediaPads = reinterpret_cast<struct media_v2_pad *>
|
|
(topology.ptr_pads);
|
|
|
|
for (unsigned int i = 0; i < topology.num_pads; ++i) {
|
|
unsigned int entity_id = mediaPads[i].entity_id;
|
|
|
|
/* Store a reference to this MediaPad in entity. */
|
|
MediaEntity *mediaEntity = dynamic_cast<MediaEntity *>
|
|
(object(entity_id));
|
|
if (!mediaEntity) {
|
|
LOG(MediaDevice, Error)
|
|
<< "Failed to find entity with id: "
|
|
<< entity_id;
|
|
return false;
|
|
}
|
|
|
|
MediaPad *pad = new MediaPad(&mediaPads[i], mediaEntity);
|
|
if (!addObject(pad)) {
|
|
delete pad;
|
|
return false;
|
|
}
|
|
|
|
mediaEntity->addPad(pad);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MediaDevice::populateLinks(const struct media_v2_topology &topology)
|
|
{
|
|
struct media_v2_link *mediaLinks = reinterpret_cast<struct media_v2_link *>
|
|
(topology.ptr_links);
|
|
|
|
for (unsigned int i = 0; i < topology.num_links; ++i) {
|
|
if ((mediaLinks[i].flags & MEDIA_LNK_FL_LINK_TYPE) ==
|
|
MEDIA_LNK_FL_INTERFACE_LINK)
|
|
continue;
|
|
|
|
/* Look up the source and sink objects. */
|
|
unsigned int source_id = mediaLinks[i].source_id;
|
|
MediaObject *source = object(source_id);
|
|
if (!source) {
|
|
LOG(MediaDevice, Error)
|
|
<< "Failed to find MediaObject with id "
|
|
<< source_id;
|
|
return false;
|
|
}
|
|
|
|
unsigned int sink_id = mediaLinks[i].sink_id;
|
|
MediaObject *sink = object(sink_id);
|
|
if (!sink) {
|
|
LOG(MediaDevice, Error)
|
|
<< "Failed to find MediaObject with id "
|
|
<< sink_id;
|
|
return false;
|
|
}
|
|
|
|
switch (mediaLinks[i].flags & MEDIA_LNK_FL_LINK_TYPE) {
|
|
case MEDIA_LNK_FL_DATA_LINK: {
|
|
MediaPad *sourcePad = dynamic_cast<MediaPad *>(source);
|
|
MediaPad *sinkPad = dynamic_cast<MediaPad *>(sink);
|
|
if (!source || !sink) {
|
|
LOG(MediaDevice, Error)
|
|
<< "Source or sink is not a pad";
|
|
return false;
|
|
}
|
|
|
|
MediaLink *link = new MediaLink(&mediaLinks[i],
|
|
sourcePad, sinkPad);
|
|
if (!addObject(link)) {
|
|
delete link;
|
|
return false;
|
|
}
|
|
|
|
link->source()->addLink(link);
|
|
link->sink()->addLink(link);
|
|
|
|
break;
|
|
}
|
|
|
|
case MEDIA_LNK_FL_ANCILLARY_LINK: {
|
|
MediaEntity *primary = dynamic_cast<MediaEntity *>(source);
|
|
MediaEntity *ancillary = dynamic_cast<MediaEntity *>(sink);
|
|
if (!primary || !ancillary) {
|
|
LOG(MediaDevice, Error)
|
|
<< "Source or sink is not an entity";
|
|
return false;
|
|
}
|
|
|
|
primary->addAncillaryEntity(ancillary);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
LOG(MediaDevice, Warning)
|
|
<< "Unknown media link type";
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Fixup entity flags using the legacy API
|
|
* \param[in] entity The entity
|
|
*
|
|
* This function is used as a fallback to query entity flags using the legacy
|
|
* MEDIA_IOC_ENUM_ENTITIES ioctl when running on a kernel version that doesn't
|
|
* provide them through the MEDIA_IOC_G_TOPOLOGY ioctl.
|
|
*/
|
|
void MediaDevice::fixupEntityFlags(struct media_v2_entity *entity)
|
|
{
|
|
struct media_entity_desc desc = {};
|
|
desc.id = entity->id;
|
|
|
|
int ret = ioctl(fd_.get(), MEDIA_IOC_ENUM_ENTITIES, &desc);
|
|
if (ret < 0) {
|
|
ret = -errno;
|
|
LOG(MediaDevice, Debug)
|
|
<< "Failed to retrieve information for entity "
|
|
<< entity->id << ": " << strerror(-ret);
|
|
return;
|
|
}
|
|
|
|
entity->flags = desc.flags;
|
|
}
|
|
|
|
/**
|
|
* \brief Apply \a flags to a link between two pads
|
|
* \param[in] link The link to apply flags to
|
|
* \param[in] flags The flags to apply to the link
|
|
*
|
|
* This function applies the link \a flags (as defined by the MEDIA_LNK_FL_*
|
|
* macros from the Media Controller API) to the given \a link. It implements
|
|
* low-level link setup as it performs no checks on the validity of the \a
|
|
* flags, and assumes that the supplied \a flags are valid for the link (e.g.
|
|
* immutable links cannot be disabled).
|
|
*
|
|
* \sa MediaLink::setEnabled(bool enable)
|
|
*
|
|
* \return 0 on success or a negative error code otherwise
|
|
*/
|
|
int MediaDevice::setupLink(const MediaLink *link, unsigned int flags)
|
|
{
|
|
struct media_link_desc linkDesc = {};
|
|
MediaPad *source = link->source();
|
|
MediaPad *sink = link->sink();
|
|
|
|
linkDesc.source.entity = source->entity()->id();
|
|
linkDesc.source.index = source->index();
|
|
linkDesc.source.flags = MEDIA_PAD_FL_SOURCE;
|
|
|
|
linkDesc.sink.entity = sink->entity()->id();
|
|
linkDesc.sink.index = sink->index();
|
|
linkDesc.sink.flags = MEDIA_PAD_FL_SINK;
|
|
|
|
linkDesc.flags = flags;
|
|
|
|
int ret = ioctl(fd_.get(), MEDIA_IOC_SETUP_LINK, &linkDesc);
|
|
if (ret) {
|
|
ret = -errno;
|
|
LOG(MediaDevice, Error)
|
|
<< "Failed to setup link " << *link << ": "
|
|
<< strerror(-ret);
|
|
return ret;
|
|
}
|
|
|
|
LOG(MediaDevice, Debug) << *link << ": " << flags;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Identify all entities of a common function in the MediaDevice
|
|
* \param[in] function The entity function to search for
|
|
*
|
|
* Search all entities within the graph of the MediaDevice and return
|
|
* a vector of those which match the given function.
|
|
*
|
|
* \return A vector of matching entities
|
|
*/
|
|
std::vector<MediaEntity *> MediaDevice::locateEntities(unsigned int function)
|
|
{
|
|
std::vector<MediaEntity *> found;
|
|
|
|
/* Gather all the entities matching the function they expose. */
|
|
for (MediaEntity *entity : entities()) {
|
|
if (entity->function() == function)
|
|
found.push_back(entity);
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
} /* namespace libcamera */
|