From patchwork Fri Dec 28 07:57:41 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 92 Return-Path: Received: from relay8-d.mail.gandi.net (relay8-d.mail.gandi.net [217.70.183.201]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8A8DA600CC for ; Fri, 28 Dec 2018 08:57:59 +0100 (CET) X-Originating-IP: 2.224.242.101 Received: from uno.lan (2-224-242-101.ip172.fastwebnet.it [2.224.242.101]) (Authenticated sender: jacopo@jmondi.org) by relay8-d.mail.gandi.net (Postfix) with ESMTPSA id 16B5E1BF207; Fri, 28 Dec 2018 07:57:58 +0000 (UTC) From: Jacopo Mondi To: libcamera-devel@lists.libcamera.org Date: Fri, 28 Dec 2018 08:57:41 +0100 Message-Id: <20181228075743.28637-3-jacopo@jmondi.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20181228075743.28637-1-jacopo@jmondi.org> References: <20181228075743.28637-1-jacopo@jmondi.org> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH RESEND v2 2/4] libcamera: Add MediaDevice class X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 28 Dec 2018 07:57:59 -0000 The MediaDevice object implements handling and configuration of the media graph associated with a V4L2 media device. The class allows enumeration of all pads, links and entities registered in the media graph, and provides methods to setup and reset media links. Signed-off-by: Jacopo Mondi --- src/libcamera/include/media_device.h | 71 ++++ src/libcamera/media_device.cpp | 596 +++++++++++++++++++++++++++ src/libcamera/meson.build | 1 + 3 files changed, 668 insertions(+) create mode 100644 src/libcamera/include/media_device.h create mode 100644 src/libcamera/media_device.cpp -- 2.20.1 diff --git a/src/libcamera/include/media_device.h b/src/libcamera/include/media_device.h new file mode 100644 index 0000000..642eea9 --- /dev/null +++ b/src/libcamera/include/media_device.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018, Google Inc. + * + * media_device.h - Media device handler + */ +#ifndef __LIBCAMERA_MEDIA_DEVICE_H__ +#define __LIBCAMERA_MEDIA_DEVICE_H__ + +#include +#include +#include +#include + +#include + +#include "log.h" +#include "media_object.h" + +namespace libcamera { + +class MediaDevice +{ +public: + MediaDevice() : fd_(-1) { }; + ~MediaDevice(); + + const std::string name() const { return name_; } + const std::string path() const { return path_; } + + int open(const std::string &path); + int close(); + int enumerate(std::map &entitiesMap); + void dumpGraph(std::ostream &os); + + int resetLinks(); + int link(const std::string &source, unsigned int sourceIdx, + const std::string &sink, unsigned int sinkIdx, + unsigned int flags); + +private: + /** The media device file descriptor */ + int fd_; + /** The media device name as returned by MEDIA_IOC_DEVICE_INFO */ + std::string name_; + /** The media device path */ + std::string path_; + + std::map mediaObjects_; + MediaObject *getObject(unsigned int id); + void addObject(MediaObject *obj); + void deleteObjects(); + + std::vector entities_; + MediaEntity *getEntityByName(const std::string &name); + + int enumerateEntities(std::map &entitiesMap, + struct media_v2_topology &topology); + int enumeratePads(struct media_v2_topology &topology); + int enumerateLinks(struct media_v2_topology &topology); + + int setupLink(const MediaPad *source, const MediaPad *sink, + MediaLink *link, unsigned int flags); + + void dumpLocal(MediaEntity *e, MediaPad *p, std::ostream &os); + void dumpRemote(MediaLink *l, std::ostream &os); + void dumpLink(MediaLink *l, std::ostream &os); +}; + +} /* namespace libcamera */ +#endif /* __LIBCAMERA_MEDIA_DEVICE_H__ */ diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp new file mode 100644 index 0000000..b98d62f --- /dev/null +++ b/src/libcamera/media_device.cpp @@ -0,0 +1,596 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018, Google Inc. + * + * media_device.cpp - Media device handler + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "log.h" +#include "media_device.h" + +/** + * \file media_device.h + */ +namespace libcamera { + +/** + * \class MediaDevice + * \brief Media device handler + * + * MediaDevice handles the media graph associated with a V4L2 media device. + */ + +/** + * \fn MediaDevice::~MediaDevice() + * \brief Close the media device file descriptor and release entities + */ +MediaDevice::~MediaDevice() +{ + if (fd_ > -1) + ::close(fd_); + deleteObjects(); +} + +/** + * \fn MediaDevice::name() + * \brief Return the media device name + */ + +/** + * \fn MediaDevice::path() + * \brief Return the media device path node associated with this MediaDevice + */ + +/** + * \fn MediaDevice::deleteObjects() + * \brief Delete all registered entities in the MediaDevice object + */ +void MediaDevice::deleteObjects() +{ + for (auto const &e : mediaObjects_) + delete e.second; + + mediaObjects_.clear(); + entities_.clear(); +} + +/** + * \fn int MediaDevice::open(std::string) + * \brief Open a media device and initialize its components. + * \param path The media device path + */ +int MediaDevice::open(const std::string &path) +{ + fd_ = ::open(path.c_str(), O_RDWR); + if (fd_ < 0) { + LOG(Error) << "Failed to open media device at " << path + << ": " << strerror(errno); + return -errno; + } + path_ = path; + + struct media_device_info info = { }; + int ret = ioctl(fd_, MEDIA_IOC_DEVICE_INFO, &info); + if (ret) { + LOG(Error) << "Failed to get media device info " + << ": " << strerror(errno); + return -errno; + } + + name_ = info.driver; + + return 0; +} + +/** + * \fn MediaDevice::close() + * \brief Close the file descriptor associated with the media device + */ +int MediaDevice::close() +{ + if (fd_ > -1) + return ::close(fd_); + + return 0; +} + +void MediaDevice::addObject(MediaObject *obj) +{ + + if (mediaObjects_.find(obj->id()) != mediaObjects_.end()) { + LOG(Error) << "Element with id " << obj->id() + << " already enumerated."; + return; + } + + mediaObjects_[obj->id()] = obj; +} + +MediaObject *MediaDevice::getObject(unsigned int id) +{ + auto it = mediaObjects_.find(id); + return (it == mediaObjects_.end()) ? + nullptr : it->second; +} + +/** + * \fn MediaDevice::getEntityByName(std::string) + * \brief Return entity with name \a name + * \param name The entity name + */ +MediaEntity *MediaDevice::getEntityByName(const std::string &name) +{ + auto it = entities_.begin(); + + while (it != entities_.end()) { + MediaEntity *e = *it; + if (!(e->name().compare(name))) + return e; + it++; + } + + return nullptr; +} + +/** + * \fn MediaDevice::enumerateLinks(struct media_v2_topology &topology) + * \brief Enumerate all links in the system and associate them with their + * source and sink pads + * \param topology The media topology as returned by MEDIA_IOC_G_TOPOLOGY + */ +int MediaDevice::enumerateLinks(struct media_v2_topology &topology) +{ + struct media_v2_link *link = reinterpret_cast + (topology.ptr_links); + + for (unsigned int i = 0; i < topology.num_links; i++, link++) { + /* + * Skip links between entities and interfaces: we only care + * about pad-2-pad links here. + */ + if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) == + MEDIA_LNK_FL_INTERFACE_LINK) + continue; + + MediaLink *mediaLink = new MediaLink(link); + addObject(mediaLink); + + /* Store reference to this mediaLink in the link's source pad. */ + MediaPad *mediaPad = dynamic_cast + (getObject(mediaLink->source())); + if (!mediaPad) { + LOG(Error) << "Failed to find pad with id: " + << mediaLink->source(); + return -ENODEV; + } + mediaPad->addLink(mediaLink); + + /* Store reference to this mediaLink in the link's sink pad. */ + mediaPad = dynamic_cast(getObject(mediaLink->sink())); + if (!mediaPad) { + LOG(Error) << "Failed to find pad with id: " + << mediaLink->sink(); + return -ENODEV; + } + mediaPad->addLink(mediaLink); + } + + return 0; +} + +/** + * \fn MediaDevice::enumeratePads(struct media_v2_topology &topology) + * \brief Enumerate all pads in the system and associate them with the + * entity they belong to + * \param topology The media topology as returned by MEDIA_IOC_G_TOPOLOGY + */ +int MediaDevice::enumeratePads(struct media_v2_topology &topology) +{ + struct media_v2_pad *pad = reinterpret_cast + (topology.ptr_pads); + + for (unsigned int i = 0; i < topology.num_pads; i++, pad++) { + MediaPad *mediaPad = new MediaPad(pad); + addObject(mediaPad); + + /* Store a reference to this MediaPad in pad's entity. */ + MediaEntity *mediaEntity = dynamic_cast + (getObject(mediaPad->entity())); + if (!mediaEntity) { + LOG(Error) << "Failed to find entity with id: " + << mediaPad->entity(); + return -ENODEV; + } + + mediaEntity->addPad(mediaPad); + } + + return 0; +} + +/** + * \fn MediaDevice::enumerateEntities(std::map &, + * struct media_v2_topology &topology) + * \brief Enumerate and initialize entities in the media graph + * \param entitiesMap Map entities names to their video (sub)device node + * \param topology The media topology as returned by MEDIA_IOC_G_TOPOLOGY + * + * Associate the video (sub)device path to the entity name as returned by + * MEDIA_IOC_G_TOPOLOGY + */ +int MediaDevice::enumerateEntities(std::map &entitiesMap, + struct media_v2_topology &topology) +{ + struct media_v2_entity *entities = reinterpret_cast + (topology.ptr_entities); + + for (unsigned int i = 0; i < topology.num_entities; ++i) { + auto it = entitiesMap.find(entities[i].name); + if (it == entitiesMap.end()) { + LOG(Error) << "Entity " << entities[i].name + << " not found in media entities map"; + return -ENOENT; + } + + MediaEntity *entity = new MediaEntity(&entities[i]); + if (entity->setDevice(it->second)) { + delete entity; + goto delete_entities; + } + + addObject(entity); + entities_.push_back(entity); + } + + return 0; + +delete_entities: + deleteObjects(); + + return -errno; +} + +/** + * \fn MediaDevice::enumerate(std::map) + * \brief Enumerate the media graph topology + * \param entitiesMap Map entities names to their video (sub)device node + * FIXME: this is statically provided by the caller at the moment. + * + * This functions enumerates all media objects, registered in the media graph, + * through the MEDIA_IOC_G_TOPOLOGY ioctl. For each returned entity, + * it creates and store its representation for later reuse. + */ +int MediaDevice::enumerate(std::map &entitiesMap) +{ + struct media_v2_topology topology = { }; + unsigned int num_interfaces; + unsigned int num_links; + unsigned int num_pads; + unsigned int num_ent; + + do { + num_ent = topology.num_entities; + num_pads = topology.num_pads; + num_links = topology.num_links; + num_interfaces = topology.num_interfaces; + + /* Call G_TOPOLOGY the first time here to enumerate .*/ + if (ioctl(fd_, MEDIA_IOC_G_TOPOLOGY, &topology)) { + LOG(Error) << "Failed to enumerate media topology on" + << path_ << ": " << strerror(errno); + return -errno; + } + + /* + * Repeat the call until we don't get a 'stable' number + * of media objects. + */ + } while (num_ent != topology.num_entities || + num_pads != topology.num_pads || + num_links != topology.num_links || + num_interfaces != topology.num_interfaces); + + auto *_ptr_e = new media_v2_entity[topology.num_entities]; + topology.ptr_entities = reinterpret_cast<__u64>(_ptr_e); + + auto *_ptr_p = new media_v2_pad[topology.num_pads]; + topology.ptr_pads = reinterpret_cast<__u64>(_ptr_p); + + auto *_ptr_l = new media_v2_link[topology.num_links]; + topology.ptr_links = reinterpret_cast<__u64>(_ptr_l); + + /* Call G_TOPOLOGY again, this time with memory reserved. */ + int ret = ioctl(fd_, MEDIA_IOC_G_TOPOLOGY, &topology); + if (ret < 0) { + LOG(Error) << "Failed to enumerate media topology on " << path_ + << ": " << strerror(errno); + ret = -errno; + goto error_free_mem; + } + + ret = enumerateEntities(entitiesMap, topology); + if (ret) + goto error_free_mem; + + ret = enumeratePads(topology); + if (ret) + goto error_free_objs; + + ret = enumerateLinks(topology); + if (ret) + goto error_free_objs; + + delete[] _ptr_e; + delete[] _ptr_p; + delete[] _ptr_l; + + return 0; + +error_free_objs: + deleteObjects(); + +error_free_mem: + delete[] _ptr_e; + delete[] _ptr_p; + delete[] _ptr_l; + + return ret; +} + +void MediaDevice::dumpLocal(MediaEntity *e, MediaPad *p, std::ostream &os) +{ + os << "\t \"" << e->name() << "\"[" + << p->index() << "]"; +} + +void MediaDevice::dumpRemote(MediaLink *l, std::ostream &os) +{ + + MediaPad *remotePad = dynamic_cast + (getObject(l->sink())); + if (!remotePad) + return; + + MediaEntity *remoteEntity = dynamic_cast + (getObject(remotePad->entity())); + if (!remoteEntity) + return; + + os << "\"" << remoteEntity->name() << "\"[" + << remotePad->index() << "]"; +} + +void MediaDevice::dumpLink(MediaLink *l, std::ostream &os) +{ + unsigned int flags = l->flags(); + + os << " ["; + if (flags) { + os << (flags & MEDIA_LNK_FL_ENABLED ? "ENABLED," : "") + << (flags & MEDIA_LNK_FL_IMMUTABLE ? "IMMUTABLE" : ""); + } + os << "]\n"; +} + +/** + * \fn MediaDevice::dumpGraph(std::ostream) + * \brief Dump the media device topology in textual form to an output stream + * \param os The output stream where to append the printed topology to + */ +void MediaDevice::dumpGraph(std::ostream &os) +{ + os << "\n" << name_ << " - " << path_ << "\n\n"; + + for (auto const &e : entities_) { + os << "\"" << e->name() << "\"\n"; + + for (auto const &p : e->sinks()) { + os << " [" << p->index() << "]" << ": Sink\n"; + for (auto const &l : p->links()) { + dumpLocal(e, p, os); + os << " <- "; + dumpRemote(l, os); + dumpLink(l, os); + } + os << "\n"; + } + + for (auto const &p : e->sources()) { + os << " [" << p->index() << "]" << ": Source\n"; + for (auto const &l : p->links()) { + dumpLocal(e, p, os); + os << " -> "; + dumpRemote(l, os); + dumpLink(l, os); + } + os << "\n"; + } + } +} + +/** + * \fn MediaDevice::setupLink(MediaPad *source, MediaPad *sink) + * \brief Apply \a flags to the link between \a source and \a sink pads + * \param source The source MediaPad + * \param sink The sink MediaPad + * \param link The MediaLink to operate on + * \param flags Flags to be applied to the link (MEDIA_LNK_FL_*) + */ +int MediaDevice::setupLink(const MediaPad *source, const MediaPad *sink, + MediaLink *link, unsigned int flags) +{ + struct media_link_desc linkDesc = { }; + + linkDesc.source.entity = source->entity(); + linkDesc.source.index = source->index(); + linkDesc.source.flags = MEDIA_PAD_FL_SOURCE; + + linkDesc.sink.entity = sink->entity(); + linkDesc.sink.index = sink->index(); + linkDesc.sink.flags = MEDIA_PAD_FL_SINK; + + linkDesc.flags = flags; + + if (ioctl(fd_, MEDIA_IOC_SETUP_LINK, &linkDesc)) { + LOG(Error) << "Failed to setup link: " + << strerror(errno); + return -errno; + } + + link->setFlags(0); + + return 0; +} + +/** + * \fn MediaDevice::resetLinks() + * \brief Reset all links on the media graph + * + * Walk all registered entities, and disable all links from their + * source pads to other pads. + */ +int MediaDevice::resetLinks() +{ + for (MediaEntity *e : entities_) { + for (MediaPad *sourcePad : e->sources()) { + for (MediaLink *l : sourcePad->links()) { + /* + * Do not reset links that are not enabled + * or immutable. + */ + if (l->flags() & MEDIA_LNK_FL_IMMUTABLE) + continue; + + if (!(l->flags() & MEDIA_LNK_FL_ENABLED)) + continue; + + /* Get the remote sink pad. */ + MediaPad *sinkPad = dynamic_cast + (getObject(l->sink())); + if (!sinkPad) + return -ENOENT; + + /* Also get entity to make sure IDs are ok. */ + MediaEntity *sinkEntity = + dynamic_cast + (getObject(sinkPad->entity())); + if (!sinkEntity) + return -ENOENT; + + int ret = setupLink(sourcePad, sinkPad, l, 0); + if (ret) { + LOG(Error) << "Link reset failed: " + << e->name() << "[" + << sourcePad->index() + << "] -> " + << sinkEntity->name() << "[" + << sinkPad->index() << "]"; + return ret; + } + + LOG(Info) << "Link reset: " + << e->name() << "[" + << sourcePad->index() + << "] -> " + << sinkEntity->name() << "[" + << sinkPad->index() << "]"; + } + } + } + + return 0; +} + +/** + * \fn MediaDevice::link(std::string, unsigned int, std::string, unsigned int) + * \brief Setup a link identified by the entities name and their source and + * sink pad indexes + * \param source The source entity name + * \param sourceIdx The source pad index + * \param sink The sink entity name + * \param sinkIdx The sink pad index + * \param flags The link setup flag (see MEDIA_LNK_FL_*) + */ +int MediaDevice::link(const std::string &source, unsigned int sourceIdx, + const std::string &sink, unsigned int sinkIdx, + unsigned int flags) +{ + + /* Make sure the supplied link is well formed. */ + MediaEntity *sourceEntity = getEntityByName(source); + if (!sourceEntity) { + LOG(Error) << "Entity name: " << source << "not found"; + return -ENOENT; + } + + MediaEntity *sinkEntity = getEntityByName(sink); + if (!sinkEntity) { + LOG(Error) << "Entity name: " << source << "not found"; + return -ENOENT; + } + + const MediaPad *sourcePad = sourceEntity->getPadByIndex(sourceIdx); + if (!sourcePad) { + LOG(Error) << "Pad " << sourceIdx << "not found in entity " + << sourceEntity->name(); + return -ENOENT; + } + + const MediaPad *sinkPad = sinkEntity->getPadByIndex(sinkIdx); + if (!sinkPad) { + LOG(Error) << "Pad " << sinkIdx << "not found in entity " + << sinkEntity->name(); + return -ENOENT; + } + + /* + * Walk all links in the source and search for an entry matching the + * pad ids. If none, the requested link does not exists. + */ + MediaLink *validLink = nullptr; + for (MediaLink *link : sourcePad->links()) { + if (link->source() != sourcePad->id()) + continue; + + if (link->sink() != sinkPad->id()) + continue; + + validLink = link; + break; + } + + if (!validLink) { + LOG(Error) << "Link not found" + << "\"" << sourceEntity->name() << "\"[" + << sourcePad->index() << "] -> " + << "\"" << sinkEntity->name() << "\"[" + << sinkPad->index() << "]"; + return -EINVAL; + } + + int ret = setupLink(sourcePad, sinkPad, validLink, flags); + if (ret) + return ret; + + LOG(Info) << "Setup link: " + << "\"" << sourceEntity->name() << "\"[" + << sourcePad->index() << "] -> " + << "\"" << sinkEntity->name() << "\"[" + << sinkPad->index() << "]" + << " [" << flags << "]"; + + return 0; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index da06eba..4cac687 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -2,6 +2,7 @@ libcamera_sources = files([ 'log.cpp', 'main.cpp', 'media_object.cpp', + 'media_device.cpp', ]) libcamera_headers = files([