Add timing and polling infrastructure

Add timeout mechanism as an asynchronous equivalent of sleeping (uru4000
needs this).

Start implementing polling infrastructure which also accounts for pending
timeouts. We don't expose file descriptors yet, but this is a start.
This commit is contained in:
Daniel Drake 2008-02-16 12:44:50 +00:00
parent eff26bf0be
commit 866dd941cc
6 changed files with 277 additions and 13 deletions

View file

@ -8,6 +8,7 @@ AC_PROG_CC
AC_PROG_LIBTOOL AC_PROG_LIBTOOL
AC_C_INLINE AC_C_INLINE
AM_PROG_CC_C_O AM_PROG_CC_C_O
AC_DEFINE([_GNU_SOURCE], [], [Use GNU extensions])
# Library versioning # Library versioning
lt_major="0" lt_major="0"

View file

@ -59,6 +59,7 @@ libfprint_la_SOURCES = \
drv.c \ drv.c \
img.c \ img.c \
imgdev.c \ imgdev.c \
poll.c \
aeslib.c \ aeslib.c \
aeslib.h \ aeslib.h \
$(DRIVER_SRC) \ $(DRIVER_SRC) \

View file

@ -598,7 +598,7 @@ API_EXPORTED struct fp_dev *fp_dev_open(struct fp_dscv_dev *ddev)
} }
while (dev->state == DEV_STATE_INITIALIZING) while (dev->state == DEV_STATE_INITIALIZING)
if (libusb_poll() < 0) if (fp_handle_events() < 0)
goto err_deinit; goto err_deinit;
if (dev->state != DEV_STATE_INITIALIZED) if (dev->state != DEV_STATE_INITIALIZED)
goto err_deinit; goto err_deinit;
@ -609,7 +609,7 @@ API_EXPORTED struct fp_dev *fp_dev_open(struct fp_dscv_dev *ddev)
err_deinit: err_deinit:
fpi_drv_deinit(dev); fpi_drv_deinit(dev);
while (dev->state == DEV_STATE_DEINITIALIZING) { while (dev->state == DEV_STATE_DEINITIALIZING) {
if (libusb_poll() < 0) if (fp_handle_events() < 0)
break; break;
} }
err: err:
@ -623,7 +623,7 @@ static void do_close(struct fp_dev *dev)
{ {
fpi_drv_deinit(dev); fpi_drv_deinit(dev);
while (dev->state == DEV_STATE_DEINITIALIZING) while (dev->state == DEV_STATE_DEINITIALIZING)
if (libusb_poll() < 0) if (fp_handle_events() < 0)
break; break;
libusb_close(dev->udev); libusb_close(dev->udev);
@ -936,7 +936,7 @@ API_EXPORTED int fp_enroll_finger_img(struct fp_dev *dev,
return r; return r;
} }
while (dev->state == DEV_STATE_ENROLL_STARTING) { while (dev->state == DEV_STATE_ENROLL_STARTING) {
r = libusb_poll(); r = fp_handle_events();
if (r < 0) if (r < 0)
goto err; goto err;
} }
@ -961,7 +961,7 @@ API_EXPORTED int fp_enroll_finger_img(struct fp_dev *dev,
edata = dev->enroll_data; edata = dev->enroll_data;
while (!edata->populated) { while (!edata->populated) {
r = libusb_poll(); r = fp_handle_events();
if (r < 0) { if (r < 0) {
g_free(edata); g_free(edata);
goto err; goto err;
@ -1017,7 +1017,7 @@ out:
fp_dbg("ending enrollment"); fp_dbg("ending enrollment");
if (fpi_drv_enroll_stop(dev) == 0) if (fpi_drv_enroll_stop(dev) == 0)
while (dev->state == DEV_STATE_ENROLL_STOPPING) { while (dev->state == DEV_STATE_ENROLL_STOPPING) {
if (libusb_poll() < 0) if (fp_handle_events() < 0)
break; break;
} }
g_free(dev->enroll_data); g_free(dev->enroll_data);
@ -1028,7 +1028,7 @@ out:
err: err:
if (fpi_drv_enroll_stop(dev) == 0) if (fpi_drv_enroll_stop(dev) == 0)
while (dev->state == DEV_STATE_ENROLL_STOPPING) while (dev->state == DEV_STATE_ENROLL_STOPPING)
if (libusb_poll() < 0) if (fp_handle_events() < 0)
break; break;
return r; return r;
} }
@ -1091,7 +1091,7 @@ API_EXPORTED int fp_verify_finger_img(struct fp_dev *dev,
return r; return r;
} }
while (dev->state == DEV_STATE_VERIFY_STARTING) { while (dev->state == DEV_STATE_VERIFY_STARTING) {
r = libusb_poll(); r = fp_handle_events();
if (r < 0) if (r < 0)
goto err; goto err;
} }
@ -1104,7 +1104,7 @@ API_EXPORTED int fp_verify_finger_img(struct fp_dev *dev,
vdata = dev->sync_verify_data; vdata = dev->sync_verify_data;
while (!vdata->populated) { while (!vdata->populated) {
r = libusb_poll(); r = fp_handle_events();
if (r < 0) { if (r < 0) {
g_free(vdata); g_free(vdata);
goto err; goto err;
@ -1146,7 +1146,7 @@ err:
fp_dbg("ending verification"); fp_dbg("ending verification");
if (fpi_drv_verify_stop(dev) == 0) { if (fpi_drv_verify_stop(dev) == 0) {
while (dev->state == DEV_STATE_VERIFY_STOPPING) { while (dev->state == DEV_STATE_VERIFY_STOPPING) {
if (libusb_poll() < 0) if (fp_handle_events() < 0)
break; break;
} }
} }
@ -1222,7 +1222,7 @@ API_EXPORTED int fp_identify_finger_img(struct fp_dev *dev,
return r; return r;
} }
while (dev->state == DEV_STATE_IDENTIFY_STARTING) { while (dev->state == DEV_STATE_IDENTIFY_STARTING) {
r = libusb_poll(); r = fp_handle_events();
if (r < 0) if (r < 0)
goto err; goto err;
} }
@ -1235,7 +1235,7 @@ API_EXPORTED int fp_identify_finger_img(struct fp_dev *dev,
idata = dev->sync_identify_data; idata = dev->sync_identify_data;
while (!idata->populated) { while (!idata->populated) {
r = libusb_poll(); r = fp_handle_events();
if (r < 0) { if (r < 0) {
g_free(idata); g_free(idata);
goto err; goto err;
@ -1277,7 +1277,7 @@ API_EXPORTED int fp_identify_finger_img(struct fp_dev *dev,
err: err:
if (fpi_drv_identify_stop(dev) == 0) { if (fpi_drv_identify_stop(dev) == 0) {
while (dev->state == DEV_STATE_IDENTIFY_STOPPING) { while (dev->state == DEV_STATE_IDENTIFY_STOPPING) {
if (libusb_poll() < 0) if (fp_handle_events() < 0)
break; break;
} }
} }
@ -1323,8 +1323,10 @@ API_EXPORTED void fp_exit(void)
} }
fpi_data_exit(); fpi_data_exit();
fpi_poll_exit();
g_slist_free(registered_drivers); g_slist_free(registered_drivers);
registered_drivers = NULL; registered_drivers = NULL;
libusb_exit(); libusb_exit();
} }

View file

@ -319,6 +319,14 @@ int fpi_img_compare_print_data(struct fp_print_data *enrolled_print,
int fpi_img_compare_print_data_to_gallery(struct fp_print_data *print, int fpi_img_compare_print_data_to_gallery(struct fp_print_data *print,
struct fp_print_data **gallery, int match_threshold, size_t *match_offset); struct fp_print_data **gallery, int match_threshold, size_t *match_offset);
/* polling and timeouts */
void fpi_poll_exit(void);
typedef void (*fpi_timeout_fn)(void *data);
int fpi_timeout_add(unsigned int msec, fpi_timeout_fn callback, void *data);
/* async drv <--> lib comms */ /* async drv <--> lib comms */
struct fpi_ssm; struct fpi_ssm;

View file

@ -21,6 +21,7 @@
#define __FPRINT_H__ #define __FPRINT_H__
#include <stdint.h> #include <stdint.h>
#include <sys/time.h>
/* structs that applications are not allowed to peek into */ /* structs that applications are not allowed to peek into */
struct fp_dscv_dev; struct fp_dscv_dev;
@ -265,6 +266,10 @@ struct fp_img *fp_img_binarize(struct fp_img *img);
struct fp_minutia **fp_img_get_minutiae(struct fp_img *img, int *nr_minutiae); struct fp_minutia **fp_img_get_minutiae(struct fp_img *img, int *nr_minutiae);
void fp_img_free(struct fp_img *img); void fp_img_free(struct fp_img *img);
/* Polling and timing */
int fp_handle_events_timeout(struct timeval *timeout);
int fp_handle_events(void);
/* Library */ /* Library */
int fp_init(void); int fp_init(void);
void fp_exit(void); void fp_exit(void);

247
libfprint/poll.c Normal file
View file

@ -0,0 +1,247 @@
/*
* Polling/timing management
* Copyright (C) 2008 Daniel Drake <dsd@gentoo.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define FP_COMPONENT "poll"
#include <config.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <glib.h>
#include <libusb.h>
#include "fp_internal.h"
/**
* @defgroup poll Polling and timing operations
* These functions are only applicable to users of libfprint's asynchronous
* API.
*
* libfprint does not create internal library threads and hence can only
* execute when your application is calling a libfprint function. However,
* libfprint often has work to be do, such as handling of completed USB
* transfers, and processing of timeouts required in order for the library
* to function. Therefore it is essential that your own application must
* regularly "phone into" libfprint so that libfprint can handle any pending
* events.
*
* The function you must call is fp_handle_events() or a variant of it. This
* function will handle any pending events, and it is from this context that
* all asynchronous event callbacks from the library will occur. You can view
* this function as a kind of iteration function.
*
* If there are no events pending, fp_handle_events() will block for a few
* seconds (and will handle any new events should anything occur in that time).
* If you wish to customise this timeout, you can use
* fp_handle_events_timeout() instead. If you wish to do a nonblocking
* iteration, call fp_handle_events_timeout() with a zero timeout.
*
* TODO: document how application is supposed to know when to call these
* functions.
*/
/* this is a singly-linked list of pending timers, sorted with the timer that
* is expiring soonest at the head. */
static GSList *active_timers = NULL;
struct fpi_timeout {
struct timeval expiry;
fpi_timeout_fn callback;
void *data;
};
void fpi_poll_exit(void)
{
g_slist_free(active_timers);
active_timers = NULL;
}
static int timeout_sort_fn(gconstpointer _a, gconstpointer _b)
{
struct fpi_timeout *a = (struct fpi_timeout *) _a;
struct fpi_timeout *b = (struct 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;
}
/* A timeout is the asynchronous equivalent of sleeping. You create a timeout
* saying that you'd like to have a function invoked at a certain time in
* the future. */
int fpi_timeout_add(unsigned int msec, fpi_timeout_fn callback, void *data)
{
struct timespec ts;
struct timeval add_msec;
struct fpi_timeout *timeout;
int r;
fp_dbg("in %dms", msec);
r = clock_gettime(CLOCK_MONOTONIC, &ts);
if (r < 0) {
fp_err("failed to read monotonic clock, errno=%d", errno);
return r;
}
timeout = g_malloc(sizeof(*timeout));
timeout->callback = callback;
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);
return 0;
}
/* 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)
{
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, >=)) {
fp_dbg("first timeout already expired");
timerclear(out);
} else {
timersub(&next_timeout->expiry, &tv, out);
fp_dbg("next timeout in %d.%06ds", out->tv_sec, out->tv_usec);
}
return 1;
}
/* handle a timeout that has expired */
static void handle_timeout(struct fpi_timeout *timeout)
{
fp_dbg("");
timeout->callback(timeout->data);
active_timers = g_slist_remove(active_timers, timeout);
g_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;
}
/** \ingroup poll
* Handle any pending events. If a non-zero timeout is specified, the function
* will potentially block for the specified amount of time, although it may
* return sooner if events have been handled. The function acts as non-blocking
* for a zero timeout.
*
* \param timeout Maximum timeout for this blocking function
* \returns 0 on success, non-zero on error.
*/
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;
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;
}
r = libusb_poll_timeout(&select_timeout);
*timeout = select_timeout;
if (r < 0)
return r;
return handle_timeouts();
}
/** \ingroup poll
* Convenience function for calling fp_handle_events_timeout() with a sensible
* default timeout value of two seconds (subject to change if we decide another
* value is more sensible).
*
* \returns 0 on success, non-zero on error.
*/
API_EXPORTED int fp_handle_events(void)
{
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
return fp_handle_events_timeout(&tv);
}