new file mode 100644
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * media_device_link_test.cpp - Tests link handling on VIMC media device
+ */
+#include <iostream>
+#include <memory>
+
+#include "device_enumerator.h"
+#include "media_device.h"
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+/*
+ * This link test requires a vimc device in order to exercise the
+ * MediaObject link handling API on a graph with a predetermined topology.
+ *
+ * vimc is a Media Controller kernel driver that creates virtual devices.
+ * From a userspace point of view they appear as normal media controller
+ * devices, but are not backed by any particular piece of hardware. They can
+ * thus be used for testing purpose without depending on a particular hardware
+ * platform.
+ *
+ * If no vimc device is found (most likely because the vimc driver is not
+ * loaded) the test is skipped.
+ */
+
+class MediaDeviceLinkTest : public Test
+{
+ int init()
+ {
+ enumerator = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());
+ if (!enumerator) {
+ cerr << "Failed to create device enumerator" << endl;
+ return TestFail;
+ }
+
+ if (enumerator->enumerate()) {
+ cerr << "Failed to enumerate media devices" << endl;
+ return TestFail;
+ }
+
+ DeviceMatch dm("vimc");
+ dev_ = enumerator->search(dm);
+ if (!dev_) {
+ cerr << "No VIMC media device found: skip test" << endl;
+ return TestSkip;
+ }
+
+ dev_->acquire();
+
+ if (dev_->open()) {
+ cerr << "Failed to open media device at "
+ << dev_->devnode() << endl;
+ return TestFail;
+ }
+
+ return 0;
+ }
+
+ int run()
+ {
+ /*
+ * First of all disable all links in the media graph to
+ * ensure we start from a known state.
+ */
+ if (dev_->disableLinks()) {
+ cerr << "Failed to disable all links in the media graph";
+ return TestFail;
+ }
+
+ /*
+ * Test if link can be consistently retrieved through the
+ * different methods the media device offers.
+ */
+ string linkName("'Debayer A':[1] -> 'Scaler':[0]'");
+ MediaLink *link = dev_->link("Debayer A", 1, "Scaler", 0);
+ if (!link) {
+ cerr << "Unable to find link: " << linkName
+ << " using lookup by name" << endl;
+ return TestFail;
+ }
+
+ MediaEntity *source = dev_->getEntityByName("Debayer A");
+ if (!source) {
+ cerr << "Unable to find entity: 'Debayer A'" << endl;
+ return TestFail;
+ }
+
+ MediaEntity *sink = dev_->getEntityByName("Scaler");
+ if (!sink) {
+ cerr << "Unable to find entity: 'Scaler'" << endl;
+ return TestFail;
+ }
+
+ MediaLink *link2 = dev_->link(source, 1, sink, 0);
+ if (!link2) {
+ cerr << "Unable to find link: " << linkName
+ << " using lookup by entity" << endl;
+ return TestFail;
+ }
+
+ if (link != link2) {
+ cerr << "Link lookup by name and by entity don't match"
+ << endl;
+ return TestFail;
+ }
+
+ link2 = dev_->link(source->getPadByIndex(1),
+ sink->getPadByIndex(0));
+ if (!link2) {
+ cerr << "Unable to find link: " << linkName
+ << " using lookup by pad" << endl;
+ return TestFail;
+ }
+
+ if (link != link2) {
+ cerr << "Link lookup by name and by pad don't match"
+ << endl;
+ return TestFail;
+ }
+
+ /* After reset the link shall not be enabled. */
+ if (link->flags() & MEDIA_LNK_FL_ENABLED) {
+ cerr << "Link " << linkName
+ << " should not be enabled after a device reset"
+ << endl;
+ return TestFail;
+ }
+
+ /* Enable the link and test if enabling was successful. */
+ if (link->setEnabled(true)) {
+ cerr << "Failed to enable link: " << linkName
+ << endl;
+ return TestFail;
+ }
+
+ if (!(link->flags() & MEDIA_LNK_FL_ENABLED)) {
+ cerr << "Link " << linkName
+ << " was enabled but it is reported as disabled"
+ << endl;
+ return TestFail;
+ }
+
+ /* Disable the link and test if disabling was successful. */
+ if (link->setEnabled(false)) {
+ cerr << "Failed to disable link: " << linkName
+ << endl;
+ return TestFail;
+ }
+
+ if (link->flags() & MEDIA_LNK_FL_ENABLED) {
+ cerr << "Link " << linkName
+ << " was disabled but it is reported as enabled"
+ << endl;
+ return TestFail;
+ }
+
+ /* Try to get a non existing link. */
+ linkName = "'Sensor A':[1] -> 'Scaler':[0]";
+ link = dev_->link("Sensor A", 1, "Scaler", 0);
+ if (link) {
+ cerr << "Link lookup for " << linkName
+ << " succeeded but link does not exist"
+ << endl;
+ return TestFail;
+ }
+
+ /* Now get an immutable link and try to disable it. */
+ linkName = "'Sensor A':[0] -> 'Raw Capture 0':[0]";
+ link = dev_->link("Sensor A", 0, "Raw Capture 0", 0);
+ if (!link) {
+ cerr << "Unable to find link: " << linkName
+ << " using lookup by name" << endl;
+ return TestFail;
+ }
+
+ if (!(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) {
+ cerr << "Link " << linkName
+ << " should be 'IMMUTABLE'" << endl;
+ return TestFail;
+ }
+
+ /* Disabling an immutable link shall fail. */
+ if (!link->setEnabled(false)) {
+ cerr << "Disabling immutable link " << linkName
+ << " succeeded but should have failed" << endl;
+ return TestFail;
+ }
+
+ /*
+ * Enable an disabled link, and verify it is disabled again
+ * after disabling all links in the media graph.
+ */
+ linkName = "'Debayer B':[1] -> 'Scaler':[0]'";
+ link = dev_->link("Debayer B", 1, "Scaler", 0);
+ if (!link) {
+ cerr << "Unable to find link: " << linkName
+ << " using lookup by name" << endl;
+ return TestFail;
+ }
+
+ if (link->setEnabled(true)) {
+ cerr << "Failed to enable link: " << linkName
+ << endl;
+ return TestFail;
+ }
+
+ if (!(link->flags() & MEDIA_LNK_FL_ENABLED)) {
+ cerr << "Link " << linkName
+ << " was enabled but it is reported as disabled"
+ << endl;
+ return TestFail;
+ }
+
+ if (dev_->disableLinks()) {
+ cerr << "Failed to disable all links in the media graph";
+ return TestFail;
+ }
+
+ if (link->flags() & MEDIA_LNK_FL_ENABLED) {
+ cerr << "All links in the media graph have been disabled"
+ << " but link " << linkName
+ << " is still reported as enabled" << endl;
+ return TestFail;
+ }
+
+ return 0;
+ }
+
+ void cleanup()
+ {
+ dev_->close();
+ dev_->release();
+ }
+
+private:
+ unique_ptr<DeviceEnumerator> enumerator;
+ MediaDevice *dev_;
+};
+
+TEST_REGISTER(MediaDeviceLinkTest);
@@ -1,5 +1,6 @@
media_device_tests = [
['media_device_print_test', 'media_device_print_test.cpp'],
+ ['media_device_link_test', 'media_device_link_test.cpp'],
]
foreach t : media_device_tests