From 8356f8a6ab875680087032285c3b7bbfbdbddba9 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 4 Jan 2019 21:26:14 +0200 Subject: libcamera: Add a poll-based event dispatcher Provide a poll-based event dispatcher implementation as convenience for applications that don't need a custom event loop. The poll-based dispatcher is automatically instantiated if the application doesn't provide its own dispatcher. Signed-off-by: Laurent Pinchart --- src/libcamera/camera_manager.cpp | 19 ++- src/libcamera/event_dispatcher_poll.cpp | 235 ++++++++++++++++++++++++++ src/libcamera/include/event_dispatcher_poll.h | 50 ++++++ src/libcamera/meson.build | 2 + 4 files changed, 301 insertions(+), 5 deletions(-) create mode 100644 src/libcamera/event_dispatcher_poll.cpp create mode 100644 src/libcamera/include/event_dispatcher_poll.h (limited to 'src/libcamera') diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp index 6e1d5db1..be327f5d 100644 --- a/src/libcamera/camera_manager.cpp +++ b/src/libcamera/camera_manager.cpp @@ -9,6 +9,7 @@ #include #include "device_enumerator.h" +#include "event_dispatcher_poll.h" #include "log.h" #include "pipeline_handler.h" @@ -188,9 +189,10 @@ CameraManager *CameraManager::instance() * \param dispatcher Pointer to the event dispatcher * * libcamera requires an event dispatcher to integrate event notification and - * timers with the application event loop. Applications shall call this function - * once and only once before the camera manager is started with start() to set - * the event dispatcher. + * timers with the application event loop. Applications that want to provide + * their own event dispatcher shall call this function once and only once before + * the camera manager is started with start(). If no event dispatcher is + * provided, a default poll-based implementation will be used. * * The CameraManager takes ownership of the event dispatcher and will delete it * when the application terminates. @@ -207,11 +209,18 @@ void CameraManager::setEventDispatcher(EventDispatcher *dispatcher) /** * \brief Retrieve the event dispatcher - * \return Pointer to the event dispatcher, or nullptr if no event dispatcher - * has been set + * + * This function retrieves the event dispatcher set with setEventDispatcher(). + * If no dispatcher has been set, a default poll-based implementation is created + * and returned, and no custom event dispatcher may be installed anymore. + * + * \return Pointer to the event dispatcher */ EventDispatcher *CameraManager::eventDispatcher() { + if (!dispatcher_) + dispatcher_ = new EventDispatcherPoll(); + return dispatcher_; } diff --git a/src/libcamera/event_dispatcher_poll.cpp b/src/libcamera/event_dispatcher_poll.cpp new file mode 100644 index 00000000..a497a7fe --- /dev/null +++ b/src/libcamera/event_dispatcher_poll.cpp @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * event_dispatcher_poll.cpp - Poll-based event dispatcher + */ + +#include +#include +#include +#include + +#include +#include + +#include "event_dispatcher_poll.h" +#include "log.h" + +/** + * \file event_dispatcher_poll.h + */ + +namespace libcamera { + +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() +{ +} + +EventDispatcherPoll::~EventDispatcherPoll() +{ +} + +void EventDispatcherPoll::registerEventNotifier(EventNotifier *notifier) +{ + EventNotifierSetPoll &set = notifiers_[notifier->fd()]; + EventNotifier::Type type = notifier->type(); + + if (set.notifiers[type] && set.notifiers[type] != notifier) { + LOG(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(Warning) << notifierType(type) << " notifier for fd " + << notifier->fd() << " is not registered"; + return; + } + + set.notifiers[type] = nullptr; + 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; + + /* Create the pollfd array. */ + std::vector pollfds; + pollfds.reserve(notifiers_.size()); + + for (auto notifier : notifiers_) + pollfds.push_back({ notifier.first, notifier.second.events(), 0 }); + + /* Compute the timeout. */ + Timer *nextTimer = !timers_.empty() ? timers_.front() : nullptr; + struct timespec timeout; + + if (nextTimer) { + clock_gettime(CLOCK_MONOTONIC, &timeout); + uint64_t now = timeout.tv_sec * 1000000000ULL + timeout.tv_nsec; + + if (nextTimer->deadline() > now) { + uint64_t delta = nextTimer->deadline() - now; + timeout.tv_sec = delta / 1000000000ULL; + timeout.tv_nsec = delta % 1000000000ULL; + } else { + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + } + + LOG(Debug) << "timeout " << timeout.tv_sec << "." + << std::setfill('0') << std::setw(9) + << timeout.tv_nsec; + } + + /* Wait for events and process notifers and timers. */ + ret = ppoll(pollfds.data(), pollfds.size(), + nextTimer ? &timeout : nullptr, nullptr); + if (ret < 0) { + ret = -errno; + LOG(Warning) << "poll() failed with " << strerror(-ret); + } else if (ret > 0) { + processNotifiers(pollfds); + } + + processTimers(); +} + +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; +} + +void EventDispatcherPoll::processNotifiers(std::vector &pollfds) +{ + static const struct { + EventNotifier::Type type; + short events; + } events[] = { + { EventNotifier::Read, POLLIN }, + { EventNotifier::Write, POLLOUT }, + { EventNotifier::Exception, POLLPRI }, + }; + + for (const struct 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(Warning) << "Disabling " << notifierType(event.type) + << " due to invalid file descriptor " + << pfd.fd; + unregisterEventNotifier(notifier); + continue; + } + + if (pfd.revents & event.events) + notifier->activated.emit(notifier); + } + } +} + +void EventDispatcherPoll::processTimers() +{ + struct timespec ts; + uint64_t now; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ts.tv_sec * 1000000000ULL + ts.tv_nsec; + + 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/include/event_dispatcher_poll.h b/src/libcamera/include/event_dispatcher_poll.h new file mode 100644 index 00000000..30fd5850 --- /dev/null +++ b/src/libcamera/include/event_dispatcher_poll.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * event_dispatcher_poll.h - Poll-based event dispatcher + */ +#ifndef __LIBCAMERA_EVENT_DISPATCHER_POLL_H__ +#define __LIBCAMERA_EVENT_DISPATCHER_POLL_H__ + +#include + +#include +#include +#include + +namespace libcamera { + +class EventNotifier; +class Timer; + +class EventDispatcherPoll final : public EventDispatcher +{ +public: + EventDispatcherPoll(); + ~EventDispatcherPoll(); + + void registerEventNotifier(EventNotifier *notifier); + void unregisterEventNotifier(EventNotifier *notifier); + + void registerTimer(Timer *timer); + void unregisterTimer(Timer *timer); + + void processEvents(); + +private: + struct EventNotifierSetPoll { + short events() const; + EventNotifier *notifiers[3]; + }; + + std::map notifiers_; + std::list timers_; + + void processNotifiers(std::vector &pollfds); + void processTimers(); +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_EVENT_DISPATCHER_POLL_H__ */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 61fb9320..abf9a71d 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -3,6 +3,7 @@ libcamera_sources = files([ 'camera_manager.cpp', 'device_enumerator.cpp', 'event_dispatcher.cpp', + 'event_dispatcher_poll.cpp', 'event_notifier.cpp', 'log.cpp', 'media_device.cpp', @@ -14,6 +15,7 @@ libcamera_sources = files([ libcamera_headers = files([ 'include/device_enumerator.h', + 'include/event_dispatcher_poll.h', 'include/log.h', 'include/media_device.h', 'include/media_object.h', -- cgit v1.2.1