/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2019, Google Inc. * * request.cpp - Capture request handling */ #include "libcamera/internal/request.h" #include #include #include #include #include #include #include #include #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_controls.h" #include "libcamera/internal/framebuffer.h" #include "libcamera/internal/tracepoints.h" /** * \file libcamera/request.h * \brief Describes a frame capture request to be processed by a camera */ namespace libcamera { LOG_DEFINE_CATEGORY(Request) /** * \class Request::Private * \brief Request private data * * The Request::Private class stores all private data associated with a * request. It implements the d-pointer design pattern to hide core * Request data from the public API, and exposes utility functions to * internal users of the request (namely the PipelineHandler class and its * subclasses). */ /** * \brief Create a Request::Private * \param camera The Camera that creates the request */ Request::Private::Private(Camera *camera) : camera_(camera), cancelled_(false) { } Request::Private::~Private() { doCancelRequest(); } /** * \fn Request::Private::camera() * \brief Retrieve the camera this request has been queued to * \return The Camera this request has been queued to, or nullptr if the * request hasn't been queued */ /** * \brief Check if a request has buffers yet to be completed * * \return True if the request has buffers pending for completion, false * otherwise */ bool Request::Private::hasPendingBuffers() const { return !pending_.empty(); } /** * \brief Complete a buffer for the request * \param[in] buffer The buffer that has completed * * A request tracks the status of all buffers it contains through a set of * pending buffers. This function removes the \a buffer from the set to mark it * as complete. All buffers associate with the request shall be marked as * complete by calling this function once and once only before reporting the * request as complete with the complete() function. * * \return True if all buffers contained in the request have completed, false * otherwise */ bool Request::Private::completeBuffer(FrameBuffer *buffer) { LIBCAMERA_TRACEPOINT(request_complete_buffer, this, buffer); int ret = pending_.erase(buffer); ASSERT(ret == 1); buffer->_d()->setRequest(nullptr); if (buffer->metadata().status == FrameMetadata::FrameCancelled) cancelled_ = true; return !hasPendingBuffers(); } /** * \brief Complete a queued request * * Mark the request as complete by updating its status to RequestComplete, * unless buffers have been cancelled in which case the status is set to * RequestCancelled. */ void Request::Private::complete() { Request *request = _o(); ASSERT(request->status() == RequestPending); ASSERT(!hasPendingBuffers()); request->status_ = cancelled_ ? RequestCancelled : RequestComplete; LOG(Request, Debug) << request->toString(); LIBCAMERA_TRACEPOINT(request_complete, this); } void Request::Private::doCancelRequest() { Request *request = _o(); for (FrameBuffer *buffer : pending_) { buffer->cancel(); camera_->bufferCompleted.emit(request, buffer); } cancelled_ = true; pending_.clear(); notifiers_.clear(); timer_.reset(); } /** * \brief Cancel a queued request * * Mark the request and its associated buffers as cancelled and complete it. * * Set each pending buffer in error state and emit the buffer completion signal * before completing the Request. */ void Request::Private::cancel() { LIBCAMERA_TRACEPOINT(request_cancel, this); Request *request = _o(); ASSERT(request->status() == RequestPending); doCancelRequest(); } /** * \copydoc Request::reuse() */ void Request::Private::reuse() { sequence_ = 0; cancelled_ = false; prepared_ = false; pending_.clear(); notifiers_.clear(); timer_.reset(); } /* * Helper function to save some lines of code and make sure prepared_ is set * to true before emitting the signal. */ void Request::Private::emitPrepareCompleted() { prepared_ = true; prepared.emit(); } /** * \brief Prepare the Request to be queued to the device * \param[in] timeout Optional expiration timeout * * Prepare a Request to be queued to the hardware device by ensuring it is * ready for the incoming memory transfers. * * This currently means waiting on each frame buffer acquire fence to be * signalled. An optional expiration timeout can be specified. If not all the * fences have been signalled correctly before the timeout expires the Request * is cancelled. * * The function immediately emits the prepared signal if all the prepare * operations have been completed synchronously. If instead the prepare * operations require to wait the completion of asynchronous events, such as * fences notifications or timer expiration, the prepared signal is emitted upon * the asynchronous event completion. * * As we currently only handle fences, the function emits the prepared signal * immediately if there are no fences to wait on. Otherwise the prepared signal * is emitted when all fences have been signalled or the optional timeout has * expired. * * If not all the fences have been correctly signalled or the optional timeout * has expired the Request will be cancelled and the Request::prepared signal * emitted. * * The intended user of this function is the PipelineHandler base class, which * 'prepares' a Request before queuing it to the hardware device. */ void Request::Private::prepare(std::chrono::milliseconds timeout) { /* Create and connect one notifier for each synchronization fence. */ for (FrameBuffer *buffer : pending_) { const Fence *fence = buffer->_d()->fence(); if (!fence) continue; std::unique_ptr notifier = std::make_unique(fence->fd().get(), EventNotifier::Read); notifier->activated.connect(this, [this, buffer] { notifierActivated(buffer); }); notifiers_[buffer] = std::move(notifier); } if (notifiers_.empty()) { emitPrepareCompleted(); return; } /* * In case a timeout is specified, create a timer and set it up. * * The timer must be created here instead of in the Request constructor, * in order to be bound to the pipeline handler thread. */ if (timeout != 0ms) { timer_ = std::make_unique(); timer_->timeout.connect(this, &Request::Private::timeout); timer_->start(timeout); } } /** * \var Request::Private::prepared * \brief Request preparation completed Signal * * The signal is emitted once the request preparation has completed and is ready * to be queued. The Request might complete with errors in which case it is * cancelled. * * The intended slot for this signal is the PipelineHandler::doQueueRequests() * function which queues Request after they have been prepared or cancel them * if they have failed preparing. */ void Request::Private::notifierActivated(FrameBuffer *buffer) { /* Close the fence if successfully signalled. */ ASSERT(buffer); buffer->releaseFence(); /* Remove the entry from the map and check if other fences are pending. */ auto it = notifiers_.find(buffer); ASSERT(it != notifiers_.end()); notifiers_.erase(it); Request *request = _o(); LOG(Request, Debug) << "Request " << request->cookie() << " buffer " << buffer << " fence signalled"; if (!notifiers_.empty()) return; /* All fences completed, delete the timer and emit the prepared signal. */ timer_.reset(); emitPrepareCompleted(); } void Request::Private::timeout() { /* A timeout can only happen if there are fences not yet signalled. */ ASSERT(!notifiers_.empty()); notifiers_.clear(); Request *request = _o(); LOG(Request, Debug) << "Request prepare timeout: " << request->cookie(); cancel(); emitPrepareCompleted(); } /** * \enum Request::Status * Request completion status * \var Request::RequestPending * The request hasn't completed yet * \var Request::RequestComplete * The request has completed * \var Request::RequestCancelled * The request has been cancelled due to capture stop */ /** * \enum Request::ReuseFlag * Flags to control the behavior of Request::reuse() * \var Request::Default * Don't reuse buffers * \var Request::ReuseBuffers * Reuse the buffers that were previously added by addBuffer() */ /** * \typedef Request::BufferMap * \brief A map of Stream to FrameBuffer pointers */ /** * \class Request * \brief A frame capture request * * A Request allows an application to associate buffers and controls on a * per-frame basis to be queued to the camera device for processing. */ /** * \brief Create a capture request for a camera * \param[in] camera The camera that creates the request * \param[in] cookie Opaque cookie for application use * * The \a cookie is stored in the request and is accessible through the * cookie() function at any time. It is typically used by applications to map * the request to an external resource in the request completion handler, and is * completely opaque to libcamera. */ Request::Request(Camera *camera, uint64_t cookie) : Extensible(std::make_unique(camera)), cookie_(cookie), status_(RequestPending) { controls_ = new ControlList(controls::controls, camera->_d()->validator()); /** * \todo: Add a validator for metadata controls. */ metadata_ = new ControlList(controls::controls); LIBCAMERA_TRACEPOINT(request_construct, this); LOG(Request, Debug) << "Created request - cookie: " << cookie_; } Request::~Request() { LIBCAMERA_TRACEPOINT(request_destroy, this); delete metadata_; delete controls_; } /** * \brief Reset the request for reuse * \param[in] flags Indicate whether or not to reuse the buffers * * Reset the status and controls associated with the request, to allow it to * be reused and requeued without destruction. This function shall be called * prior to queueing the request to the camera, in lieu of constructing a new * request. The application can reuse the buffers that were previously added * to the request via addBuffer() by setting \a flags to ReuseBuffers. */ void Request::reuse(ReuseFlag flags) { LIBCAMERA_TRACEPOINT(request_reuse, this); _d()->reuse(); if (flags & ReuseBuffers) { for (auto pair : bufferMap_) { FrameBuffer *buffer = pair.second; buffer->_d()->setRequest(this); _d()->pending_.insert(buffer); } } else { bufferMap_.clear(); } status_ = RequestPending; controls_->clear(); metadata_->clear(); } /** * \fn Request::controls() * \brief Retrieve the request's ControlList * * Requests store a list of controls to be applied to all frames captured for * the request. They are created with an empty list of controls that can be * accessed through this function. Control values can be retrieved using * ControlList::get() and updated using ControlList::set(). * * Only controls supported by the camera to which this request will be * submitted shall be included in the controls list. Attempting to add an * unsupported control causes undefined behaviour. * * \return A reference to the ControlList in this request */ /** * \fn Request::buffers() * \brief Retrieve the request's streams to buffers map * * Return a reference to the map that associates each Stream part of the * request to the FrameBuffer the Stream output should be directed to. * * \return The map of Stream to FrameBuffer */ /** * \brief Add a FrameBuffer with its associated Stream to the Request * \param[in] stream The stream the buffer belongs to * \param[in] buffer The FrameBuffer to add to the request * \param[in] fence The optional fence * * A reference to the buffer is stored in the request. The caller is responsible * for ensuring that the buffer will remain valid until the request complete * callback is called. * * A request can only contain one buffer per stream. If a buffer has already * been added to the request for the same stream, this function returns -EEXIST. * * A Fence can be optionally associated with the \a buffer. * * When a valid Fence is provided to this function, \a fence is moved to \a * buffer and this Request will only be queued to the device once the * fences of all its buffers have been correctly signalled. * * If the \a fence associated with \a buffer isn't signalled, the request will * fail after a timeout. The buffer will still contain the fence, which * applications must retrieve with FrameBuffer::releaseFence() before the buffer * can be reused in another request. Attempting to add a buffer that still * contains a fence to a request will result in this function returning -EEXIST. * * \sa FrameBuffer::releaseFence() * * \return 0 on success or a negative error code otherwise * \retval -EEXIST The request already contains a buffer for the stream * or the buffer still references a fence * \retval -EINVAL The buffer does not reference a valid Stream */ int Request::addBuffer(const Stream *stream, FrameBuffer *buffer, std::unique_ptr fence) { if (!stream) { LOG(Request, Error) << "Invalid stream reference"; return -EINVAL; } auto it = bufferMap_.find(stream); if (it != bufferMap_.end()) { LOG(Request, Error) << "FrameBuffer already set for stream"; return -EEXIST; } buffer->_d()->setRequest(this); _d()->pending_.insert(buffer); bufferMap_[stream] = buffer; /* * Make sure the fence has been extracted from the buffer * to avoid waiting on a stale fence. */ if (buffer->_d()->fence()) { LOG(Request, Error) << "Can't add buffer that still references a fence"; return -EEXIST; } if (fence && fence->isValid()) buffer->_d()->setFence(std::move(fence)); return 0; } /** * \var Request::bufferMap_ * \brief Mapping of streams to buffers for this request * * The bufferMap_ tracks the buffers associated with each stream. If a stream is * not utilised in this request there will be no buffer for that stream in the * map. */ /** * \brief Return the buffer associated with a stream * \param[in] stream The stream the buffer is associated to * \return The buffer associated with the stream, or nullptr if the stream is * not part of this request */ FrameBuffer *Request::findBuffer(const Stream *stream) const { const auto it = bufferMap_.find(stream); if (it == bufferMap_.end()) return nullptr; return it->second; } /** * \fn Request::metadata() * \brief Retrieve the request's metadata * \todo Offer a read-only API towards applications while keeping a read/write * API internally. * \return The metadata associated with the request */ /** * \brief Retrieve the sequence number for the request * * When requests are queued, they are given a sequential number to track the * order in which requests are queued to a camera. This number counts all * requests given to a camera through its lifetime, and is not reset to zero * between camera stop/start sequences. * * It can be used to support debugging and identifying the flow of requests * through a pipeline, but does not guarantee to represent the sequence number * of any images in the stream. The sequence number is stored as an unsigned * integer and will wrap when overflowed. * * \return The request sequence number */ uint32_t Request::sequence() const { return _d()->sequence_; } /** * \fn Request::cookie() * \brief Retrieve the cookie set when the request was created * \return The request cookie */ /** * \fn Request::status() * \brief Retrieve the request completion status * * The request status indicates whether the request has completed successfully * or with an error. When requests are created and before they complete the * request status is set to RequestPending, and is updated at completion time * to RequestComplete. If a request is cancelled at capture stop before it has * completed, its status is set to RequestCancelled. * * \return The request completion status */ /** * \brief Check if a request has buffers yet to be completed * * \return True if the request has buffers pending for completion, false * otherwise */ bool Request::hasPendingBuffers() const { return !_d()->pending_.empty(); } /** * \brief Generate a string representation of the Request internals * * This function facilitates debugging of Request state while it is used * internally within libcamera. * * \return A string representing the current state of the request */ std::string Request::toString() const { std::stringstream ss; /* Pending, Completed, Cancelled(X). */ static const char *statuses = "PCX"; /* Example Output: Request(55:P:1/2:6523524) */ ss << "Request(" << sequence() << ":" << statuses[status_] << ":" << _d()->pending_.size() << "/" << bufferMap_.size() << ":" << cookie_ << ")"; return ss.str(); } } /* namespace libcamera */