1a66cad3fb
Tested by bru@, jsg@ and others
478 lines
12 KiB
C
478 lines
12 KiB
C
/*
|
|
* 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 <dix-config.h>
|
|
#endif
|
|
|
|
#include <X11/X.h>
|
|
#include <X11/Xproto.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include "misc.h" /* for typedef of pointer */
|
|
#include "ospoll.h"
|
|
#include "list.h"
|
|
|
|
#if !HAVE_OSPOLL && HAVE_EPOLL_CREATE1
|
|
#include <sys/epoll.h>
|
|
#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 <sys/epoll.h>
|
|
|
|
/* 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
|
|
}
|