From patchwork Fri Jul 25 10:33:51 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 23960 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id D35ABC3237 for ; Fri, 25 Jul 2025 10:34:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 79AF0690DC; Fri, 25 Jul 2025 12:34:09 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OkqJBlUT"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B3E14690BC for ; Fri, 25 Jul 2025 12:33:59 +0200 (CEST) Received: from [192.168.0.172] (mob-5-90-139-29.net.vodafone.it [5.90.139.29]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DF72178E; Fri, 25 Jul 2025 12:33:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1753439600; bh=MheqSlSDoWQPQzlMwHnXKCZq6yEzivBzqHP37UxYB2Y=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=OkqJBlUTUoCxcnxqaO+g/AoqKU8GBgg/swmziWgALM1tlaffNLQgHQid8QH/iifgq lm6GJZkpL5x8ZllQ6SXxDvV41R3oZoy9m/sLCoeLFGIkx4tY3iFeimXqoSxI/gUBfv 5WP0++Z0HEiRdmf67IfIMbFOxzcUTUUgibhEo/4k= From: Jacopo Mondi Date: Fri, 25 Jul 2025 12:33:51 +0200 Subject: [PATCH 6/9] libcamera: media-device: Introduce SharedMediaDevice MIME-Version: 1.0 Message-Id: <20250725-multicontext-v1-6-ea558291e101@ideasonboard.com> References: <20250725-multicontext-v1-0-ea558291e101@ideasonboard.com> In-Reply-To: <20250725-multicontext-v1-0-ea558291e101@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=9954; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=MheqSlSDoWQPQzlMwHnXKCZq6yEzivBzqHP37UxYB2Y=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBog12UFPasn238Sp/uOOJcU82+qewXNDgbdEoEX xUbsUBVRFqJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaINdlAAKCRByNAaPFqFW PAG9D/44+42lD1nKTlxPur9EmawIO7/bcUpbKVt4e3FniiVdO1oVr6MI9AN/B2uluazHuHzhYLs 5KQx6QZkwLw6T20qJRHReUnF3TbP8sCNnvBVJ598xalRcSIcYHYSCoom/Mn5ta/vE8csWER3sey fZvu66oqohukfXBXcxLgoImRAZb/nk8MRrJ4h08WFc1tCZPR10RFkNeM/kX+F8PhKLGgiDkPdL8 4iqFB5o7WMoPxNH4TnZkkF8ELCMNliULlBxgkrtKpvub8LDygrioQn4KD9n4aBWYA+Q/cGNbhQf oI2oFM4Wk+xv6k3EsOFGZeLsE4d08RN1OcJblbmyxnW1icAEf5N7/zT5ZwpxFQ5abo/ZTneIjrj A8Ymxrt5cxLVIogWbtaHPrKWp8RvrrjhqXdMzqf87ToPKTyeWeDpYJMqHcfdSpDvYC8uuBYrRai zzu0DvmQC1PZYjO8OZnAPfrRd6yp1EuNhH8/6SMifYgeO+slkO1WvLmMxbnloyWj0PcurAA5D0q oYFYF8+hECCcXCJnVdjzy3hhbtNktkCajEYXabR5HhkshBxlvV9PegkNd+ieN7aKMpWqqCRuUV5 SdRi4GAFqfeJg1x3ccEQ23vQvQ8kqEpEKp3lNMJAzTMeoThN91caz8Qk1xngtPJStM57bNawnER rdZnnkDFxeGyTdw== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Introduce the SharedMediaDevice class derived from the MediaDevice class, that represents a media device that supports multi-context operations. Disallow direct construction of MediaDevice and introduce the MediaDeviceFactory class that instantiate the correct class from the MediaDevice class hierarchy inspecting the newly introduced media_device_info.flags field. The new SharedMediaDevice class represents a media device that can be concurrently accessed by different processes, for this reason overload the acquire/lock operations to be effectively nops. Signed-off-by: Jacopo Mondi Reviewed-by: Isaac Scott --- include/libcamera/internal/media_device.h | 39 +++++++-- src/libcamera/device_enumerator.cpp | 8 +- src/libcamera/media_device.cpp | 139 ++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 8 deletions(-) diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h index b3a48b98d64b019fb42c5224d29105e1d57a3e3c..ea0e9d66009933bbb9613d27423d2f951520c887 100644 --- a/include/libcamera/internal/media_device.h +++ b/include/libcamera/internal/media_device.h @@ -21,18 +21,24 @@ namespace libcamera { +class MediaDeviceFactory +{ +public: + static std::unique_ptr + createMediaDevice(const std::string &deviceNode); +}; + class MediaDevice : protected Loggable { public: - MediaDevice(const std::string &deviceNode); - ~MediaDevice(); + virtual ~MediaDevice(); - bool acquire(); - void release(); - bool busy() const { return acquired_; } + virtual bool acquire(); + virtual void release(); + virtual bool busy() const { return acquired_; } - bool lock(); - void unlock(); + virtual bool lock(); + virtual void unlock(); int populate(); bool isValid() const { return valid_; } @@ -58,9 +64,12 @@ public: std::vector locateEntities(unsigned int function); protected: + MediaDevice(const std::string &deviceNode); std::string logPrefix() const override; private: + friend class MediaDeviceFactory; + int open(); void close(); @@ -92,4 +101,20 @@ private: std::vector entities_; }; +class SharedMediaDevice : public MediaDevice +{ +public: + bool acquire() override; + void release() override; + bool busy() const override { return false; } + + bool lock() override; + void unlock() override; + +private: + friend class MediaDeviceFactory; + + SharedMediaDevice(const std::string &deviceNode); +}; + } /* namespace libcamera */ diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp index ae17862f676310ef568e5331106ed661e84b5130..e73bb0050843c5281986e8e05d2013102905d270 100644 --- a/src/libcamera/device_enumerator.cpp +++ b/src/libcamera/device_enumerator.cpp @@ -217,7 +217,13 @@ DeviceEnumerator::~DeviceEnumerator() */ std::unique_ptr DeviceEnumerator::createDevice(const std::string &deviceNode) { - std::unique_ptr media = std::make_unique(deviceNode); + std::unique_ptr media = MediaDeviceFactory::createMediaDevice(deviceNode); + if (!media) { + LOG(DeviceEnumerator, Info) + << "Unable to create a media device for " << deviceNode + << ", skipping"; + return nullptr; + } int ret = media->populate(); if (ret < 0) { diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp index 353f34a81eca600a4a35594d782d3dc5dd690bdf..70d073f964bde0236585de083baca4cd9c30d193 100644 --- a/src/libcamera/media_device.cpp +++ b/src/libcamera/media_device.cpp @@ -31,6 +31,56 @@ namespace libcamera { LOG_DEFINE_CATEGORY(MediaDevice) +/** + * \class MediaDeviceFactory + * \brief Factory class that instantiates a MediaDevice or SharedMediaDevice + * + * The MediaDevice and SharedMediaDevice classes cannot be instantiated + * directly, but instead the MediaDeviceFactory shall be used to create + * an instance of these classes using the MediaDeviceFactory::createMediaDevice + * function. + */ + +/** + * \brief Create an instance of the MediaDevice class hierarchy + * \param[in] deviceNode The media device node path + * + * If a media device supports multi-context operations and advertise it through + * the presence of the MEDIA_DEVICE_FL_CONTEXT flag in the + * media_device_info.flags field an instance of SharedMediaDevice will be + * created and returned. If instead the media device doesn's support + * multi-context operations an instance of the MediaDevice class is created and + * returned. + * + * \return A unique_ptr<> that wraps an instance of SharedMediaDevice or + * MediaDevice + */ +std::unique_ptr +MediaDeviceFactory::createMediaDevice(const std::string &deviceNode) +{ + /* + * Inspect the media device info flags to decide which class to + * instantiate. + */ + auto fd = UniqueFD(::open(deviceNode.c_str(), O_RDWR | O_CLOEXEC)); + if (!fd.isValid()) + return {}; + + struct media_device_info info = {}; + int ret = ioctl(fd.get(), MEDIA_IOC_DEVICE_INFO, &info); + if (ret) { + LOG(MediaDevice, Error) + << "Failed to get media device info " << strerror(-ret); + return {}; + } + + if (info.flags & MEDIA_DEVICE_FL_CONTEXT) + return std::unique_ptr + (new SharedMediaDevice(deviceNode)); + + return std::unique_ptr(new MediaDevice(deviceNode)); +} + /** * \class MediaDevice * \brief The MediaDevice represents a Media Controller device with its full @@ -40,6 +90,9 @@ LOG_DEFINE_CATEGORY(MediaDevice) * created, and that association is kept for the lifetime of the MediaDevice * instance. * + * Instances of MediaDevice are created by the MediaDeviceFactory + * createMediaDevice() function and cannot be directly instantiated. + * * 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, @@ -56,6 +109,30 @@ LOG_DEFINE_CATEGORY(MediaDevice) * managers to claim media devices they support during enumeration. */ +/** + * \class SharedMediaDevice + * \brief The SharedMediaDevice represents a Media Controller device that + * supports multi-context operations + * + * \sa MediaDevice + * \sa MediaDeviceFactory + * + * A SharedMediaDevice is associated with a media controller device node that + * supports multi-context operations. Compared to the MediaDevice base class a + * SharedMediaDevice allows multiple open() calls to happen and is never + * uniquely owned as each time the media device gets open a new context gets + * created. This means that the acquire() and release() operations are + * effectively nop (and as a consequence busy() always return false). + * + * Instances of MediaDevice are created by the MediaDeviceFactory + * createMediaDevice() function and cannot be directly instantiated. + * + * Similarly to the MediaDevice class, instances of SharedMediaDevice are + * created with an empty media graph and needs to be populated using the + * populate() function. Media entities enumerated in the graph can be accessed + * through the same functions as the ones provided by the MediaDevice class. + */ + /** * \brief Construct a MediaDevice * \param[in] deviceNode The media device node path @@ -68,6 +145,17 @@ MediaDevice::MediaDevice(const std::string &deviceNode) { } +/** + * \brief Construct a SharedMediaDevice + * \param[in] deviceNode The media device node path + * + * \copydoc MediaDevice::MediaDevice + */ +SharedMediaDevice::SharedMediaDevice(const std::string &deviceNode) + : MediaDevice(deviceNode) +{ +} + MediaDevice::~MediaDevice() { fd_.reset(); @@ -113,6 +201,19 @@ bool MediaDevice::acquire() return true; } +/** + * \brief Acquiring a SharedMediaDevice is a nop + * + * A SharedMediaDevice is designed to be opened multiple times. Acquiring + * a SharedMediaDevice is effectively a nop. + * + * \return Always return true + */ +bool SharedMediaDevice::acquire() +{ + return true; +} + /** * \brief Release a device previously claimed for exclusive use * \sa acquire(), busy() @@ -123,6 +224,15 @@ void MediaDevice::release() acquired_ = false; } +/** + * \brief Releasing a SharedMediaDevice is a nop + * + * As acquiring a SharedMediaDevice is a nop, releasing it is a nop as well. + */ +void SharedMediaDevice::release() +{ +} + /** * \brief Lock the device to prevent it from being used by other instances of * libcamera @@ -151,6 +261,19 @@ bool MediaDevice::lock() return true; } +/** + * \brief Locking a SharedMediaDevice is a nop + * + * As SharedMediaDevice is designed to be opened multiple times locking + * a SharedMediaDevice is effectively a nop. + * + * \return Always return true + */ +bool SharedMediaDevice::lock() +{ + return true; +} + /** * \brief Unlock the device and free it for use for libcamera instances * @@ -168,6 +291,15 @@ void MediaDevice::unlock() std::ignore = lockf(fd_.get(), F_ULOCK, 0); } +/** + * \brief Unlocking a SharedMediaDevice is a nop + * + * As locking a SharedMediaDevice is a nop, unlocking it is a nop as well. + */ +void SharedMediaDevice::unlock() +{ +} + /** * \fn MediaDevice::busy() * \brief Check if a device is in use @@ -176,6 +308,13 @@ void MediaDevice::unlock() * \sa acquire(), release() */ +/** + * \fn SharedMediaDevice::busy() + * \brief As SharedMediaDevice is designed to be opened multiple times, for this + * reason it is never marked as busy + * \return Always returns false + */ + /** * \brief Populate the MediaDevice with device information and media objects *