summaryrefslogtreecommitdiff
path: root/src/libcamera/base
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcamera/base')
-rw-r--r--src/libcamera/base/bound_method.cpp109
-rw-r--r--src/libcamera/base/event_dispatcher.cpp116
-rw-r--r--src/libcamera/base/event_dispatcher_poll.cpp308
-rw-r--r--src/libcamera/base/log.cpp998
-rw-r--r--src/libcamera/base/meson.build11
-rw-r--r--src/libcamera/base/message.cpp166
-rw-r--r--src/libcamera/base/object.cpp300
-rw-r--r--src/libcamera/base/semaphore.cpp103
-rw-r--r--src/libcamera/base/signal.cpp179
-rw-r--r--src/libcamera/base/thread.cpp680
-rw-r--r--src/libcamera/base/timer.cpp185
11 files changed, 3155 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/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..d76ca7fc
--- /dev/null
+++ b/src/libcamera/base/event_dispatcher_poll.cpp
@@ -0,0 +1,308 @@
+/* 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/log.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/event_notifier.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/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
index 302a2886..7a19c67c 100644
--- a/src/libcamera/base/meson.build
+++ b/src/libcamera/base/meson.build
@@ -2,10 +2,21 @@
libcamera_base_sources = files([
'class.cpp',
+ 'bound_method.cpp',
+ 'event_dispatcher.cpp',
+ 'event_dispatcher_poll.cpp',
+ 'log.cpp',
+ 'message.cpp',
+ 'object.cpp',
+ 'semaphore.cpp',
+ 'signal.cpp',
+ 'thread.cpp',
+ 'timer.cpp',
'utils.cpp',
])
libcamera_base_deps = [
+ dependency('threads'),
]
libcamera_base_lib = shared_library('libcamera-base',
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..c7c2d6b2
--- /dev/null
+++ b/src/libcamera/base/thread.cpp
@@ -0,0 +1,680 @@
+/* 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 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.
+ *
+ * \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.
+ */
+void Thread::dispatchMessages(Message::Type type)
+{
+ ASSERT(data_ == ThreadData::current());
+
+ MutexLocker locker(data_->messages_.mutex_);
+
+ std::list<std::unique_ptr<Message>> &messages = data_->messages_.list_;
+
+ for (auto iter = messages.begin(); iter != messages.end(); ) {
+ std::unique_ptr<Message> &msg = *iter;
+
+ if (!msg) {
+ iter = data_->messages_.list_.erase(iter);
+ continue;
+ }
+
+ if (type != Message::Type::None && msg->type() != type) {
+ ++iter;
+ continue;
+ }
+
+ std::unique_ptr<Message> message = std::move(msg);
+ iter = data_->messages_.list_.erase(iter);
+
+ Object *receiver = message->receiver_;
+ ASSERT(data_ == receiver->thread()->data_);
+ receiver->pendingMessages_--;
+
+ locker.unlock();
+ receiver->message(message.get());
+ message.reset();
+ locker.lock();
+ }
+}
+
+/**
+ * \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 */