blob: 0fd8ab70d561b1fa52c66b12d46d38f28ead827b [file] [log] [blame]
/* 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;
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(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(100);
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.c_str())) {
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)