blob: 7238a3165da8caba542e43f4039cbd132f3e3b84 [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* event_dispatcher_poll.cpp - Poll-based event dispatcher
*/
#include <libcamera/base/event_dispatcher_poll.h>
#include <algorithm>
#include <chrono>
#include <iomanip>
#include <poll.h>
#include <stdint.h>
#include <string.h>
#include <sys/eventfd.h>
#include <unistd.h>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/log.h>
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
#include <libcamera/base/utils.h>
/**
* \file base/event_dispatcher_poll.h
*/
namespace libcamera {
LOG_DECLARE_CATEGORY(Event)
static const char *notifierType(EventNotifier::Type type)
{
if (type == EventNotifier::Read)
return "read";
if (type == EventNotifier::Write)
return "write";
if (type == EventNotifier::Exception)
return "exception";
return "";
}
/**
* \class EventDispatcherPoll
* \brief A poll-based event dispatcher
*/
EventDispatcherPoll::EventDispatcherPoll()
: processingEvents_(false)
{
/*
* Create the event fd. Failures are fatal as we can't implement an
* interruptible dispatcher without the fd.
*/
eventfd_ = UniqueFD(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
if (!eventfd_.isValid())
LOG(Event, Fatal) << "Unable to create eventfd";
}
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(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 function 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_.get(), 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_.get(), &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)
<< "next timer " << nextTimer << " expires in "
<< 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_.get(), &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();
}
/* 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();
}
}
} /* namespace libcamera */