[libcamera-devel] [PATCH v2 07/11] libcamera: Add a poll-based event dispatcher

Laurent Pinchart laurent.pinchart at ideasonboard.com
Tue Jan 8 00:11:47 CET 2019


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 <laurent.pinchart at ideasonboard.com>
---
Changes since v1:

- Fix ASSERT() condition check
- Ignore duplicate notifiers
- Clarify warning messages for event notifier (un)registration errors
---
 include/libcamera/libcamera.h                 |   1 +
 src/libcamera/camera_manager.cpp              |  19 +-
 src/libcamera/event_dispatcher_poll.cpp       | 234 ++++++++++++++++++
 src/libcamera/include/event_dispatcher_poll.h |  50 ++++
 src/libcamera/meson.build                     |   2 +
 5 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

diff --git a/include/libcamera/libcamera.h b/include/libcamera/libcamera.h
index 785babefa1c8..2dcaeda49812 100644
--- a/include/libcamera/libcamera.h
+++ b/include/libcamera/libcamera.h
@@ -10,5 +10,6 @@
 #include <libcamera/camera.h>
 #include <libcamera/camera_manager.h>
 #include <libcamera/event_dispatcher.h>
+#include <libcamera/event_dispatcher_poll.h>
 
 #endif /* __LIBCAMERA_LIBCAMERA_H__ */
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index cbfd977f1e3c..348ea2fedf64 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -9,6 +9,7 @@
 #include <libcamera/event_dispatcher.h>
 
 #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.
@@ -208,11 +210,18 @@ void CameraManager::setEventDispatcher(EventDispatcher *dispatcher)
 /**
  * \fn CameraManager::eventDispatcher()
  * \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.
+ *
+ * \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 000000000000..0cd08b2b4cc1
--- /dev/null
+++ b/src/libcamera/event_dispatcher_poll.cpp
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * event_dispatcher_poll.cpp - Poll-based event dispatcher
+ */
+
+#include <algorithm>
+#include <iomanip>
+#include <poll.h>
+#include <string.h>
+
+#include <libcamera/event_notifier.h>
+#include <libcamera/timer.h>
+
+#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<struct pollfd> 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<struct pollfd> &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);
+			}
+
+			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 000000000000..600289e455e2
--- /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_loop.h - Camera object interface
+ */
+#ifndef __LIBCAMERA_EVENT_DISPATCHER_POLL_H__
+#define __LIBCAMERA_EVENT_DISPATCHER_POLL_H__
+
+#include <libcamera/event_dispatcher.h>
+
+#include <list>
+#include <map>
+#include <vector>
+
+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<int, EventNotifierSetPoll> notifiers_;
+	std::list<Timer *> timers_;
+
+	void processNotifiers(std::vector<struct pollfd> &pollfds);
+	void processTimers();
+};
+
+} /* namespace libcamera */
+
+#endif /* __LIBCAMERA_EVENT_DISPATCHER_POLL_H__ */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 61fb93205b34..abf9a71d4172 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',
-- 
Regards,

Laurent Pinchart



More information about the libcamera-devel mailing list