/* * Generic Event Loop * * Copyright (C) 2014-2016 Ideas on board Oy * * Contact: Laurent Pinchart * * 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 #include #include #include #include #include #include #include #include #include #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); } }