poll: Port to use GMainContext

To support other bus types (including virtual) we will need to poll
other source than libusb. So use GMainContext so that we will not
need to implement polling ourselves, hopefully simplifying the logic to
add more event sources.
This commit is contained in:
Benjamin Berg 2019-06-09 20:14:53 +02:00
parent 8a53591766
commit afe5e1ad4c

View file

@ -75,64 +75,38 @@
* for example.
*/
/* this is a singly-linked list of pending timers, sorted with the timer that
* is expiring soonest at the head. */
static GMainContext *fpi_main_ctx = NULL;
static GSList *active_timers = NULL;
/* notifiers for added or removed poll fds */
static fp_pollfd_added_cb fd_added_cb = NULL;
static fp_pollfd_removed_cb fd_removed_cb = NULL;
struct fpi_timeout {
struct timeval expiry;
fpi_timeout_fn callback;
struct fp_dev *dev;
void *data;
char *name;
fpi_timeout_fn callback;
struct fp_dev *dev;
void *data;
GSource *source;
};
static int timeout_sort_fn(gconstpointer _a, gconstpointer _b)
{
fpi_timeout *a = (fpi_timeout *) _a;
fpi_timeout *b = (fpi_timeout *) _b;
struct timeval *tv_a = &a->expiry;
struct timeval *tv_b = &b->expiry;
if (timercmp(tv_a, tv_b, <))
return -1;
else if (timercmp(tv_a, tv_b, >))
return 1;
else
return 0;
}
static void
fpi_timeout_free(fpi_timeout *timeout)
fpi_timeout_destroy (gpointer data)
{
if (timeout == NULL)
return;
fpi_timeout *timeout = data;
g_free(timeout->name);
g_free(timeout);
active_timers = g_slist_remove (active_timers, timeout);
g_free (timeout);
}
/**
* fpi_timeout_set_name:
* @timeout: a #fpi_timeout
* @name: the name to give the timeout
*
* Sets a name for a timeout, allowing that name to be printed
* along with any timeout related debug.
*/
void
fpi_timeout_set_name(fpi_timeout *timeout,
const char *name)
static gboolean
fpi_timeout_wrapper_cb (gpointer data)
{
g_return_if_fail (timeout != NULL);
g_return_if_fail (name != NULL);
g_return_if_fail (timeout->name == NULL);
fpi_timeout *timeout = (fpi_timeout*) data;
timeout->name = g_strdup(name);
timeout->callback (timeout->dev, timeout->data);
return G_SOURCE_REMOVE;
}
/**
@ -154,45 +128,39 @@ fpi_timeout_set_name(fpi_timeout *timeout,
*
* Returns: an #fpi_timeout structure
*/
fpi_timeout *fpi_timeout_add(unsigned int msec,
fpi_timeout_fn callback,
struct fp_dev *dev,
void *data)
fpi_timeout *
fpi_timeout_add(unsigned int msec,
fpi_timeout_fn callback,
struct fp_dev *dev,
void *data)
{
struct timespec ts;
struct timeval add_msec;
fpi_timeout *timeout;
int r;
g_return_val_if_fail (dev != NULL, NULL);
timeout = g_new0 (fpi_timeout, 1);
timeout->source = g_timeout_source_new (msec);
active_timers = g_slist_prepend (active_timers, timeout);
fp_dbg("in %dms", msec);
r = clock_gettime(CLOCK_MONOTONIC, &ts);
if (r < 0) {
fp_err("failed to read monotonic clock, errno=%d", errno);
BUG();
return NULL;
}
timeout = g_new0(fpi_timeout, 1);
timeout->callback = callback;
timeout->dev = dev;
timeout->data = data;
TIMESPEC_TO_TIMEVAL(&timeout->expiry, &ts);
/* calculate timeout expiry by adding delay to current monotonic clock */
timerclear(&add_msec);
add_msec.tv_sec = msec / 1000;
add_msec.tv_usec = (msec % 1000) * 1000;
timeradd(&timeout->expiry, &add_msec, &timeout->expiry);
active_timers = g_slist_insert_sorted(active_timers, timeout,
timeout_sort_fn);
g_source_set_callback (timeout->source, fpi_timeout_wrapper_cb, timeout, fpi_timeout_destroy);
g_source_attach (timeout->source, fpi_main_ctx);
return timeout;
}
/**
* fpi_timeout_set_name:
* @timeout: a #fpi_timeout
* @name: the name to give the timeout
*
* Sets a name for a timeout, allowing that name to be printed
* along with any timeout related debug.
*/
void
fpi_timeout_set_name(fpi_timeout *timeout,
const char *name)
{
g_source_set_name (timeout->source, name);
}
/**
* fpi_timeout_cancel:
* @timeout: an #fpi_timeout structure
@ -200,81 +168,16 @@ fpi_timeout *fpi_timeout_add(unsigned int msec,
* Cancels a timeout scheduled with fpi_timeout_add(), and frees the
* @timeout structure.
*/
void fpi_timeout_cancel(fpi_timeout *timeout)
void
fpi_timeout_cancel(fpi_timeout *timeout)
{
G_DEBUG_HERE();
active_timers = g_slist_remove(active_timers, timeout);
fpi_timeout_free(timeout);
g_source_destroy (timeout->source);
}
/* get the expiry time and optionally the timeout structure for the next
* timeout. returns 0 if there are no expired timers, or 1 if the
* timeval/timeout output parameters were populated. if the returned timeval
* is zero then it means the timeout has already expired and should be handled
* ASAP. */
static int get_next_timeout_expiry(struct timeval *out,
struct fpi_timeout **out_timeout)
static gboolean
dummy_cb (gpointer user_data)
{
struct timespec ts;
struct timeval tv;
struct fpi_timeout *next_timeout;
int r;
if (active_timers == NULL)
return 0;
r = clock_gettime(CLOCK_MONOTONIC, &ts);
if (r < 0) {
fp_err("failed to read monotonic clock, errno=%d", errno);
return r;
}
TIMESPEC_TO_TIMEVAL(&tv, &ts);
next_timeout = active_timers->data;
if (out_timeout)
*out_timeout = next_timeout;
if (timercmp(&tv, &next_timeout->expiry, >=)) {
if (next_timeout->name)
fp_dbg("first timeout '%s' already expired", next_timeout->name);
else
fp_dbg("first timeout already expired");
timerclear(out);
} else {
timersub(&next_timeout->expiry, &tv, out);
if (next_timeout->name)
fp_dbg("next timeout '%s' in %ld.%06lds", next_timeout->name,
out->tv_sec, out->tv_usec);
else
fp_dbg("next timeout in %ld.%06lds", out->tv_sec, out->tv_usec);
}
return 1;
}
/* handle a timeout that has expired */
static void handle_timeout(struct fpi_timeout *timeout)
{
G_DEBUG_HERE();
timeout->callback(timeout->dev, timeout->data);
active_timers = g_slist_remove(active_timers, timeout);
fpi_timeout_free(timeout);
}
static int handle_timeouts(void)
{
struct timeval next_timeout_expiry;
struct fpi_timeout *next_timeout;
int r;
r = get_next_timeout_expiry(&next_timeout_expiry, &next_timeout);
if (r <= 0)
return r;
if (!timerisset(&next_timeout_expiry))
handle_timeout(next_timeout);
return 0;
return G_SOURCE_REMOVE;
}
/**
@ -290,37 +193,22 @@ static int handle_timeouts(void)
*/
API_EXPORTED int fp_handle_events_timeout(struct timeval *timeout)
{
struct timeval next_timeout_expiry;
struct timeval select_timeout;
struct fpi_timeout *next_timeout;
int r;
GSource *timeout_source;
r = get_next_timeout_expiry(&next_timeout_expiry, &next_timeout);
if (r < 0)
return r;
if (r) {
/* timer already expired? */
if (!timerisset(&next_timeout_expiry)) {
handle_timeout(next_timeout);
return 0;
}
/* choose the smallest of next URB timeout or user specified timeout */
if (timercmp(&next_timeout_expiry, timeout, <))
select_timeout = next_timeout_expiry;
else
select_timeout = *timeout;
} else {
select_timeout = *timeout;
if (timeout->tv_sec == 0 && timeout->tv_usec == 0) {
g_main_context_iteration (fpi_main_ctx, FALSE);
return 0;
}
r = libusb_handle_events_timeout(fpi_usb_ctx, &select_timeout);
*timeout = select_timeout;
if (r < 0)
return r;
/* Register a timeout on the mainloop and then run in blocking mode */
timeout_source = g_timeout_source_new (timeout->tv_sec * 1000 + timeout->tv_usec / 1000);
g_source_set_name (timeout_source, "fpi poll timeout");
g_source_set_callback (timeout_source, dummy_cb, NULL, NULL);
g_source_attach (timeout_source, fpi_main_ctx);
g_main_context_iteration (fpi_main_ctx, TRUE);
g_source_destroy (timeout_source);
return handle_timeouts();
return 0;
}
/**
@ -350,35 +238,37 @@ API_EXPORTED int fp_handle_events(void)
*/
API_EXPORTED int fp_get_next_timeout(struct timeval *tv)
{
struct timeval fprint_timeout = { 0, 0 };
struct timeval libusb_timeout = { 0, 0 };
int r_fprint;
int r_libusb;
int timeout_;
r_fprint = get_next_timeout_expiry(&fprint_timeout, NULL);
r_libusb = libusb_get_next_timeout(fpi_usb_ctx, &libusb_timeout);
g_return_val_if_fail (g_main_context_acquire (fpi_main_ctx), 0);
/* if we have no pending timeouts and the same is true for libusb,
* indicate that we have no pending timouts */
if (r_fprint <= 0 && r_libusb <= 0)
g_main_context_query (fpi_main_ctx,
G_MININT,
&timeout_,
NULL,
0);
if (timeout_ < 0)
return 0;
/* if fprint have no pending timeouts return libusb timeout */
else if (r_fprint == 0)
*tv = libusb_timeout;
tv->tv_sec = timeout_ / 1000;
tv->tv_usec = (timeout_ % 1000) * 1000;
/* if libusb have no pending timeouts return fprint timeout */
else if (r_libusb == 0)
*tv = fprint_timeout;
/* otherwise return the smaller of the 2 timeouts */
else if (timercmp(&fprint_timeout, &libusb_timeout, <))
*tv = fprint_timeout;
else
*tv = libusb_timeout;
return 1;
}
typedef struct {
GSource source;
GSList *fds;
} fpi_libusb_source;
typedef struct {
int fd;
gpointer tag;
} fpi_libusb_fd;
static fpi_libusb_source *libusb_source = NULL;
/**
* fp_get_pollfds:
* @pollfds: output location for a list of pollfds. If non-%NULL, must be
@ -394,33 +284,52 @@ API_EXPORTED int fp_get_next_timeout(struct timeval *tv)
*/
API_EXPORTED ssize_t fp_get_pollfds(struct fp_pollfd **pollfds)
{
const struct libusb_pollfd **usbfds;
const struct libusb_pollfd *usbfd;
struct fp_pollfd *ret;
ssize_t cnt = 0;
size_t i = 0;
gint timeout_;
GPollFD fds_static[16];
GPollFD *fds = fds_static;
gint n_fds;
int i;
g_return_val_if_fail (fpi_usb_ctx != NULL, -EIO);
g_return_val_if_fail (g_main_context_acquire (fpi_main_ctx), -1);
usbfds = libusb_get_pollfds(fpi_usb_ctx);
if (!usbfds) {
*pollfds = NULL;
return -EIO;
n_fds = g_main_context_query (fpi_main_ctx,
G_MININT,
&timeout_,
fds,
G_N_ELEMENTS (fds_static));
if (n_fds > G_N_ELEMENTS (fds_static)) {
fds = g_new0 (GPollFD, n_fds);
n_fds = g_main_context_query (fpi_main_ctx,
G_MININT,
&timeout_,
fds,
n_fds);
}
while ((usbfd = usbfds[i++]) != NULL)
cnt++;
g_main_context_release (fpi_main_ctx);
ret = g_malloc(sizeof(struct fp_pollfd) * cnt);
i = 0;
while ((usbfd = usbfds[i]) != NULL) {
ret[i].fd = usbfd->fd;
ret[i].events = usbfd->events;
i++;
*pollfds = g_new0 (struct fp_pollfd, n_fds);
for (i = 0; i < n_fds; i++) {
(*pollfds)[i].fd = fds[i].fd;
if (fds[i].events & G_IO_IN)
(*pollfds)[i].events |= POLL_IN;
if (fds[i].events & G_IO_OUT)
(*pollfds)[i].events |= POLL_OUT;
if (fds[i].events & G_IO_PRI)
(*pollfds)[i].events |= POLL_PRI;
if (fds[i].events & G_IO_ERR)
(*pollfds)[i].events |= POLL_ERR;
if (fds[i].events & G_IO_HUP)
(*pollfds)[i].events |= POLL_HUP;
}
*pollfds = ret;
return cnt;
if (fds != fds_static)
g_free (fds);
return n_fds;
}
/**
@ -440,30 +349,129 @@ API_EXPORTED void fp_set_pollfd_notifiers(fp_pollfd_added_cb added_cb,
static void add_pollfd(int fd, short events, void *user_data)
{
GIOCondition io_cond = 0;
fpi_libusb_fd *data;
gpointer tag;
if (events & POLL_IN)
io_cond |= G_IO_IN;
if (events & POLL_OUT)
io_cond |= G_IO_OUT;
if (events & POLL_PRI)
io_cond |= G_IO_PRI;
if (events & POLL_ERR)
io_cond |= G_IO_ERR;
if (events & POLL_HUP)
io_cond |= G_IO_HUP;
tag = g_source_add_unix_fd (&libusb_source->source, fd, io_cond);
data = g_new0 (fpi_libusb_fd, 1);
data->fd = fd;
data->tag = tag;
libusb_source->fds = g_slist_prepend (libusb_source->fds, data);
if (fd_added_cb)
fd_added_cb(fd, events);
}
static void remove_pollfd(int fd, void *user_data)
{
GSList *elem = g_slist_find_custom (libusb_source->fds, &fd, g_int_equal);
fpi_libusb_fd *item;
g_return_if_fail (elem != NULL);
item = (fpi_libusb_fd*) elem->data;
g_source_remove_unix_fd (&libusb_source->source, item->tag);
libusb_source->fds = g_slist_remove_link (libusb_source->fds, elem);
g_slist_free (elem);
g_free (item);
if (fd_removed_cb)
fd_removed_cb(fd);
}
static gboolean
fpi_libusb_prepare (GSource *source,
gint *timeout_)
{
struct timeval tv;
*timeout_ = -1;
if (libusb_get_next_timeout(fpi_usb_ctx, &tv) == 1) {
if (tv.tv_sec == 0 && tv.tv_usec == 0)
return TRUE;
*timeout_ = tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
return FALSE;
}
static gboolean
fpi_libusb_check (GSource *source)
{
/* Just call into libusb for every mainloop cycle */
return TRUE;
}
static gboolean
fpi_libusb_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
struct timeval zero_tv = { 0, 0 };
libusb_handle_events_timeout (fpi_usb_ctx, &zero_tv);
return G_SOURCE_CONTINUE;
}
static void
fpi_libusb_finalize (GSource *source)
{
fpi_libusb_source *fpi_source = (fpi_libusb_source*) source;
g_slist_free_full (fpi_source->fds, g_free);
}
GSourceFuncs libusb_source_funcs = {
.prepare = fpi_libusb_prepare,
.check = fpi_libusb_check,
.dispatch = fpi_libusb_dispatch,
.finalize = fpi_libusb_finalize,
};
void fpi_poll_init(void)
{
fpi_main_ctx = g_main_context_new ();
libusb_source = (fpi_libusb_source*) g_source_new (&libusb_source_funcs, sizeof(fpi_libusb_source));
g_source_set_name (&libusb_source->source, "libfprint internal libusb source");
g_source_attach (&libusb_source->source, fpi_main_ctx);
libusb_set_pollfd_notifiers(fpi_usb_ctx, add_pollfd, remove_pollfd, NULL);
}
void fpi_poll_exit(void)
{
g_slist_free_full(active_timers, (GDestroyNotify) fpi_timeout_free);
active_timers = NULL;
g_source_destroy (&libusb_source->source);
libusb_source = NULL;
g_main_context_unref (fpi_main_ctx);
fpi_main_ctx = NULL;
fd_added_cb = NULL;
fd_removed_cb = NULL;
libusb_set_pollfd_notifiers(fpi_usb_ctx, NULL, NULL, NULL);
}
void
fpi_timeout_cancel_all_for_dev(struct fp_dev *dev)
{
@ -473,13 +481,10 @@ fpi_timeout_cancel_all_for_dev(struct fp_dev *dev)
l = active_timers;
while (l) {
struct fpi_timeout *timeout = l->data;
GSList *current = l;
fpi_timeout *cb_data = l->data;
l = l->next;
if (timeout->dev == dev) {
g_free (timeout);
active_timers = g_slist_delete_link (active_timers, current);
}
if (cb_data->dev == dev)
g_source_destroy (cb_data->source);
}
}