/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2021, Google Inc. * * Test: * - Multiple reconfigurations of the Camera without stopping the CameraManager * - Validate there are no file descriptor leaks when using IPC */ #include <dirent.h> #include <fstream> #include <iostream> #include <libcamera/base/event_dispatcher.h> #include <libcamera/base/file.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> #include <libcamera/framebuffer_allocator.h> #include "camera_test.h" #include "test.h" using namespace libcamera; using namespace std; using namespace std::chrono_literals; namespace { class CameraReconfigure : public CameraTest, public Test { public: /* Initialize CameraTest with isolated IPA */ CameraReconfigure() : CameraTest(kCamId_, true) { } private: static constexpr const char *kCamId_ = "platform/vimc.0 Sensor B"; static constexpr const char *kIpaProxyName_ = "vimc_ipa_proxy"; static constexpr unsigned int kNumOfReconfigures_ = 10; void requestComplete(Request *request) { if (request->status() != Request::RequestComplete) return; const Request::BufferMap &buffers = request->buffers(); const Stream *stream = buffers.begin()->first; FrameBuffer *buffer = buffers.begin()->second; /* Reuse the request and re-queue it with the same buffers. */ request->reuse(); request->addBuffer(stream, buffer); camera_->queueRequest(request); } int startAndStop() { StreamConfiguration &cfg = config_->at(0); if (camera_->acquire()) { cerr << "Failed to acquire the camera" << endl; return TestFail; } if (camera_->configure(config_.get())) { cerr << "Failed to set default configuration" << endl; return TestFail; } Stream *stream = cfg.stream(); /* * The configuration is consistent so we can re-use the * same buffer allocation for each run. */ if (!allocated_) { int ret = allocator_->allocate(stream); if (ret < 0) { cerr << "Failed to allocate buffers" << endl; return TestFail; } allocated_ = true; } for (const unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) { unique_ptr<Request> request = camera_->createRequest(); if (!request) { cerr << "Failed to create request" << endl; return TestFail; } if (request->addBuffer(stream, buffer.get())) { cerr << "Failed to associate buffer with request" << endl; return TestFail; } requests_.push_back(std::move(request)); } camera_->requestCompleted.connect(this, &CameraReconfigure::requestComplete); if (camera_->start()) { cerr << "Failed to start camera" << endl; return TestFail; } for (unique_ptr<Request> &request : requests_) { if (camera_->queueRequest(request.get())) { cerr << "Failed to queue request" << endl; return TestFail; } } EventDispatcher *dispatcher = Thread::current()->eventDispatcher(); Timer timer; timer.start(100ms); while (timer.isRunning()) dispatcher->processEvents(); if (camera_->stop()) { cerr << "Failed to stop camera" << endl; return TestFail; } if (camera_->release()) { cerr << "Failed to release camera" << endl; return TestFail; } camera_->requestCompleted.disconnect(this); requests_.clear(); return 0; } int fdsOpen(pid_t pid) { string proxyFdPath = "/proc/" + to_string(pid) + "/fd"; DIR *dir; struct dirent *ptr; unsigned int openFds = 0; dir = opendir(proxyFdPath.c_str()); if (dir == nullptr) { int err = errno; cerr << "Error opening " << proxyFdPath << ": " << strerror(-err) << endl; return 0; } while ((ptr = readdir(dir)) != nullptr) { if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0)) continue; openFds++; } closedir(dir); return openFds; } pid_t findProxyPid() { string proxyPid; string proxyName(kIpaProxyName_); DIR *dir; struct dirent *ptr; dir = opendir("/proc"); while ((ptr = readdir(dir)) != nullptr) { if (ptr->d_type != DT_DIR) continue; string pname("/proc/" + string(ptr->d_name) + "/comm"); if (File::exists(pname)) { ifstream pfile(pname.c_str()); string comm; getline(pfile, comm); pfile.close(); proxyPid = comm == proxyName ? string(ptr->d_name) : ""; } if (!proxyPid.empty()) break; } closedir(dir); if (!proxyPid.empty()) return atoi(proxyPid.c_str()); return -1; } int init() override { if (status_ != TestPass) return status_; config_ = camera_->generateConfiguration({ StreamRole::StillCapture }); if (!config_ || config_->size() != 1) { cerr << "Failed to generate default configuration" << endl; return TestFail; } allocator_ = make_unique<FrameBufferAllocator>(camera_); allocated_ = false; return TestPass; } int run() override { unsigned int openFdsAtStart = 0; unsigned int openFds = 0; pid_t proxyPid = findProxyPid(); if (proxyPid < 0) { cerr << "Cannot find " << kIpaProxyName_ << " pid, exiting" << endl; return TestFail; } openFdsAtStart = fdsOpen(proxyPid); for (unsigned int i = 0; i < kNumOfReconfigures_; i++) { startAndStop(); openFds = fdsOpen(proxyPid); if (openFds == 0) { cerr << "No open fds found whereas " << "open fds at start: " << openFdsAtStart << endl; return TestFail; } if (openFds != openFdsAtStart) { cerr << "Leaking fds for " << kIpaProxyName_ << " - Open fds: " << openFds << " vs " << "Open fds at start: " << openFdsAtStart << endl; return TestFail; } } return TestPass; } bool allocated_; vector<unique_ptr<Request>> requests_; unique_ptr<CameraConfiguration> config_; unique_ptr<FrameBufferAllocator> allocator_; }; } /* namespace */ TEST_REGISTER(CameraReconfigure)