265 lines
5.7 KiB
C++
265 lines
5.7 KiB
C++
/* 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)
|