From patchwork Wed Oct 28 12:02:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 28657 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 5FA9DC324C for ; Wed, 28 Oct 2026 12:02:32 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 676916907F; Wed, 28 Oct 2026 13:02:30 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gj1plPgG"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D01E368FC6 for ; Wed, 28 Oct 2026 13:02:26 +0100 (CET) Received: from ping.linuxembedded.co.uk (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E364AC6A; Wed, 28 Oct 2026 13:00:13 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793188814; bh=ZDRD6gDy9g1k1LvrbBguB7c34tLPP3D1b5wXY5z8+MQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=gj1plPgGdqJ3BhrRb2nfvz1m+5kqxWFwB4oVZkkqRQKgzK+oBwu2Gn2N47yIjKApq 5G/Dl354O4qhqgq3rZMEmna/swJLQ+3fx0aSzN8fXydypJWDqkSlh07RL3xzbIGOce v/7zQos7hNmeu6zl35PhMpENkn/NJ22pgfTKqVCQ= From: Kieran Bingham Date: Wed, 28 Oct 2026 12:02:18 +0000 Subject: [PATCH v2 1/2] libcamera: camera_manager: Non-fatal duplicate id detection MIME-Version: 1.0 Message-Id: <20261028-kbingham-duplicate-id-v2-1-e82448998d88@ideasonboard.com> References: <20261028-kbingham-duplicate-id-v2-0-e82448998d88@ideasonboard.com> In-Reply-To: <20261028-kbingham-duplicate-id-v2-0-e82448998d88@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham , =?utf-8?q?Barnab?= =?utf-8?b?w6FzIFDFkWN6ZQ==?= X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1793188945; l=1420; i=kieran.bingham@ideasonboard.com; s=20260207; h=from:subject:message-id; bh=ZDRD6gDy9g1k1LvrbBguB7c34tLPP3D1b5wXY5z8+MQ=; b=tC+m9GJsoevkWCByBzLrVAAC8GmUv2pr6wRbIm4oMgt/zXUJ0K+ZUTmXKsvMOkfC3clLwrnyy L6OTrIkJj1qDj1CAgaQW7lZDtglTr3NOqdd/s6Ebft3hv0eoHwhpcsh X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=FVXKN7YuwHc6UtbRUeTMAmranfsQomA+vnilfglWdaY= 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" There are some hardware configurations which currently generate a duplicate camera ID when the camera is on a shared but multiplexed bus. At present this is somewhat difficult to adapt to ensure a consistent and unique id. While this case is undesired, it presently causes libcamera to hit a Fatal asssertion on real world hardware which then causes pipewire to close. This behaviour is even less desirable, and we can easily continue to operate libcamera without a potentially duplicate camera. Reduce the Fatal log level to an Error allowing the code to continue without constructing this duplicate camera. Reviewed-by: Barnabás Pőcze Signed-off-by: Kieran Bingham --- src/libcamera/camera_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp index 0dd4e0c590a1..9c9073053710 100644 --- a/src/libcamera/camera_manager.cpp +++ b/src/libcamera/camera_manager.cpp @@ -210,7 +210,7 @@ void CameraManager::Private::addCamera(std::shared_ptr camera) for (const std::shared_ptr &c : cameras_) { if (c->id() == camera->id()) { - LOG(Camera, Fatal) + LOG(Camera, Error) << "Trying to register a camera with a duplicated ID '" << camera->id() << "'"; return; From patchwork Wed Oct 28 12:02:19 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 28658 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 0CF5DC3307 for ; Wed, 28 Oct 2026 12:02:34 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3241D69079; Wed, 28 Oct 2026 13:02:31 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="loWzPYM9"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0701269073 for ; Wed, 28 Oct 2026 13:02:27 +0100 (CET) Received: from ping.linuxembedded.co.uk (cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 27E6C13D3; Wed, 28 Oct 2026 13:00:14 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793188814; bh=foXpu5E5+Gg+U5YDZpW454pU3SY4B1zT4zxLZLgXt9s=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=loWzPYM9G8eHRSHTkEfXZBcxecIomlja5ubS2tfaih6AGtueP0D0eSDg26nF8cy+b OheDy2tzKULoD9Jn7hXqr13UoTTGDYtUfIDI413krXPd8Q49TQ8atVhfzlS3LKDZnk RTI8kLDHYdgV4AKJ4DrJkWiicnbcCNWUu2izFL+w= From: Kieran Bingham Date: Wed, 28 Oct 2026 12:02:19 +0000 Subject: [PATCH v2 2/2] test: pipeline: Add a duplicate camera construction test MIME-Version: 1.0 Message-Id: <20261028-kbingham-duplicate-id-v2-2-e82448998d88@ideasonboard.com> References: <20261028-kbingham-duplicate-id-v2-0-e82448998d88@ideasonboard.com> In-Reply-To: <20261028-kbingham-duplicate-id-v2-0-e82448998d88@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1793188945; l=6199; i=kieran.bingham@ideasonboard.com; s=20260207; h=from:subject:message-id; bh=foXpu5E5+Gg+U5YDZpW454pU3SY4B1zT4zxLZLgXt9s=; b=cYH5dXxyEOzxVJODYxDC21UzRTn2KEkTMa4DRZvDIUH7dHsKbZlfE8Kn7pEl8m/zXRP6KMkxO 50ffdAGvilGAtQMZ/sK7zCIbcSsJvVEvhZrR3Kwqqym4UYbFB1DbXvr X-Developer-Key: i=kieran.bingham@ideasonboard.com; a=ed25519; pk=FVXKN7YuwHc6UtbRUeTMAmranfsQomA+vnilfglWdaY= 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" Prepare a test pipeline handler which will allow us to construct custom cameras. Use this pipeline handler to specifically prepare an environment to be able to construct multiple cameras with the same ID to ensure we do not have any system regressions from this in the future. Signed-off-by: Kieran Bingham --- test/meson.build | 1 + test/pipeline/duplicate_camera_id.cpp | 202 ++++++++++++++++++++++++++++++++++ test/pipeline/meson.build | 14 +++ 3 files changed, 217 insertions(+) diff --git a/test/meson.build b/test/meson.build index 363a2829d895..62f8c7da8b30 100644 --- a/test/meson.build +++ b/test/meson.build @@ -32,6 +32,7 @@ subdir('ipa') subdir('ipc') subdir('log') subdir('media_device') +subdir('pipeline') subdir('process') subdir('py') subdir('serialization') diff --git a/test/pipeline/duplicate_camera_id.cpp b/test/pipeline/duplicate_camera_id.cpp new file mode 100644 index 000000000000..d91cb02f53e6 --- /dev/null +++ b/test/pipeline/duplicate_camera_id.cpp @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy. + */ + +#include +#include + +#include +#include + +#include "libcamera/internal/camera.h" +#include "libcamera/internal/pipeline_handler.h" + +#include "test.h" + +using namespace libcamera; + +namespace { + +/* + * There's no easy way to communicate to our custom test pipeline + * handler. Use a global here to state and track how many cameras should + * be constructed. + */ +struct MatchState { + unsigned int requested; + unsigned int created; +} matchState; + +std::string cameraId = "NotVeryUniqueCameraId"; + +} /* namespace */ + +namespace libcamera { + +class TestCameraData : public Camera::Private +{ +public: + TestCameraData(PipelineHandler *pipe) + : Camera::Private(pipe) + { + } +}; + +class PipelineHandlerTest : public PipelineHandler +{ +public: + PipelineHandlerTest(CameraManager *manager) + : PipelineHandler(manager) + { + (void)manager; + } + + std::unique_ptr + generateConfiguration(Camera *camera, + std::span roles) override + { + (void)camera; + (void)roles; + return {}; + } + + int configure(Camera *camera, CameraConfiguration *config) override + { + (void)camera; + (void)config; + return true; + } + + int exportFrameBuffers(Camera *camera, Stream *stream, + std::vector> *buffers) override + { + (void)camera; + (void)stream; + (void)buffers; + return true; + } + + int start(Camera *camera, const ControlList *controls) override + { + (void)camera; + (void)controls; + return false; + } + + void stopDevice(Camera *camera) override + { + (void)camera; + } + + int queueRequestDevice(Camera *camera, Request *request) override + { + (void)camera; + (void)request; + return false; + } + + bool match(DeviceEnumerator *enumerator) override + { + (void)enumerator; + + /* Only construct requested number of pipelines */ + if (matchState.created >= matchState.requested) + return false; + + std::unique_ptr data = std::make_unique(this); + + std::set streams; /* No streams yet on this camera... */ + std::shared_ptr camera = Camera::create(std::move(data), cameraId, streams); + + registerCamera(std::move(camera)); + + matchState.created++; + + return true; + } +}; + +REGISTER_PIPELINE_HANDLER(PipelineHandlerTest, "test") + +} /* namespace libcamera */ + +namespace { + +class CameraIdTest : public Test +{ +protected: + int init() override + { + /* Make sure CameraManager only uses our custom test pipeline */ + if (setenv("LIBCAMERA_PIPELINES_MATCH_LIST", "test", 1) != 0) { + std::cerr << "Failed to set pipeline match list" << std::endl; + return TestFail; + } + + return TestPass; + } + + int cameraConstructionTest() + { + libcamera::CameraManager cm; + + cm.start(); + + /* + * Even if we try to create multiple cameras, we always expect + * only one to succeed. + */ + const std::vector> &cameras = cm.cameras(); + if (cameras.size() != 1) { + std::cerr + << "Expected 1 camera, got " + << cameras.size() << std::endl; + return TestFail; + } + + /* + * I can't see how it could ever be anything but this here, but + * let's check anyway as the intent of the test is that we get + * only a single camera with the expected ID. + */ + const std::string &id = cameras[0]->id(); + if (id != cameraId) { + std::cerr + << "Expected '" << cameraId + << "', got '" << id << "'" << std::endl; + return TestFail; + } + + return TestPass; + } + + int run() override + { + int ret; + + /* Validate a single camera construction */ + matchState.created = 0; + matchState.requested = 1; + + ret = cameraConstructionTest(); + if (ret != TestPass) { + return ret; + } + + /* Validate multiple camera construction */ + matchState.created = 0; + matchState.requested = 3; + + ret = cameraConstructionTest(); + if (ret != TestPass) { + return ret; + } + + return TestPass; + } +}; + +} /* namespace */ + +TEST_REGISTER(CameraIdTest) diff --git a/test/pipeline/meson.build b/test/pipeline/meson.build new file mode 100644 index 000000000000..c1f4ee969202 --- /dev/null +++ b/test/pipeline/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: CC0-1.0 + +pipeline_tests = [ + {'name': 'duplicate_camera_id', 'sources': ['duplicate_camera_id.cpp']}, +] + +foreach test : pipeline_tests + exe = executable(test['name'], test['sources'], + dependencies : libcamera_private, + link_with : test_libraries, + include_directories : test_includes_internal) + + test(test['name'], exe, suite : 'pipeline', is_parallel : true) +endforeach