summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libcamera/include/media_device.h56
-rw-r--r--src/libcamera/media_device.cpp375
-rw-r--r--src/libcamera/meson.build2
3 files changed, 433 insertions, 0 deletions
diff --git a/src/libcamera/include/media_device.h b/src/libcamera/include/media_device.h
new file mode 100644
index 00000000..bca7c9a1
--- /dev/null
+++ b/src/libcamera/include/media_device.h
@@ -0,0 +1,56 @@
+/* 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 <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <linux/media.h>
+
+#include "media_object.h"
+
+namespace libcamera {
+
+class MediaDevice
+{
+public:
+ MediaDevice(const std::string &devnode);
+ ~MediaDevice();
+
+ int open();
+ void close();
+
+ int populate();
+
+ const std::string driver() const { return driver_; }
+ const std::string devnode() const { return devnode_; }
+ const std::vector<MediaEntity *> &entities() const { return entities_; }
+
+private:
+ std::string driver_;
+ std::string devnode_;
+ int fd_;
+
+ std::map<unsigned int, MediaObject *> objects_;
+ MediaObject *object(unsigned int id);
+ int addObject(MediaObject *obj);
+ void clear();
+
+ std::vector<MediaEntity *> entities_;
+ MediaEntity *getEntityByName(const std::string &name);
+
+ void populateEntities(const struct media_v2_topology &topology);
+ int populatePads(const struct media_v2_topology &topology);
+ int populateLinks(const struct media_v2_topology &topology);
+};
+
+} /* 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 00000000..d9a5196a
--- /dev/null
+++ b/src/libcamera/media_device.cpp
@@ -0,0 +1,375 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018, Google Inc.
+ *
+ * media_device.cpp - Media device handler
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <linux/media.h>
+
+#include "log.h"
+#include "media_device.h"
+
+/**
+ * \file media_device.h
+ * \brief Provide a representation of a Linux kernel Media Controller device
+ * that exposes the full graph topology.
+ */
+
+namespace libcamera {
+
+/**
+ * \class MediaDevice
+ * \brief The MediaDevice represents a Media Controller device with its full
+ * graph of connected objects.
+ *
+ * Media devices are created with an empty graph, which must be populated from
+ *
+ *
+ * The caller is responsible for opening the MediaDevice explicitly before
+ * operating on it, and shall close it when not needed anymore, as access
+ * to the MediaDevice is exclusive.
+ *
+ * A MediaDevice is created empty and gets populated by inspecting the media
+ * graph topology using the MEDIA_IOC_G_TOPOLOGY ioctls. Representation
+ * of each entity, pad and link described are created using MediaObject
+ * derived classes.
+ *
+ * All MediaObject are stored in a global pool, where they could be retrieved
+ * from by their globally unique id.
+ *
+ * References to MediaEntity registered in the graph are stored in a vector
+ * to allow easier by-name lookup, and the list of MediaEntities is accessible.
+ */
+
+/**
+ * \brief Construct a MediaDevice
+ * \param devnode The media device node path
+ */
+MediaDevice::MediaDevice(const std::string &devnode)
+ : devnode_(devnode), fd_(-1)
+{
+}
+
+/**
+ * \brief Close the media device file descriptor and delete all object
+ */
+MediaDevice::~MediaDevice()
+{
+ if (fd_ != -1)
+ ::close(fd_);
+ clear();
+}
+
+/**
+ * \fn MediaDevice::driver()
+ * \brief Retrieve the media device driver name
+ * \return The name of the kernel driver that handles the MediaDevice
+ */
+
+/**
+ * \fn MediaDevice::devnode()
+ * \brief Retrieve the media device device node path
+ * \return The MediaDevice devnode path
+ */
+
+/**
+ * \brief Delete all media objects in the MediaDevice.
+ *
+ * Delete all MediaEntities; entities will then delete their pads,
+ * and each source pad will delete links.
+ *
+ * After this function has been called, the media graph will be unpopulated
+ * and its media objects deleted. The media device has to be populated
+ * before it could be used again.
+ */
+void MediaDevice::clear()
+{
+ for (auto const &o : objects_)
+ delete o.second;
+
+ objects_.clear();
+ entities_.clear();
+}
+
+/**
+ * \brief Open a media device and retrieve informations from it
+ *
+ * The function fails if the media device is already open or if either
+ * open or the media device information retrieval operations fail.
+ * \return 0 for success or a negative error number otherwise
+ */
+int MediaDevice::open()
+{
+ if (fd_ != -1) {
+ LOG(Error) << "MediaDevice already open";
+ return -EBUSY;
+ }
+
+ int ret = ::open(devnode_.c_str(), O_RDWR);
+ if (ret < 0) {
+ ret = -errno;
+ LOG(Error) << "Failed to open media device at " << devnode_
+ << ": " << strerror(-ret);
+ return ret;
+ }
+ fd_ = ret;
+
+ struct media_device_info info = { };
+ ret = ioctl(fd_, MEDIA_IOC_DEVICE_INFO, &info);
+ if (ret) {
+ ret = -errno;
+ LOG(Error) << "Failed to get media device info "
+ << ": " << strerror(-ret);
+ return ret;
+ }
+
+ driver_ = info.driver;
+
+ return 0;
+}
+
+/**
+ * \brief Close the file descriptor associated with the media device.
+ *
+ * After this function has been called, for the MediaDevice to be operated on,
+ * the caller shall open it again.
+ */
+void MediaDevice::close()
+{
+ if (fd_ == -1)
+ return;
+
+ ::close(fd_);
+ fd_ = -1;
+}
+
+/**
+ * \fn MediaDevice::entities()
+ * \brief Retrieve the list of entities in the media graph
+ * \return The list of MediaEntities registered in the MediaDevice
+ */
+
+/*
+ * Add a new object to the global objects pool and fail if the object
+ * has already been registered.
+ */
+int MediaDevice::addObject(MediaObject *obj)
+{
+
+ if (objects_.find(obj->id()) != objects_.end()) {
+ LOG(Error) << "Element with id " << obj->id()
+ << " already enumerated.";
+ return -EEXIST;
+ }
+
+ objects_[obj->id()] = obj;
+
+ return 0;
+}
+
+/*
+ * MediaObject pool lookup by id.
+ */
+MediaObject *MediaDevice::object(unsigned int id)
+{
+ auto it = objects_.find(id);
+ return (it == objects_.end()) ? nullptr : it->second;
+}
+
+/**
+ * \brief Return the MediaEntity with name \a name
+ * \param name The entity name
+ * \return The entity with \a name
+ * \return nullptr if no entity with \a name is found
+ */
+MediaEntity *MediaDevice::getEntityByName(const std::string &name)
+{
+ for (MediaEntity *e : entities_)
+ if (e->name() == name)
+ return e;
+
+ return nullptr;
+}
+
+int MediaDevice::populateLinks(const struct media_v2_topology &topology)
+{
+ media_v2_link *mediaLinks = reinterpret_cast<media_v2_link *>
+ (topology.ptr_links);
+
+ for (unsigned int i = 0; i < topology.num_links; ++i) {
+ /*
+ * Skip links between entities and interfaces: we only care
+ * about pad-2-pad links here.
+ */
+ if ((mediaLinks[i].flags & MEDIA_LNK_FL_LINK_TYPE) ==
+ MEDIA_LNK_FL_INTERFACE_LINK)
+ continue;
+
+ /* Store references to source and sink pads in the link. */
+ unsigned int source_id = mediaLinks[i].source_id;
+ MediaPad *source = dynamic_cast<MediaPad *>
+ (object(source_id));
+ if (!source) {
+ LOG(Error) << "Failed to find pad with id: "
+ << source_id;
+ return -ENODEV;
+ }
+
+ unsigned int sink_id = mediaLinks[i].sink_id;
+ MediaPad *sink = dynamic_cast<MediaPad *>
+ (object(sink_id));
+ if (!sink) {
+ LOG(Error) << "Failed to find pad with id: "
+ << sink_id;
+ return -ENODEV;
+ }
+
+ MediaLink *link = new MediaLink(&mediaLinks[i], source, sink);
+ source->addLink(link);
+ sink->addLink(link);
+
+ addObject(link);
+ }
+
+ return 0;
+}
+
+int MediaDevice::populatePads(const struct media_v2_topology &topology)
+{
+ media_v2_pad *mediaPads = reinterpret_cast<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(Error) << "Failed to find entity with id: "
+ << entity_id;
+ return -ENODEV;
+ }
+
+ MediaPad *pad = new MediaPad(&mediaPads[i], mediaEntity);
+ mediaEntity->addPad(pad);
+
+ addObject(pad);
+ }
+
+ return 0;
+}
+
+/*
+ * For each entity in the media graph create a MediaEntity and store a
+ * reference in the MediaObject global pool and in the global vector of
+ * entities.
+ */
+void MediaDevice::populateEntities(const struct media_v2_topology &topology)
+{
+ media_v2_entity *mediaEntities = reinterpret_cast<media_v2_entity *>
+ (topology.ptr_entities);
+
+ for (unsigned int i = 0; i < topology.num_entities; ++i) {
+ MediaEntity *entity = new MediaEntity(&mediaEntities[i]);
+
+ addObject(entity);
+ entities_.push_back(entity);
+ }
+}
+
+/**
+ * \brief Populate the media graph with media objects
+ *
+ * This function 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.
+ *
+ * MediaEntities are stored in a global list in the MediaDevice itself to ease
+ * lookup, while MediaPads are accessible from the MediaEntity they belong
+ * to only and MediaLinks from the MediaPad they connect.
+ *
+ * \return 0 on success, a negative error code otherwise
+ */
+int MediaDevice::populate()
+{
+ struct media_v2_topology topology = { };
+ struct media_v2_entity *ents = nullptr;
+ struct media_v2_link *links = nullptr;
+ struct media_v2_pad *pads = nullptr;
+ __u64 version = -1;
+ int ret;
+
+ /*
+ * Keep calling G_TOPOLOGY until the version number stays stable.
+ */
+ while (true) {
+ topology.topology_version = 0;
+ topology.ptr_entities = reinterpret_cast<__u64>(ents);
+ topology.ptr_links = reinterpret_cast<__u64>(links);
+ topology.ptr_pads = reinterpret_cast<__u64>(pads);
+
+ ret = ioctl(fd_, MEDIA_IOC_G_TOPOLOGY, &topology);
+ if (ret < 0) {
+ ret = -errno;
+ LOG(Error) << "Failed to enumerate topology: "
+ << strerror(-ret);
+ return ret;
+ }
+
+ if (version == topology.topology_version)
+ break;
+
+ delete[] links;
+ delete[] ents;
+ delete[] pads;
+
+ ents = new media_v2_entity[topology.num_entities];
+ links = new media_v2_link[topology.num_links];
+ pads = new media_v2_pad[topology.num_pads];
+
+ version = topology.topology_version;
+ }
+
+ /* Populate entities, pads and links. */
+ populateEntities(topology);
+
+ ret = populatePads(topology);
+ if (ret)
+ goto error_free_objs;
+
+ ret = populateLinks(topology);
+error_free_objs:
+ if (ret)
+ clear();
+
+ delete[] links;
+ delete[] ents;
+ delete[] pads;
+
+ return ret;
+}
+
+/**
+ * \var MediaDevice::objects_
+ * \brief Global map of media objects (entities, pads, links) keyed by their
+ * object id.
+ */
+
+/**
+ * \var MediaDevice::entities_
+ * \brief Global list of media entities in the media graph
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 2985d40c..2ff5bb5e 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -4,6 +4,7 @@ libcamera_sources = files([
'device_enumerator.cpp',
'log.cpp',
'main.cpp',
+ 'media_device.cpp',
'media_object.cpp',
'pipeline_handler.cpp',
])
@@ -11,6 +12,7 @@ libcamera_sources = files([
libcamera_headers = files([
'include/device_enumerator.h',
'include/log.h',
+ 'include/media_device.h',
'include/media_object.h',
'include/pipeline_handler.h',
'include/utils.h',