diff options
Diffstat (limited to 'src/libcamera/base')
-rw-r--r-- | src/libcamera/base/bound_method.cpp | 109 | ||||
-rw-r--r-- | src/libcamera/base/class.cpp | 181 | ||||
-rw-r--r-- | src/libcamera/base/event_dispatcher.cpp | 116 | ||||
-rw-r--r-- | src/libcamera/base/event_dispatcher_poll.cpp | 307 | ||||
-rw-r--r-- | src/libcamera/base/event_notifier.cpp | 141 | ||||
-rw-r--r-- | src/libcamera/base/file.cpp | 465 | ||||
-rw-r--r-- | src/libcamera/base/log.cpp | 998 | ||||
-rw-r--r-- | src/libcamera/base/meson.build | 49 | ||||
-rw-r--r-- | src/libcamera/base/message.cpp | 166 | ||||
-rw-r--r-- | src/libcamera/base/object.cpp | 300 | ||||
-rw-r--r-- | src/libcamera/base/semaphore.cpp | 103 | ||||
-rw-r--r-- | src/libcamera/base/signal.cpp | 179 | ||||
-rw-r--r-- | src/libcamera/base/thread.cpp | 705 | ||||
-rw-r--r-- | src/libcamera/base/timer.cpp | 185 | ||||
-rw-r--r-- | src/libcamera/base/utils.cpp | 463 |
15 files changed, 4467 insertions, 0 deletions
diff --git a/src/libcamera/base/bound_method.cpp b/src/libcamera/base/bound_method.cpp new file mode 100644 index 00000000..3ecec51c --- /dev/null +++ b/src/libcamera/base/bound_method.cpp @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * bound_method.cpp - Method bind and invocation + */ + +#include <libcamera/base/bound_method.h> +#include <libcamera/base/message.h> +#include <libcamera/base/semaphore.h> +#include <libcamera/base/thread.h> + +/** + * \file base/bound_method.h + * \brief Method bind and invocation + */ + +namespace libcamera { + +/** + * \enum ConnectionType + * \brief Connection type for asynchronous communication + * + * This enumeration describes the possible types of asynchronous communication + * between a sender and a receiver. It applies to Signal::emit() and + * Object::invokeMethod(). + * + * \var ConnectionTypeAuto + * \brief If the sender and the receiver live in the same thread, + * ConnectionTypeDirect is used. Otherwise ConnectionTypeQueued is used. + * + * \var ConnectionTypeDirect + * \brief The receiver is invoked immediately and synchronously in the sender's + * thread. + * + * \var ConnectionTypeQueued + * \brief The receiver is invoked asynchronously + * + * Invoke the receiver asynchronously in its thread when control returns to the + * thread's event loop. The sender proceeds without waiting for the invocation + * to complete. + * + * \var ConnectionTypeBlocking + * \brief The receiver is invoked synchronously + * + * If the sender and the receiver live in the same thread, this is equivalent to + * ConnectionTypeDirect. Otherwise, the receiver is invoked asynchronously in + * its thread when control returns to the thread's event loop. The sender + * blocks until the receiver signals the completion of the invocation. + */ + +/** + * \brief Invoke the bound method with packed arguments + * \param[in] pack Packed arguments + * \param[in] deleteMethod True to delete \a this bound method instance when + * method invocation completes + * + * The bound method stores its return value, if any, in the arguments \a pack. + * For direct and blocking invocations, this is performed synchronously, and + * the return value contained in the pack may be used. For queued invocations, + * the return value is stored at an undefined point of time and shall thus not + * be used by the caller. + * + * \return True if the return value contained in the \a pack may be used by the + * caller, false otherwise + */ +bool BoundMethodBase::activatePack(std::shared_ptr<BoundMethodPackBase> pack, + bool deleteMethod) +{ + ConnectionType type = connectionType_; + if (type == ConnectionTypeAuto) { + if (Thread::current() == object_->thread()) + type = ConnectionTypeDirect; + else + type = ConnectionTypeQueued; + } else if (type == ConnectionTypeBlocking) { + if (Thread::current() == object_->thread()) + type = ConnectionTypeDirect; + } + + switch (type) { + case ConnectionTypeDirect: + default: + invokePack(pack.get()); + if (deleteMethod) + delete this; + return true; + + case ConnectionTypeQueued: { + std::unique_ptr<Message> msg = + std::make_unique<InvokeMessage>(this, pack, nullptr, deleteMethod); + object_->postMessage(std::move(msg)); + return false; + } + + case ConnectionTypeBlocking: { + Semaphore semaphore; + + std::unique_ptr<Message> msg = + std::make_unique<InvokeMessage>(this, pack, &semaphore, deleteMethod); + object_->postMessage(std::move(msg)); + + semaphore.acquire(); + return true; + } + } +} + +} /* namespace libcamera */ diff --git a/src/libcamera/base/class.cpp b/src/libcamera/base/class.cpp new file mode 100644 index 00000000..26b49677 --- /dev/null +++ b/src/libcamera/base/class.cpp @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * class.cpp - Utilities and helpers for classes + */ + +#include <libcamera/base/class.h> + +/** + * \file class.h + * \brief Utilities to help constructing class interfaces + * + * The extensible class can be inherited to create public classes with stable + * ABIs. + */ + +namespace libcamera { + +/** + * \def LIBCAMERA_DISABLE_COPY + * \brief Disable copy construction and assignment of the \a klass + * \param klass The name of the class + * + * Example usage: + * \code{.cpp} + * class NonCopyable + * { + * public: + * NonCopyable(); + * ... + * + * private: + * LIBCAMERA_DISABLE_COPY(NonCopyable) + * }; + * \endcode + */ + +/** + * \def LIBCAMERA_DISABLE_MOVE + * \brief Disable move construction and assignment of the \a klass + * \param klass The name of the class + * + * Example usage: + * \code{.cpp} + * class NonMoveable + * { + * public: + * NonMoveable(); + * ... + * + * private: + * LIBCAMERA_DISABLE_MOVE(NonMoveable) + * }; + * \endcode + */ + +/** + * \def LIBCAMERA_DISABLE_COPY_AND_MOVE + * \brief Disable copy and move construction and assignment of the \a klass + * \param klass The name of the class + * + * Example usage: + * \code{.cpp} + * class NonCopyableNonMoveable + * { + * public: + * NonCopyableNonMoveable(); + * ... + * + * private: + * LIBCAMERA_DISABLE_COPY_AND_MOVE(NonCopyableNonMoveable) + * }; + * \endcode + */ + +/** + * \def LIBCAMERA_DECLARE_PRIVATE + * \brief Declare private data for a public class + * + * The LIBCAMERA_DECLARE_PRIVATE() macro plumbs the infrastructure necessary to + * make a class manage its private data through a d-pointer. It shall be used at + * the very top of the class definition. + */ + +/** + * \def LIBCAMERA_DECLARE_PUBLIC + * \brief Declare public data for a private class + * \param klass The public class name + * + * The LIBCAMERA_DECLARE_PUBLIC() macro is the counterpart of + * LIBCAMERA_DECLARE_PRIVATE() to be used in the private data class. It shall be + * used at the very top of the private class definition, with the public class + * name passed as the \a klass parameter. + */ + +/** + * \def LIBCAMERA_O_PTR() + * \brief Retrieve the public instance corresponding to the private data + * + * This macro is used in any member function of the private data class to access + * the public class instance corresponding to the private data. + */ + +/** + * \class Extensible + * \brief Base class to manage private data through a d-pointer + * + * The Extensible class provides a base class to implement the + * <a href="https://wiki.qt.io/D-Pointer">d-pointer</a> design pattern (also + * known as <a href="https://en.wikipedia.org/wiki/Opaque_pointer">opaque pointer</a> + * or <a href="https://en.cppreference.com/w/cpp/language/pimpl">pImpl idiom</a>). + * It helps creating public classes that can be extended without breaking their + * ABI. Such classes store their private data in a separate private data object, + * referenced by a pointer in the public class (hence the name d-pointer). + * + * Classes that follow this design pattern are referred herein as extensible + * classes. To be extensible, a class PublicClass shall: + * + * - inherit from the Extensible class or from another extensible class + * - invoke the LIBCAMERA_DECLARE_PRIVATE() macro at the very top of the class + * definition + * - define a private data class named PublicClass::Private that inherits from + * the Private data class of the base class + * - invoke the LIBCAMERA_DECLARE_PUBLIC() macro at the very top of the Private + * data class definition + * - pass a pointer to a newly allocated Private data object to the constructor + * of the base class + * + * Additionally, if the PublicClass is not final, it shall expose one or more + * constructors that takes a pointer to a Private data instance, to be used by + * derived classes. + * + * The Private class is fully opaque to users of the libcamera public API. + * Internally, it can be kept private to the implementation of PublicClass, or + * be exposed to other classes. In the latter case, the members of the Private + * class need to be qualified with appropriate access specifiers. The + * PublicClass and Private classes always have full access to each other's + * protected and private members. + * + * The PublicClass exposes its Private data pointer through the _d() function. + */ + +/** + * \brief Construct an instance of an Extensible class + * \param[in] d Pointer to the private data instance + */ +Extensible::Extensible(Extensible::Private *d) + : d_(d) +{ +} + +/** + * \var Extensible::d_ + * \brief Pointer to the private data instance + */ + +/** + * \class Extensible::Private + * \brief Base class for private data managed through a d-pointer + */ + +/** + * \brief Construct an instance of an Extensible class private data + * \param[in] o Pointer to the public class object + */ +Extensible::Private::Private(Extensible *o) + : o_(o) +{ +} + +Extensible::Private::~Private() +{ +} + +/** + * \var Extensible::Private::o_ + * \brief Pointer to the public class object + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/base/event_dispatcher.cpp b/src/libcamera/base/event_dispatcher.cpp new file mode 100644 index 00000000..4be89e81 --- /dev/null +++ b/src/libcamera/base/event_dispatcher.cpp @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * event_dispatcher.cpp - Event dispatcher + */ + +#include <libcamera/base/event_dispatcher.h> +#include <libcamera/base/log.h> + +/** + * \file base/event_dispatcher.h + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Event) + +/** + * \class EventDispatcher + * \brief Interface to manage the libcamera events and timers + * + * The EventDispatcher class allows the integration of the application event + * loop with libcamera by abstracting how events and timers are managed and + * processed. + * + * To listen to events, libcamera creates EventNotifier instances and registers + * them with the dispatcher with registerEventNotifier(). The event notifier + * \ref EventNotifier::activated signal is then emitted by the dispatcher + * whenever the event is detected. + * + * To set timers, libcamera creates Timer instances and registers them with the + * dispatcher with registerTimer(). The timer \ref Timer::timeout signal is then + * emitted by the dispatcher when the timer times out. + */ + +EventDispatcher::~EventDispatcher() +{ +} + +/** + * \fn EventDispatcher::registerEventNotifier() + * \brief Register an event notifier + * \param[in] notifier The event notifier to register + * + * Once the \a notifier is registered with the dispatcher, the dispatcher will + * emit the notifier \ref EventNotifier::activated signal whenever a + * corresponding event is detected on the notifier's file descriptor. The event + * is monitored until the notifier is unregistered with + * unregisterEventNotifier(). + * + * Registering multiple notifiers for the same file descriptor and event type is + * not allowed and results in undefined behaviour. + */ + +/** + * \fn EventDispatcher::unregisterEventNotifier() + * \brief Unregister an event notifier + * \param[in] notifier The event notifier to unregister + * + * After this function returns the \a notifier is guaranteed not to emit the + * \ref EventNotifier::activated signal. + * + * If the notifier isn't registered, this function performs no operation. + */ + +/** + * \fn EventDispatcher::registerTimer() + * \brief Register a timer + * \param[in] timer The timer to register + * + * Once the \a timer is registered with the dispatcher, the dispatcher will emit + * the timer \ref Timer::timeout signal when the timer times out. The timer can + * be unregistered with unregisterTimer() before it times out, in which case the + * signal will not be emitted. + * + * When the \a timer times out, it is automatically unregistered by the + * dispatcher and can be registered back as early as from the \ref Timer::timeout + * signal handlers. + * + * Registering the same timer multiple times is not allowed and results in + * undefined behaviour. + */ + +/** + * \fn EventDispatcher::unregisterTimer() + * \brief Unregister a timer + * \param[in] timer The timer to unregister + * + * After this function returns the \a timer is guaranteed not to emit the + * \ref Timer::timeout signal. + * + * If the timer isn't registered, this function performs no operation. + */ + +/** + * \fn EventDispatcher::processEvents() + * \brief Wait for and process pending events + * + * This function processes all pending events associated with registered event + * notifiers and timers and signals the corresponding EventNotifier and Timer + * objects. If no events are pending, it waits for the first event and processes + * it before returning. + */ + +/** + * \fn EventDispatcher::interrupt() + * \brief Interrupt any running processEvents() call as soon as possible + * + * Calling this function interrupts any blocking processEvents() call in + * progress. The processEvents() function will return as soon as possible, + * after processing pending timers and events. If processEvents() isn't in + * progress, it will be interrupted immediately the next time it gets called. + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/base/event_dispatcher_poll.cpp b/src/libcamera/base/event_dispatcher_poll.cpp new file mode 100644 index 00000000..5839373a --- /dev/null +++ b/src/libcamera/base/event_dispatcher_poll.cpp @@ -0,0 +1,307 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * event_dispatcher_poll.cpp - Poll-based event dispatcher + */ + +#include <libcamera/base/event_dispatcher_poll.h> + +#include <algorithm> +#include <chrono> +#include <iomanip> +#include <poll.h> +#include <stdint.h> +#include <string.h> +#include <sys/eventfd.h> +#include <unistd.h> + +#include <libcamera/base/event_notifier.h> +#include <libcamera/base/log.h> +#include <libcamera/base/thread.h> +#include <libcamera/base/timer.h> +#include <libcamera/base/utils.h> + +/** + * \file base/event_dispatcher_poll.h + */ + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Event) + +static const char *notifierType(EventNotifier::Type type) +{ + if (type == EventNotifier::Read) + return "read"; + if (type == EventNotifier::Write) + return "write"; + if (type == EventNotifier::Exception) + return "exception"; + + return ""; +} + +/** + * \class EventDispatcherPoll + * \brief A poll-based event dispatcher + */ + +EventDispatcherPoll::EventDispatcherPoll() + : processingEvents_(false) +{ + /* + * Create the event fd. Failures are fatal as we can't implement an + * interruptible dispatcher without the fd. + */ + eventfd_ = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (eventfd_ < 0) + LOG(Event, Fatal) << "Unable to create eventfd"; +} + +EventDispatcherPoll::~EventDispatcherPoll() +{ + close(eventfd_); +} + +void EventDispatcherPoll::registerEventNotifier(EventNotifier *notifier) +{ + EventNotifierSetPoll &set = notifiers_[notifier->fd()]; + EventNotifier::Type type = notifier->type(); + + if (set.notifiers[type] && set.notifiers[type] != notifier) { + LOG(Event, Warning) + << "Ignoring duplicate " << notifierType(type) + << " notifier for fd " << notifier->fd(); + return; + } + + set.notifiers[type] = notifier; +} + +void EventDispatcherPoll::unregisterEventNotifier(EventNotifier *notifier) +{ + auto iter = notifiers_.find(notifier->fd()); + if (iter == notifiers_.end()) + return; + + EventNotifierSetPoll &set = iter->second; + EventNotifier::Type type = notifier->type(); + + if (!set.notifiers[type]) + return; + + if (set.notifiers[type] != notifier) { + LOG(Event, Warning) + << notifierType(type) << " notifier for fd " + << notifier->fd() << " is not registered"; + return; + } + + set.notifiers[type] = nullptr; + + /* + * Don't race with event processing if this method is called from an + * event notifier. The notifiers_ entry will be erased by + * processEvents(). + */ + if (processingEvents_) + return; + + if (!set.notifiers[0] && !set.notifiers[1] && !set.notifiers[2]) + notifiers_.erase(iter); +} + +void EventDispatcherPoll::registerTimer(Timer *timer) +{ + for (auto iter = timers_.begin(); iter != timers_.end(); ++iter) { + if ((*iter)->deadline() > timer->deadline()) { + timers_.insert(iter, timer); + return; + } + } + + timers_.push_back(timer); +} + +void EventDispatcherPoll::unregisterTimer(Timer *timer) +{ + for (auto iter = timers_.begin(); iter != timers_.end(); ++iter) { + if (*iter == timer) { + timers_.erase(iter); + return; + } + + /* + * As the timers list is ordered, we can stop as soon as we go + * past the deadline. + */ + if ((*iter)->deadline() > timer->deadline()) + break; + } +} + +void EventDispatcherPoll::processEvents() +{ + int ret; + + Thread::current()->dispatchMessages(); + + /* Create the pollfd array. */ + std::vector<struct pollfd> pollfds; + pollfds.reserve(notifiers_.size() + 1); + + for (auto notifier : notifiers_) + pollfds.push_back({ notifier.first, notifier.second.events(), 0 }); + + pollfds.push_back({ eventfd_, POLLIN, 0 }); + + /* Wait for events and process notifiers and timers. */ + do { + ret = poll(&pollfds); + } while (ret == -1 && errno == EINTR); + + if (ret < 0) { + ret = -errno; + LOG(Event, Warning) << "poll() failed with " << strerror(-ret); + } else if (ret > 0) { + processInterrupt(pollfds.back()); + pollfds.pop_back(); + processNotifiers(pollfds); + } + + processTimers(); +} + +void EventDispatcherPoll::interrupt() +{ + uint64_t value = 1; + ssize_t ret = write(eventfd_, &value, sizeof(value)); + if (ret != sizeof(value)) { + if (ret < 0) + ret = -errno; + LOG(Event, Error) + << "Failed to interrupt event dispatcher (" + << ret << ")"; + } +} + +short EventDispatcherPoll::EventNotifierSetPoll::events() const +{ + short events = 0; + + if (notifiers[EventNotifier::Read]) + events |= POLLIN; + if (notifiers[EventNotifier::Write]) + events |= POLLOUT; + if (notifiers[EventNotifier::Exception]) + events |= POLLPRI; + + return events; +} + +int EventDispatcherPoll::poll(std::vector<struct pollfd> *pollfds) +{ + /* Compute the timeout. */ + Timer *nextTimer = !timers_.empty() ? timers_.front() : nullptr; + struct timespec timeout; + + if (nextTimer) { + utils::time_point now = utils::clock::now(); + + if (nextTimer->deadline() > now) + timeout = utils::duration_to_timespec(nextTimer->deadline() - now); + else + timeout = { 0, 0 }; + + LOG(Event, Debug) + << "timeout " << timeout.tv_sec << "." + << std::setfill('0') << std::setw(9) + << timeout.tv_nsec; + } + + return ppoll(pollfds->data(), pollfds->size(), + nextTimer ? &timeout : nullptr, nullptr); +} + +void EventDispatcherPoll::processInterrupt(const struct pollfd &pfd) +{ + if (!(pfd.revents & POLLIN)) + return; + + uint64_t value; + ssize_t ret = read(eventfd_, &value, sizeof(value)); + if (ret != sizeof(value)) { + if (ret < 0) + ret = -errno; + LOG(Event, Error) + << "Failed to process interrupt (" << ret << ")"; + } +} + +void EventDispatcherPoll::processNotifiers(const std::vector<struct pollfd> &pollfds) +{ + static const struct { + EventNotifier::Type type; + short events; + } events[] = { + { EventNotifier::Read, POLLIN }, + { EventNotifier::Write, POLLOUT }, + { EventNotifier::Exception, POLLPRI }, + }; + + processingEvents_ = true; + + for (const pollfd &pfd : pollfds) { + auto iter = notifiers_.find(pfd.fd); + ASSERT(iter != notifiers_.end()); + + EventNotifierSetPoll &set = iter->second; + + for (const auto &event : events) { + EventNotifier *notifier = set.notifiers[event.type]; + + if (!notifier) + continue; + + /* + * If the file descriptor is invalid, disable the + * notifier immediately. + */ + if (pfd.revents & POLLNVAL) { + LOG(Event, Warning) + << "Disabling " << notifierType(event.type) + << " due to invalid file descriptor " + << pfd.fd; + unregisterEventNotifier(notifier); + continue; + } + + if (pfd.revents & event.events) + notifier->activated.emit(notifier); + } + + /* Erase the notifiers_ entry if it is now empty. */ + if (!set.notifiers[0] && !set.notifiers[1] && !set.notifiers[2]) + notifiers_.erase(iter); + } + + processingEvents_ = false; +} + +void EventDispatcherPoll::processTimers() +{ + utils::time_point now = utils::clock::now(); + + while (!timers_.empty()) { + Timer *timer = timers_.front(); + if (timer->deadline() > now) + break; + + timers_.pop_front(); + timer->stop(); + timer->timeout.emit(timer); + } +} + +} /* namespace libcamera */ diff --git a/src/libcamera/base/event_notifier.cpp b/src/libcamera/base/event_notifier.cpp new file mode 100644 index 00000000..fd93c087 --- /dev/null +++ b/src/libcamera/base/event_notifier.cpp @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * event_notifier.cpp - File descriptor event notifier + */ + +#include <libcamera/base/event_notifier.h> + +#include <libcamera/base/event_dispatcher.h> +#include <libcamera/base/message.h> +#include <libcamera/base/thread.h> + +#include <libcamera/camera_manager.h> + +/** + * \file event_notifier.h + * \brief File descriptor event notifier + */ + +namespace libcamera { + +/** + * \class EventNotifier + * \brief Notify of activity on a file descriptor + * + * The EventNotifier models a file descriptor event source that can be + * monitored. It is created with the file descriptor to be monitored and the + * type of event, and is enabled by default. It will emit the \ref activated + * signal whenever an event of the monitored type occurs on the file descriptor. + * + * Supported type of events are EventNotifier::Read, EventNotifier::Write and + * EventNotifier::Exception. The type is specified when constructing the + * notifier, and can be retrieved using the type() function. To listen to + * multiple event types on the same file descriptor multiple notifiers must be + * created. + * + * The notifier can be disabled with the setEnabled() function. When the notifier + * is disabled it ignores events and does not emit the \ref activated signal. + * The notifier can then be re-enabled with the setEnabled() function. + * + * Creating multiple notifiers of the same type for the same file descriptor is + * not allowed and results in undefined behaviour. + * + * Notifier events are detected and dispatched from the + * EventDispatcher::processEvents() function. + */ + +/** + * \enum EventNotifier::Type + * Type of file descriptor event to listen for. + * \var EventNotifier::Read + * Data is available to be read from the file descriptor + * \var EventNotifier::Write + * Data can be written to the file descriptor + * \var EventNotifier::Exception + * An exception has occurred on the file descriptor + */ + +/** + * \brief Construct an event notifier with a file descriptor and event type + * \param[in] fd The file descriptor to monitor + * \param[in] type The event type to monitor + * \param[in] parent The parent Object + */ +EventNotifier::EventNotifier(int fd, Type type, Object *parent) + : Object(parent), fd_(fd), type_(type), enabled_(false) +{ + setEnabled(true); +} + +EventNotifier::~EventNotifier() +{ + setEnabled(false); +} + +/** + * \fn EventNotifier::type() + * \brief Retrieve the type of the event being monitored + * \return The type of the event + */ + +/** + * \fn EventNotifier::fd() + * \brief Retrieve the file descriptor being monitored + * \return The file descriptor + */ + +/** + * \fn EventNotifier::enabled() + * \brief Retrieve the notifier state + * \return True if the notifier is enabled, or false otherwise + * \sa setEnabled() + */ + +/** + * \brief Enable or disable the notifier + * \param[in] enable True to enable the notifier, false to disable it + * + * This function enables or disables the notifier. A disabled notifier ignores + * events and does not emit the \ref activated signal. + * + * \context This function is \threadbound. + */ +void EventNotifier::setEnabled(bool enable) +{ + if (enabled_ == enable) + return; + + enabled_ = enable; + + EventDispatcher *dispatcher = thread()->eventDispatcher(); + if (enable) + dispatcher->registerEventNotifier(this); + else + dispatcher->unregisterEventNotifier(this); +} + +/** + * \var EventNotifier::activated + * \brief Signal emitted when the event occurs + * + * This signal is emitted when the event \ref type() occurs on the file + * descriptor monitored by the notifier. The notifier pointer is passed as a + * parameter. + */ + +void EventNotifier::message(Message *msg) +{ + if (msg->type() == Message::ThreadMoveMessage) { + if (enabled_) { + setEnabled(false); + invokeMethod(&EventNotifier::setEnabled, + ConnectionTypeQueued, true); + } + } + + Object::message(msg); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/base/file.cpp b/src/libcamera/base/file.cpp new file mode 100644 index 00000000..073666fa --- /dev/null +++ b/src/libcamera/base/file.cpp @@ -0,0 +1,465 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * file.cpp - File I/O operations + */ + +#include <libcamera/base/file.h> + +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libcamera/base/log.h> + +/** + * \file base/file.h + * \brief File I/O operations + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(File) + +/** + * \class File + * \brief Interface for I/O operations on files + * + * The File class provides an interface to perform I/O operations on files. It + * wraps opening, closing and mapping files in memory, and handles the cleaning + * of allocated resources. + * + * File instances are usually constructed with a file name, but the name can be + * set later through the setFileName() function. Instances are not automatically + * opened when constructed, and shall be opened explictly with open(). + * + * Files can be mapped to the process memory with map(). Mapped regions can be + * unmapped manually with munmap(), and are automatically unmapped when the File + * is destroyed or when it is used to reference another file with setFileName(). + */ + +/** + * \enum File::MapFlag + * \brief Flags for the File::map() function + * \var File::MapNoOption + * \brief No option (used as default value) + * \var File::MapPrivate + * \brief The memory region is mapped as private, changes are not reflected in + * the file constents + */ + +/** + * \enum File::OpenMode + * \brief Mode in which a file is opened + * \var File::NotOpen + * \brief The file is not open + * \var File::ReadOnly + * \brief The file is open for reading + * \var File::WriteOnly + * \brief The file is open for writing + * \var File::ReadWrite + * \brief The file is open for reading and writing + */ + +/** + * \brief Construct a File to represent the file \a name + * \param[in] name The file name + * + * Upon construction the File object is closed and shall be opened with open() + * before performing I/O operations. + */ +File::File(const std::string &name) + : name_(name), fd_(-1), mode_(NotOpen), error_(0) +{ +} + +/** + * \brief Construct a File without an associated name + * + * Before being used for any purpose, the file name shall be set with + * setFileName(). + */ +File::File() + : fd_(-1), mode_(NotOpen), error_(0) +{ +} + +/** + * \brief Destroy a File instance + * + * Any memory mapping associated with the File is unmapped, and the File is + * closed if it is open. + */ +File::~File() +{ + unmapAll(); + close(); +} + +/** + * \fn const std::string &File::fileName() const + * \brief Retrieve the file name + * \return The file name + */ + +/** + * \brief Set the name of the file + * \param[in] name The name of the file + * + * The \a name can contain an absolute path, a relative path or no path at all. + * Calling this function on an open file results in undefined behaviour. + * + * Any memory mapping associated with the File is unmapped. + */ +void File::setFileName(const std::string &name) +{ + if (isOpen()) { + LOG(File, Error) + << "Can't set file name on already open file " << name_; + return; + } + + unmapAll(); + + name_ = name; +} + +/** + * \brief Check if the file specified by fileName() exists + * + * This function checks if the file specified by fileName() exists. The File + * instance doesn't need to be open to check for file existence, and this + * function may return false even if the file is open, if it was deleted from + * the file system. + * + * \return True if the the file exists, false otherwise + */ +bool File::exists() const +{ + return exists(name_); +} + +/** + * \brief Open the file in the given mode + * \param[in] mode The open mode + * + * This function opens the file specified by fileName() in \a mode. If the file + * doesn't exist and the mode is WriteOnly or ReadWrite, this function will + * attempt to create the file with initial permissions set to 0666 (modified by + * the process' umask). + * + * The error() status is updated. + * + * \return True on success, false otherwise + */ +bool File::open(File::OpenMode mode) +{ + if (isOpen()) { + LOG(File, Error) << "File " << name_ << " is already open"; + return false; + } + + int flags = (mode & ReadWrite) - 1; + if (mode & WriteOnly) + flags |= O_CREAT; + + fd_ = ::open(name_.c_str(), flags, 0666); + if (fd_ < 0) { + error_ = -errno; + return false; + } + + mode_ = mode; + error_ = 0; + return true; +} + +/** + * \fn bool File::isOpen() const + * \brief Check if the file is open + * \return True if the file is open, false otherwise + */ + +/** + * \fn OpenMode File::openMode() const + * \brief Retrieve the file open mode + * \return The file open mode + */ + +/** + * \brief Close the file + * + * This function closes the File. If the File is not open, it performs no + * operation. Memory mappings created with map() are not destroyed when the + * file is closed. + */ +void File::close() +{ + if (fd_ == -1) + return; + + ::close(fd_); + fd_ = -1; + mode_ = NotOpen; +} + +/** + * \fn int File::error() const + * \brief Retrieve the file error status + * + * This function retrieves the error status from the last file open or I/O + * operation. The error status is a negative number as defined by errno.h. If + * no error occurred, this function returns 0. + * + * \return The file error status + */ + +/** + * \brief Retrieve the file size + * + * This function retrieves the size of the file on the filesystem. The File + * instance shall be open to retrieve its size. The error() status is not + * modified, error codes are returned directly on failure. + * + * \return The file size in bytes on success, or a negative error code otherwise + */ +ssize_t File::size() const +{ + if (!isOpen()) + return -EINVAL; + + struct stat st; + int ret = fstat(fd_, &st); + if (ret < 0) + return -errno; + + return st.st_size; +} + +/** + * \brief Return current read or write position + * + * If the file is closed, this function returns 0. + * + * \return The current read or write position + */ +off_t File::pos() const +{ + if (!isOpen()) + return 0; + + return lseek(fd_, 0, SEEK_CUR); +} + +/** + * \brief Set the read or write position + * \param[in] pos The desired position + * \return The resulting offset from the beginning of the file on success, or a + * negative error code otherwise + */ +off_t File::seek(off_t pos) +{ + if (!isOpen()) + return -EINVAL; + + off_t ret = lseek(fd_, pos, SEEK_SET); + if (ret < 0) + return -errno; + + return ret; +} + +/** + * \brief Read data from the file + * \param[in] data Memory to read data into + * + * Read at most \a data.size() bytes from the file into \a data.data(), and + * return the number of bytes read. If less data than requested is available, + * the returned byte count may be smaller than the requested size. If no more + * data is available, 0 is returned. + * + * The position of the file as returned by pos() is advanced by the number of + * bytes read. If an error occurs, the position isn't modified. + * + * \return The number of bytes read on success, or a negative error code + * otherwise + */ +ssize_t File::read(const Span<uint8_t> &data) +{ + if (!isOpen()) + return -EINVAL; + + size_t readBytes = 0; + ssize_t ret = 0; + + /* Retry in case of interrupted system calls. */ + while (readBytes < data.size()) { + ret = ::read(fd_, data.data() + readBytes, + data.size() - readBytes); + if (ret <= 0) + break; + + readBytes += ret; + } + + if (ret < 0 && !readBytes) + return -errno; + + return readBytes; +} + +/** + * \brief Write data to the file + * \param[in] data Memory containing data to be written + * + * Write at most \a data.size() bytes from \a data.data() to the file, and + * return the number of bytes written. If the file system doesn't have enough + * space for the data, the returned byte count may be less than requested. + * + * The position of the file as returned by pos() is advanced by the number of + * bytes written. If an error occurs, the position isn't modified. + * + * \return The number of bytes written on success, or a negative error code + * otherwise + */ +ssize_t File::write(const Span<const uint8_t> &data) +{ + if (!isOpen()) + return -EINVAL; + + size_t writtenBytes = 0; + + /* Retry in case of interrupted system calls. */ + while (writtenBytes < data.size()) { + ssize_t ret = ::write(fd_, data.data() + writtenBytes, + data.size() - writtenBytes); + if (ret <= 0) + break; + + writtenBytes += ret; + } + + if (data.size() && !writtenBytes) + return -errno; + + return writtenBytes; +} + +/** + * \brief Map a region of the file in the process memory + * \param[in] offset The region offset within the file + * \param[in] size The region sise + * \param[in] flags The mapping flags + * + * This function maps a region of \a size bytes of the file starting at \a + * offset into the process memory. The File instance shall be open, but may be + * closed after mapping the region. Mappings stay valid when the File is + * closed, and are destroyed automatically when the File is deleted. + * + * If \a size is a negative value, this function maps the region starting at \a + * offset until the end of the file. + * + * The mapping memory protection is controlled by the file open mode, unless \a + * flags contains MapPrivate in which case the region is mapped in read/write + * mode. + * + * The error() status is updated. + * + * \return The mapped memory on success, or an empty span otherwise + */ +Span<uint8_t> File::map(off_t offset, ssize_t size, enum File::MapFlag flags) +{ + if (!isOpen()) { + error_ = -EBADF; + return {}; + } + + if (size < 0) { + size = File::size(); + if (size < 0) { + error_ = size; + return {}; + } + + size -= offset; + } + + int mmapFlags = flags & MapPrivate ? MAP_PRIVATE : MAP_SHARED; + + int prot = 0; + if (mode_ & ReadOnly) + prot |= PROT_READ; + if (mode_ & WriteOnly) + prot |= PROT_WRITE; + if (flags & MapPrivate) + prot |= PROT_WRITE; + + void *map = mmap(NULL, size, prot, mmapFlags, fd_, offset); + if (map == MAP_FAILED) { + error_ = -errno; + return {}; + } + + maps_.emplace(map, size); + + error_ = 0; + return { static_cast<uint8_t *>(map), static_cast<size_t>(size) }; +} + +/** + * \brief Unmap a region mapped with map() + * \param[in] addr The region address + * + * The error() status is updated. + * + * \return True on success, or false if an error occurs + */ +bool File::unmap(uint8_t *addr) +{ + auto iter = maps_.find(static_cast<void *>(addr)); + if (iter == maps_.end()) { + error_ = -ENOENT; + return false; + } + + int ret = munmap(addr, iter->second); + if (ret < 0) { + error_ = -errno; + return false; + } + + maps_.erase(iter); + + error_ = 0; + return true; +} + +void File::unmapAll() +{ + for (const auto &map : maps_) + munmap(map.first, map.second); + + maps_.clear(); +} + +/** + * \brief Check if the file specified by \a name exists + * \param[in] name The file name + * \return True if the file exists, false otherwise + */ +bool File::exists(const std::string &name) +{ + struct stat st; + int ret = stat(name.c_str(), &st); + if (ret < 0) + return false; + + /* Directories can not be handled here, even if they exist. */ + return !S_ISDIR(st.st_mode); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp new file mode 100644 index 00000000..1801ae26 --- /dev/null +++ b/src/libcamera/base/log.cpp @@ -0,0 +1,998 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018, Google Inc. + * + * log.cpp - Logging infrastructure + */ + +#include <libcamera/base/log.h> + +#include <array> +#if HAVE_BACKTRACE +#include <execinfo.h> +#endif +#include <fstream> +#include <iostream> +#include <list> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unordered_set> + +#include <libcamera/logging.h> + +#include <libcamera/base/thread.h> +#include <libcamera/base/utils.h> + +/** + * \file base/log.h + * \brief Logging infrastructure + * + * libcamera includes a logging infrastructure used through the library that + * allows inspection of internal operation in a user-configurable way. The log + * messages are grouped in categories that represent areas of libcamera, and + * output of messages for each category can be controlled by independent log + * levels. + * + * The levels are configurable through the LIBCAMERA_LOG_LEVELS environment + * variable that contains a comma-separated list of 'category:level' pairs. + * + * The category names are strings and can include a wildcard ('*') character at + * the end to match multiple categories. + * + * The level are either numeric values, or strings containing the log level + * name. The available log levels are DEBUG, INFO, WARN, ERROR and FATAL. Log + * message with a level higher than or equal to the configured log level for + * their category are output to the log, while other messages are silently + * discarded. + * + * By default log messages are output to stderr. They can be redirected to a log + * file by setting the LIBCAMERA_LOG_FILE environment variable to the name of + * the file. The file must be writable and is truncated if it exists. If any + * error occurs when opening the file, the file is ignored and the log is output + * to stderr. + */ + +/** + * \file logging.h + * \brief Logging management + * + * API to change the logging output destination and log levels programatically. + */ + +namespace libcamera { + +static int log_severity_to_syslog(LogSeverity severity) +{ + switch (severity) { + case LogDebug: + return LOG_DEBUG; + case LogInfo: + return LOG_INFO; + case LogWarning: + return LOG_WARNING; + case LogError: + return LOG_ERR; + case LogFatal: + return LOG_ALERT; + default: + return LOG_NOTICE; + } +} + +static const char *log_severity_name(LogSeverity severity) +{ + static const char *const names[] = { + "DEBUG", + " INFO", + " WARN", + "ERROR", + "FATAL", + }; + + if (static_cast<unsigned int>(severity) < std::size(names)) + return names[severity]; + else + return "UNKWN"; +} + +/** + * \brief Log output + * + * The LogOutput class models a log output destination + */ +class LogOutput +{ +public: + LogOutput(const char *path); + LogOutput(std::ostream *stream); + LogOutput(); + ~LogOutput(); + + bool isValid() const; + void write(const LogMessage &msg); + void write(const std::string &msg); + +private: + void writeSyslog(LogSeverity severity, const std::string &msg); + void writeStream(const std::string &msg); + + std::ostream *stream_; + LoggingTarget target_; +}; + +/** + * \brief Construct a log output based on a file + * \param[in] path Full path to log file + */ +LogOutput::LogOutput(const char *path) + : target_(LoggingTargetFile) +{ + stream_ = new std::ofstream(path); +} + +/** + * \brief Construct a log output based on a stream + * \param[in] stream Stream to send log output to + */ +LogOutput::LogOutput(std::ostream *stream) + : stream_(stream), target_(LoggingTargetStream) +{ +} + +/** + * \brief Construct a log output to syslog + */ +LogOutput::LogOutput() + : stream_(nullptr), target_(LoggingTargetSyslog) +{ + openlog("libcamera", LOG_PID, 0); +} + +LogOutput::~LogOutput() +{ + switch (target_) { + case LoggingTargetFile: + delete stream_; + break; + case LoggingTargetSyslog: + closelog(); + break; + default: + break; + } +} + +/** + * \brief Check if the log output is valid + * \return True if the log output is valid + */ +bool LogOutput::isValid() const +{ + switch (target_) { + case LoggingTargetFile: + return stream_->good(); + case LoggingTargetStream: + return stream_ != nullptr; + default: + return true; + } +} + +/** + * \brief Write message to log output + * \param[in] msg Message to write + */ +void LogOutput::write(const LogMessage &msg) +{ + std::string str; + + switch (target_) { + case LoggingTargetSyslog: + str = std::string(log_severity_name(msg.severity())) + " " + + msg.category().name() + " " + msg.fileInfo() + " " + + msg.msg(); + writeSyslog(msg.severity(), str); + break; + case LoggingTargetStream: + case LoggingTargetFile: + str = "[" + utils::time_point_to_string(msg.timestamp()) + "] [" + + std::to_string(Thread::currentId()) + "] " + + log_severity_name(msg.severity()) + " " + + msg.category().name() + " " + msg.fileInfo() + " " + + msg.msg(); + writeStream(str); + break; + default: + break; + } +} + +/** + * \brief Write string to log output + * \param[in] str String to write + */ +void LogOutput::write(const std::string &str) +{ + switch (target_) { + case LoggingTargetSyslog: + writeSyslog(LogDebug, str); + break; + case LoggingTargetStream: + case LoggingTargetFile: + writeStream(str); + break; + default: + break; + } +} + +void LogOutput::writeSyslog(LogSeverity severity, const std::string &str) +{ + syslog(log_severity_to_syslog(severity), "%s", str.c_str()); +} + +void LogOutput::writeStream(const std::string &str) +{ + stream_->write(str.c_str(), str.size()); + stream_->flush(); +} + +/** + * \brief Message logger + * + * The Logger class handles log configuration. + */ +class Logger +{ +public: + ~Logger(); + + static Logger *instance(); + + void write(const LogMessage &msg); + void backtrace(); + + int logSetFile(const char *path); + int logSetStream(std::ostream *stream); + int logSetTarget(LoggingTarget target); + void logSetLevel(const char *category, const char *level); + +private: + Logger(); + + void parseLogFile(); + void parseLogLevels(); + static LogSeverity parseLogLevel(const std::string &level); + + friend LogCategory; + void registerCategory(LogCategory *category); + + std::unordered_set<LogCategory *> categories_; + std::list<std::pair<std::string, LogSeverity>> levels_; + + std::shared_ptr<LogOutput> output_; +}; + +/** + * \enum LoggingTarget + * \brief Log destination type + * \var LoggingTargetNone + * \brief No logging destination + * \sa Logger::logSetTarget + * \var LoggingTargetSyslog + * \brief Log to syslog + * \sa Logger::logSetTarget + * \var LoggingTargetFile + * \brief Log to file + * \sa Logger::logSetFile + * \var LoggingTargetStream + * \brief Log to stream + * \sa Logger::logSetStream + */ + +/** + * \brief Direct logging to a file + * \param[in] path Full path to the log file + * + * This function directs the log output to the file identified by \a path. The + * previous log target, if any, is closed, and all new log messages will be + * written to the new log file. + * + * If the function returns an error, the log target is not changed. + * + * \return Zero on success, or a negative error code otherwise + */ +int logSetFile(const char *path) +{ + return Logger::instance()->logSetFile(path); +} + +/** + * \brief Direct logging to a stream + * \param[in] stream Stream to send log output to + * + * This function directs the log output to \a stream. The previous log target, + * if any, is closed, and all new log messages will be written to the new log + * stream. + * + * If the function returns an error, the log file is not changed + * + * \return Zero on success, or a negative error code otherwise. + */ +int logSetStream(std::ostream *stream) +{ + return Logger::instance()->logSetStream(stream); +} + +/** + * \brief Set the logging target + * \param[in] target Logging destination + * + * This function sets the logging output to the target specified by \a target. + * The allowed values of \a target are LoggingTargetNone and + * LoggingTargetSyslog. LoggingTargetNone will send the log output to nowhere, + * and LoggingTargetSyslog will send the log output to syslog. The previous + * log target, if any, is closed, and all new log messages will be written to + * the new log destination. + * + * LoggingTargetFile and LoggingTargetStream are not valid values for \a target. + * Use logSetFile() and logSetStream() instead, respectively. + * + * If the function returns an error, the log file is not changed. + * + * \return Zero on success, or a negative error code otherwise. + */ +int logSetTarget(LoggingTarget target) +{ + return Logger::instance()->logSetTarget(target); +} + +/** + * \brief Set the log level + * \param[in] category Logging category + * \param[in] level Log level + * + * This function sets the log level of \a category to \a level. + * \a level shall be one of the following strings: + * - "DEBUG" + * - "INFO" + * - "WARN" + * - "ERROR" + * - "FATAL" + * + * "*" is not a valid \a category for this function. + */ +void logSetLevel(const char *category, const char *level) +{ + Logger::instance()->logSetLevel(category, level); +} + +Logger::~Logger() +{ + for (LogCategory *category : categories_) + delete category; +} + +/** + * \brief Retrieve the logger instance + * + * The Logger is a singleton and can't be constructed manually. This function + * shall instead be used to retrieve the single global instance of the logger. + * + * \return The logger instance + */ +Logger *Logger::instance() +{ + static Logger instance; + return &instance; +} + +/** + * \brief Write a message to the configured logger output + * \param[in] msg The message object + */ +void Logger::write(const LogMessage &msg) +{ + std::shared_ptr<LogOutput> output = std::atomic_load(&output_); + if (!output) + return; + + output->write(msg); +} + +/** + * \brief Write a backtrace to the log + */ +void Logger::backtrace() +{ +#if HAVE_BACKTRACE + std::shared_ptr<LogOutput> output = std::atomic_load(&output_); + if (!output) + return; + + void *buffer[32]; + int num_entries = ::backtrace(buffer, std::size(buffer)); + char **strings = backtrace_symbols(buffer, num_entries); + if (!strings) + return; + + std::ostringstream msg; + msg << "Backtrace:" << std::endl; + + /* + * Skip the first two entries that correspond to this method and + * ~LogMessage(). + */ + for (int i = 2; i < num_entries; ++i) + msg << strings[i] << std::endl; + + output->write(msg.str()); + + free(strings); +#endif +} + +/** + * \brief Set the log file + * \param[in] path Full path to the log file + * + * \sa libcamera::logSetFile() + * + * \return Zero on success, or a negative error code otherwise. + */ +int Logger::logSetFile(const char *path) +{ + std::shared_ptr<LogOutput> output = std::make_shared<LogOutput>(path); + if (!output->isValid()) + return -EINVAL; + + std::atomic_store(&output_, output); + return 0; +} + +/** + * \brief Set the log stream + * \param[in] stream Stream to send log output to + * + * \sa libcamera::logSetStream() + * + * \return Zero on success, or a negative error code otherwise. + */ +int Logger::logSetStream(std::ostream *stream) +{ + std::shared_ptr<LogOutput> output = std::make_shared<LogOutput>(stream); + std::atomic_store(&output_, output); + return 0; +} + +/** + * \brief Set the log target + * \param[in] target Log destination + * + * \sa libcamera::logSetTarget() + * + * \return Zero on success, or a negative error code otherwise. + */ +int Logger::logSetTarget(enum LoggingTarget target) +{ + std::shared_ptr<LogOutput> output; + + switch (target) { + case LoggingTargetSyslog: + output = std::make_shared<LogOutput>(); + std::atomic_store(&output_, output); + break; + case LoggingTargetNone: + output = nullptr; + std::atomic_store(&output_, std::shared_ptr<LogOutput>()); + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * \brief Set the log level + * \param[in] category Logging category + * \param[in] level Log level + * + * \sa libcamera::logSetLevel() + */ +void Logger::logSetLevel(const char *category, const char *level) +{ + LogSeverity severity = parseLogLevel(level); + if (severity == LogInvalid) + return; + + for (LogCategory *c : categories_) { + if (!strcmp(c->name(), category)) { + c->setSeverity(severity); + break; + } + } +} + +/** + * \brief Construct a logger + */ +Logger::Logger() +{ + parseLogFile(); + parseLogLevels(); +} + +/** + * \brief Parse the log output file from the environment + * + * If the LIBCAMERA_LOG_FILE environment variable is set, open the file it + * points to and redirect the logger output to it. If the environment variable + * is set to "syslog", then the logger output will be directed to syslog. Errors + * are silently ignored and don't affect the logger output (set to stderr). + */ +void Logger::parseLogFile() +{ + const char *file = utils::secure_getenv("LIBCAMERA_LOG_FILE"); + if (!file) { + logSetStream(&std::cerr); + return; + } + + if (!strcmp(file, "syslog")) { + logSetTarget(LoggingTargetSyslog); + return; + } + + logSetFile(file); +} + +/** + * \brief Parse the log levels from the environment + * + * The log levels are stored in the LIBCAMERA_LOG_LEVELS environment variable + * as a list of "category:level" pairs, separated by commas (','). Parse the + * variable and store the levels to configure all log categories. + */ +void Logger::parseLogLevels() +{ + const char *debug = utils::secure_getenv("LIBCAMERA_LOG_LEVELS"); + if (!debug) + return; + + for (const char *pair = debug; *debug != '\0'; pair = debug) { + const char *comma = strchrnul(debug, ','); + size_t len = comma - pair; + + /* Skip over the comma. */ + debug = *comma == ',' ? comma + 1 : comma; + + /* Skip to the next pair if the pair is empty. */ + if (!len) + continue; + + std::string category; + std::string level; + + const char *colon = static_cast<const char *>(memchr(pair, ':', len)); + if (!colon) { + /* 'x' is a shortcut for '*:x'. */ + category = "*"; + level = std::string(pair, len); + } else { + category = std::string(pair, colon - pair); + level = std::string(colon + 1, comma - colon - 1); + } + + /* Both the category and the level must be specified. */ + if (category.empty() || level.empty()) + continue; + + LogSeverity severity = parseLogLevel(level); + if (severity == LogInvalid) + continue; + + levels_.push_back({ category, severity }); + } +} + +/** + * \brief Parse a log level string into a LogSeverity + * \param[in] level The log level string + * + * Log levels can be specified as an integer value in the range from LogDebug to + * LogFatal, or as a string corresponding to the severity name in uppercase. Any + * other value is invalid. + * + * \return The log severity, or LogInvalid if the string is invalid + */ +LogSeverity Logger::parseLogLevel(const std::string &level) +{ + static const char *const names[] = { + "DEBUG", + "INFO", + "WARN", + "ERROR", + "FATAL", + }; + + int severity; + + if (std::isdigit(level[0])) { + char *endptr; + severity = strtoul(level.c_str(), &endptr, 10); + if (*endptr != '\0' || severity > LogFatal) + severity = LogInvalid; + } else { + severity = LogInvalid; + for (unsigned int i = 0; i < std::size(names); ++i) { + if (names[i] == level) { + severity = i; + break; + } + } + } + + return static_cast<LogSeverity>(severity); +} + +/** + * \brief Register a log category with the logger + * \param[in] category The log category + * + * Log categories must have unique names. If a category with the same name + * already exists this function performs no operation. + */ +void Logger::registerCategory(LogCategory *category) +{ + categories_.insert(category); + + const std::string &name = category->name(); + for (const std::pair<std::string, LogSeverity> &level : levels_) { + bool match = true; + + for (unsigned int i = 0; i < level.first.size(); ++i) { + if (level.first[i] == '*') + break; + + if (i >= name.size() || + name[i] != level.first[i]) { + match = false; + break; + } + } + + if (match) { + category->setSeverity(level.second); + break; + } + } +} + +/** + * \enum LogSeverity + * Log message severity + * \var LogDebug + * Debug message + * \var LogInfo + * Informational message + * \var LogWarning + * Warning message, signals a potential issue + * \var LogError + * Error message, signals an unrecoverable issue + * \var LogFatal + * Fatal message, signals an unrecoverable issue and aborts execution + */ + +/** + * \class LogCategory + * \brief A category of log message + * + * The LogCategory class represents a category of log messages, related to an + * area of the library. It groups all messages belonging to the same category, + * and is used to control the log level per group. + */ + +/** + * \brief Construct a log category + * \param[in] name The category name + */ +LogCategory::LogCategory(const char *name) + : name_(name), severity_(LogSeverity::LogInfo) +{ + Logger::instance()->registerCategory(this); +} + +/** + * \fn LogCategory::name() + * \brief Retrieve the log category name + * \return The log category name + */ + +/** + * \fn LogCategory::severity() + * \brief Retrieve the severity of the log category + * \sa setSeverity() + * \return Return the severity of the log category + */ + +/** + * \brief Set the severity of the log category + * + * Messages of severity higher than or equal to the severity of the log category + * are printed, other messages are discarded. + */ +void LogCategory::setSeverity(LogSeverity severity) +{ + severity_ = severity; +} + +/** + * \brief Retrieve the default log category + * + * The default log category is named "default" and is used by the LOG() macro + * when no log category is specified. + * + * \return A reference to the default log category + */ +const LogCategory &LogCategory::defaultCategory() +{ + static const LogCategory *category = new LogCategory("default"); + return *category; +} + +/** + * \class LogMessage + * \brief Internal log message representation. + * + * The LogMessage class models a single message in the log. It serves as a + * helper to provide the std::ostream API for logging, and must never be used + * directly. Use the LOG() macro instead access the log infrastructure. + */ + +/** + * \brief Construct a log message for a given category + * \param[in] fileName The file name where the message is logged from + * \param[in] line The line number where the message is logged from + * \param[in] category The log message category, controlling how the message + * will be displayed + * \param[in] severity The log message severity, controlling how the message + * will be displayed + * + * Create a log message pertaining to line \a line of file \a fileName. The + * \a severity argument sets the message severity to control whether it will be + * output or dropped. + */ +LogMessage::LogMessage(const char *fileName, unsigned int line, + const LogCategory &category, LogSeverity severity) + : category_(category), severity_(severity) +{ + init(fileName, line); +} + +/** + * \brief Move-construct a log message + * \param[in] other The other message + * + * The move constructor is meant to support the _log() functions. Thanks to copy + * elision it will likely never be called, but C++11 only permits copy elision, + * it doesn't enforce it unlike C++17. To avoid potential link errors depending + * on the compiler type and version, and optimization level, the move + * constructor is defined even if it will likely never be called, and ensures + * that the destructor of the \a other message will not output anything to the + * log by setting the severity to LogInvalid. + */ +LogMessage::LogMessage(LogMessage &&other) + : msgStream_(std::move(other.msgStream_)), category_(other.category_), + severity_(other.severity_) +{ + other.severity_ = LogInvalid; +} + +void LogMessage::init(const char *fileName, unsigned int line) +{ + /* Log the timestamp, severity and file information. */ + timestamp_ = utils::clock::now(); + + std::ostringstream ossFileInfo; + ossFileInfo << utils::basename(fileName) << ":" << line; + fileInfo_ = ossFileInfo.str(); +} + +LogMessage::~LogMessage() +{ + /* Don't print anything if we have been moved to another LogMessage. */ + if (severity_ == LogInvalid) + return; + + msgStream_ << std::endl; + + if (severity_ >= category_.severity()) + Logger::instance()->write(*this); + + if (severity_ == LogSeverity::LogFatal) { + Logger::instance()->backtrace(); + std::abort(); + } +} + +/** + * \fn std::ostream& LogMessage::stream() + * + * Data is added to a LogMessage through the stream returned by this function. + * The stream implements the std::ostream API and can be used for logging as + * std::cout. + * + * \return A reference to the log message stream + */ + +/** + * \fn LogMessage::timestamp() + * \brief Retrieve the timestamp of the log message + * \return The timestamp of the message + */ + +/** + * \fn LogMessage::severity() + * \brief Retrieve the severity of the log message + * \return The severity of the message + */ + +/** + * \fn LogMessage::category() + * \brief Retrieve the category of the log message + * \return The category of the message + */ + +/** + * \fn LogMessage::fileInfo() + * \brief Retrieve the file info of the log message + * \return The file info of the message + */ + +/** + * \fn LogMessage::msg() + * \brief Retrieve the message text of the log message + * \return The message text of the message, as a string + */ + +/** + * \class Loggable + * \brief Base class to support log message extensions + * + * The Loggable class allows classes to extend log messages without any change + * to the way the LOG() macro is invoked. By inheriting from Loggable and + * implementing the logPrefix() virtual method, a class can specify extra + * information to be automatically added to messages logged from class member + * methods. + */ + +Loggable::~Loggable() +{ +} + +/** + * \fn Loggable::logPrefix() + * \brief Retrieve a string to be prefixed to the log message + * + * This method allows classes inheriting from the Loggable class to extend the + * logger with an object-specific prefix output right before the log message + * contents. + * + * \return A string to be prefixed to the log message + */ + +/** + * \brief Create a temporary LogMessage object to log a message + * \param[in] category The log message category + * \param[in] severity The log message severity + * \param[in] fileName The file name where the message is logged from + * \param[in] line The line number where the message is logged from + * + * This method is used as a backeng by the LOG() macro to create a log message + * for locations inheriting from the Loggable class. + * + * \return A log message + */ +LogMessage Loggable::_log(const LogCategory *category, LogSeverity severity, + const char *fileName, unsigned int line) const +{ + LogMessage msg(fileName, line, + category ? *category : LogCategory::defaultCategory(), + severity); + + msg.stream() << logPrefix() << ": "; + return msg; +} + +/** + * \brief Create a temporary LogMessage object to log a message + * \param[in] category The log message category + * \param[in] severity The log message severity + * \param[in] fileName The file name where the message is logged from + * \param[in] line The line number where the message is logged from + * + * This function is used as a backeng by the LOG() macro to create a log + * message for locations not inheriting from the Loggable class. + * + * \return A log message + */ +LogMessage _log(const LogCategory *category, LogSeverity severity, + const char *fileName, unsigned int line) +{ + return LogMessage(fileName, line, + category ? *category : LogCategory::defaultCategory(), + severity); +} + +/** + * \def LOG_DECLARE_CATEGORY(name) + * \hideinitializer + * \brief Declare a category of log messages + * + * This macro is used to declare a log category defined in another compilation + * unit by the LOG_DEFINE_CATEGORY() macro. + * + * The LOG_DECLARE_CATEGORY() macro must be used in the libcamera namespace. + * + * \sa LogCategory + */ + +/** + * \def LOG_DEFINE_CATEGORY(name) + * \hideinitializer + * \brief Define a category of log messages + * + * This macro is used to define a log category that can then be used with the + * LOGC() macro. Category names shall be unique, if a category is shared between + * compilation units, it shall be defined in one compilation unit only and + * declared with LOG_DECLARE_CATEGORY() in the other compilation units. + * + * The LOG_DEFINE_CATEGORY() macro must be used in the libcamera namespace. + * + * \sa LogCategory + */ + +/** + * \def LOG(category, severity) + * \hideinitializer + * \brief Log a message + * \param[in] category Category (optional) + * \param[in] severity Severity + * + * Return an std::ostream reference to which a message can be logged using the + * iostream API. The \a category, if specified, sets the message category. When + * absent the default category is used. The \a severity controls whether the + * message is printed or discarded, depending on the log level for the category. + * + * If the severity is set to Fatal, execution is aborted and the program + * terminates immediately after printing the message. + * + * \warning Logging from the destructor of a global object, either directly or + * indirectly, results in undefined behaviour. + * + * \todo Allow logging from destructors of global objects to the largest + * possible extent + */ + +/** + * \def ASSERT(condition) + * \hideinitializer + * \brief Abort program execution if assertion fails + * + * If \a condition is false, ASSERT() logs an error message with the Fatal log + * level and aborts program execution. + * + * If the macro NDEBUG is defined before including log.h, ASSERT() generates no + * code. + * + * Using conditions that have side effects with ASSERT() is not recommended, as + * these effects would depend on whether NDEBUG is defined or not. Similarly, + * ASSERT() should not be used to check for errors that can occur under normal + * conditions as those checks would then be removed when compiling with NDEBUG. + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/base/meson.build b/src/libcamera/base/meson.build new file mode 100644 index 00000000..87172157 --- /dev/null +++ b/src/libcamera/base/meson.build @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: CC0-1.0 + +libcamera_base_sources = files([ + 'class.cpp', + 'bound_method.cpp', + 'event_dispatcher.cpp', + 'event_dispatcher_poll.cpp', + 'event_notifier.cpp', + 'file.cpp', + 'log.cpp', + 'message.cpp', + 'object.cpp', + 'semaphore.cpp', + 'signal.cpp', + 'thread.cpp', + 'timer.cpp', + 'utils.cpp', +]) + +libcamera_base_deps = [ + dependency('threads'), +] + +# Internal components must use the libcamera_base_private dependency to enable +# the use of headers which must not be exposed to the libcamera public api. +libcamera_base_args = [ '-DLIBCAMERA_BASE_PRIVATE' ] + +libcamera_base_lib = shared_library('libcamera-base', + [libcamera_base_sources, libcamera_base_headers], + name_prefix : '', + install : true, + cpp_args : libcamera_base_args, + include_directories : libcamera_includes, + dependencies : libcamera_base_deps) + +libcamera_base = declare_dependency(sources : [ + libcamera_base_headers, + ], + include_directories : libcamera_includes, + link_with : libcamera_base_lib) + +pkg_mod = import('pkgconfig') +pkg_mod.generate(libcamera_base_lib, + version : '1.0', + description : 'Camera support base utility library', + subdirs : 'libcamera') + +libcamera_base_private = declare_dependency(dependencies : libcamera_base, + compile_args : libcamera_base_args) diff --git a/src/libcamera/base/message.cpp b/src/libcamera/base/message.cpp new file mode 100644 index 00000000..f1d772e4 --- /dev/null +++ b/src/libcamera/base/message.cpp @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * message.cpp - Message queue support + */ + +#include <libcamera/base/message.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/signal.h> + +/** + * \file base/message.h + * \brief Message queue support + * + * The messaging API enables inter-thread communication through message + * posting. Messages can be sent from any thread to any recipient deriving from + * the Object class. + * + * To post a message, the sender allocates it dynamically as instance of a class + * derived from Message. It then posts the message to an Object recipient + * through Object::postMessage(). Message ownership is passed to the object, + * thus the message shall not store any temporary data. + * + * The message is delivered in the context of the object's thread, through the + * Object::message() virtual method. After delivery the message is + * automatically deleted. + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Message) + +std::atomic_uint Message::nextUserType_{ Message::UserMessage }; + +/** + * \class Message + * \brief A message that can be posted to a Thread + */ + +/** + * \enum Message::Type + * \brief The message type + * \var Message::None + * \brief Invalid message type + * \var Message::InvokeMessage + * \brief Asynchronous method invocation across threads + * \var Message::ThreadMoveMessage + * \brief Object is being moved to a different thread + * \var Message::DeferredDelete + * \brief Object is scheduled for deletion + * \var Message::UserMessage + * \brief First value available for user-defined messages + */ + +/** + * \brief Construct a message object of type \a type + * \param[in] type The message type + */ +Message::Message(Message::Type type) + : type_(type) +{ +} + +Message::~Message() +{ +} + +/** + * \fn Message::type() + * \brief Retrieve the message type + * \return The message type + */ + +/** + * \fn Message::receiver() + * \brief Retrieve the message receiver + * \return The message receiver + */ + +/** + * \brief Reserve and register a custom user-defined message type + * + * Custom message types use values starting at Message::UserMessage. Assigning + * custom types manually may lead to accidental duplicated types. To avoid this + * problem, this method reserves and returns the next available user-defined + * message type. + * + * The recommended way to use this method is to subclass Message and provide a + * static accessor for the custom message type. + * + * \code{.cpp} + * class MyCustomMessage : public Message + * { + * public: + * MyCustomMessage() : Message(type()) {} + * + * static Message::Type type() + * { + * static MessageType type = registerMessageType(); + * return type; + * } + * }; + * \endcode + * + * \return A new unique message type + */ +Message::Type Message::registerMessageType() +{ + return static_cast<Message::Type>(nextUserType_++); +} + +/** + * \class InvokeMessage + * \brief A message carrying a method invocation across threads + */ + +/** + * \brief Construct an InvokeMessage for method invocation on an Object + * \param[in] method The bound method + * \param[in] pack The packed method arguments + * \param[in] semaphore The semaphore used to signal message delivery + * \param[in] deleteMethod True to delete the \a method when the message is + * destroyed + */ +InvokeMessage::InvokeMessage(BoundMethodBase *method, + std::shared_ptr<BoundMethodPackBase> pack, + Semaphore *semaphore, bool deleteMethod) + : Message(Message::InvokeMessage), method_(method), pack_(pack), + semaphore_(semaphore), deleteMethod_(deleteMethod) +{ +} + +InvokeMessage::~InvokeMessage() +{ + if (deleteMethod_) + delete method_; +} + +/** + * \fn InvokeMessage::semaphore() + * \brief Retrieve the message semaphore passed to the constructor + * \return The message semaphore + */ + +/** + * \brief Invoke the method bound to InvokeMessage::method_ with arguments + * InvokeMessage::pack_ + */ +void InvokeMessage::invoke() +{ + method_->invokePack(pack_.get()); +} + +/** + * \var InvokeMessage::method_ + * \brief The method to be invoked + */ + +/** + * \var InvokeMessage::pack_ + * \brief The packed method invocation arguments + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/base/object.cpp b/src/libcamera/base/object.cpp new file mode 100644 index 00000000..25410ecd --- /dev/null +++ b/src/libcamera/base/object.cpp @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * object.cpp - Base object + */ + +#include <libcamera/base/object.h> + +#include <algorithm> + +#include <libcamera/base/log.h> +#include <libcamera/base/message.h> +#include <libcamera/base/semaphore.h> +#include <libcamera/base/signal.h> +#include <libcamera/base/thread.h> +#include <libcamera/base/utils.h> + +/** + * \file base/object.h + * \brief Base object to support automatic signal disconnection + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Object) + +/** + * \class Object + * \brief Base object to support automatic signal disconnection + * + * The Object class simplifies signal/slot handling for classes implementing + * slots. By inheriting from Object, an object is automatically disconnected + * from all connected signals when it gets destroyed. + * + * Object instances are bound to the thread of their parent, or the thread in + * which they're created when they have no parent. When a message is posted to + * an object, its handler will run in the object's thread. This allows + * implementing easy message passing between threads by inheriting from the + * Object class. + * + * Deleting an object from a thread other than the one the object is bound to is + * unsafe, unless the caller ensures that the object isn't processing any + * message concurrently. + * + * Object slots connected to signals will also run in the context of the + * object's thread, regardless of whether the signal is emitted in the same or + * in another thread. + * + * \sa Message, Signal, Thread + */ + +/** + * \brief Construct an Object instance + * \param[in] parent The object parent + * + * The new Object instance is bound to the thread of its \a parent, or to the + * current thread if the \a parent is nullptr. + */ +Object::Object(Object *parent) + : parent_(parent), pendingMessages_(0) +{ + thread_ = parent ? parent->thread() : Thread::current(); + + if (parent) + parent->children_.push_back(this); +} + +/** + * \brief Destroy an Object instance + * + * Deleting an Object automatically disconnects all signals from the Object's + * slots. All the Object's children are made orphan, but stay bound to their + * current thread. + * + * Object instances shall be destroyed from the thread they are bound to, + * otherwise undefined behaviour may occur. If deletion of an Object needs to + * be scheduled from a different thread, deleteLater() shall be used. + */ +Object::~Object() +{ + /* + * Move signals to a private list to avoid concurrent iteration and + * deletion of items from Signal::disconnect(). + */ + std::list<SignalBase *> signals(std::move(signals_)); + for (SignalBase *signal : signals) + signal->disconnect(this); + + if (pendingMessages_) + thread()->removeMessages(this); + + if (parent_) { + auto it = std::find(parent_->children_.begin(), + parent_->children_.end(), this); + ASSERT(it != parent_->children_.end()); + parent_->children_.erase(it); + } + + for (auto child : children_) + child->parent_ = nullptr; +} + +/** + * \brief Schedule deletion of the instance in the thread it belongs to + * + * This function schedules deletion of the Object when control returns to the + * event loop that the object belongs to. This ensures the object is destroyed + * from the right context, as required by the libcamera threading model. + * + * If this function is called before the thread's event loop is started, the + * object will be deleted when the event loop starts. + * + * Deferred deletion can be used to control the destruction context with shared + * pointers. An object managed with shared pointers is deleted when the last + * reference is destroyed, which makes difficult to ensure through software + * design which context the deletion will take place in. With a custom deleter + * for the shared pointer using deleteLater(), the deletion can be guaranteed to + * happen in the thread the object is bound to. + * + * \code{.cpp} + * std::shared_ptr<MyObject> createObject() + * { + * struct Deleter : std::default_delete<MyObject> { + * void operator()(MyObject *obj) + * { + * obj->deleteLater(); + * } + * }; + * + * MyObject *obj = new MyObject(); + * + * return std::shared_ptr<MyObject>(obj, Deleter()); + * } + * \endcode + * + * \context This function is \threadsafe. + */ +void Object::deleteLater() +{ + postMessage(std::make_unique<Message>(Message::DeferredDelete)); +} + +/** + * \brief Post a message to the object's thread + * \param[in] msg The message + * + * This method posts the message \a msg to the message queue of the object's + * thread, to be delivered to the object through the message() method in the + * context of its thread. Message ownership is passed to the thread, and the + * message will be deleted after being delivered. + * + * Messages are delivered through the thread's event loop. If the thread is not + * running its event loop the message will not be delivered until the event + * loop gets started. + * + * Due to their asynchronous nature, threads do not provide any guarantee that + * all posted messages are delivered before the thread is stopped. See + * \ref thread-stop for additional information. + * + * \context This function is \threadsafe. + */ +void Object::postMessage(std::unique_ptr<Message> msg) +{ + thread()->postMessage(std::move(msg), this); +} + +/** + * \brief Message handler for the object + * \param[in] msg The message + * + * This virtual method receives messages for the object. It is called in the + * context of the object's thread, and can be overridden to process custom + * messages. The parent Object::message() method shall be called for any + * message not handled by the override method. + * + * The message \a msg is valid only for the duration of the call, no reference + * to it shall be kept after this method returns. + */ +void Object::message(Message *msg) +{ + switch (msg->type()) { + case Message::InvokeMessage: { + InvokeMessage *iMsg = static_cast<InvokeMessage *>(msg); + Semaphore *semaphore = iMsg->semaphore(); + iMsg->invoke(); + + if (semaphore) + semaphore->release(); + + break; + } + + case Message::DeferredDelete: + delete this; + break; + + default: + break; + } +} + +/** + * \fn R Object::invokeMethod() + * \brief Invoke a method asynchronously on an Object instance + * \param[in] func The object method to invoke + * \param[in] type Connection type for method invocation + * \param[in] args The method arguments + * + * This method invokes the member method \a func with arguments \a args, based + * on the connection \a type. Depending on the type, the method will be called + * synchronously in the same thread or asynchronously in the object's thread. + * + * Arguments \a args passed by value or reference are copied, while pointers + * are passed untouched. The caller shall ensure that any pointer argument + * remains valid until the method is invoked. + * + * Due to the asynchronous nature of threads, functions invoked asynchronously + * with the ConnectionTypeQueued type are not guaranteed to be called before + * the thread is stopped. See \ref thread-stop for additional information. + * + * \context This function is \threadsafe. + * + * \return For connection types ConnectionTypeDirect and + * ConnectionTypeBlocking, return the return value of the invoked method. For + * connection type ConnectionTypeQueued, return a default-constructed R value. + */ + +/** + * \fn Object::thread() + * \brief Retrieve the thread the object is bound to + * \context This function is \threadsafe. + * \return The thread the object is bound to + */ + +/** + * \brief Move the object and all its children to a different thread + * \param[in] thread The target thread + * + * This method moves the object and all its children from the current thread to + * the new \a thread. + * + * Before the object is moved, a Message::ThreadMoveMessage message is sent to + * it. The message() method can be reimplement in derived classes to be notified + * of the upcoming thread move and perform any required processing. + * + * Moving an object that has a parent is not allowed, and causes undefined + * behaviour. + * + * \context This function is thread-bound. + */ +void Object::moveToThread(Thread *thread) +{ + ASSERT(Thread::current() == thread_); + + if (thread_ == thread) + return; + + if (parent_) { + LOG(Object, Error) + << "Moving object to thread with a parent is not permitted"; + return; + } + + notifyThreadMove(); + + thread->moveObject(this); +} + +void Object::notifyThreadMove() +{ + Message msg(Message::ThreadMoveMessage); + message(&msg); + + for (auto child : children_) + child->notifyThreadMove(); +} + +/** + * \fn Object::parent() + * \brief Retrieve the object's parent + * \return The object's parent + */ + +void Object::connect(SignalBase *signal) +{ + signals_.push_back(signal); +} + +void Object::disconnect(SignalBase *signal) +{ + for (auto iter = signals_.begin(); iter != signals_.end(); ) { + if (*iter == signal) + iter = signals_.erase(iter); + else + iter++; + } +} + +} /* namespace libcamera */ diff --git a/src/libcamera/base/semaphore.cpp b/src/libcamera/base/semaphore.cpp new file mode 100644 index 00000000..7aedc6a8 --- /dev/null +++ b/src/libcamera/base/semaphore.cpp @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * semaphore.cpp - General-purpose counting semaphore + */ + +#include <libcamera/base/semaphore.h> +#include <libcamera/base/thread.h> + +/** + * \file base/semaphore.h + * \brief General-purpose counting semaphore + */ + +namespace libcamera { + +/** + * \class Semaphore + * \brief General-purpose counting semaphore + * + * A semaphore is a locking primitive that protects resources. It is created + * with an initial number of resources (which may be 0), and offers two + * primitives to acquire and release resources. The acquire() method tries to + * acquire a number of resources, and blocks if not enough resources are + * available until they get released. The release() method releases a number of + * resources, waking up any consumer blocked on an acquire() call. + */ + +/** + * \brief Construct a semaphore with \a n resources + * \param[in] n The resource count + */ +Semaphore::Semaphore(unsigned int n) + : available_(n) +{ +} + +/** + * \brief Retrieve the number of available resources + * \return The number of available resources + */ +unsigned int Semaphore::available() +{ + MutexLocker locker(mutex_); + return available_; +} + +/** + * \brief Acquire \a n resources + * \param[in] n The resource count + * + * This method attempts to acquire \a n resources. If \a n is higher than the + * number of available resources, the call will block until enough resources + * become available. + */ +void Semaphore::acquire(unsigned int n) +{ + MutexLocker locker(mutex_); + cv_.wait(locker, [&] { return available_ >= n; }); + available_ -= n; +} + +/** + * \brief Try to acquire \a n resources without blocking + * \param[in] n The resource count + * + * This method attempts to acquire \a n resources. If \a n is higher than the + * number of available resources, it returns false immediately without + * acquiring any resource. Otherwise it acquires the resources and returns + * true. + * + * \return True if the resources have been acquired, false otherwise + */ +bool Semaphore::tryAcquire(unsigned int n) +{ + MutexLocker locker(mutex_); + if (available_ < n) + return false; + + available_ -= n; + return true; +} + +/** + * \brief Release \a n resources + * \param[in] n The resource count + * + * This method releases \a n resources, increasing the available resource count + * by \a n. If the number of available resources becomes large enough for any + * consumer blocked on an acquire() call, those consumers get woken up. + */ +void Semaphore::release(unsigned int n) +{ + { + MutexLocker locker(mutex_); + available_ += n; + } + + cv_.notify_all(); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/base/signal.cpp b/src/libcamera/base/signal.cpp new file mode 100644 index 00000000..298b2d4b --- /dev/null +++ b/src/libcamera/base/signal.cpp @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * signal.cpp - Signal & slot implementation + */ + +#include <libcamera/base/signal.h> + +#include <libcamera/base/thread.h> + +/** + * \file base/signal.h + * \brief Signal & slot implementation + */ + +namespace libcamera { + +namespace { + +/* + * Mutex to protect the SignalBase::slots_ and Object::signals_ lists. If lock + * contention needs to be decreased, this could be replaced with locks in + * Object and SignalBase, or with a mutex pool. + */ +Mutex signalsLock; + +} /* namespace */ + +void SignalBase::connect(BoundMethodBase *slot) +{ + MutexLocker locker(signalsLock); + + Object *object = slot->object(); + if (object) + object->connect(this); + slots_.push_back(slot); +} + +void SignalBase::disconnect(Object *object) +{ + disconnect([object](SlotList::iterator &iter) { + return (*iter)->match(object); + }); +} + +void SignalBase::disconnect(std::function<bool(SlotList::iterator &)> match) +{ + MutexLocker locker(signalsLock); + + for (auto iter = slots_.begin(); iter != slots_.end(); ) { + if (match(iter)) { + Object *object = (*iter)->object(); + if (object) + object->disconnect(this); + + delete *iter; + iter = slots_.erase(iter); + } else { + ++iter; + } + } +} + +SignalBase::SlotList SignalBase::slots() +{ + MutexLocker locker(signalsLock); + return slots_; +} + +/** + * \class Signal + * \brief Generic signal and slot communication mechanism + * + * Signals and slots are a language construct aimed at communication between + * objects through the observer pattern without the need for boilerplate code. + * See http://doc.qt.io/qt-5/signalsandslots.html for more information. + * + * Signals model events that can be observed from objects unrelated to the event + * source. Slots are functions that are called in response to a signal. Signals + * can be connected to and disconnected from slots dynamically at runtime. When + * a signal is emitted, all connected slots are called sequentially in the order + * they have been connected. + * + * Signals are defined with zero, one or more typed parameters. They are emitted + * with a value for each of the parameters, and those values are passed to the + * connected slots. + * + * Slots are normal static or class member functions. In order to be connected + * to a signal, their signature must match the signal type (taking the same + * arguments as the signal and returning void). + * + * Connecting a signal to a slot results in the slot being called with the + * arguments passed to the emit() function when the signal is emitted. Multiple + * slots can be connected to the same signal, and multiple signals can connected + * to the same slot. Duplicate connections between a signal and a slot are + * allowed and result in the slot being called multiple times for the same + * signal emission. + * + * When a slot belongs to an instance of the Object class, the slot is called + * in the context of the thread that the object is bound to. If the signal is + * emitted from the same thread, the slot will be called synchronously, before + * Signal::emit() returns. If the signal is emitted from a different thread, + * the slot will be called asynchronously from the object's thread's event + * loop, after the Signal::emit() method returns, with a copy of the signal's + * arguments. The emitter shall thus ensure that any pointer or reference + * passed through the signal will remain valid after the signal is emitted. + */ + +/** + * \fn Signal::connect(T *object, R (T::*func)(Args...)) + * \brief Connect the signal to a member function slot + * \param[in] object The slot object pointer + * \param[in] func The slot member function + * + * If the typename T inherits from Object, the signal will be automatically + * disconnected from the \a func slot of \a object when \a object is destroyed. + * Otherwise the caller shall disconnect signals manually before destroying \a + * object. + * + * \context This function is \threadsafe. + */ + +/** + * \fn Signal::connect(R (*func)(Args...)) + * \brief Connect the signal to a static function slot + * \param[in] func The slot static function + * + * \context This function is \threadsafe. + */ + +/** + * \fn Signal::disconnect() + * \brief Disconnect the signal from all slots + * + * \context This function is \threadsafe. + */ + +/** + * \fn Signal::disconnect(T *object) + * \brief Disconnect the signal from all slots of the \a object + * \param[in] object The object pointer whose slots to disconnect + * + * \context This function is \threadsafe. + */ + +/** + * \fn Signal::disconnect(T *object, R (T::*func)(Args...)) + * \brief Disconnect the signal from the \a object slot member function \a func + * \param[in] object The object pointer whose slots to disconnect + * \param[in] func The slot member function to disconnect + * + * \context This function is \threadsafe. + */ + +/** + * \fn Signal::disconnect(R (*func)(Args...)) + * \brief Disconnect the signal from the slot static function \a func + * \param[in] func The slot static function to disconnect + * + * \context This function is \threadsafe. + */ + +/** + * \fn Signal::emit(Args... args) + * \brief Emit the signal and call all connected slots + * \param args The arguments passed to the connected slots + * + * Emitting a signal calls all connected slots synchronously and sequentially in + * the order the slots have been connected. The arguments passed to the emit() + * function are passed to the slot functions unchanged. If a slot modifies one + * of the arguments (when passed by pointer or reference), the modification is + * thus visible to all subsequently called slots. + * + * This function is not \threadsafe, but thread-safety is guaranteed against + * concurrent connect() and disconnect() calls. + */ + +} /* namespace libcamera */ diff --git a/src/libcamera/base/thread.cpp b/src/libcamera/base/thread.cpp new file mode 100644 index 00000000..1232f895 --- /dev/null +++ b/src/libcamera/base/thread.cpp @@ -0,0 +1,705 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * thread.cpp - Thread support + */ + +#include <libcamera/base/thread.h> + +#include <atomic> +#include <condition_variable> +#include <list> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libcamera/base/event_dispatcher.h> +#include <libcamera/base/event_dispatcher_poll.h> +#include <libcamera/base/log.h> +#include <libcamera/base/message.h> + +/** + * \page thread Thread Support + * + * libcamera supports multi-threaded applications through a threading model that + * sets precise rules to guarantee thread-safe usage of the API. Additionally, + * libcamera makes internal use of threads, and offers APIs that simplify + * interactions with application threads. Careful compliance with the threading + * model will ensure avoidance of race conditions. + * + * Every thread created by libcamera is associated with an instance of the + * Thread class. Those threads run an internal event loop by default to + * dispatch events to objects. Additionally, the main thread of the application + * (defined as the thread that calls CameraManager::start()) is also associated + * with a Thread instance, but has no event loop accessible to libcamera. Other + * application threads are not visible to libcamera. + * + * \section thread-objects Threads and Objects + * + * Instances of the Object class and all its derived classes are thread-aware + * and are bound to the thread they are created in. They are said to *live* in + * a thread, and they interact with the event loop of their thread for the + * purpose of message passing and signal delivery. Messages posted to the + * object with Object::postMessage() will be delivered from the event loop of + * the thread that the object lives in. Signals delivered to the object, unless + * explicitly connected with ConnectionTypeDirect, will also be delivered from + * the object thread's event loop. + * + * All Object instances created internally by libcamera are bound to internal + * threads. As objects interact with thread event loops for proper operation, + * creating an Object instance in a thread that has no internal event loop (such + * as the main application thread, or libcamera threads that have a custom main + * loop), prevents some features of the Object class from being used. See + * Thread::exec() for more details. + * + * \section thread-signals Threads and Signals + * + * When sent to a receiver that does not inherit from the Object class, signals + * are delivered synchronously in the thread of the sender. When the receiver + * inherits from the Object class, delivery is by default asynchronous if the + * sender and receiver live in different threads. In that case, the signal is + * posted to the receiver's message queue and will be delivered from the + * receiver's event loop, running in the receiver's thread. This mechanism can + * be overridden by selecting a different connection type when calling + * Signal::connect(). + * + * \section thread-reentrancy Reentrancy and Thread-Safety + * + * Through the documentation, several terms are used to define how classes and + * their member functions can be used from multiple threads. + * + * - A **reentrant** function may be called simultaneously from multiple + * threads if and only if each invocation uses a different instance of the + * class. This is the default for all member functions not explictly marked + * otherwise. + * + * - \anchor thread-safe A **thread-safe** function may be called + * simultaneously from multiple threads on the same instance of a class. A + * thread-safe function is thus reentrant. Thread-safe functions may also be + * called simultaneously with any other reentrant function of the same class + * on the same instance. + * + * - \anchor thread-bound A **thread-bound** function may be called only from + * the thread that the class instances lives in (see section \ref + * thread-objects). For instances of classes that do not derive from the + * Object class, this is the thread in which the instance was created. A + * thread-bound function is not thread-safe, and may or may not be reentrant. + * + * Neither reentrancy nor thread-safety, in this context, mean that a function + * may be called simultaneously from the same thread, for instance from a + * callback invoked by the function. This may deadlock and isn't allowed unless + * separately documented. + * + * A class is defined as reentrant, thread-safe or thread-bound if all its + * member functions are reentrant, thread-safe or thread-bound respectively. + * Some member functions may additionally be documented as having additional + * thread-related attributes. + * + * Most classes are reentrant but not thread-safe, as making them fully + * thread-safe would incur locking costs considered prohibitive for the + * expected use cases. + */ + +/** + * \file base/thread.h + * \brief Thread support + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Thread) + +class ThreadMain; + +/** + * \brief A queue of posted messages + */ +class MessageQueue +{ +public: + /** + * \brief List of queued Message instances + */ + std::list<std::unique_ptr<Message>> list_; + /** + * \brief Protects the \ref list_ + */ + Mutex mutex_; + /** + * \brief The recursion level for recursive Thread::dispatchMessages() + * calls + */ + unsigned int recursion_ = 0; +}; + +/** + * \brief Thread-local internal data + */ +class ThreadData +{ +public: + ThreadData() + : thread_(nullptr), running_(false), dispatcher_(nullptr) + { + } + + static ThreadData *current(); + +private: + friend class Thread; + friend class ThreadMain; + + Thread *thread_; + bool running_; + pid_t tid_; + + Mutex mutex_; + + std::atomic<EventDispatcher *> dispatcher_; + + std::condition_variable cv_; + std::atomic<bool> exit_; + int exitCode_; + + MessageQueue messages_; +}; + +/** + * \brief Thread wrapper for the main thread + */ +class ThreadMain : public Thread +{ +public: + ThreadMain() + { + data_->running_ = true; + } + +protected: + void run() override + { + LOG(Thread, Fatal) << "The main thread can't be restarted"; + } +}; + +static thread_local ThreadData *currentThreadData = nullptr; +static ThreadMain mainThread; + +/** + * \brief Retrieve thread-local internal data for the current thread + * \return The thread-local internal data for the current thread + */ +ThreadData *ThreadData::current() +{ + if (currentThreadData) + return currentThreadData; + + /* + * The main thread doesn't receive thread-local data when it is + * started, set it here. + */ + ThreadData *data = mainThread.data_; + data->tid_ = syscall(SYS_gettid); + currentThreadData = data; + return data; +} + +/** + * \typedef Mutex + * \brief An alias for std::mutex + */ + +/** + * \typedef MutexLocker + * \brief An alias for std::unique_lock<std::mutex> + */ + +/** + * \class Thread + * \brief A thread of execution + * + * The Thread class is a wrapper around std::thread that handles integration + * with the Object, Signal and EventDispatcher classes. + * + * Thread instances by default run an event loop until the exit() method is + * called. The event loop dispatches events (messages, notifiers and timers) + * sent to the objects living in the thread. This behaviour can be modified by + * overriding the run() function. + * + * \section thread-stop Stopping Threads + * + * Threads can't be forcibly stopped. Instead, a thread user first requests the + * thread to exit and then waits for the thread's main function to react to the + * request and return, at which points the thread will stop. + * + * For threads running exec(), the exit() function is used to request the thread + * to exit. For threads subclassing the Thread class and implementing a custom + * run() function, a subclass-specific mechanism shall be provided. In either + * case, the wait() function shall be called to wait for the thread to stop. + * + * Due to their asynchronous nature, threads are subject to race conditions when + * they stop. This is of particular importance for messages posted to the thread + * with postMessage() (and the other mechanisms that rely on it, such as + * Object::invokeMethod() or asynchronous signal delivery). To understand the + * issues, three contexts need to be considered: + * + * - The worker is the Thread performing work and being instructed to stop. + * - The controller is the context which instructs the worker thread to stop. + * - The other contexts are any threads other than the worker and controller + * that interact with the worker thread. + * + * Messages posted to the worker thread from the controller context before + * calling exit() are queued to the thread's message queue, and the Thread class + * offers no guarantee that those messages will be processed before the thread + * stops. This allows threads to stop fast. + * + * A thread that requires delivery of messages posted from the controller + * context before exit() should reimplement the run() function and call + * dispatchMessages() after exec(). + * + * Messages posted to the worker thread from the other contexts are asynchronous + * with respect to the exit() call from the controller context. There is no + * guarantee as to whether those messages will be processed or not before the + * thread stops. + * + * Messages that are not processed will stay in the queue, in the exact same way + * as messages posted after the thread has stopped. They will be processed when + * the thread is restarted. If the thread is never restarted, they will be + * deleted without being processed when the Thread instance is destroyed. + */ + +/** + * \brief Create a thread + */ +Thread::Thread() +{ + data_ = new ThreadData; + data_->thread_ = this; +} + +Thread::~Thread() +{ + delete data_->dispatcher_.load(std::memory_order_relaxed); + delete data_; +} + +/** + * \brief Start the thread + */ +void Thread::start() +{ + MutexLocker locker(data_->mutex_); + + if (data_->running_) + return; + + data_->running_ = true; + data_->exitCode_ = -1; + data_->exit_.store(false, std::memory_order_relaxed); + + thread_ = std::thread(&Thread::startThread, this); +} + +void Thread::startThread() +{ + struct ThreadCleaner { + ThreadCleaner(Thread *thread, void (Thread::*cleaner)()) + : thread_(thread), cleaner_(cleaner) + { + } + ~ThreadCleaner() + { + (thread_->*cleaner_)(); + } + + Thread *thread_; + void (Thread::*cleaner_)(); + }; + + /* + * Make sure the thread is cleaned up even if the run method exits + * abnormally (for instance via a direct call to pthread_cancel()). + */ + thread_local ThreadCleaner cleaner(this, &Thread::finishThread); + + data_->tid_ = syscall(SYS_gettid); + currentThreadData = data_; + + run(); +} + +/** + * \brief Enter the event loop + * + * This method enters an event loop based on the event dispatcher instance for + * the thread, and blocks until the exit() method is called. It is meant to be + * called within the thread from the run() method and shall not be called + * outside of the thread. + * + * \return The exit code passed to the exit() method + */ +int Thread::exec() +{ + MutexLocker locker(data_->mutex_); + + EventDispatcher *dispatcher = eventDispatcher(); + + locker.unlock(); + + while (!data_->exit_.load(std::memory_order_acquire)) + dispatcher->processEvents(); + + locker.lock(); + + return data_->exitCode_; +} + +/** + * \brief Main method of the thread + * + * When the thread is started with start(), it calls this method in the context + * of the new thread. The run() method can be overridden to perform custom + * work, either custom initialization and cleanup before and after calling the + * Thread::exec() function, or a custom thread loop altogether. When this + * method returns the thread execution is stopped, and the \ref finished signal + * is emitted. + * + * Note that if this function is overridden and doesn't call Thread::exec(), no + * events will be dispatched to the objects living in the thread. These objects + * will not be able to use the EventNotifier, Timer or Message facilities. This + * includes functions that rely on message dispatching, such as + * Object::deleteLater(). + * + * The base implementation just calls exec(). + */ +void Thread::run() +{ + exec(); +} + +void Thread::finishThread() +{ + data_->mutex_.lock(); + data_->running_ = false; + data_->mutex_.unlock(); + + finished.emit(this); + data_->cv_.notify_all(); +} + +/** + * \brief Stop the thread's event loop + * \param[in] code The exit code + * + * This method interrupts the event loop started by the exec() method, causing + * exec() to return \a code. + * + * Calling exit() on a thread that reimplements the run() method and doesn't + * call exec() will likely have no effect. + * + * \context This function is \threadsafe. + */ +void Thread::exit(int code) +{ + data_->exitCode_ = code; + data_->exit_.store(true, std::memory_order_release); + + EventDispatcher *dispatcher = data_->dispatcher_.load(std::memory_order_relaxed); + if (!dispatcher) + return; + + dispatcher->interrupt(); +} + +/** + * \brief Wait for the thread to finish + * \param[in] duration Maximum wait duration + * + * This function waits until the thread finishes or the \a duration has + * elapsed, whichever happens first. If \a duration is equal to + * utils::duration::max(), the wait never times out. If the thread is not + * running the function returns immediately. + * + * \context This function is \threadsafe. + * + * \return True if the thread has finished, or false if the wait timed out + */ +bool Thread::wait(utils::duration duration) +{ + bool hasFinished = true; + + { + MutexLocker locker(data_->mutex_); + + if (duration == utils::duration::max()) + data_->cv_.wait(locker, [&]() { return !data_->running_; }); + else + hasFinished = data_->cv_.wait_for(locker, duration, + [&]() { return !data_->running_; }); + } + + if (thread_.joinable()) + thread_.join(); + + return hasFinished; +} + +/** + * \brief Check if the thread is running + * + * A Thread instance is considered as running once the underlying thread has + * started. This method guarantees that it returns true after the start() + * method returns, and false after the wait() method returns. + * + * \context This function is \threadsafe. + * + * \return True if the thread is running, false otherwise + */ +bool Thread::isRunning() +{ + MutexLocker locker(data_->mutex_); + return data_->running_; +} + +/** + * \var Thread::finished + * \brief Signal the end of thread execution + */ + +/** + * \brief Retrieve the Thread instance for the current thread + * \context This function is \threadsafe. + * \return The Thread instance for the current thread + */ +Thread *Thread::current() +{ + ThreadData *data = ThreadData::current(); + return data->thread_; +} + +/** + * \brief Retrieve the ID of the current thread + * + * The thread ID corresponds to the Linux thread ID (TID) as returned by the + * gettid system call. + * + * \context This function is \threadsafe. + * + * \return The ID of the current thread + */ +pid_t Thread::currentId() +{ + ThreadData *data = ThreadData::current(); + return data->tid_; +} + +/** + * \brief Retrieve the event dispatcher + * + * This function retrieves the internal event dispatcher for the thread. The + * returned event dispatcher is valid until the thread is destroyed. + * + * \context This function is \threadsafe. + * + * \return Pointer to the event dispatcher + */ +EventDispatcher *Thread::eventDispatcher() +{ + if (!data_->dispatcher_.load(std::memory_order_relaxed)) + data_->dispatcher_.store(new EventDispatcherPoll(), + std::memory_order_release); + + return data_->dispatcher_.load(std::memory_order_relaxed); +} + +/** + * \brief Post a message to the thread for the \a receiver + * \param[in] msg The message + * \param[in] receiver The receiver + * + * This method stores the message \a msg in the message queue of the thread for + * the \a receiver and wake up the thread's event loop. Message ownership is + * passed to the thread, and the message will be deleted after being delivered. + * + * Messages are delivered through the thread's event loop. If the thread is not + * running its event loop the message will not be delivered until the event + * loop gets started. + * + * When the thread is stopped, posted messages may not have all been processed. + * See \ref thread-stop for additional information. + * + * If the \a receiver is not bound to this thread the behaviour is undefined. + * + * \context This function is \threadsafe. + * + * \sa exec() + */ +void Thread::postMessage(std::unique_ptr<Message> msg, Object *receiver) +{ + msg->receiver_ = receiver; + + ASSERT(data_ == receiver->thread()->data_); + + MutexLocker locker(data_->messages_.mutex_); + data_->messages_.list_.push_back(std::move(msg)); + receiver->pendingMessages_++; + locker.unlock(); + + EventDispatcher *dispatcher = + data_->dispatcher_.load(std::memory_order_acquire); + if (dispatcher) + dispatcher->interrupt(); +} + +/** + * \brief Remove all posted messages for the \a receiver + * \param[in] receiver The receiver + * + * If the \a receiver is not bound to this thread the behaviour is undefined. + */ +void Thread::removeMessages(Object *receiver) +{ + ASSERT(data_ == receiver->thread()->data_); + + MutexLocker locker(data_->messages_.mutex_); + if (!receiver->pendingMessages_) + return; + + std::vector<std::unique_ptr<Message>> toDelete; + for (std::unique_ptr<Message> &msg : data_->messages_.list_) { + if (!msg) + continue; + if (msg->receiver_ != receiver) + continue; + + /* + * Move the message to the pending deletion list to delete it + * after releasing the lock. The messages list element will + * contain a null pointer, and will be removed when dispatching + * messages. + */ + toDelete.push_back(std::move(msg)); + receiver->pendingMessages_--; + } + + ASSERT(!receiver->pendingMessages_); + locker.unlock(); + + toDelete.clear(); +} + +/** + * \brief Dispatch posted messages for this thread + * \param[in] type The message type + * + * This function immediately dispatches all the messages previously posted for + * this thread with postMessage() that match the message \a type. If the \a type + * is Message::Type::None, all messages are dispatched. + * + * Messages shall only be dispatched from the current thread, typically within + * the thread from the run() function. Calling this function outside of the + * thread results in undefined behaviour. + * + * This function is not thread-safe, but it may be called recursively in the + * same thread from an object's message handler. It guarantees delivery of + * messages in the order they have been posted in all cases. + */ +void Thread::dispatchMessages(Message::Type type) +{ + ASSERT(data_ == ThreadData::current()); + + ++data_->messages_.recursion_; + + MutexLocker locker(data_->messages_.mutex_); + + std::list<std::unique_ptr<Message>> &messages = data_->messages_.list_; + + for (std::unique_ptr<Message> &msg : messages) { + if (!msg) + continue; + + if (type != Message::Type::None && msg->type() != type) + continue; + + /* + * Move the message, setting the entry in the list to null. It + * will cause recursive calls to ignore the entry, and the erase + * loop at the end of the function to delete it from the list. + */ + std::unique_ptr<Message> message = std::move(msg); + + Object *receiver = message->receiver_; + ASSERT(data_ == receiver->thread()->data_); + receiver->pendingMessages_--; + + locker.unlock(); + receiver->message(message.get()); + message.reset(); + locker.lock(); + } + + /* + * If the recursion level is 0, erase all null messages in the list. We + * can't do so during recursion, as it would invalidate the iterator of + * the outer calls. + */ + if (!--data_->messages_.recursion_) { + for (auto iter = messages.begin(); iter != messages.end(); ) { + if (!*iter) + iter = messages.erase(iter); + else + ++iter; + } + } +} + +/** + * \brief Move an \a object and all its children to the thread + * \param[in] object The object + */ +void Thread::moveObject(Object *object) +{ + ThreadData *currentData = object->thread_->data_; + ThreadData *targetData = data_; + + MutexLocker lockerFrom(currentData->messages_.mutex_, std::defer_lock); + MutexLocker lockerTo(targetData->messages_.mutex_, std::defer_lock); + std::lock(lockerFrom, lockerTo); + + moveObject(object, currentData, targetData); +} + +void Thread::moveObject(Object *object, ThreadData *currentData, + ThreadData *targetData) +{ + /* Move pending messages to the message queue of the new thread. */ + if (object->pendingMessages_) { + unsigned int movedMessages = 0; + + for (std::unique_ptr<Message> &msg : currentData->messages_.list_) { + if (!msg) + continue; + if (msg->receiver_ != object) + continue; + + targetData->messages_.list_.push_back(std::move(msg)); + movedMessages++; + } + + if (movedMessages) { + EventDispatcher *dispatcher = + targetData->dispatcher_.load(std::memory_order_acquire); + if (dispatcher) + dispatcher->interrupt(); + } + } + + object->thread_ = this; + + /* Move all children. */ + for (auto child : object->children_) + moveObject(child, currentData, targetData); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/base/timer.cpp b/src/libcamera/base/timer.cpp new file mode 100644 index 00000000..9c54352d --- /dev/null +++ b/src/libcamera/base/timer.cpp @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * timer.cpp - Generic timer + */ + +#include <libcamera/base/timer.h> + +#include <chrono> + +#include <libcamera/base/event_dispatcher.h> +#include <libcamera/base/log.h> +#include <libcamera/base/message.h> +#include <libcamera/base/thread.h> +#include <libcamera/base/utils.h> + +#include <libcamera/camera_manager.h> + +/** + * \file base/timer.h + * \brief Generic timer + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Timer) + +/** + * \class Timer + * \brief Single-shot timer interface + * + * The Timer class models a single-shot timer that is started with start() and + * emits the \ref timeout signal when it times out. + * + * Once started the timer will run until it times out. It can be stopped with + * stop(), and once it times out or is stopped, can be started again with + * start(). + * + * The timer deadline is specified as either a duration in milliseconds or an + * absolute time point. If the deadline is set to the current time or to the + * past, the timer will time out immediately when execution returns to the + * event loop of the timer's thread. + * + * Timers run in the thread they belong to, and thus emit the \a ref timeout + * signal from that thread. To avoid race conditions they must not be started + * or stopped from a different thread, attempts to do so will be rejected and + * logged, and may cause undefined behaviour. + */ + +/** + * \brief Construct a timer + * \param[in] parent The parent Object + */ +Timer::Timer(Object *parent) + : Object(parent), running_(false) +{ +} + +Timer::~Timer() +{ + stop(); +} + +/** + * \fn Timer::start(unsigned int msec) + * \brief Start or restart the timer with a timeout of \a msec + * \param[in] msec The timer duration in milliseconds + * + * If the timer is already running it will be stopped and restarted. + * + * \context This function is \threadbound. + */ + +/** + * \brief Start or restart the timer with a timeout of \a duration + * \param[in] duration The timer duration in milliseconds + * + * If the timer is already running it will be stopped and restarted. + * + * \context This function is \threadbound. + */ +void Timer::start(std::chrono::milliseconds duration) +{ + start(utils::clock::now() + duration); +} + +/** + * \brief Start or restart the timer with a \a deadline + * \param[in] deadline The timer deadline + * + * If the timer is already running it will be stopped and restarted. + * + * \context This function is \threadbound. + */ +void Timer::start(std::chrono::steady_clock::time_point deadline) +{ + if (Thread::current() != thread()) { + LOG(Timer, Error) << "Timer can't be started from another thread"; + return; + } + + deadline_ = deadline; + + LOG(Timer, Debug) + << "Starting timer " << this << ": deadline " + << utils::time_point_to_string(deadline_); + + if (isRunning()) + unregisterTimer(); + + registerTimer(); +} + +/** + * \brief Stop the timer + * + * After this function returns the timer is guaranteed not to emit the + * \ref timeout signal. + * + * If the timer is not running this function performs no operation. + * + * \context This function is \threadbound. + */ +void Timer::stop() +{ + if (!isRunning()) + return; + + if (Thread::current() != thread()) { + LOG(Timer, Error) << "Timer can't be stopped from another thread"; + return; + } + + unregisterTimer(); +} + +void Timer::registerTimer() +{ + thread()->eventDispatcher()->registerTimer(this); + running_ = true; +} + +void Timer::unregisterTimer() +{ + running_ = false; + thread()->eventDispatcher()->unregisterTimer(this); +} + +/** + * \brief Check if the timer is running + * \return True if the timer is running, false otherwise + */ +bool Timer::isRunning() const +{ + return running_; +} + +/** + * \fn Timer::deadline() + * \brief Retrieve the timer deadline + * \return The timer deadline + */ + +/** + * \var Timer::timeout + * \brief Signal emitted when the timer times out + * + * The timer pointer is passed as a parameter. + */ + +void Timer::message(Message *msg) +{ + if (msg->type() == Message::ThreadMoveMessage) { + if (isRunning()) { + unregisterTimer(); + invokeMethod(&Timer::registerTimer, + ConnectionTypeQueued); + } + } + + Object::message(msg); +} + +} /* namespace libcamera */ diff --git a/src/libcamera/base/utils.cpp b/src/libcamera/base/utils.cpp new file mode 100644 index 00000000..45b92b67 --- /dev/null +++ b/src/libcamera/base/utils.cpp @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * utils.cpp - Miscellaneous utility functions + */ + +#include <libcamera/base/utils.h> + +#include <iomanip> +#include <sstream> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/** + * \file base/utils.h + * \brief Miscellaneous utility functions + */ + +namespace libcamera { + +namespace utils { + +/** + * \brief Strip the directory prefix from the path + * \param[in] path The path to process + * + * basename is implemented differently across different C libraries. This + * implementation matches the one provided by the GNU libc, and does not + * modify its input parameter. + * + * \return A pointer within the given path without any leading directory + * components. + */ +const char *basename(const char *path) +{ + const char *base = strrchr(path, '/'); + return base ? base + 1 : path; +} + +/** + * \brief Get an environment variable + * \param[in] name The name of the variable to return + * + * The environment list is searched to find the variable 'name', and the + * corresponding string is returned. + * + * If 'secure execution' is required then this function always returns NULL to + * avoid vulnerabilities that could occur if set-user-ID or set-group-ID + * programs accidentally trust the environment. + * + * \note Not all platforms may support the features required to implement the + * secure execution check, in which case this function behaves as getenv(). A + * notable example of this is Android. + * + * \return A pointer to the value in the environment or NULL if the requested + * environment variable doesn't exist or if secure execution is required. + */ +char *secure_getenv(const char *name) +{ +#if HAVE_SECURE_GETENV + return ::secure_getenv(name); +#else +#if HAVE_ISSETUGID + if (issetugid()) + return NULL; +#endif + return getenv(name); +#endif +} + +/** + * \brief Identify the dirname portion of a path + * \param[in] path The full path to parse + * + * This function conforms with the behaviour of the %dirname() function as + * defined by POSIX. + * + * \return A string of the directory component of the path + */ +std::string dirname(const std::string &path) +{ + if (path.empty()) + return "."; + + /* + * Skip all trailing slashes. If the path is only made of slashes, + * return "/". + */ + size_t pos = path.size() - 1; + while (path[pos] == '/') { + if (!pos) + return "/"; + pos--; + } + + /* + * Find the previous slash. If the path contains no non-trailing slash, + * return ".". + */ + while (path[pos] != '/') { + if (!pos) + return "."; + pos--; + } + + /* + * Return the directory name up to (but not including) any trailing + * slash. If this would result in an empty string, return "/". + */ + while (path[pos] == '/') { + if (!pos) + return "/"; + pos--; + } + + return path.substr(0, pos + 1); +} + +/** + * \fn std::vector<typename T::key_type> map_keys(const T &map) + * \brief Retrieve the keys of a std::map<> + * \param[in] map The map whose keys to retrieve + * \return A std::vector<> containing the keys of \a map + */ + +/** + * \fn libcamera::utils::set_overlap(InputIt1 first1, InputIt1 last1, + * InputIt2 first2, InputIt2 last2) + * \brief Count the number of elements in the intersection of two ranges + * + * Count the number of elements in the intersection of the sorted ranges [\a + * first1, \a last1) and [\a first1, \a last2). Elements are compared using + * operator< and the ranges must be sorted with respect to the same. + * + * \return The number of elements in the intersection of the two ranges + */ + +/** + * \typedef clock + * \brief The libcamera clock (monotonic) + */ + +/** + * \typedef duration + * \brief The libcamera duration related to libcamera::utils::clock + */ + +/** + * \typedef time_point + * \brief The libcamera time point related to libcamera::utils::clock + */ + +/** + * \brief Convert a duration to a timespec + * \param[in] value The duration + * \return A timespec expressing the duration + */ +struct timespec duration_to_timespec(const duration &value) +{ + uint64_t nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>(value).count(); + struct timespec ts; + ts.tv_sec = nsecs / 1000000000ULL; + ts.tv_nsec = nsecs % 1000000000ULL; + return ts; +} + +/** + * \brief Convert a time point to a string representation + * \param[in] time The time point + * \return A string representing the time point in hh:mm:ss.nanoseconds format + */ +std::string time_point_to_string(const time_point &time) +{ + uint64_t nsecs = std::chrono::duration_cast<std::chrono::nanoseconds>(time.time_since_epoch()).count(); + unsigned int secs = nsecs / 1000000000ULL; + + std::ostringstream ossTimestamp; + ossTimestamp.fill('0'); + ossTimestamp << secs / (60 * 60) << ":" + << std::setw(2) << (secs / 60) % 60 << ":" + << std::setw(2) << secs % 60 << "." + << std::setw(9) << nsecs % 1000000000ULL; + return ossTimestamp.str(); +} + +std::basic_ostream<char, std::char_traits<char>> & +operator<<(std::basic_ostream<char, std::char_traits<char>> &stream, const _hex &h) +{ + stream << "0x"; + + std::ostream::fmtflags flags = stream.setf(std::ios_base::hex, + std::ios_base::basefield); + std::streamsize width = stream.width(h.w); + char fill = stream.fill('0'); + + stream << h.v; + + stream.flags(flags); + stream.width(width); + stream.fill(fill); + + return stream; +} + +/** + * \fn hex(T value, unsigned int width) + * \brief Write an hexadecimal value to an output string + * \param value The value + * \param width The width + * + * Return an object of unspecified type such that, if \a os is the name of an + * output stream of type std::ostream, and T is an integer type, then the + * expression + * + * \code{.cpp} + * os << utils::hex(value) + * \endcode + * + * will output the \a value to the stream in hexadecimal form with the base + * prefix and the filling character set to '0'. The field width is set to \a + * width if specified to a non-zero value, or to the native width of type T + * otherwise. The \a os stream configuration is not modified. + */ + +/** + * \brief Copy a string with a size limit + * \param[in] dst The destination string + * \param[in] src The source string + * \param[in] size The size of the destination string + * + * This function copies the null-terminated string \a src to \a dst with a limit + * of \a size - 1 characters, and null-terminates the result if \a size is + * larger than 0. If \a src is larger than \a size - 1, \a dst is truncated. + * + * \return The size of \a src + */ +size_t strlcpy(char *dst, const char *src, size_t size) +{ + if (size) { + strncpy(dst, src, size); + dst[size - 1] = '\0'; + } + + return strlen(src); +} + +details::StringSplitter::StringSplitter(const std::string &str, const std::string &delim) + : str_(str), delim_(delim) +{ +} + +details::StringSplitter::iterator::iterator(const details::StringSplitter *ss, std::string::size_type pos) + : ss_(ss), pos_(pos) +{ + next_ = ss_->str_.find(ss_->delim_, pos_); +} + +details::StringSplitter::iterator &details::StringSplitter::iterator::operator++() +{ + pos_ = next_; + if (pos_ != std::string::npos) { + pos_ += ss_->delim_.length(); + next_ = ss_->str_.find(ss_->delim_, pos_); + } + + return *this; +} + +std::string details::StringSplitter::iterator::operator*() const +{ + std::string::size_type count; + count = next_ != std::string::npos ? next_ - pos_ : next_; + return ss_->str_.substr(pos_, count); +} + +bool details::StringSplitter::iterator::operator!=(const details::StringSplitter::iterator &other) const +{ + return pos_ != other.pos_; +} + +details::StringSplitter::iterator details::StringSplitter::begin() const +{ + return iterator(this, 0); +} + +details::StringSplitter::iterator details::StringSplitter::end() const +{ + return iterator(this, std::string::npos); +} + +/** + * \fn template<typename Container, typename UnaryOp> \ + * std::string utils::join(const Container &items, const std::string &sep, UnaryOp op) + * \brief Join elements of a container in a string with a separator + * \param[in] items The container + * \param[in] sep The separator to add between elements + * \param[in] op A function that converts individual elements to strings + * + * This function joins all elements in the \a items container into a string and + * returns it. The \a sep separator is added between elements. If the container + * elements are not implicitly convertible to std::string, the \a op function + * shall be provided to perform conversion of elements to std::string. + * + * \return A string that concatenates all elements in the container + */ + +/** + * \fn split(const std::string &str, const std::string &delim) + * \brief Split a string based on a delimiter + * \param[in] str The string to split + * \param[in] delim The delimiter string + * + * This function splits the string \a str into substrings based on the + * delimiter \a delim. It returns an object of unspecified type that can be + * used in a range-based for loop and yields the substrings in sequence. + * + * \return An object that can be used in a range-based for loop to iterate over + * the substrings + */ +details::StringSplitter split(const std::string &str, const std::string &delim) +{ + /** \todo Try to avoid copies of str and delim */ + return details::StringSplitter(str, delim); +} + +/** + * \brief Remove any non-ASCII characters from a string + * \param[in] str The string to strip + * + * Remove all non-ASCII characters from a string. + * + * \return A string equal to \a str stripped out of all non-ASCII characters + */ +std::string toAscii(const std::string &str) +{ + std::string ret; + for (const char &c : str) + if (!(c & 0x80)) + ret += c; + return ret; +} + +/** + * \fn alignDown(unsigned int value, unsigned int alignment) + * \brief Align \a value down to \a alignment + * \param[in] value The value to align + * \param[in] alignment The alignment + * \return The value rounded down to the nearest multiple of \a alignment + */ + +/** + * \fn alignUp(unsigned int value, unsigned int alignment) + * \brief Align \a value up to \a alignment + * \param[in] value The value to align + * \param[in] alignment The alignment + * \return The value rounded up to the nearest multiple of \a alignment + */ + +/** + * \fn reverse(T &&iterable) + * \brief Wrap an iterable to reverse iteration in a range-based loop + * \param[in] iterable The iterable + * \return A value of unspecified type that, when used in a range-based for + * loop, will cause the loop to iterate over the \a iterable in reverse order + */ + +/** + * \fn enumerate(T &iterable) + * \brief Wrap an iterable to enumerate index and value in a range-based loop + * \param[in] iterable The iterable + * + * Range-based for loops are handy and widely preferred in C++, but are limited + * in their ability to replace for loops that require access to a loop counter. + * The enumerate() function solves this problem by wrapping the \a iterable in + * an adapter that, when used as a range-expression, will provide iterators + * whose value_type is a pair of index and value reference. + * + * The iterable must support std::begin() and std::end(). This includes all + * containers provided by the standard C++ library, as well as C-style arrays. + * + * A typical usage pattern would use structured binding to store the index and + * value in two separate variables: + * + * \code{.cpp} + * std::vector<int> values = ...; + * + * for (auto [index, value] : utils::enumerate(values)) { + * ... + * } + * \endcode + * + * Note that the argument to enumerate() has to be an lvalue, as the lifetime + * of any rvalue would not be extended to the whole for loop. The compiler will + * complain if an rvalue is passed to the function, in which case it should be + * stored in a local variable before the loop. + * + * \return A value of unspecified type that, when used in a range-based for + * loop, iterates over an indexed view of the \a iterable + */ + +/** + * \class Duration + * \brief Helper class from std::chrono::duration that represents a time + * duration in nanoseconds with double precision + */ + +/** + * \fn Duration::Duration(const std::chrono::duration<Rep, Period> &d) + * \brief Construct a Duration by converting an arbitrary std::chrono::duration + * \param[in] d The std::chrono::duration object to convert from + * + * The constructed \a Duration object is internally represented in double + * precision with nanoseconds ticks. + */ + +/** + * \fn Duration::get<Period>() + * \brief Retrieve the tick count, converted to the timebase provided by the + * template argument Period of type \a std::ratio + * + * A typical usage example is given below: + * + * \code{.cpp} + * utils::Duration d = 5s; + * double d_in_ms = d.get<std::milli>(); + * \endcode + * + * \return The tick count of the Duration expressed in \a Period + */ + +/** + * \fn Duration::operator bool() + * \brief Boolean operator to test if a \a Duration holds a non-zero time value + * + * \return True if \a Duration is a non-zero time value, False otherwise + */ + +} /* namespace utils */ + +#ifndef __DOXYGEN__ +template<class CharT, class Traits> +std::basic_ostream<CharT, Traits> &operator<<(std::basic_ostream<CharT, Traits> &os, + const utils::Duration &d) +{ + std::basic_ostringstream<CharT, Traits> s; + + s.flags(os.flags()); + s.imbue(os.getloc()); + s.setf(std::ios_base::fixed, std::ios_base::floatfield); + s.precision(2); + s << d.get<std::micro>() << "us"; + return os << s.str(); +} + +template +std::basic_ostream<char, std::char_traits<char>> & +operator<< <char, std::char_traits<char>>(std::basic_ostream<char, std::char_traits<char>> &os, + const utils::Duration &d); +#endif + +} /* namespace libcamera */ |