| /* 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) |