diff options
author | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2014-11-18 22:52:06 +0200 |
---|---|---|
committer | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2016-05-24 03:03:01 +0300 |
commit | c9601d407a7ca943e53fa49a5a5f69b868c6ed11 (patch) | |
tree | 248d13a9e817ba3c73fc50e2d655b4cd12013e10 /lib/xlnx-events.c | |
parent | 3653f5e4d877176a30035d9123ac65daf46ba787 (diff) |
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Diffstat (limited to 'lib/xlnx-events.c')
-rw-r--r-- | lib/xlnx-events.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/lib/xlnx-events.c b/lib/xlnx-events.c new file mode 100644 index 0000000..933f27a --- /dev/null +++ b/lib/xlnx-events.c @@ -0,0 +1,269 @@ +/* + * Generic Event Loop + * + * Copyright (C) 2014-2016 Ideas on board Oy + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define _DEFAULT_SOURCE +#include <errno.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/select.h> + +#include <xlnx-events.h> +#include <xlnx-list.h> +#include <xlnx-tools.h> + +#include "xlnx-priv.h" + +#define SELECT_TIMEOUT 2000 /* in milliseconds */ + +struct xlnx_events { + struct list_entry events; + bool done; + + int maxfd; + fd_set rfds; + fd_set wfds; + fd_set efds; +}; + +struct xlnx_event_fd { + struct list_entry list; + + int fd; + int type; + void (*callback)(void *priv); + void *priv; +}; + +static void xlnx_events_watch_fd(void *handler, int fd, int event_type, + void(*callback)(void *), void *priv) +{ + struct xlnx_events *events = handler; + struct xlnx_event_fd *event; + + event = malloc(sizeof *event); + if (event == NULL) + return; + + event->fd = fd; + event->type = event_type; + event->callback = callback; + event->priv = priv; + + switch (event->type) { + case POLLIN: + FD_SET(fd, &events->rfds); + break; + case POLLOUT: + FD_SET(fd, &events->wfds); + break; + case POLLERR: + FD_SET(fd, &events->efds); + break; + } + + events->maxfd = max(events->maxfd, fd); + + list_append(&event->list, &events->events); +} + +static void xlnx_events_unwatch_fd(void *handler, int fd) +{ + struct xlnx_events *events = handler; + struct xlnx_event_fd *event = NULL; + struct xlnx_event_fd *entry; + int maxfd = 0; + + list_for_each_entry(entry, &events->events, list) { + if (entry->fd == fd) + event = entry; + else + maxfd = max(maxfd, entry->fd); + } + + if (event == NULL) + return; + + switch (event->type) { + case POLLIN: + FD_CLR(fd, &events->rfds); + break; + case POLLOUT: + FD_CLR(fd, &events->wfds); + break; + case POLLERR: + FD_CLR(fd, &events->efds); + break; + } + + events->maxfd = maxfd; + + list_remove(&event->list); + free(event); +} + +static void xlnx_events_dispatch(struct xlnx_events *events, const fd_set *rfds, + const fd_set *wfds, const fd_set *efds) +{ + struct xlnx_event_fd *event; + struct xlnx_event_fd *next; + + list_for_each_entry_safe(event, next, &events->events, list) { + if (event->type == POLLIN && FD_ISSET(event->fd, rfds)) + event->callback(event->priv); + else if (event->type == POLLOUT && FD_ISSET(event->fd, wfds)) + event->callback(event->priv); + else if (event->type == POLLERR && FD_ISSET(event->fd, efds)) + event->callback(event->priv); + + /* If the callback stopped events processing, we're done. */ + if (events->done) + break; + } +} + +/** + * \brief Start and run the event loop + * \param xv the library context + * + * This function starts and runs the event loop synchronously, waiting for + * events and dispatching them as they arrive. It doesn't return until the loop + * is requested to stop by a call to xlnx_events_stop() or a fatal error occurs. + * + * \return false if the loop exited due to a stop request, or true if it exited + * due to a fatal error. + */ +bool xlnx_events_loop(struct xlnx_video *xv) +{ + struct xlnx_events *events = xlnx_video_get_event_handler_data(xv); + + events->done = false; + + while (!events->done) { + struct timeval timeout; + fd_set rfds; + fd_set wfds; + fd_set efds; + int ret; + + timeout.tv_sec = SELECT_TIMEOUT / 1000; + timeout.tv_usec = (SELECT_TIMEOUT % 1000) * 1000; + rfds = events->rfds; + wfds = events->wfds; + efds = events->efds; + + ret = select(events->maxfd + 1, &rfds, &wfds, &efds, &timeout); + if (ret < 0) { + /* EINTR means that a signal has been received, continue + * to the next iteration in that case. + */ + if (errno == EINTR) + continue; + + printf("error: select failed with %d\n", errno); + break; + } + if (ret == 0) { + /* select() should never time out as the library is + * supposed to process images continuously. A timeout is + * thus considered as a fatal error. + */ + printf("error: select timeout\n"); + break; + } + + xlnx_events_dispatch(events, &rfds, &wfds, &efds); + } + + return !events->done; +} + +/** + * \brief Stop the event loop + * \param xv the library context + * + * Stop the running event loop started with xlnx_events_loop(). The loop is + * stopped asynchronously after processing the next event. This function doesn't + * wait for the loop to exit before returning. + */ +void xlnx_events_stop(struct xlnx_video *xv) +{ + struct xlnx_events *events = xlnx_video_get_event_handler_data(xv); + + events->done = true; +} + +static const struct xlnx_video_event_operations xlnx_events_ops = { + .watch = xlnx_events_watch_fd, + .unwatch = xlnx_events_unwatch_fd, +}; + +/** + * \brief Initialize event handling + * \param xv the library context + * + * This function registers event handling operations with the library context + * \a xv. The event handling implementation is based on a select() loop that is + * controlled through the xlnx_events_loop() and xlnx_events_stop() functions. + * + * Before destroying the library context event handling must be cleaned up with + * a call to xlnx_events_fini(). + * + * \return 0 on success or a negative error code on failure. + */ +int xlnx_events_init(struct xlnx_video *xv) +{ + struct xlnx_events *events; + + events = calloc(1, sizeof(*events)); + if (!events) + return -ENOMEM; + + FD_ZERO(&events->rfds); + FD_ZERO(&events->wfds); + FD_ZERO(&events->efds); + events->maxfd = 0; + list_init(&events->events); + + xlnx_video_set_event_handler(xv, &xlnx_events_ops, events); + return 0; +} + +/** + * \brief Clean up event handling + * \param xv the library context + * + * This function cleans up event handling. It must be called before destroying + * any library context previously initialized by a call to xlnx_events_init(). + */ +void xlnx_events_fini(struct xlnx_video *xv) +{ + struct xlnx_events *events = xlnx_video_get_event_handler_data(xv); + + if (events) { + xlnx_video_set_event_handler(xv, NULL, NULL); + free(events); + } +} |