diff options
Diffstat (limited to 'src/libcamera/base/event_dispatcher_poll.cpp')
-rw-r--r-- | src/libcamera/base/event_dispatcher_poll.cpp | 308 |
1 files changed, 308 insertions, 0 deletions
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 */ |