diff --git a/libfprint/fp-device-private.h b/libfprint/fp-device-private.h index ed9fe864..9255076b 100644 --- a/libfprint/fp-device-private.h +++ b/libfprint/fp-device-private.h @@ -79,6 +79,11 @@ typedef struct gboolean wait_for_finger; FpFingerStatusFlags finger_status; + /* Driver critical sections */ + guint critical_section; + GSource *critical_section_flush_source; + gboolean cancel_queued; + /* Device temperature model information and state */ GSource *temp_timeout; FpTemperature temp_current; diff --git a/libfprint/fp-device.c b/libfprint/fp-device.c index 8d508d3c..16758645 100644 --- a/libfprint/fp-device.c +++ b/libfprint/fp-device.c @@ -94,7 +94,10 @@ fp_device_cancel_in_idle_cb (gpointer user_data) priv->current_idle_cancel_source = NULL; - cls->cancel (self); + if (priv->critical_section) + priv->cancel_queued = TRUE; + else + cls->cancel (self); fpi_device_report_finger_status (self, FP_FINGER_STATUS_NONE); diff --git a/libfprint/fpi-device.c b/libfprint/fpi-device.c index cfbd167f..7aabd3f1 100644 --- a/libfprint/fpi-device.c +++ b/libfprint/fpi-device.c @@ -808,6 +808,104 @@ fpi_device_action_error (FpDevice *device, } } +/** + * fpi_device_critical_enter: + * @device: The #FpDevice + * + * Enter a critical section in the driver code where no outside calls from + * libfprint should happen. Drivers can already assume that everything + * happens from the same thread, however, that still allows e.g. the cancel + * vfunc to be called at any point in time. + * + * Using this kind of critical section, the driver can assume that libfprint + * will not forward any external requests to the driver for the time being. + * This is for example useful to prevent cancellation while the device is being + * set up. Or, said differently, using this feature means that the cancel + * handler is able to make more assumptions about the current state. + * + * Please note that the driver is not shielded from all external changes. For + * example the cancellable as returned by fpi_device_get_cancellable() will + * still change immediately. + * + * The driver may call this function multiple times, but must also ensure that + * fpi_device_critical_leave() is called an equal amount of times and that all + * critical sections are left before command completion. + */ +void +fpi_device_critical_enter (FpDevice *device) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + g_return_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE); + + priv->critical_section += 1; + + /* Stop flushing events if that was previously queued. */ + if (priv->critical_section_flush_source) + g_source_destroy (priv->critical_section_flush_source); + priv->critical_section_flush_source = NULL; +} + +static gboolean +fpi_device_critical_section_flush_idle_cb (FpDevice *device) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpDeviceClass *cls = FP_DEVICE_GET_CLASS (device); + + if (priv->cancel_queued) + { + /* Cancellation must only happen if the driver is busy. */ + if (priv->current_action != FPI_DEVICE_ACTION_NONE && + priv->current_task_idle_return_source == NULL) + cls->cancel (device); + priv->cancel_queued = FALSE; + + return G_SOURCE_CONTINUE; + } + + priv->critical_section_flush_source = NULL; + + return G_SOURCE_REMOVE; +} + +/** + * fpi_device_critical_leave: + * @device: The #FpDevice + * + * Leave a critical section started by fpi_device_critical_enter(). + * + * Once all critical sections have been left, libfprint will start flushing + * out the queued up requests. This is done from the mainloop and the driver + * is protected from reentrency issues. + */ +void +fpi_device_critical_leave (FpDevice *device) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + g_return_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE); + g_return_if_fail (priv->critical_section); + + priv->critical_section -= 1; + if (priv->critical_section) + return; + + /* We left the critical section, make sure a flush is queued. */ + if (priv->critical_section_flush_source) + return; + + priv->critical_section_flush_source = g_idle_source_new (); + g_source_set_callback (priv->critical_section_flush_source, + (GSourceFunc) fpi_device_critical_section_flush_idle_cb, + device, + NULL); + g_source_set_name (priv->critical_section_flush_source, + "Flush libfprint driver critical section"); + g_source_attach (priv->critical_section_flush_source, + g_task_get_context (priv->current_task)); + g_source_unref (priv->critical_section_flush_source); +} + static void clear_device_cancel_action (FpDevice *device) { diff --git a/libfprint/fpi-device.h b/libfprint/fpi-device.h index f52e435a..6519a555 100644 --- a/libfprint/fpi-device.h +++ b/libfprint/fpi-device.h @@ -264,6 +264,9 @@ void fpi_device_update_features (FpDevice *device, void fpi_device_action_error (FpDevice *device, GError *error); +void fpi_device_critical_enter (FpDevice *device); +void fpi_device_critical_leave (FpDevice *device); + void fpi_device_probe_complete (FpDevice *device, const gchar *device_id, const gchar *device_name,