diff --git a/configure.ac b/configure.ac index c6fa8287..44dc151d 100644 --- a/configure.ac +++ b/configure.ac @@ -8,6 +8,7 @@ AC_PROG_CC AC_PROG_LIBTOOL AC_C_INLINE AM_PROG_CC_C_O +AC_DEFINE([_GNU_SOURCE], [], [Use GNU extensions]) # Library versioning lt_major="0" diff --git a/libfprint/Makefile.am b/libfprint/Makefile.am index 70aea683..9b014205 100644 --- a/libfprint/Makefile.am +++ b/libfprint/Makefile.am @@ -59,6 +59,7 @@ libfprint_la_SOURCES = \ drv.c \ img.c \ imgdev.c \ + poll.c \ aeslib.c \ aeslib.h \ $(DRIVER_SRC) \ diff --git a/libfprint/core.c b/libfprint/core.c index d6298117..43d92c99 100644 --- a/libfprint/core.c +++ b/libfprint/core.c @@ -598,7 +598,7 @@ API_EXPORTED struct fp_dev *fp_dev_open(struct fp_dscv_dev *ddev) } while (dev->state == DEV_STATE_INITIALIZING) - if (libusb_poll() < 0) + if (fp_handle_events() < 0) goto err_deinit; if (dev->state != DEV_STATE_INITIALIZED) goto err_deinit; @@ -609,7 +609,7 @@ API_EXPORTED struct fp_dev *fp_dev_open(struct fp_dscv_dev *ddev) err_deinit: fpi_drv_deinit(dev); while (dev->state == DEV_STATE_DEINITIALIZING) { - if (libusb_poll() < 0) + if (fp_handle_events() < 0) break; } err: @@ -623,7 +623,7 @@ static void do_close(struct fp_dev *dev) { fpi_drv_deinit(dev); while (dev->state == DEV_STATE_DEINITIALIZING) - if (libusb_poll() < 0) + if (fp_handle_events() < 0) break; libusb_close(dev->udev); @@ -936,7 +936,7 @@ API_EXPORTED int fp_enroll_finger_img(struct fp_dev *dev, return r; } while (dev->state == DEV_STATE_ENROLL_STARTING) { - r = libusb_poll(); + r = fp_handle_events(); if (r < 0) goto err; } @@ -961,7 +961,7 @@ API_EXPORTED int fp_enroll_finger_img(struct fp_dev *dev, edata = dev->enroll_data; while (!edata->populated) { - r = libusb_poll(); + r = fp_handle_events(); if (r < 0) { g_free(edata); goto err; @@ -1017,7 +1017,7 @@ out: fp_dbg("ending enrollment"); if (fpi_drv_enroll_stop(dev) == 0) while (dev->state == DEV_STATE_ENROLL_STOPPING) { - if (libusb_poll() < 0) + if (fp_handle_events() < 0) break; } g_free(dev->enroll_data); @@ -1028,7 +1028,7 @@ out: err: if (fpi_drv_enroll_stop(dev) == 0) while (dev->state == DEV_STATE_ENROLL_STOPPING) - if (libusb_poll() < 0) + if (fp_handle_events() < 0) break; return r; } @@ -1091,7 +1091,7 @@ API_EXPORTED int fp_verify_finger_img(struct fp_dev *dev, return r; } while (dev->state == DEV_STATE_VERIFY_STARTING) { - r = libusb_poll(); + r = fp_handle_events(); if (r < 0) goto err; } @@ -1104,7 +1104,7 @@ API_EXPORTED int fp_verify_finger_img(struct fp_dev *dev, vdata = dev->sync_verify_data; while (!vdata->populated) { - r = libusb_poll(); + r = fp_handle_events(); if (r < 0) { g_free(vdata); goto err; @@ -1146,7 +1146,7 @@ err: fp_dbg("ending verification"); if (fpi_drv_verify_stop(dev) == 0) { while (dev->state == DEV_STATE_VERIFY_STOPPING) { - if (libusb_poll() < 0) + if (fp_handle_events() < 0) break; } } @@ -1222,7 +1222,7 @@ API_EXPORTED int fp_identify_finger_img(struct fp_dev *dev, return r; } while (dev->state == DEV_STATE_IDENTIFY_STARTING) { - r = libusb_poll(); + r = fp_handle_events(); if (r < 0) goto err; } @@ -1235,7 +1235,7 @@ API_EXPORTED int fp_identify_finger_img(struct fp_dev *dev, idata = dev->sync_identify_data; while (!idata->populated) { - r = libusb_poll(); + r = fp_handle_events(); if (r < 0) { g_free(idata); goto err; @@ -1277,7 +1277,7 @@ API_EXPORTED int fp_identify_finger_img(struct fp_dev *dev, err: if (fpi_drv_identify_stop(dev) == 0) { while (dev->state == DEV_STATE_IDENTIFY_STOPPING) { - if (libusb_poll() < 0) + if (fp_handle_events() < 0) break; } } @@ -1323,8 +1323,10 @@ API_EXPORTED void fp_exit(void) } fpi_data_exit(); + fpi_poll_exit(); g_slist_free(registered_drivers); registered_drivers = NULL; libusb_exit(); } + diff --git a/libfprint/fp_internal.h b/libfprint/fp_internal.h index dc6a5036..ee1230b9 100644 --- a/libfprint/fp_internal.h +++ b/libfprint/fp_internal.h @@ -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, 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 */ struct fpi_ssm; diff --git a/libfprint/fprint.h b/libfprint/fprint.h index 2b855840..f7626756 100644 --- a/libfprint/fprint.h +++ b/libfprint/fprint.h @@ -21,6 +21,7 @@ #define __FPRINT_H__ #include +#include /* structs that applications are not allowed to peek into */ 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); 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 */ int fp_init(void); void fp_exit(void); diff --git a/libfprint/poll.c b/libfprint/poll.c new file mode 100644 index 00000000..6355f5cf --- /dev/null +++ b/libfprint/poll.c @@ -0,0 +1,247 @@ +/* + * Polling/timing management + * Copyright (C) 2008 Daniel Drake + * + * 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 +#include +#include +#include + +#include +#include + +#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); +} +