1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 1#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2020, Google Inc. # # Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com> # # gen-formats.py - Generate formats definitions from YAML import argparse import re import string import sys import yaml class DRMFourCC(object): format_regex = re.compile(r"#define (DRM_FORMAT_[A-Z0-9_]+)[ \t]+fourcc_code\(('.', '.', '.', '.')\)") mod_vendor_regex = re.compile(r"#define DRM_FORMAT_MOD_VENDOR_([A-Z0-9_]+)[ \t]+([0-9a-fA-Fx]+)") mod_regex = re.compile(r"#define ([A-Za-z0-9_]+)[ \t]+fourcc_mod_code\(([A-Z0-9_]+), ([0-9a-fA-Fx]+)\)") def __init__(self, filename): self.formats = {} self.vendors = {} self.mods = {} for line in open(filename, 'rb').readlines(): line = line.decode('utf-8') match = DRMFourCC.format_regex.match(line) if match: format, fourcc = match.groups() self.formats[format] = fourcc continue match = DRMFourCC.mod_vendor_regex.match(line) if match: vendor, value = match.groups() self.vendors[vendor] = int(value, 0) continue match = DRMFourCC.mod_regex.match(line) if match: mod, vendor, value = match.groups() self.mods[mod] = (vendor, int(value, 0)) continue def fourcc(self, name): return self.formats[name] def mod(self, name): vendor, value = self.mods[name] return self.vendors[vendor], value def generate_h(formats, drm_fourcc): template = string.Template('constexpr PixelFormat ${name}{ __fourcc(${fourcc}), __mod(${mod}) };') fmts = [] for format in formats: name, format = format.popitem() fourcc = drm_fourcc.fourcc(format['fourcc']) if format.get('big-endian'): fourcc += '| DRM_FORMAT_BIG_ENDIAN' data = { 'name': name, 'fourcc': fourcc, 'mod': '0, 0', } mod = format/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2021, Google Inc. * * Fence test */ #include <iostream> #include <memory> #include <sys/eventfd.h> #include <unistd.h> #include <libcamera/base/event_dispatcher.h> #include <libcamera/base/thread.h> #include <libcamera/base/timer.h> #include <libcamera/base/unique_fd.h> #include <libcamera/base/utils.h> #include <libcamera/fence.h> #include <libcamera/framebuffer_allocator.h> #include "camera_test.h" #include "test.h" using namespace libcamera; using namespace std; using namespace std::chrono_literals; class FenceTest : public CameraTest, public Test { public: FenceTest(); protected: int init() override; int run() override; private: int validateExpiredRequest(Request *request); int validateRequest(Request *request); void requestComplete(Request *request); void requestRequeue(Request *request); void signalFence(); EventDispatcher *dispatcher_; UniqueFD eventFd_; UniqueFD eventFd2_; Timer fenceTimer_; std::vector<std::unique_ptr<Request>> requests_; std::unique_ptr<CameraConfiguration> config_; std::unique_ptr<FrameBufferAllocator> allocator_; Stream *stream_; bool expectedCompletionResult_ = true; bool setFence_ = true; /* * Request IDs track the number of requests that have completed. They * are one-based, and don't wrap. */ unsigned int completedRequestId_; unsigned int signalledRequestId_; unsigned int expiredRequestId_; unsigned int nbuffers_; int efd2_; int efd_; }; FenceTest::FenceTest() : CameraTest("platform/vimc.0 Sensor B") { } int FenceTest::init() { /* Make sure the CameraTest constructor succeeded. */ if (status_ != TestPass) return status_; dispatcher_ = Thread::current()->eventDispatcher(); /* * Create two eventfds to model the fences. This is enough to support the * needs of libcamera which only needs to wait for read events through * poll(). Once native support for fences will be available in the * backend kernel APIs this will need to be replaced by a sw_sync fence, * but that requires debugfs. */ eventFd_ = UniqueFD(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)); eventFd2_ = UniqueFD(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)); if (!eventFd_.isValid() || !eventFd2_.isValid()) { cerr << "Unable to create eventfd" << endl; return TestFail; } efd_ = eventFd_.get(); efd2_ = eventFd2_.get(); config_ = camera_->generateConfiguration({ StreamRole::Viewfinder }); if (!config_ || config_->size() != 1) { cerr << "Failed to generate default configuration" << endl; return TestFail; } 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; } StreamConfiguration &cfg = config_->at(0); stream_ = cfg.stream(); allocator_ = std::make_unique<FrameBufferAllocator>(camera_); if (allocator_->allocate(stream_) < 0) return TestFail; nbuffers_ = allocator_->buffers(stream_).size(); if (nbuffers_ < 2) { cerr << "Not enough buffers available" << endl; return TestFail; } completedRequestId_ = 0; /* * All but two requests are queued without a fence. Request * expiredRequestId_ will be queued with a fence that we won't signal * (which is then expected to expire), and request signalledRequestId_ * will be queued with a fence that gets signalled. Select nbuffers_ * and nbuffers_ * 2 for those two requests, to space them by a few * frames while still not requiring a long time for the test to * complete. */ expiredRequestId_ = nbuffers_; signalledRequestId_ = nbuffers_ * 2; return TestPass; } int FenceTest::validateExpiredRequest(Request *request) { /* The last request is expected to fail. */ if (request->status() != Request::RequestCancelled) { cerr << "The last request should have failed: " << endl; return TestFail; } FrameBuffer *buffer = request->buffers().begin()->second; std::unique_ptr<Fence> fence = buffer->releaseFence(); if (!fence) { cerr << "The expired fence should be present" << endl; return TestFail; } if (!fence->isValid()) { cerr << "The expired fence should be valid" << endl; return TestFail; } UniqueFD fd = fence->release(); if (fd.get() != efd_) { cerr << "The expired fence file descriptor should not change" << endl; return TestFail; } return TestPass; } int FenceTest::validateRequest(Request *request) { uint64_t cookie = request->cookie(); /* All requests but the last are expected to succeed. */ if (request->status() != Request::RequestComplete) { cerr << "Unexpected request failure: " << cookie << endl; return TestFail; } /* A successfully completed request should have the Fence closed. */ const Request::BufferMap &buffers = request->buffers(); FrameBuffer *buffer = buffers.begin()->second; std::unique_ptr<Fence> bufferFence = buffer->releaseFence(); if (bufferFence) { cerr << "Unexpected valid fence in completed request" << endl; return TestFail; } return TestPass; } void FenceTest::requestRequeue(Request *request) { const Request::BufferMap &buffers = request->buffers(); const Stream *stream = buffers.begin()->first; FrameBuffer *buffer = buffers.begin()->second; request->reuse(); if (completedRequestId_ == signalledRequestId_ - nbuffers_ && setFence_) { /* * This is the request that will be used to test fence * signalling when it completes next time. Add a fence to it, * using efd2_. The main loop will signal the fence by using a * timer to write to the efd2_ file descriptor before the fence * expires. */ std::unique_ptr<Fence> fence = std::make_unique<Fence>(std::move(eventFd2_)); request->addBuffer(stream, buffer, std::move(fence)); } else { /* All the other requests continue to operate without fences. */ request->addBuffer(stream, buffer); } camera_->queueRequest(request); } void FenceTest::requestComplete(Request *request) { completedRequestId_++; /* * Request expiredRequestId_ is expected to fail as its fence has not * been signalled. * * Validate the fence status but do not re-queue it. */ if (completedRequestId_ == expiredRequestId_) { if (validateExpiredRequest(request) != TestPass) expectedCompletionResult_ = false; dispatcher_->interrupt(); return; } /* Validate all other requests. */ if (validateRequest(request) != TestPass) { expectedCompletionResult_ = false; dispatcher_->interrupt(); return; } requestRequeue(request); /* * Interrupt the dispatcher to return control to the main loop and * activate the fenceTimer. */ dispatcher_->interrupt(); } /* Callback to signal a fence waiting on the eventfd file descriptor. */ void FenceTest::signalFence() { uint64_t value = 1; int ret; ret = write(efd2_, &value, sizeof(value)); if (ret != sizeof(value)) cerr << "Failed to signal fence" << endl; setFence_ = false; dispatcher_->processEvents(); } int FenceTest::run() { for (const auto &[i, buffer] : utils::enumerate(allocator_->buffers(stream_))) { std::unique_ptr<Request> request = camera_->createRequest(i); if (!request) { cerr << "Failed to create request" << endl; return TestFail; } int ret; if (i == expiredRequestId_ - 1) { /* This request will have a fence, and it will expire. */ std::unique_ptr<Fence> fence = std::make_unique<Fence>(std::move(eventFd_)); if (!fence->isValid()) { cerr << "Fence should be valid" << endl; return TestFail; } ret = request->addBuffer(stream_, buffer.get(), std::move(fence)); } else { /* All other requests will have no Fence. */ ret = request->addBuffer(stream_, buffer.get()); } if (ret) { cerr << "Failed to associate buffer with request" << endl; return TestFail; } requests_.push_back(std::move(request)); } camera_->requestCompleted.connect(this, &FenceTest::requestComplete); if (camera_->start()) { cerr << "Failed to start camera" << endl; return TestFail; } for (std::unique_ptr<Request> &request : requests_) { if (camera_->queueRequest(request.get())) { cerr << "Failed to queue request" << endl; return TestFail; } } expectedCompletionResult_ = true; /* This timer serves to signal fences associated with "signalledRequestId_" */ Timer fenceTimer; fenceTimer.timeout.connect(this, &FenceTest::signalFence); /* * Loop long enough for all requests to complete, allowing 500ms per * request. */ Timer timer; timer.start(500ms * (signalledRequestId_ + 1)); while (timer.isRunning() && expectedCompletionResult_ && completedRequestId_ <= signalledRequestId_ + 1) { if (completedRequestId_ == signalledRequestId_ - 1 && setFence_) /* * The request just before signalledRequestId_ has just * completed. Request signalledRequestId_ has been * queued with a fence, and libcamera is likely already * waiting on the fence, or will soon. Start the timer * to signal the fence in 10 msec. */ fenceTimer.start(10ms); dispatcher_->processEvents(); } camera_->requestCompleted.disconnect(); if (camera_->stop()) { cerr << "Failed to stop camera" << endl; return TestFail; } return expectedCompletionResult_ ? TestPass : TestFail; } TEST_REGISTER(FenceTest)