/* * Copyright © 2016 Keith Packard * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ #ifdef HAVE_DIX_CONFIG_H #include #endif #include #include #include #include #include "misc.h" /* for typedef of pointer */ #include "ospoll.h" #include "list.h" #if !HAVE_OSPOLL && HAVE_EPOLL_CREATE1 #include #define EPOLL 1 #define HAVE_OSPOLL 1 #endif #if !HAVE_OSPOLL #include "xserver_poll.h" #define POLL 1 #define HAVE_OSPOLL 1 #endif #if EPOLL #include /* epoll-based implementation */ struct ospollfd { int fd; int xevents; enum ospoll_trigger trigger; void (*callback)(int fd, int xevents, void *data); void *data; struct xorg_list deleted; }; struct ospoll { int epoll_fd; struct ospollfd **fds; int num; int size; struct xorg_list deleted; }; #endif #if POLL /* poll-based implementation */ struct ospollfd { short revents; enum ospoll_trigger trigger; void (*callback)(int fd, int revents, void *data); void *data; }; struct ospoll { struct pollfd *fds; struct ospollfd *osfds; int num; int size; Bool changed; }; #endif /* Binary search for the specified file descriptor * * Returns position if found * Returns -position - 1 if not found */ static int ospoll_find(struct ospoll *ospoll, int fd) { int lo = 0; int hi = ospoll->num - 1; while (lo <= hi) { int m = (lo + hi) >> 1; #if EPOLL int t = ospoll->fds[m]->fd; #endif #if POLL int t = ospoll->fds[m].fd; #endif if (t < fd) lo = m + 1; else if (t > fd) hi = m - 1; else return m; } return -(lo + 1); } #if EPOLL static void ospoll_clean_deleted(struct ospoll *ospoll) { struct ospollfd *osfd, *tmp; xorg_list_for_each_entry_safe(osfd, tmp, &ospoll->deleted, deleted) { xorg_list_del(&osfd->deleted); free(osfd); } } #endif /* Insert an element into an array * * base: base address of array * num: number of elements in the array before the insert * size: size of each element * pos: position to insert at */ static inline void array_insert(void *base, size_t num, size_t size, size_t pos) { char *b = base; memmove(b + (pos+1) * size, b + pos * size, (num - pos) * size); } /* Delete an element from an array * * base: base address of array * num: number of elements in the array before the delete * size: size of each element * pos: position to delete from */ static inline void array_delete(void *base, size_t num, size_t size, size_t pos) { char *b = base; memmove(b + pos * size, b + (pos + 1) * size, (num - pos - 1) * size); } struct ospoll * ospoll_create(void) { #if EPOLL struct ospoll *ospoll = calloc(1, sizeof (struct ospoll)); ospoll->epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (ospoll->epoll_fd < 0) { free (ospoll); return NULL; } xorg_list_init(&ospoll->deleted); return ospoll; #endif #if POLL return calloc(1, sizeof (struct ospoll)); #endif } void ospoll_destroy(struct ospoll *ospoll) { #if EPOLL if (ospoll) { assert (ospoll->num == 0); close(ospoll->epoll_fd); ospoll_clean_deleted(ospoll); free(ospoll->fds); free(ospoll); } #endif #if POLL if (ospoll) { assert (ospoll->num == 0); free (ospoll->fds); free (ospoll->osfds); free (ospoll); } #endif } Bool ospoll_add(struct ospoll *ospoll, int fd, enum ospoll_trigger trigger, void (*callback)(int fd, int xevents, void *data), void *data) { int pos = ospoll_find(ospoll, fd); #if EPOLL struct ospollfd *osfd; if (pos < 0) { struct epoll_event ev; osfd = calloc(1, sizeof (struct ospollfd)); if (!osfd) return FALSE; if (ospoll->num >= ospoll->size) { struct ospollfd **new_fds; int new_size = ospoll->size ? ospoll->size * 2 : MAXCLIENTS * 2; new_fds = reallocarray(ospoll->fds, new_size, sizeof (ospoll->fds[0])); if (!new_fds) { free (osfd); return FALSE; } ospoll->fds = new_fds; ospoll->size = new_size; } ev.events = 0; ev.data.ptr = osfd; if (trigger == ospoll_trigger_edge) ev.events |= EPOLLET; if (epoll_ctl(ospoll->epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { free(osfd); return FALSE; } osfd->fd = fd; osfd->xevents = 0; pos = -pos - 1; array_insert(ospoll->fds, ospoll->num, sizeof (ospoll->fds[0]), pos); ospoll->fds[pos] = osfd; ospoll->num++; } else { osfd = ospoll->fds[pos]; } osfd->data = data; osfd->callback = callback; osfd->trigger = trigger; #endif #if POLL if (pos < 0) { if (ospoll->num == ospoll->size) { struct pollfd *new_fds; struct ospollfd *new_osfds; int new_size = ospoll->size ? ospoll->size * 2 : MAXCLIENTS * 2; new_fds = reallocarray(ospoll->fds, new_size, sizeof (ospoll->fds[0])); if (!new_fds) return FALSE; ospoll->fds = new_fds; new_osfds = reallocarray(ospoll->osfds, new_size, sizeof (ospoll->osfds[0])); if (!new_osfds) return FALSE; ospoll->osfds = new_osfds; ospoll->size = new_size; } pos = -pos - 1; array_insert(ospoll->fds, ospoll->num, sizeof (ospoll->fds[0]), pos); array_insert(ospoll->osfds, ospoll->num, sizeof (ospoll->osfds[0]), pos); ospoll->num++; ospoll->changed = TRUE; ospoll->fds[pos].fd = fd; ospoll->fds[pos].events = 0; ospoll->fds[pos].revents = 0; ospoll->osfds[pos].revents = 0; } ospoll->osfds[pos].trigger = trigger; ospoll->osfds[pos].callback = callback; ospoll->osfds[pos].data = data; #endif return TRUE; } void ospoll_remove(struct ospoll *ospoll, int fd) { int pos = ospoll_find(ospoll, fd); pos = ospoll_find(ospoll, fd); if (pos >= 0) { #if EPOLL struct ospollfd *osfd = ospoll->fds[pos]; struct epoll_event ev; ev.events = 0; ev.data.ptr = osfd; (void) epoll_ctl(ospoll->epoll_fd, EPOLL_CTL_DEL, fd, &ev); array_delete(ospoll->fds, ospoll->num, sizeof (ospoll->fds[0]), pos); ospoll->num--; osfd->callback = NULL; osfd->data = NULL; xorg_list_add(&osfd->deleted, &ospoll->deleted); #endif #if POLL array_delete(ospoll->fds, ospoll->num, sizeof (ospoll->fds[0]), pos); array_delete(ospoll->osfds, ospoll->num, sizeof (ospoll->osfds[0]), pos); ospoll->num--; ospoll->changed = TRUE; #endif } } #if EPOLL static void epoll_mod(struct ospoll *ospoll, struct ospollfd *osfd) { struct epoll_event ev; ev.events = 0; if (osfd->xevents & X_NOTIFY_READ) ev.events |= EPOLLIN; if (osfd->xevents & X_NOTIFY_WRITE) ev.events |= EPOLLOUT; if (osfd->trigger == ospoll_trigger_edge) ev.events |= EPOLLET; ev.data.ptr = osfd; (void) epoll_ctl(ospoll->epoll_fd, EPOLL_CTL_MOD, osfd->fd, &ev); } #endif void ospoll_listen(struct ospoll *ospoll, int fd, int xevents) { int pos = ospoll_find(ospoll, fd); if (pos >= 0) { #if EPOLL struct ospollfd *osfd = ospoll->fds[pos]; osfd->xevents |= xevents; epoll_mod(ospoll, osfd); #endif #if POLL if (xevents & X_NOTIFY_READ) { ospoll->fds[pos].events |= POLLIN; ospoll->osfds[pos].revents &= ~POLLIN; } if (xevents & X_NOTIFY_WRITE) { ospoll->fds[pos].events |= POLLOUT; ospoll->osfds[pos].revents &= ~POLLOUT; } #endif } } void ospoll_mute(struct ospoll *ospoll, int fd, int xevents) { int pos = ospoll_find(ospoll, fd); if (pos >= 0) { #if EPOLL struct ospollfd *osfd = ospoll->fds[pos]; osfd->xevents &= ~xevents; epoll_mod(ospoll, osfd); #endif #if POLL if (xevents & X_NOTIFY_READ) ospoll->fds[pos].events &= ~POLLIN; if (xevents & X_NOTIFY_WRITE) ospoll->fds[pos].events &= ~POLLOUT; #endif } } int ospoll_wait(struct ospoll *ospoll, int timeout) { int nready; #if EPOLL #define MAX_EVENTS 256 struct epoll_event events[MAX_EVENTS]; int i; nready = epoll_wait(ospoll->epoll_fd, events, MAX_EVENTS, timeout); for (i = 0; i < nready; i++) { struct epoll_event *ev = &events[i]; struct ospollfd *osfd = ev->data.ptr; uint32_t revents = ev->events; int xevents = 0; if (revents & EPOLLIN) xevents |= X_NOTIFY_READ; if (revents & EPOLLOUT) xevents |= X_NOTIFY_WRITE; if (revents & (~(EPOLLIN|EPOLLOUT))) xevents |= X_NOTIFY_ERROR; if (osfd->callback) osfd->callback(osfd->fd, xevents, osfd->data); } ospoll_clean_deleted(ospoll); #endif #if POLL nready = xserver_poll(ospoll->fds, ospoll->num, timeout); ospoll->changed = FALSE; if (nready > 0) { int f; for (f = 0; f < ospoll->num; f++) { short revents = ospoll->fds[f].revents; short oldevents = ospoll->osfds[f].revents; ospoll->osfds[f].revents = (revents & (POLLIN|POLLOUT)); if (ospoll->osfds[f].trigger == ospoll_trigger_edge) revents &= ~oldevents; if (revents) { int xevents = 0; if (revents & POLLIN) xevents |= X_NOTIFY_READ; if (revents & POLLOUT) xevents |= X_NOTIFY_WRITE; if (revents & (~(POLLIN|POLLOUT))) xevents |= X_NOTIFY_ERROR; ospoll->osfds[f].callback(ospoll->fds[f].fd, xevents, ospoll->osfds[f].data); /* Check to see if the arrays have changed, and just go back * around again */ if (ospoll->changed) break; } } } #endif return nready; } void ospoll_reset_events(struct ospoll *ospoll, int fd) { #if POLL int pos = ospoll_find(ospoll, fd); if (pos < 0) return; ospoll->osfds[pos].revents = 0; #endif } void * ospoll_data(struct ospoll *ospoll, int fd) { int pos = ospoll_find(ospoll, fd); if (pos < 0) return NULL; #if EPOLL return ospoll->fds[pos]->data; #endif #if POLL return ospoll->osfds[pos].data; #endif }