diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index be0b46e7e8683a5dbf312cf3eaad10932ade1d0b..96fae75f645560cdab8bb02bf9a8dfdeac59874f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -69,6 +69,7 @@ test: - cat _build/meson-logs/coverage.txt artifacts: expose_as: 'Coverage Report' + when: always paths: - _build/meson-logs - _build/meson-logs/coveragereport/index.html @@ -86,6 +87,7 @@ test_valgrind: - meson test -C _build --print-errorlogs --no-stdsplit --setup=valgrind artifacts: expose_as: 'Valgrind test logs' + when: always paths: - _build/meson-logs - _build/meson-logs/testlog-valgrind.txt diff --git a/libfprint/drivers/elanmoc/elanmoc.c b/libfprint/drivers/elanmoc/elanmoc.c index 712af90dddc3b28e0b06624a6e1eb4e563837829..49ea2671c6f96221266aa8965d03d18a44ec257c 100644 --- a/libfprint/drivers/elanmoc/elanmoc.c +++ b/libfprint/drivers/elanmoc/elanmoc.c @@ -1121,6 +1121,7 @@ fpi_device_elanmoc_class_init (FpiDeviceElanmocClass *klass) dev_class->scan_type = FP_SCAN_TYPE_PRESS; dev_class->id_table = id_table; dev_class->nr_enroll_stages = ELAN_MOC_ENROLL_TIMES; + dev_class->temp_hot_seconds = -1; dev_class->open = elanmoc_open; dev_class->close = elanmoc_close; diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index d98e733c7046bad528400677a30f2edb523f4cb5..d012d46f3f7517d86bdee6f5701d040ae82302e3 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -1565,6 +1565,7 @@ fpi_device_goodixmoc_class_init (FpiDeviceGoodixMocClass *klass) dev_class->scan_type = FP_SCAN_TYPE_PRESS; dev_class->id_table = id_table; dev_class->nr_enroll_stages = DEFAULT_ENROLL_SAMPLES; + dev_class->temp_hot_seconds = -1; dev_class->open = gx_fp_init; dev_class->close = gx_fp_exit; diff --git a/libfprint/drivers/synaptics/synaptics.c b/libfprint/drivers/synaptics/synaptics.c index d92135fbd3803d805fb6951c83d9f91f19342606..fbafc1850312b53e1de7d3e24854a84bc8c20a2f 100644 --- a/libfprint/drivers/synaptics/synaptics.c +++ b/libfprint/drivers/synaptics/synaptics.c @@ -198,12 +198,17 @@ cmd_interrupt_cb (FpiUsbTransfer *transfer, GError *error) { g_debug ("interrupt transfer done"); + fpi_device_critical_enter (device); + if (error) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_error_free (error); - fpi_ssm_jump_to_state (transfer->ssm, SYNAPTICS_CMD_GET_RESP); + if (FPI_DEVICE_SYNAPTICS (device)->cmd_suspended) + fpi_ssm_jump_to_state (transfer->ssm, SYNAPTICS_CMD_SUSPENDED); + else + fpi_ssm_jump_to_state (transfer->ssm, SYNAPTICS_CMD_GET_RESP); return; } @@ -264,6 +269,9 @@ synaptics_cmd_run_state (FpiSsm *ssm, break; case SYNAPTICS_CMD_WAIT_INTERRUPT: + /* Interruptions are permitted only during an interrupt transfer */ + fpi_device_critical_leave (dev); + transfer = fpi_usb_transfer_new (dev); transfer->ssm = ssm; fpi_usb_transfer_fill_interrupt (transfer, USB_EP_INTERRUPT, USB_INTERRUPT_DATA_SIZE); @@ -291,6 +299,17 @@ synaptics_cmd_run_state (FpiSsm *ssm, case SYNAPTICS_CMD_RESTART: fpi_ssm_jump_to_state (ssm, SYNAPTICS_CMD_SEND_PENDING); break; + + case SYNAPTICS_CMD_SUSPENDED: + /* The resume handler continues to the next state! */ + fpi_device_critical_leave (dev); + fpi_device_suspend_complete (dev, NULL); + break; + + case SYNAPTICS_CMD_RESUME: + fpi_device_critical_enter (dev); + fpi_ssm_jump_to_state (ssm, SYNAPTICS_CMD_WAIT_INTERRUPT); + break; } } @@ -306,6 +325,7 @@ cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) if (error || self->cmd_complete_on_removal) callback (self, NULL, error); + fpi_device_critical_leave (dev); self->cmd_complete_on_removal = FALSE; } @@ -415,6 +435,7 @@ synaptics_sensor_cmd (FpiDeviceSynaptics *self, SYNAPTICS_CMD_NUM_STATES); fpi_ssm_set_data (self->cmd_ssm, callback, NULL); + fpi_device_critical_enter (FP_DEVICE (self)); fpi_ssm_start (self->cmd_ssm, cmd_ssm_done); } } @@ -510,6 +531,12 @@ verify_msg_cb (FpiDeviceSynaptics *self, FpDevice *device = FP_DEVICE (self); bmkt_verify_resp_t *verify_resp; + if (self->action_starting) + { + fpi_device_critical_leave (device); + self->action_starting = FALSE; + } + if (error) { fpi_device_verify_complete (device, error); @@ -602,6 +629,8 @@ verify (FpDevice *device) G_DEBUG_HERE (); + self->action_starting = TRUE; + fpi_device_critical_enter (device); synaptics_sensor_cmd (self, 0, BMKT_CMD_VERIFY_USER, user_id, user_id_len, verify_msg_cb); } @@ -629,6 +658,12 @@ identify_msg_cb (FpiDeviceSynaptics *self, { FpDevice *device = FP_DEVICE (self); + if (self->action_starting) + { + fpi_device_critical_leave (device); + self->action_starting = FALSE; + } + if (error) { fpi_device_identify_complete (device, error); @@ -718,6 +753,7 @@ static void identify (FpDevice *device) { GPtrArray *prints = NULL; + FpiDeviceSynaptics *self = FPI_DEVICE_SYNAPTICS (device); fpi_device_get_identify_data (device, &prints); @@ -732,6 +768,9 @@ identify (FpDevice *device) return; } + self->action_starting = TRUE; + fpi_device_critical_enter (device); + init_identify_msg (device); compose_and_send_identify_msg (device); } @@ -834,6 +873,12 @@ enroll_msg_cb (FpiDeviceSynaptics *self, FpDevice *device = FP_DEVICE (self); bmkt_enroll_resp_t *enroll_resp; + if (self->action_starting) + { + fpi_device_critical_leave (device); + self->action_starting = FALSE; + } + if (error) { fpi_device_enroll_complete (device, NULL, error); @@ -980,6 +1025,9 @@ enroll (FpDevice *device) payload[1] = finger; memcpy (payload + 2, user_id, user_id_len); + self->action_starting = TRUE; + fpi_device_critical_enter (device); + synaptics_sensor_cmd (self, 0, BMKT_CMD_ENROLL_USER, payload, user_id_len + 2, enroll_msg_cb); } @@ -993,6 +1041,7 @@ delete_msg_cb (FpiDeviceSynaptics *self, if (error) { + fpi_device_critical_leave (device); fpi_device_delete_complete (device, error); return; } @@ -1007,6 +1056,7 @@ delete_msg_cb (FpiDeviceSynaptics *self, break; case BMKT_RSP_DEL_USER_FP_FAIL: + fpi_device_critical_leave (device); if (resp->result == BMKT_FP_DATABASE_NO_RECORD_EXISTS || resp->result == BMKT_FP_DATABASE_EMPTY) { @@ -1023,6 +1073,7 @@ delete_msg_cb (FpiDeviceSynaptics *self, case BMKT_RSP_DEL_USER_FP_OK: fp_info ("Successfully deleted enrolled user"); + fpi_device_critical_leave (device); fpi_device_delete_complete (device, NULL); break; } @@ -1057,6 +1108,7 @@ delete_print (FpDevice *device) payload[0] = finger; memcpy (payload + 1, user_id, user_id_len); + fpi_device_critical_enter (device); synaptics_sensor_cmd (self, 0, BMKT_CMD_DEL_USER_FP, payload, user_id_len + 1, delete_msg_cb); } @@ -1368,6 +1420,59 @@ cancel (FpDevice *dev) self->interrupt_cancellable = g_cancellable_new (); } +static void +suspend (FpDevice *dev) +{ + FpiDeviceSynaptics *self = FPI_DEVICE_SYNAPTICS (dev); + FpiDeviceAction action = fpi_device_get_current_action (dev); + + g_debug ("got suspend request"); + + if (action != FPI_DEVICE_ACTION_VERIFY && action != FPI_DEVICE_ACTION_IDENTIFY) + { + fpi_device_suspend_complete (dev, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + return; + } + + /* We are guaranteed to have a cmd_ssm running at this time. */ + g_assert (self->cmd_ssm); + g_assert (fpi_ssm_get_cur_state (self->cmd_ssm) == SYNAPTICS_CMD_WAIT_INTERRUPT); + self->cmd_suspended = TRUE; + + /* Cancel the current transfer. + * The CMD SSM will go into the suspend state and signal readyness. */ + g_cancellable_cancel (self->interrupt_cancellable); + g_clear_object (&self->interrupt_cancellable); + self->interrupt_cancellable = g_cancellable_new (); +} + +static void +resume (FpDevice *dev) +{ + FpiDeviceSynaptics *self = FPI_DEVICE_SYNAPTICS (dev); + FpiDeviceAction action = fpi_device_get_current_action (dev); + + g_debug ("got resume request"); + + if (action != FPI_DEVICE_ACTION_VERIFY && action != FPI_DEVICE_ACTION_IDENTIFY) + { + g_assert_not_reached (); + fpi_device_resume_complete (dev, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + return; + } + + /* We must have a suspended cmd_ssm at this point */ + g_assert (self->cmd_ssm); + g_assert (self->cmd_suspended); + g_assert (fpi_ssm_get_cur_state (self->cmd_ssm) == SYNAPTICS_CMD_SUSPENDED); + self->cmd_suspended = FALSE; + + /* Restart interrupt transfer. */ + fpi_ssm_jump_to_state (self->cmd_ssm, SYNAPTICS_CMD_RESUME); + + fpi_device_resume_complete (dev, NULL); +} + static void fpi_device_synaptics_init (FpiDeviceSynaptics *self) { @@ -1385,6 +1490,7 @@ fpi_device_synaptics_class_init (FpiDeviceSynapticsClass *klass) dev_class->scan_type = FP_SCAN_TYPE_PRESS; dev_class->id_table = id_table; dev_class->nr_enroll_stages = ENROLL_SAMPLES; + dev_class->temp_hot_seconds = -1; dev_class->open = dev_init; dev_class->close = dev_exit; @@ -1395,6 +1501,8 @@ fpi_device_synaptics_class_init (FpiDeviceSynapticsClass *klass) dev_class->delete = delete_print; dev_class->clear_storage = clear_storage; dev_class->cancel = cancel; + dev_class->suspend = suspend; + dev_class->resume = resume; fpi_device_class_auto_initialize_features (dev_class); } diff --git a/libfprint/drivers/synaptics/synaptics.h b/libfprint/drivers/synaptics/synaptics.h index deb3a2245bebbede1efd0fe44a75cd87fbb34fde..5fc0a192e9368027472c076e3b194252e6a70cae 100644 --- a/libfprint/drivers/synaptics/synaptics.h +++ b/libfprint/drivers/synaptics/synaptics.h @@ -93,6 +93,8 @@ typedef enum { SYNAPTICS_CMD_WAIT_INTERRUPT, SYNAPTICS_CMD_SEND_ASYNC, SYNAPTICS_CMD_RESTART, + SYNAPTICS_CMD_SUSPENDED, + SYNAPTICS_CMD_RESUME, SYNAPTICS_CMD_NUM_STATES, } SynapticsCmdState; @@ -110,10 +112,12 @@ struct _FpiDeviceSynaptics FpiSsm *cmd_ssm; FpiUsbTransfer *cmd_pending_transfer; gboolean cmd_complete_on_removal; + gboolean cmd_suspended; guint8 id_idx; bmkt_sensor_version_t mis_version; + gboolean action_starting; GCancellable *interrupt_cancellable; gint enroll_stage; diff --git a/libfprint/fp-device-private.h b/libfprint/fp-device-private.h index 3f8bf0f184e90b99bb605db1f2865eed82ddada1..99eba410850c07b6492b94a81e8db30d82992a59 100644 --- a/libfprint/fp-device-private.h +++ b/libfprint/fp-device-private.h @@ -22,6 +22,23 @@ #include "fpi-device.h" +/* Chosen so that if we turn on after WARM -> COLD, it takes exactly one time + * constant to go from COLD -> HOT. + * TEMP_COLD_THRESH = 1 / (e + 1) + */ +#define TEMP_COLD_THRESH (0.26894142136999512075) +#define TEMP_WARM_HOT_THRESH (1.0 - TEMP_COLD_THRESH) +#define TEMP_HOT_WARM_THRESH (0.5) + +/* Delay updates by 100ms to avoid hitting the border exactly */ +#define TEMP_DELAY_SECONDS 0.1 + +/* Hopefully 3min is long enough to not get in the way, while also not + * properly overheating any devices. + */ +#define DEFAULT_TEMP_HOT_SECONDS (3 * 60) +#define DEFAULT_TEMP_COLD_SECONDS (9 * 60) + typedef struct { FpDeviceType type; @@ -36,6 +53,7 @@ typedef struct gboolean is_removed; gboolean is_open; + gboolean is_suspended; gchar *device_id; gchar *device_name; @@ -50,14 +68,37 @@ typedef struct /* We always make sure that only one task is run at a time. */ FpiDeviceAction current_action; GTask *current_task; + GError *current_cancellation_reason; GAsyncReadyCallback current_user_cb; + GCancellable *current_cancellable; gulong current_cancellable_id; + gulong current_task_cancellable_id; GSource *current_idle_cancel_source; GSource *current_task_idle_return_source; /* State for tasks */ gboolean wait_for_finger; FpFingerStatusFlags finger_status; + + /* Driver critical sections */ + guint critical_section; + GSource *critical_section_flush_source; + gboolean cancel_queued; + gboolean suspend_queued; + gboolean resume_queued; + + /* Suspend/resume tasks */ + GTask *suspend_resume_task; + GError *suspend_error; + + /* Device temperature model information and state */ + GSource *temp_timeout; + FpTemperature temp_current; + gint32 temp_hot_seconds; + gint32 temp_cold_seconds; + gint64 temp_last_update; + gboolean temp_last_active; + gdouble temp_current_ratio; } FpDevicePrivate; @@ -88,3 +129,8 @@ typedef struct } FpMatchData; void match_data_free (FpMatchData *match_data); + +void fpi_device_configure_wakeup (FpDevice *device, + gboolean enabled); +void fpi_device_update_temp (FpDevice *device, + gboolean is_active); diff --git a/libfprint/fp-device.c b/libfprint/fp-device.c index eeaa875a7df051ec0876f254dd86bedb02f868ec..4d19bf0e1407331561f7e8a42330d2e3d085e073 100644 --- a/libfprint/fp-device.c +++ b/libfprint/fp-device.c @@ -48,6 +48,7 @@ enum { PROP_NR_ENROLL_STAGES, PROP_SCAN_TYPE, PROP_FINGER_STATUS, + PROP_TEMPERATURE, PROP_FPI_ENVIRON, PROP_FPI_USB_DEVICE, PROP_FPI_UDEV_DATA_SPIDEV, @@ -93,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); @@ -118,20 +122,40 @@ fp_device_cancelled_cb (GCancellable *cancellable, FpDevice *self) g_source_unref (priv->current_idle_cancel_source); } +/* Forward the external task cancellable to the internal one. */ static void -maybe_cancel_on_cancelled (FpDevice *device, - GCancellable *cancellable) +fp_device_task_cancelled_cb (GCancellable *cancellable, FpDevice *self) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (self); + + g_cancellable_cancel (priv->current_cancellable); +} + +static void +setup_task_cancellable (FpDevice *device) { - FpDeviceClass *cls = FP_DEVICE_GET_CLASS (device); FpDevicePrivate *priv = fp_device_get_instance_private (device); + FpDeviceClass *cls = FP_DEVICE_GET_CLASS (device); - if (!cancellable || !cls->cancel) - return; + /* Create an internal cancellable and hook it up. */ + priv->current_cancellable = g_cancellable_new (); + if (cls->cancel) + { + priv->current_cancellable_id = g_cancellable_connect (priv->current_cancellable, + G_CALLBACK (fp_device_cancelled_cb), + device, + NULL); + } - priv->current_cancellable_id = g_cancellable_connect (cancellable, - G_CALLBACK (fp_device_cancelled_cb), - device, - NULL); + /* Task cancellable is the externally visible one, make our internal one + * a slave of the external one. */ + if (g_task_get_cancellable (priv->current_task)) + { + priv->current_task_cancellable_id = g_cancellable_connect (g_task_get_cancellable (priv->current_task), + G_CALLBACK (fp_device_task_cancelled_cb), + device, + NULL); + } } static void @@ -151,6 +175,36 @@ fp_device_constructed (GObject *object) priv->device_name = g_strdup (cls->full_name); priv->device_id = g_strdup ("0"); + if (cls->temp_hot_seconds > 0) + { + priv->temp_hot_seconds = cls->temp_hot_seconds; + priv->temp_cold_seconds = cls->temp_cold_seconds; + g_assert (priv->temp_cold_seconds > 0); + } + else if (cls->temp_hot_seconds == 0) + { + priv->temp_hot_seconds = DEFAULT_TEMP_HOT_SECONDS; + priv->temp_cold_seconds = DEFAULT_TEMP_COLD_SECONDS; + } + else + { + /* Temperature management disabled */ + priv->temp_hot_seconds = -1; + priv->temp_cold_seconds = -1; + } + + /* Start out at not completely cold (i.e. assume we are only at the upper + * bound of COLD). + * To be fair, the warm-up from 0 to WARM should be really short either way. + * + * Note that a call to fpi_device_update_temp() is not needed here as no + * timeout must be registered. + */ + priv->temp_current = FP_TEMPERATURE_COLD; + priv->temp_current_ratio = TEMP_COLD_THRESH; + priv->temp_last_update = g_get_monotonic_time (); + priv->temp_last_active = FALSE; + G_OBJECT_CLASS (fp_device_parent_class)->constructed (object); } @@ -165,6 +219,8 @@ fp_device_finalize (GObject *object) if (priv->is_open) g_warning ("User destroyed open device! Not cleaning up properly!"); + g_clear_pointer (&priv->temp_timeout, g_source_destroy); + g_slist_free_full (priv->sources, (GDestroyNotify) g_source_destroy); g_clear_pointer (&priv->current_idle_cancel_source, g_source_destroy); @@ -204,6 +260,10 @@ fp_device_get_property (GObject *object, g_value_set_flags (value, priv->finger_status); break; + case PROP_TEMPERATURE: + g_value_set_enum (value, priv->temp_current); + break; + case PROP_DRIVER: g_value_set_static_string (value, FP_DEVICE_GET_CLASS (self)->id); break; @@ -279,6 +339,24 @@ fp_device_set_property (GObject *object, } } +static void +device_idle_probe_cb (FpDevice *self, gpointer user_data) +{ + /* This should not be an idle handler, see comment where it is registered. + * + * This effectively disables USB "persist" for us, and possibly turns off + * USB wakeup if it was enabled for some reason. + */ + fpi_device_configure_wakeup (self, FALSE); + + if (!FP_DEVICE_GET_CLASS (self)->probe) + fpi_device_probe_complete (self, NULL, NULL, NULL); + else + FP_DEVICE_GET_CLASS (self)->probe (self); + + return; +} + static void fp_device_async_initable_init_async (GAsyncInitable *initable, int io_priority, @@ -298,17 +376,16 @@ fp_device_async_initable_init_async (GAsyncInitable *initable, if (g_task_return_error_if_cancelled (task)) return; - if (!FP_DEVICE_GET_CLASS (self)->probe) - { - g_task_return_boolean (task, TRUE); - return; - } - priv->current_action = FPI_DEVICE_ACTION_PROBE; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (self, cancellable); + setup_task_cancellable (self); - FP_DEVICE_GET_CLASS (self)->probe (self); + /* We push this into an idle handler for compatibility with libgusb + * 0.3.7 and before. + * See https://github.com/hughsie/libgusb/pull/50 + */ + g_source_set_name (fpi_device_add_timeout (self, 0, device_idle_probe_cb, NULL, NULL), + "libusb probe in idle"); } static gboolean @@ -358,6 +435,13 @@ fp_device_class_init (FpDeviceClass *klass) FP_TYPE_FINGER_STATUS_FLAGS, FP_FINGER_STATUS_NONE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + properties[PROP_TEMPERATURE] = + g_param_spec_enum ("temperature", + "Temperature", + "The temperature estimation for device to prevent overheating.", + FP_TYPE_TEMPERATURE, FP_TEMPERATURE_COLD, + G_PARAM_STATIC_STRINGS | G_PARAM_READABLE); + properties[PROP_DRIVER] = g_param_spec_string ("driver", "Driver", @@ -616,6 +700,25 @@ fp_device_get_nr_enroll_stages (FpDevice *device) return priv->nr_enroll_stages; } +/** + * fp_device_get_temperature: + * @device: A #FpDevice + * + * Retrieves simple temperature information for device. It is not possible + * to use a device when this is #FP_TEMPERATURE_HOT. + * + * Returns: The current temperature estimation. + */ +FpTemperature +fp_device_get_temperature (FpDevice *device) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + g_return_val_if_fail (FP_IS_DEVICE (device), -1); + + return priv->temp_current; +} + /** * fp_device_supports_identify: * @device: A #FpDevice @@ -708,7 +811,7 @@ fp_device_open (FpDevice *device, return; } - if (priv->current_task) + if (priv->current_task || priv->is_suspended) { g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); @@ -737,7 +840,7 @@ fp_device_open (FpDevice *device, priv->current_action = FPI_DEVICE_ACTION_OPEN; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (device, cancellable); + setup_task_cancellable (device); fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); FP_DEVICE_GET_CLASS (device)->open (device); @@ -793,7 +896,7 @@ fp_device_close (FpDevice *device, return; } - if (priv->current_task) + if (priv->current_task || priv->is_suspended) { g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); @@ -802,7 +905,7 @@ fp_device_close (FpDevice *device, priv->current_action = FPI_DEVICE_ACTION_CLOSE; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (device, cancellable); + setup_task_cancellable (device); FP_DEVICE_GET_CLASS (device)->close (device); } @@ -826,6 +929,230 @@ fp_device_close_finish (FpDevice *device, return g_task_propagate_boolean (G_TASK (result), error); } +static void +complete_suspend_resume_task (FpDevice *device) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + g_assert (priv->suspend_resume_task); + + g_task_return_boolean (g_steal_pointer (&priv->suspend_resume_task), TRUE); +} + +/** + * fp_device_suspend: + * @device: a #FpDevice + * @cancellable: (nullable): a #GCancellable, or %NULL, currently not used + * @callback: the function to call on completion + * @user_data: the data to pass to @callback + * + * Prepare the device for system suspend. Retrieve the result with + * fp_device_suspend_finish(). + * + * The suspend method can be called at any time (even if the device is not + * opened) and must be paired with a corresponding resume call. It is undefined + * when or how any ongoing operation is finished. This call might wait for an + * ongoing operation to finish, might cancel the ongoing operation or may + * prepare the device so that the host is resumed when the operation can be + * finished. + * + * If an ongoing operation must be cancelled then it will complete with an error + * code of #FP_DEVICE_ERROR_BUSY before the suspend async routine finishes. + * + * Any operation started while the device is suspended will fail with + * #FP_DEVICE_ERROR_BUSY, this includes calls to open or close the device. + */ +void +fp_device_suspend (FpDevice *device, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + task = g_task_new (device, cancellable, callback, user_data); + + if (priv->suspend_resume_task || priv->is_suspended) + { + g_task_return_error (task, + fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); + return; + } + + if (priv->is_removed) + { + g_task_return_error (task, + fpi_device_error_new (FP_DEVICE_ERROR_REMOVED)); + return; + } + + priv->suspend_resume_task = g_steal_pointer (&task); + + /* If the device is currently idle, just complete immediately. + * For long running tasks, call the driver handler right away, for short + * tasks, wait for completion and then return the task. + */ + switch (priv->current_action) + { + case FPI_DEVICE_ACTION_NONE: + fpi_device_suspend_complete (device, NULL); + break; + + case FPI_DEVICE_ACTION_ENROLL: + case FPI_DEVICE_ACTION_VERIFY: + case FPI_DEVICE_ACTION_IDENTIFY: + case FPI_DEVICE_ACTION_CAPTURE: + if (FP_DEVICE_GET_CLASS (device)->suspend) + { + if (priv->critical_section) + priv->suspend_queued = TRUE; + else + FP_DEVICE_GET_CLASS (device)->suspend (device); + } + else + { + fpi_device_suspend_complete (device, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + } + break; + + default: + case FPI_DEVICE_ACTION_PROBE: + case FPI_DEVICE_ACTION_OPEN: + case FPI_DEVICE_ACTION_CLOSE: + case FPI_DEVICE_ACTION_DELETE: + case FPI_DEVICE_ACTION_LIST: + case FPI_DEVICE_ACTION_CLEAR_STORAGE: + g_signal_connect_object (priv->current_task, + "notify::completed", + G_CALLBACK (complete_suspend_resume_task), + device, + G_CONNECT_SWAPPED); + + break; + } +} + +/** + * fp_device_suspend_finish: + * @device: A #FpDevice + * @result: A #GAsyncResult + * @error: Return location for errors, or %NULL to ignore + * + * Finish an asynchronous operation to prepare the device for suspend. + * See fp_device_suspend(). + * + * The API user should accept an error of #FP_DEVICE_ERROR_NOT_SUPPORTED. + * + * Returns: (type void): %FALSE on error, %TRUE otherwise + */ +gboolean +fp_device_suspend_finish (FpDevice *device, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +/** + * fp_device_resume: + * @device: a #FpDevice + * @cancellable: (nullable): a #GCancellable, or %NULL, currently not used + * @callback: the function to call on completion + * @user_data: the data to pass to @callback + * + * Resume device after system suspend. Retrieve the result with + * fp_device_suspend_finish(). + * + * Note that it is not defined when any ongoing operation may return (success or + * error). You must be ready to handle this before, during or after the + * resume operation. + */ +void +fp_device_resume (FpDevice *device, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + task = g_task_new (device, cancellable, callback, user_data); + + if (priv->suspend_resume_task || !priv->is_suspended) + { + g_task_return_error (task, + fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); + return; + } + + if (priv->is_removed) + { + g_task_return_error (task, + fpi_device_error_new (FP_DEVICE_ERROR_REMOVED)); + return; + } + + priv->suspend_resume_task = g_steal_pointer (&task); + + switch (priv->current_action) + { + case FPI_DEVICE_ACTION_NONE: + fpi_device_resume_complete (device, NULL); + break; + + case FPI_DEVICE_ACTION_ENROLL: + case FPI_DEVICE_ACTION_VERIFY: + case FPI_DEVICE_ACTION_IDENTIFY: + case FPI_DEVICE_ACTION_CAPTURE: + if (FP_DEVICE_GET_CLASS (device)->resume) + { + if (priv->critical_section) + priv->resume_queued = TRUE; + else + FP_DEVICE_GET_CLASS (device)->resume (device); + } + else + { + fpi_device_resume_complete (device, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + } + break; + + default: + case FPI_DEVICE_ACTION_PROBE: + case FPI_DEVICE_ACTION_OPEN: + case FPI_DEVICE_ACTION_CLOSE: + case FPI_DEVICE_ACTION_DELETE: + case FPI_DEVICE_ACTION_LIST: + case FPI_DEVICE_ACTION_CLEAR_STORAGE: + /* cannot happen as we make sure these tasks complete before suspend */ + g_assert_not_reached (); + complete_suspend_resume_task (device); + break; + } +} + +/** + * fp_device_resume_finish: + * @device: A #FpDevice + * @result: A #GAsyncResult + * @error: Return location for errors, or %NULL to ignore + * + * Finish an asynchronous operation to resume the device after suspend. + * See fp_device_resume(). + * + * The API user should accept an error of #FP_DEVICE_ERROR_NOT_SUPPORTED. + * + * Returns: (type void): %FALSE on error, %TRUE otherwise + */ +gboolean +fp_device_resume_finish (FpDevice *device, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + /** * fp_device_enroll: @@ -874,7 +1201,7 @@ fp_device_enroll (FpDevice *device, return; } - if (priv->current_task) + if (priv->current_task || priv->is_suspended) { g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); @@ -900,7 +1227,15 @@ fp_device_enroll (FpDevice *device, priv->current_action = FPI_DEVICE_ACTION_ENROLL; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (device, cancellable); + setup_task_cancellable (device); + + fpi_device_update_temp (device, TRUE); + if (priv->temp_current == FP_TEMPERATURE_HOT) + { + g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_TOO_HOT)); + fpi_device_update_temp (device, FALSE); + return; + } data = g_new0 (FpEnrollData, 1); data->print = g_object_ref_sink (template_print); @@ -976,7 +1311,7 @@ fp_device_verify (FpDevice *device, return; } - if (priv->current_task) + if (priv->current_task || priv->is_suspended) { g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); @@ -993,7 +1328,15 @@ fp_device_verify (FpDevice *device, priv->current_action = FPI_DEVICE_ACTION_VERIFY; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (device, cancellable); + setup_task_cancellable (device); + + fpi_device_update_temp (device, TRUE); + if (priv->temp_current == FP_TEMPERATURE_HOT) + { + g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_TOO_HOT)); + fpi_device_update_temp (device, FALSE); + return; + } data = g_new0 (FpMatchData, 1); data->enrolled_print = g_object_ref (enrolled_print); @@ -1095,7 +1438,7 @@ fp_device_identify (FpDevice *device, return; } - if (priv->current_task) + if (priv->current_task || priv->is_suspended) { g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); @@ -1112,7 +1455,15 @@ fp_device_identify (FpDevice *device, priv->current_action = FPI_DEVICE_ACTION_IDENTIFY; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (device, cancellable); + setup_task_cancellable (device); + + fpi_device_update_temp (device, TRUE); + if (priv->temp_current == FP_TEMPERATURE_HOT) + { + g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_TOO_HOT)); + fpi_device_update_temp (device, FALSE); + return; + } data = g_new0 (FpMatchData, 1); /* We cannot store the gallery directly, because the ptr array may not own @@ -1212,7 +1563,7 @@ fp_device_capture (FpDevice *device, return; } - if (priv->current_task) + if (priv->current_task || priv->is_suspended) { g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); @@ -1229,7 +1580,15 @@ fp_device_capture (FpDevice *device, priv->current_action = FPI_DEVICE_ACTION_CAPTURE; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (device, cancellable); + setup_task_cancellable (device); + + fpi_device_update_temp (device, TRUE); + if (priv->temp_current == FP_TEMPERATURE_HOT) + { + g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_TOO_HOT)); + fpi_device_update_temp (device, FALSE); + return; + } priv->wait_for_finger = wait_for_finger; @@ -1295,7 +1654,7 @@ fp_device_delete_print (FpDevice *device, return; } - if (priv->current_task) + if (priv->current_task || priv->is_suspended) { g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); @@ -1311,7 +1670,7 @@ fp_device_delete_print (FpDevice *device, priv->current_action = FPI_DEVICE_ACTION_DELETE; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (device, cancellable); + setup_task_cancellable (device); g_task_set_task_data (priv->current_task, g_object_ref (enrolled_print), @@ -1373,7 +1732,7 @@ fp_device_list_prints (FpDevice *device, return; } - if (priv->current_task) + if (priv->current_task || priv->is_suspended) { g_task_return_error (task, fpi_device_error_new (FP_DEVICE_ERROR_BUSY)); @@ -1390,7 +1749,7 @@ fp_device_list_prints (FpDevice *device, priv->current_action = FPI_DEVICE_ACTION_LIST; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (device, cancellable); + setup_task_cancellable (device); cls->list (device); } @@ -1475,7 +1834,7 @@ fp_device_clear_storage (FpDevice *device, priv->current_action = FPI_DEVICE_ACTION_CLEAR_STORAGE; priv->current_task = g_steal_pointer (&task); - maybe_cancel_on_cancelled (device, cancellable); + setup_task_cancellable (device); cls->clear_storage (device); @@ -1769,6 +2128,7 @@ fp_device_list_prints_sync (FpDevice *device, return fp_device_list_prints_finish (device, task, error); } + /** * fp_device_clear_storage_sync: * @device: a #FpDevice @@ -1797,6 +2157,58 @@ fp_device_clear_storage_sync (FpDevice *device, return fp_device_clear_storage_finish (device, task, error); } +/** + * fp_device_suspend_sync: + * @device: a #FpDevice + * @cancellable: (nullable): a #GCancellable, or %NULL, currently not used + * @error: Return location for errors, or %NULL to ignore + * + * Prepare device for suspend. + * + * Returns: (type void): %FALSE on error, %TRUE otherwise + */ +gboolean +fp_device_suspend_sync (FpDevice *device, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GAsyncResult) task = NULL; + + g_return_val_if_fail (FP_IS_DEVICE (device), FALSE); + + fp_device_suspend (device, cancellable, async_result_ready, &task); + while (!task) + g_main_context_iteration (NULL, TRUE); + + return fp_device_suspend_finish (device, task, error); +} + +/** + * fp_device_resume_sync: + * @device: a #FpDevice + * @cancellable: (nullable): a #GCancellable, or %NULL, currently not used + * @error: Return location for errors, or %NULL to ignore + * + * Resume device after suspend. + * + * Returns: (type void): %FALSE on error, %TRUE otherwise + */ +gboolean +fp_device_resume_sync (FpDevice *device, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GAsyncResult) task = NULL; + + g_return_val_if_fail (FP_IS_DEVICE (device), FALSE); + + fp_device_resume (device, cancellable, async_result_ready, &task); + while (!task) + g_main_context_iteration (NULL, TRUE); + + return fp_device_resume_finish (device, task, error); +} + /** * fp_device_get_features: * @device: a #FpDevice diff --git a/libfprint/fp-device.h b/libfprint/fp-device.h index 5d224f824b5f39ba0995eaeae1730f4d20b4b387..85be34c444127eb3119bd7841375ed76fe1b6e63 100644 --- a/libfprint/fp-device.h +++ b/libfprint/fp-device.h @@ -58,6 +58,7 @@ typedef enum { * @FP_DEVICE_FEATURE_STORAGE_DELETE: Supports deleting stored templates * @FP_DEVICE_FEATURE_STORAGE_CLEAR: Supports clearing the whole storage * @FP_DEVICE_FEATURE_DUPLICATES_CHECK: Natively supports duplicates detection + * @FP_DEVICE_FEATURE_ALWAYS_ON: Whether the device can run continuously */ typedef enum /*< flags >*/ { FP_DEVICE_FEATURE_NONE = 0, @@ -69,6 +70,7 @@ typedef enum /*< flags >*/ { FP_DEVICE_FEATURE_STORAGE_DELETE = 1 << 5, FP_DEVICE_FEATURE_STORAGE_CLEAR = 1 << 6, FP_DEVICE_FEATURE_DUPLICATES_CHECK = 1 << 7, + FP_DEVICE_FEATURE_ALWAYS_ON = 1 << 8, } FpDeviceFeature; /** @@ -81,6 +83,23 @@ typedef enum { FP_SCAN_TYPE_PRESS, } FpScanType; +/** + * FpTemperature: + * @FP_TEMPERATURE_COLD: Sensor is considered cold. + * @FP_TEMPERATURE_WARM: Sensor is warm, usage time may be limited. + * @FP_TEMPERATURE_HOT: Sensor is hot and cannot be used. + * + * When a device is created, it is assumed to be cold. Applications such as + * fprintd may want to ensure all devices on the system are cold before + * shutting down in order to ensure that the cool-off period is not violated + * because the internal libfprint state about the device is lost. + */ +typedef enum { + FP_TEMPERATURE_COLD, + FP_TEMPERATURE_WARM, + FP_TEMPERATURE_HOT, +} FpTemperature; + /** * FpDeviceRetry: * @FP_DEVICE_RETRY_GENERAL: The scan did not succeed due to poor scan quality @@ -118,6 +137,7 @@ typedef enum { * @FP_DEVICE_ERROR_DATA_FULL: No space on device available for operation * @FP_DEVICE_ERROR_DATA_DUPLICATE: Enrolling template duplicates storaged templates * @FP_DEVICE_ERROR_REMOVED: The device has been removed. + * @FP_DEVICE_ERROR_TOO_HOT: The device might be getting too hot * * Error codes for device operations. More specific errors from other domains * such as #G_IO_ERROR or #G_USB_DEVICE_ERROR may also be reported. @@ -135,6 +155,7 @@ typedef enum { FP_DEVICE_ERROR_DATA_DUPLICATE, /* Leave some room to add more DATA related errors */ FP_DEVICE_ERROR_REMOVED = 0x100, + FP_DEVICE_ERROR_TOO_HOT, } FpDeviceError; GQuark fp_device_retry_quark (void); @@ -201,6 +222,7 @@ gboolean fp_device_is_open (FpDevice *device); FpScanType fp_device_get_scan_type (FpDevice *device); FpFingerStatusFlags fp_device_get_finger_status (FpDevice *device); gint fp_device_get_nr_enroll_stages (FpDevice *device); +FpTemperature fp_device_get_temperature (FpDevice *device); FpDeviceFeature fp_device_get_features (FpDevice *device); gboolean fp_device_has_feature (FpDevice *device, @@ -217,6 +239,16 @@ void fp_device_close (FpDevice *device, GAsyncReadyCallback callback, gpointer user_data); +void fp_device_suspend (FpDevice *device, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +void fp_device_resume (FpDevice *device, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + void fp_device_enroll (FpDevice *device, FpPrint *template_print, GCancellable *cancellable, @@ -272,6 +304,12 @@ gboolean fp_device_open_finish (FpDevice *device, gboolean fp_device_close_finish (FpDevice *device, GAsyncResult *result, GError **error); +gboolean fp_device_suspend_finish (FpDevice *device, + GAsyncResult *result, + GError **error); +gboolean fp_device_resume_finish (FpDevice *device, + GAsyncResult *result, + GError **error); FpPrint *fp_device_enroll_finish (FpDevice *device, GAsyncResult *result, GError **error); @@ -340,6 +378,13 @@ GPtrArray * fp_device_list_prints_sync (FpDevice *device, gboolean fp_device_clear_storage_sync (FpDevice *device, GCancellable *cancellable, GError **error); +gboolean fp_device_suspend_sync (FpDevice *device, + GCancellable *cancellable, + GError **error); +gboolean fp_device_resume_sync (FpDevice *device, + GCancellable *cancellable, + GError **error); + /* Deprecated functions */ G_DEPRECATED_FOR (fp_device_get_features) gboolean fp_device_supports_identify (FpDevice *device); diff --git a/libfprint/fpi-device.c b/libfprint/fpi-device.c index d91fc9415dcd1866fb8ac36b3c0ee92d124035b1..89504dab10f407e6df634a8d541f9b9e522a3f21 100644 --- a/libfprint/fpi-device.c +++ b/libfprint/fpi-device.c @@ -19,6 +19,9 @@ */ #define FP_COMPONENT "device" +#include +#include + #include "fpi-log.h" #include "fp-device-private.h" @@ -78,6 +81,9 @@ fpi_device_class_auto_initialize_features (FpDeviceClass *device_class) if (device_class->delete && (device_class->list || device_class->clear_storage)) device_class->features |= FP_DEVICE_FEATURE_STORAGE; + + if (device_class->temp_hot_seconds < 0) + device_class->features |= FP_DEVICE_FEATURE_ALWAYS_ON; } /** @@ -177,6 +183,10 @@ fpi_device_error_new (FpDeviceError error) msg = "This device has been removed from the system."; break; + case FP_DEVICE_ERROR_TOO_HOT: + msg = "Device disabled to prevent overheating."; + break; + default: g_warning ("Unsupported error, returning general error instead!"); error = FP_DEVICE_ERROR_GENERAL; @@ -488,14 +498,11 @@ gboolean fpi_device_action_is_cancelled (FpDevice *device) { FpDevicePrivate *priv = fp_device_get_instance_private (device); - GCancellable *cancellable; g_return_val_if_fail (FP_IS_DEVICE (device), TRUE); g_return_val_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE, TRUE); - cancellable = g_task_get_cancellable (priv->current_task); - - return g_cancellable_is_cancelled (cancellable); + return g_cancellable_is_cancelled (priv->current_cancellable); } /** @@ -673,7 +680,7 @@ fpi_device_get_cancellable (FpDevice *device) g_return_val_if_fail (FP_IS_DEVICE (device), NULL); g_return_val_if_fail (priv->current_action != FPI_DEVICE_ACTION_NONE, NULL); - return g_task_get_cancellable (priv->current_task); + return priv->current_cancellable; } static void @@ -802,6 +809,120 @@ 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; + } + + if (priv->suspend_queued) + { + cls->suspend (device); + priv->suspend_queued = FALSE; + + return G_SOURCE_CONTINUE; + } + + if (priv->resume_queued) + { + cls->resume (device); + priv->resume_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) { @@ -811,10 +932,17 @@ clear_device_cancel_action (FpDevice *device) if (priv->current_cancellable_id) { - g_cancellable_disconnect (g_task_get_cancellable (priv->current_task), + g_cancellable_disconnect (priv->current_cancellable, priv->current_cancellable_id); priv->current_cancellable_id = 0; } + + if (priv->current_task_cancellable_id) + { + g_cancellable_disconnect (g_task_get_cancellable (priv->current_task), + priv->current_task_cancellable_id); + priv->current_task_cancellable_id = 0; + } } typedef enum _FpDeviceTaskReturnType { @@ -841,6 +969,7 @@ fp_device_task_return_in_idle_cb (gpointer user_data) FpiDeviceAction action; g_autoptr(GTask) task = NULL; + g_autoptr(GError) cancellation_reason = NULL; action_str = g_enum_to_string (FPI_TYPE_DEVICE_ACTION, priv->current_action); @@ -850,6 +979,10 @@ fp_device_task_return_in_idle_cb (gpointer user_data) action = priv->current_action; priv->current_action = FPI_DEVICE_ACTION_NONE; priv->current_task_idle_return_source = NULL; + g_clear_object (&priv->current_cancellable); + cancellation_reason = g_steal_pointer (&priv->current_cancellation_reason); + + fpi_device_update_temp (data->device, FALSE); if (action == FPI_DEVICE_ACTION_OPEN && data->type != FP_DEVICE_TASK_RETURN_ERROR) @@ -866,6 +999,8 @@ fp_device_task_return_in_idle_cb (gpointer user_data) g_object_notify (G_OBJECT (data->device), "open"); } + /* TODO: Port/use the cancellation mechanism for device removal! */ + /* Return FP_DEVICE_ERROR_REMOVED if the device is removed, * with the exception of a successful open, which is an odd corner case. */ if (priv->is_removed && @@ -901,7 +1036,18 @@ fp_device_task_return_in_idle_cb (gpointer user_data) break; case FP_DEVICE_TASK_RETURN_ERROR: - g_task_return_error (task, g_steal_pointer (&data->result)); + /* Return internal cancellation reason instead if we have one. + * Note that an external cancellation always returns G_IO_ERROR_CANCELLED + */ + if (cancellation_reason) + { + g_task_set_task_data (task, NULL, NULL); + g_task_return_error (task, g_steal_pointer (&cancellation_reason)); + } + else + { + g_task_return_error (task, g_steal_pointer (&data->result)); + } break; default: @@ -1404,6 +1550,183 @@ fpi_device_list_complete (FpDevice *device, fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, error); } +void +fpi_device_configure_wakeup (FpDevice *device, gboolean enabled) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + switch (priv->type) + { + case FP_DEVICE_TYPE_USB: + { + g_autoptr(GString) ports = NULL; + GUsbDevice *dev, *parent; + const char *wakeup_command = enabled ? "enabled" : "disabled"; + guint8 bus, port; + g_autofree gchar *sysfs_wakeup = NULL; + g_autofree gchar *sysfs_persist = NULL; + gssize r; + int fd; + + ports = g_string_new (NULL); + bus = g_usb_device_get_bus (priv->usb_device); + + /* Walk up, skipping the root hub. */ + dev = priv->usb_device; + while ((parent = g_usb_device_get_parent (dev))) + { + port = g_usb_device_get_port_number (dev); + g_string_prepend (ports, g_strdup_printf ("%d.", port)); + dev = parent; + } + g_string_set_size (ports, ports->len - 1); + + sysfs_wakeup = g_strdup_printf ("/sys/bus/usb/devices/%d-%s/power/wakeup", bus, ports->str); + fd = open (sysfs_wakeup, O_WRONLY); + + if (fd < 0) + { + /* Wakeup not existing appears to be relatively normal. */ + g_debug ("Failed to open %s", sysfs_wakeup); + } + else + { + r = write (fd, wakeup_command, strlen (wakeup_command)); + if (r < 0) + g_warning ("Could not configure wakeup to %s by writing %s", wakeup_command, sysfs_wakeup); + close (fd); + } + + /* Persist means that the kernel tries to keep the USB device open + * in case it is "replugged" due to suspend. + * This is not helpful, as it will receive a reset and will be in a bad + * state. Instead, seeing an unplug and a new device makes more sense. + */ + sysfs_persist = g_strdup_printf ("/sys/bus/usb/devices/%d-%s/power/persist", bus, ports->str); + fd = open (sysfs_persist, O_WRONLY); + + if (fd < 0) + { + g_warning ("Failed to open %s", sysfs_persist); + return; + } + else + { + r = write (fd, "0", 1); + if (r < 0) + g_message ("Could not disable USB persist by writing to %s", sysfs_persist); + close (fd); + } + + break; + } + + case FP_DEVICE_TYPE_VIRTUAL: + case FP_DEVICE_TYPE_UDEV: + break; + + default: + g_assert_not_reached (); + fpi_device_return_task_in_idle (device, FP_DEVICE_TASK_RETURN_ERROR, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } +} + +static void +fpi_device_suspend_completed (FpDevice *device) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + /* We have an ongoing operation, allow the device to wake up the machine. */ + if (priv->current_action != FPI_DEVICE_ACTION_NONE) + fpi_device_configure_wakeup (device, TRUE); + + if (priv->critical_section) + g_warning ("Driver was in a critical section at suspend time. It likely deadlocked!"); + + if (priv->suspend_error) + g_task_return_error (g_steal_pointer (&priv->suspend_resume_task), + g_steal_pointer (&priv->suspend_error)); + else + g_task_return_boolean (g_steal_pointer (&priv->suspend_resume_task), TRUE); +} + +/** + * fpi_device_suspend_complete: + * @device: The #FpDevice + * @error: The #GError or %NULL on success + * + * Finish a suspend request. Only return a %NULL error if suspend has been + * correctly configured and the current action as returned by + * fpi_device_get_current_action() will continue to run after resume. + * + * In all other cases an error must be returned. Should this happen, the + * current action will be cancelled before the error is forwarded to the + * application. + * + * It is recommended to set @error to #FP_ERROR_NOT_IMPLEMENTED. + */ +void +fpi_device_suspend_complete (FpDevice *device, + GError *error) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + g_return_if_fail (FP_IS_DEVICE (device)); + g_return_if_fail (priv->suspend_resume_task); + g_return_if_fail (priv->suspend_error == NULL); + + priv->suspend_error = error; + priv->is_suspended = TRUE; + + /* If there is no error, we have no running task, return immediately. */ + if (error == NULL || !priv->current_task || g_task_get_completed (priv->current_task)) + { + fpi_device_suspend_completed (device); + return; + } + + /* Wait for completion of the current task. */ + g_signal_connect_object (priv->current_task, + "notify::completed", + G_CALLBACK (fpi_device_suspend_completed), + device, + G_CONNECT_SWAPPED); + + /* And cancel any action that might be long-running. */ + if (!priv->current_cancellation_reason) + priv->current_cancellation_reason = fpi_device_error_new_msg (FP_DEVICE_ERROR_BUSY, + "Cannot run while suspended."); + + g_cancellable_cancel (priv->current_cancellable); +} + +/** + * fpi_device_resume_complete: + * @device: The #FpDevice + * @error: The #GError or %NULL on success + * + * Finish a resume request. + */ +void +fpi_device_resume_complete (FpDevice *device, + GError *error) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + g_return_if_fail (FP_IS_DEVICE (device)); + g_return_if_fail (priv->suspend_resume_task); + + priv->is_suspended = FALSE; + fpi_device_configure_wakeup (device, FALSE); + + if (error) + g_task_return_error (g_steal_pointer (&priv->suspend_resume_task), error); + else + g_task_return_boolean (g_steal_pointer (&priv->suspend_resume_task), TRUE); +} + /** * fpi_device_clear_storage_complete: * @device: The #FpDevice @@ -1685,3 +2008,128 @@ fpi_device_report_finger_status_changes (FpDevice *device, return fpi_device_report_finger_status (device, finger_status); } + +static void +update_temp_timeout (FpDevice *device, gpointer user_data) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + + fpi_device_update_temp (device, priv->temp_last_active); +} + +/** + * fpi_device_update_temp: + * @device: The #FpDevice + * @is_active: Whether the device is now active + * + * Purely internal function to update the temperature. Also ensure that the + * state is updated once a threshold is reached. + */ +void +fpi_device_update_temp (FpDevice *device, gboolean is_active) +{ + FpDevicePrivate *priv = fp_device_get_instance_private (device); + gint64 now = g_get_monotonic_time (); + gdouble passed_seconds; + gdouble alpha; + gdouble next_threshold; + gdouble old_ratio; + FpTemperature old_temp; + g_autofree char *old_temp_str = NULL; + g_autofree char *new_temp_str = NULL; + + if (priv->temp_hot_seconds < 0) + { + g_debug ("Not updating temperature model, device can run continuously!"); + return; + } + + passed_seconds = (now - priv->temp_last_update) / 1e6; + old_ratio = priv->temp_current_ratio; + + if (priv->temp_last_active) + { + alpha = exp (-passed_seconds / priv->temp_hot_seconds); + priv->temp_current_ratio = alpha * priv->temp_current_ratio + 1 - alpha; + } + else + { + alpha = exp (-passed_seconds / priv->temp_cold_seconds); + priv->temp_current_ratio = alpha * priv->temp_current_ratio; + } + + priv->temp_last_active = is_active; + priv->temp_last_update = now; + + old_temp = priv->temp_current; + if (priv->temp_current_ratio < TEMP_COLD_THRESH) + { + priv->temp_current = FP_TEMPERATURE_COLD; + next_threshold = is_active ? TEMP_COLD_THRESH : -1.0; + } + else if (priv->temp_current_ratio < TEMP_HOT_WARM_THRESH) + { + priv->temp_current = FP_TEMPERATURE_WARM; + next_threshold = is_active ? TEMP_WARM_HOT_THRESH : TEMP_COLD_THRESH; + } + else if (priv->temp_current_ratio < TEMP_WARM_HOT_THRESH) + { + /* Keep HOT until we reach TEMP_HOT_WARM_THRESH */ + if (priv->temp_current != FP_TEMPERATURE_HOT) + priv->temp_current = FP_TEMPERATURE_WARM; + + next_threshold = is_active ? TEMP_WARM_HOT_THRESH : TEMP_HOT_WARM_THRESH; + } + else + { + priv->temp_current = FP_TEMPERATURE_HOT; + next_threshold = is_active ? -1.0 : TEMP_HOT_WARM_THRESH; + } + + old_temp_str = g_enum_to_string (FP_TYPE_TEMPERATURE, old_temp); + new_temp_str = g_enum_to_string (FP_TYPE_TEMPERATURE, priv->temp_current); + g_debug ("Updated temperature model after %0.2f seconds, ratio %0.2f -> %0.2f, active %d -> %d, %s -> %s", + passed_seconds, + old_ratio, + priv->temp_current_ratio, + priv->temp_last_active, + is_active, + old_temp_str, + new_temp_str); + + if (priv->temp_current != old_temp) + g_object_notify (G_OBJECT (device), "temperature"); + + /* If the device is HOT, then do an internal cancellation of long running tasks. */ + if (priv->temp_current == FP_TEMPERATURE_HOT) + { + if (priv->current_action == FPI_DEVICE_ACTION_ENROLL || + priv->current_action == FPI_DEVICE_ACTION_VERIFY || + priv->current_action == FPI_DEVICE_ACTION_IDENTIFY || + priv->current_action == FPI_DEVICE_ACTION_CAPTURE) + { + if (!priv->current_cancellation_reason) + priv->current_cancellation_reason = fpi_device_error_new (FP_DEVICE_ERROR_TOO_HOT); + + g_cancellable_cancel (priv->current_cancellable); + } + } + + g_clear_pointer (&priv->temp_timeout, g_source_destroy); + + if (next_threshold < 0) + return; + + /* Set passed_seconds to the time until the next update is needed */ + if (is_active) + passed_seconds = -priv->temp_hot_seconds * log ((next_threshold - 1.0) / (priv->temp_current_ratio - 1.0)); + else + passed_seconds = -priv->temp_cold_seconds * log (next_threshold / priv->temp_current_ratio); + + passed_seconds += TEMP_DELAY_SECONDS; + + priv->temp_timeout = fpi_device_add_timeout (device, + passed_seconds * 1000, + update_temp_timeout, + NULL, NULL); +} diff --git a/libfprint/fpi-device.h b/libfprint/fpi-device.h index eb8b8fe3d70692c76bcf2ff18b8a4d646fe2e2aa..42e26e060851387793ddcf5495b7b1c85ca0d9df 100644 --- a/libfprint/fpi-device.h +++ b/libfprint/fpi-device.h @@ -82,6 +82,10 @@ struct _FpIdEntry * fpi_device_set_nr_enroll_stages() from @probe if this is dynamic. * @scan_type: The scan type of supported devices; use * fpi_device_set_scan_type() from @probe if this is dynamic. + * @temp_hot_seconds: Assumed time in seconds for the device to become too hot + * after being mostly cold. Set to -1 if the device can be always-on. + * @temp_cold_seconds: Assumed time in seconds for the device to be mostly cold + * after having been too hot to operate. * @usb_discover: Class method to check whether a USB device is supported by * the driver. Should return 0 if the device is unsupported and a positive * score otherwise. The default score is 50 and the driver with the highest @@ -104,6 +108,10 @@ struct _FpIdEntry * @clear_storage: Delete all prints from the device * @cancel: Called on cancellation, this is a convenience to not need to handle * the #GCancellable directly by using fpi_device_get_cancellable(). + * @suspend: Called when an interactive action is running (ENROLL, VERIFY, + * IDENTIFY or CAPTURE) and the system is about to go into suspend. + * @resume: Called to resume an ongoing interactive action after the system has + * resumed from suspend. * * NOTE: If your driver is image based, then you should subclass #FpImageDevice * instead. #FpImageDevice based drivers use a different way of interacting @@ -122,6 +130,9 @@ struct _FpIdEntry * operation (i.e. any operation that requires capturing). It is entirely fine * to ignore cancellation requests for short operations (e.g. open/close). * + * Note that @cancel, @suspend and @resume will not be called while the device + * is within a fpi_device_critical_enter()/fpi_device_critical_leave() block. + * * This API is solely intended for drivers. It is purely internal and neither * API nor ABI stable. */ @@ -142,6 +153,10 @@ struct _FpDeviceClass gint nr_enroll_stages; FpScanType scan_type; + /* Simple device temperature model constants */ + gint32 temp_hot_seconds; + gint32 temp_cold_seconds; + /* Callbacks */ gint (*usb_discover) (GUsbDevice *usb_device); void (*probe) (FpDevice *device); @@ -156,6 +171,8 @@ struct _FpDeviceClass void (*clear_storage) (FpDevice * device); void (*cancel) (FpDevice *device); + void (*suspend) (FpDevice *device); + void (*resume) (FpDevice *device); }; void fpi_device_class_auto_initialize_features (FpDeviceClass *device_class); @@ -256,6 +273,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, @@ -281,6 +301,10 @@ void fpi_device_list_complete (FpDevice *device, GError *error); void fpi_device_clear_storage_complete (FpDevice *device, GError *error); +void fpi_device_suspend_complete (FpDevice *device, + GError *error); +void fpi_device_resume_complete (FpDevice *device, + GError *error); void fpi_device_enroll_progress (FpDevice *device, gint completed_stages, diff --git a/tests/test-device-fake.c b/tests/test-device-fake.c index 4dd90e59687bc379c20ee5be728b5de6aed6a61a..4c61ed3762bf9ca2765b7024be6828c86ca60a3e 100644 --- a/tests/test-device-fake.c +++ b/tests/test-device-fake.c @@ -271,6 +271,26 @@ fpi_device_fake_cancel (FpDevice *device) g_assert_cmpuint (fpi_device_get_current_action (device), !=, FPI_DEVICE_ACTION_NONE); } +static void +fpi_device_fake_suspend (FpDevice *device) +{ + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + + fake_dev->last_called_function = fpi_device_fake_suspend; + + fpi_device_suspend_complete (device, g_steal_pointer (&fake_dev->ret_suspend)); +} + +static void +fpi_device_fake_resume (FpDevice *device) +{ + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + + fake_dev->last_called_function = fpi_device_fake_resume; + + fpi_device_resume_complete (device, g_steal_pointer (&fake_dev->ret_resume)); +} + static void fpi_device_fake_init (FpiDeviceFake *self) { @@ -299,6 +319,8 @@ fpi_device_fake_class_init (FpiDeviceFakeClass *klass) dev_class->delete = fpi_device_fake_delete; dev_class->cancel = fpi_device_fake_cancel; dev_class->clear_storage = fpi_device_fake_clear_storage; + dev_class->suspend = fpi_device_fake_suspend; + dev_class->resume = fpi_device_fake_resume; fpi_device_class_auto_initialize_features (dev_class); } diff --git a/tests/test-device-fake.h b/tests/test-device-fake.h index fa8b9b9cff14b429687e527abc480bb05c990ee5..7e14b478af673271e56b8ef4f0330738fbaa570f 100644 --- a/tests/test-device-fake.h +++ b/tests/test-device-fake.h @@ -32,6 +32,8 @@ struct _FpiDeviceFake gpointer last_called_function; gboolean return_action_error; + GCancellable *ext_cancellable; + GError *ret_error; FpPrint *ret_print; FpPrint *ret_match; @@ -39,6 +41,9 @@ struct _FpiDeviceFake FpImage *ret_image; GPtrArray *ret_list; + GError *ret_suspend; + GError *ret_resume; + gpointer action_data; gpointer user_data; diff --git a/tests/test-fpi-device.c b/tests/test-fpi-device.c index b7c9f0aca2a816a1f96d16fe9173fbf5db5c6105..408e2f925445c153af92d12e7cf23577e53df906 100644 --- a/tests/test-fpi-device.c +++ b/tests/test-fpi-device.c @@ -35,9 +35,11 @@ typedef FpDevice FpAutoCloseDevice; static FpAutoCloseDevice * auto_close_fake_device_new (void) { + g_autoptr(GError) error = NULL; FpAutoCloseDevice *device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); - g_assert_true (fp_device_open_sync (device, NULL, NULL)); + if (!fp_device_open_sync (device, NULL, &error)) + g_error ("Could not open device: %s", error->message); return device; } @@ -45,6 +47,7 @@ auto_close_fake_device_new (void) static void auto_close_fake_device_free (FpAutoCloseDevice *device) { + g_autoptr(GError) error = NULL; FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); if (fake_dev->return_action_error) @@ -54,7 +57,8 @@ auto_close_fake_device_free (FpAutoCloseDevice *device) } if (fp_device_is_open (device)) - g_assert_true (fp_device_close_sync (device, NULL, NULL)); + if (!fp_device_close_sync (device, NULL, &error)) + g_error ("Could not close device: %s", error->message); g_object_unref (device); } @@ -1224,6 +1228,11 @@ test_driver_match_cb (FpDevice *device, } } +static void +fake_device_stub_verify (FpDevice *device) +{ +} + static void test_driver_verify (void) { @@ -1588,6 +1597,28 @@ fake_device_stub_identify (FpDevice *device) { } +static void +test_driver_identify_cb (FpDevice *device, + GAsyncResult *res, + gpointer user_data) +{ + MatchCbData *data = user_data; + gboolean r; + + g_assert (data->called == FALSE); + data->called = TRUE; + + r = fp_device_identify_finish (device, res, &data->match, &data->print, &data->error); + + if (r) + g_assert_no_error (data->error); + else + g_assert_nonnull (data->error); + + if (data->match) + g_assert_no_error (data->error); +} + static void test_driver_supports_identify (void) { @@ -1941,6 +1972,314 @@ test_driver_identify_report_no_callback (void) g_assert_false (match); } +static void +test_driver_identify_suspend_continues (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); + g_autoptr(MatchCbData) identify_data = g_new0 (MatchCbData, 1); + g_autoptr(GPtrArray) prints = NULL; + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(GError) error = NULL; + void (*orig_identify) (FpDevice *device); + FpiDeviceFake *fake_dev; + FpPrint *expected_matched; + + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + orig_identify = dev_class->identify; + dev_class->identify = fake_device_stub_identify; + + prints = make_fake_prints_gallery (device, 500); + expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499)); + fp_print_set_description (expected_matched, "fake-verified"); + + match_data->gallery = prints; + + fake_dev->ret_print = make_fake_print (device, NULL); + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + fp_device_identify (device, prints, NULL, + test_driver_match_cb, match_data, NULL, + (GAsyncReadyCallback) test_driver_identify_cb, identify_data); + + while (g_main_context_iteration (NULL, FALSE)) + continue; + + fake_dev->ret_suspend = NULL; + fp_device_suspend_sync (device, NULL, &error); + g_assert (fake_dev->last_called_function == dev_class->suspend); + g_assert_no_error (error); + + while (g_main_context_iteration (NULL, FALSE)) + continue; + + g_assert_false (match_data->called); + g_assert_false (identify_data->called); + + fake_dev->ret_resume = NULL; + fp_device_resume_sync (device, NULL, &error); + g_assert (fake_dev->last_called_function == dev_class->resume); + g_assert_no_error (error); + + orig_identify (device); + + /* This currently happens immediately (not ABI though) */ + g_assert_true (match_data->called); + g_assert (match_data->match == expected_matched); + + while (g_main_context_iteration (NULL, FALSE)) + continue; + + g_assert_true (identify_data->called); + g_assert (identify_data->match == expected_matched); + + g_assert (fake_dev->last_called_function == orig_identify); +} + +static void +test_driver_identify_suspend_succeeds (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); + g_autoptr(MatchCbData) identify_data = g_new0 (MatchCbData, 1); + g_autoptr(GPtrArray) prints = NULL; + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(GError) error = NULL; + void (*orig_identify) (FpDevice *device); + FpiDeviceFake *fake_dev; + FpPrint *expected_matched; + + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + orig_identify = dev_class->identify; + dev_class->identify = fake_device_stub_identify; + + prints = make_fake_prints_gallery (device, 500); + expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499)); + fp_print_set_description (expected_matched, "fake-verified"); + + match_data->gallery = prints; + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + fake_dev->ret_print = make_fake_print (device, NULL); + fp_device_identify (device, prints, NULL, + test_driver_match_cb, match_data, NULL, + (GAsyncReadyCallback) test_driver_identify_cb, identify_data); + + while (g_main_context_iteration (NULL, FALSE)) + continue; + + /* suspend_sync hangs until cancellation, so we need to trigger orig_identify + * from the mainloop after calling suspend_sync. + */ + fpi_device_add_timeout (device, 0, (FpTimeoutFunc) orig_identify, NULL, NULL); + + fake_dev->ret_suspend = fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED); + fp_device_suspend_sync (device, NULL, &error); + + /* At this point we are done with everything */ + g_assert (fake_dev->last_called_function == orig_identify); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + + /* We suspended, but device reported success and that will be reported. */ + g_assert_true (match_data->called); + g_assert (match_data->match == expected_matched); + g_assert_true (identify_data->called); + g_assert (identify_data->match == expected_matched); + + /* Resuming the device does not call resume handler, as the action was + * cancelled already. + */ + fake_dev->last_called_function = NULL; + fp_device_resume_sync (device, NULL, &error); + g_assert (fake_dev->last_called_function == NULL); + g_assert_no_error (error); +} + +static void +test_driver_identify_suspend_busy_error (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(MatchCbData) match_data = g_new0 (MatchCbData, 1); + g_autoptr(MatchCbData) identify_data = g_new0 (MatchCbData, 1); + g_autoptr(GPtrArray) prints = NULL; + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(GError) error = NULL; + void (*orig_identify) (FpDevice *device); + FpiDeviceFake *fake_dev; + FpPrint *expected_matched; + + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + orig_identify = dev_class->identify; + dev_class->identify = fake_device_stub_identify; + + prints = make_fake_prints_gallery (device, 500); + expected_matched = g_ptr_array_index (prints, g_random_int_range (0, 499)); + fp_print_set_description (expected_matched, "fake-verified"); + + match_data->gallery = prints; + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); + fake_dev->ret_print = make_fake_print (device, NULL); + fp_device_identify (device, prints, NULL, + test_driver_match_cb, match_data, NULL, + (GAsyncReadyCallback) test_driver_identify_cb, identify_data); + + while (g_main_context_iteration (NULL, FALSE)) + continue; + + /* suspend_sync hangs until cancellation, so we need to trigger orig_identify + * from the mainloop after calling suspend_sync. + */ + fpi_device_add_timeout (device, 0, (FpTimeoutFunc) orig_identify, NULL, NULL); + + fake_dev->ret_suspend = fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED); + fp_device_suspend_sync (device, NULL, &error); + fake_dev->ret_error = NULL; + + /* At this point we are done with everything */ + g_assert (fake_dev->last_called_function == orig_identify); + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + + /* The device reported an error, an this error will be overwritten. + */ + g_assert_false (match_data->called); + g_assert_true (identify_data->called); + g_assert_null (identify_data->match); + g_assert_error (identify_data->error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_BUSY); + + fake_dev->last_called_function = NULL; + fp_device_resume_sync (device, NULL, &error); + g_assert (fake_dev->last_called_function == NULL); + g_assert_no_error (error); +} + +static void +test_driver_identify_suspend_while_idle (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(GError) error = NULL; + FpiDeviceFake *fake_dev; + + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + + /* Suspending and resuming a closed device works */ + fp_device_suspend (device, NULL, (GAsyncReadyCallback) fp_device_suspend_finish, &error); + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert (fake_dev->last_called_function == NULL); + g_assert_no_error (error); + + fp_device_resume (device, NULL, (GAsyncReadyCallback) fp_device_resume_finish, NULL); + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert (fake_dev->last_called_function == NULL); + g_assert_no_error (error); + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + + fake_dev->last_called_function = NULL; + fp_device_suspend (device, NULL, (GAsyncReadyCallback) fp_device_suspend_finish, &error); + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert (fake_dev->last_called_function == NULL); + g_assert_no_error (error); + + fp_device_resume (device, NULL, (GAsyncReadyCallback) fp_device_resume_finish, NULL); + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert (fake_dev->last_called_function == NULL); + g_assert_no_error (error); +} + +static void +test_driver_identify_warmup_cooldown (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(MatchCbData) identify_data = g_new0 (MatchCbData, 1); + g_autoptr(GPtrArray) prints = NULL; + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(GError) error = NULL; + void (*orig_identify) (FpDevice *device); + FpiDeviceFake *fake_dev; + gint64 start_time; + + dev_class->temp_hot_seconds = 2; + dev_class->temp_cold_seconds = 5; + + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + orig_identify = dev_class->identify; + dev_class->identify = fake_device_stub_identify; + + prints = make_fake_prints_gallery (device, 500); + + g_assert_true (fp_device_open_sync (device, NULL, NULL)); + fake_dev->last_called_function = NULL; + + fake_dev->ret_error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); + + /* Undefined: Whether match_cb is called. */ + fp_device_identify (device, prints, NULL, + NULL, NULL, NULL, + (GAsyncReadyCallback) test_driver_identify_cb, identify_data); + + /* Identify is running, the temperature will change after only a short time. + * Changes are delayed by 100ms and we give 150ms of slack for the test. + */ + start_time = g_get_monotonic_time (); + g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_COLD); + while (fp_device_get_temperature (device) == FP_TEMPERATURE_COLD) + g_main_context_iteration (NULL, TRUE); + g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_WARM); + g_assert_false (g_cancellable_is_cancelled (fpi_device_get_cancellable (device))); + g_assert_cmpint (g_get_monotonic_time () - start_time, <, 0 + 250000); + + /* we reach hot 2 seconds later */ + while (fp_device_get_temperature (device) == FP_TEMPERATURE_WARM) + g_main_context_iteration (NULL, TRUE); + g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_HOT); + g_assert_true (g_cancellable_is_cancelled (fpi_device_get_cancellable (device))); + g_assert_cmpint (g_get_monotonic_time () - start_time, <, 2000000 + 250000); + + /* cancel vfunc will be called now */ + g_assert (fake_dev->last_called_function == NULL); + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert (fake_dev->last_called_function == dev_class->cancel); + + orig_identify (device); + fake_dev->ret_error = NULL; + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert_true (identify_data->called); + g_assert_error (identify_data->error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_TOO_HOT); + + /* Now, wait for it to cool down again; + * WARM should be reached after about 2s + * COLD after 5s but give it some more slack. */ + start_time = g_get_monotonic_time (); + while (fp_device_get_temperature (device) == FP_TEMPERATURE_HOT) + g_main_context_iteration (NULL, TRUE); + g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_WARM); + g_assert_cmpint (g_get_monotonic_time () - start_time, <, 2000000 + 250000); + + while (fp_device_get_temperature (device) == FP_TEMPERATURE_WARM) + g_main_context_iteration (NULL, TRUE); + g_assert_cmpint (fp_device_get_temperature (device), ==, FP_TEMPERATURE_COLD); + g_assert_cmpint (g_get_monotonic_time () - start_time, <, 5000000 + 500000); +} + static void fake_device_stub_capture (FpDevice *device) { @@ -2284,6 +2623,89 @@ test_driver_cancel_fail (void) g_assert_no_error (error); } +static void +test_driver_critical (void) +{ + g_autoptr(FpAutoCloseDevice) device = auto_close_fake_device_new (); + g_autoptr(GCancellable) cancellable = g_cancellable_new (); + g_autoptr(FpPrint) enrolled_print = make_fake_print_reffed (device, NULL); + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + void (*orig_verify) (FpDevice *device) = dev_class->verify; + FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); + + fake_dev->last_called_function = NULL; + + dev_class->verify = fake_device_stub_verify; + fp_device_verify (device, enrolled_print, cancellable, + NULL, NULL, NULL, + NULL, NULL); + + /* We started a verify operation, now emulate a "critical" section */ + fpi_device_critical_enter (device); + + /* Throw a suspend and external cancellation against it. */ + fp_device_suspend (device, NULL, NULL, NULL); + g_cancellable_cancel (cancellable); + + /* The only thing that happens is that the cancellable is cancelled */ + g_assert_true (fpi_device_action_is_cancelled (device)); + g_assert (fake_dev->last_called_function == NULL); + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert (fake_dev->last_called_function == NULL); + + /* Leaving and entering the critical section in the same mainloop iteration + * does not do anything. */ + fpi_device_critical_leave (device); + fpi_device_critical_enter (device); + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert (fake_dev->last_called_function == NULL); + + /* Leaving it and running the mainloop will first run the cancel handler */ + fpi_device_critical_leave (device); + while (g_main_context_iteration (NULL, FALSE) && !fake_dev->last_called_function) + continue; + g_assert (fake_dev->last_called_function == dev_class->cancel); + g_assert_true (fpi_device_action_is_cancelled (device)); + fake_dev->last_called_function = NULL; + + /* Then the suspend handler */ + while (g_main_context_iteration (NULL, FALSE) && !fake_dev->last_called_function) + continue; + g_assert (fake_dev->last_called_function == dev_class->suspend); + fake_dev->last_called_function = NULL; + + /* Nothing happens afterwards */ + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert (fake_dev->last_called_function == NULL); + + + /* Throw a resume at the system */ + fpi_device_critical_enter (device); + fp_device_resume (device, NULL, NULL, NULL); + + /* Nothing will happen, as the resume is delayed */ + while (g_main_context_iteration (NULL, FALSE)) + continue; + g_assert (fake_dev->last_called_function == NULL); + + /* Finally the resume is called from the mainloop after leaving the critical section */ + fpi_device_critical_leave (device); + g_assert (fake_dev->last_called_function == NULL); + while (g_main_context_iteration (NULL, FALSE) && !fake_dev->last_called_function) + continue; + g_assert (fake_dev->last_called_function == dev_class->resume); + fake_dev->last_called_function = NULL; + + + /* The "verify" operation is still ongoing, finish it. */ + orig_verify (device); + while (g_main_context_iteration (NULL, FALSE)) + continue; +} + static void test_driver_current_action (void) { @@ -2350,32 +2772,32 @@ test_driver_action_get_cancellable_open (void) } static void -test_driver_action_get_cancellable_open_fail_vfunc (FpDevice *device) +test_driver_action_get_cancellable_open_internal_vfunc (FpDevice *device) { FpiDeviceFake *fake_dev = FPI_DEVICE_FAKE (device); g_assert_cmpuint (fpi_device_get_current_action (device), ==, FPI_DEVICE_ACTION_OPEN); - fake_dev->last_called_function = test_driver_action_get_cancellable_open_fail_vfunc; + fake_dev->last_called_function = test_driver_action_get_cancellable_open_internal_vfunc; - g_assert_false (G_IS_CANCELLABLE (fpi_device_get_cancellable (device))); + g_assert_true (G_IS_CANCELLABLE (fpi_device_get_cancellable (device))); fpi_device_open_complete (device, NULL); } static void -test_driver_action_get_cancellable_open_fail (void) +test_driver_action_get_cancellable_open_internal (void) { g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); g_autoptr(FpAutoCloseDevice) device = NULL; FpiDeviceFake *fake_dev; - dev_class->open = test_driver_action_get_cancellable_open_fail_vfunc; + dev_class->open = test_driver_action_get_cancellable_open_internal_vfunc; device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); fake_dev = FPI_DEVICE_FAKE (device); g_assert_true (fp_device_open_sync (device, NULL, NULL)); - g_assert (fake_dev->last_called_function == test_driver_action_get_cancellable_open_fail_vfunc); + g_assert (fake_dev->last_called_function == test_driver_action_get_cancellable_open_internal_vfunc); } static void @@ -2400,7 +2822,11 @@ test_driver_action_is_cancelled_open_vfunc (FpDevice *device) g_assert_true (G_IS_CANCELLABLE (fpi_device_get_cancellable (device))); g_assert_false (fpi_device_action_is_cancelled (device)); - g_cancellable_cancel (fpi_device_get_cancellable (device)); + if (fake_dev->ext_cancellable) + g_cancellable_cancel (fake_dev->ext_cancellable); + else + g_cancellable_cancel (fpi_device_get_cancellable (device)); + g_assert_true (fpi_device_action_is_cancelled (device)); fpi_device_open_complete (device, NULL); @@ -2419,13 +2845,34 @@ test_driver_action_is_cancelled_open (void) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); fake_dev = FPI_DEVICE_FAKE (device); - cancellable = g_cancellable_new (); + cancellable = fake_dev->ext_cancellable = g_cancellable_new (); g_assert_false (fp_device_open_sync (device, cancellable, &error)); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_assert (fake_dev->last_called_function == test_driver_action_is_cancelled_open_vfunc); } +static void +test_driver_action_internally_cancelled_open (void) +{ + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(GCancellable) cancellable = NULL; + g_autoptr(GError) error = NULL; + FpiDeviceFake *fake_dev; + + dev_class->open = test_driver_action_is_cancelled_open_vfunc; + device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL); + fake_dev = FPI_DEVICE_FAKE (device); + + /* No error, just some internal cancellation but we let nothing happen externally. */ + cancellable = g_cancellable_new (); + g_assert_true (fp_device_open_sync (device, cancellable, &error)); + g_assert_null (error); + + g_assert (fake_dev->last_called_function == test_driver_action_is_cancelled_open_vfunc); +} + static void test_driver_action_is_cancelled_error (void) { @@ -2874,6 +3321,14 @@ main (int argc, char *argv[]) g_test_add_func ("/driver/identify/not_reported", test_driver_identify_not_reported); g_test_add_func ("/driver/identify/complete_retry", test_driver_identify_complete_retry); g_test_add_func ("/driver/identify/report_no_cb", test_driver_identify_report_no_callback); + + g_test_add_func ("/driver/identify/suspend_continues", test_driver_identify_suspend_continues); + g_test_add_func ("/driver/identify/suspend_succeeds", test_driver_identify_suspend_succeeds); + g_test_add_func ("/driver/identify/suspend_busy_error", test_driver_identify_suspend_busy_error); + g_test_add_func ("/driver/identify/suspend_while_idle", test_driver_identify_suspend_while_idle); + + g_test_add_func ("/driver/identify/warmup_cooldown", test_driver_identify_warmup_cooldown); + g_test_add_func ("/driver/capture", test_driver_capture); g_test_add_func ("/driver/capture/not_supported", test_driver_capture_not_supported); g_test_add_func ("/driver/capture/error", test_driver_capture_error); @@ -2887,12 +3342,15 @@ main (int argc, char *argv[]) g_test_add_func ("/driver/cancel", test_driver_cancel); g_test_add_func ("/driver/cancel/fail", test_driver_cancel_fail); + g_test_add_func ("/driver/critical", test_driver_critical); + g_test_add_func ("/driver/get_current_action", test_driver_current_action); g_test_add_func ("/driver/get_current_action/open", test_driver_current_action_open); g_test_add_func ("/driver/get_cancellable/error", test_driver_action_get_cancellable_error); g_test_add_func ("/driver/get_cancellable/open", test_driver_action_get_cancellable_open); - g_test_add_func ("/driver/get_cancellable/open/fail", test_driver_action_get_cancellable_open_fail); + g_test_add_func ("/driver/get_cancellable/open/internal", test_driver_action_get_cancellable_open_internal); g_test_add_func ("/driver/action_is_cancelled/open", test_driver_action_is_cancelled_open); + g_test_add_func ("/driver/action_is_cancelled/open/internal", test_driver_action_internally_cancelled_open); g_test_add_func ("/driver/action_is_cancelled/error", test_driver_action_is_cancelled_error); g_test_add_func ("/driver/complete_action/all/error", test_driver_complete_actions_errors); g_test_add_func ("/driver/action_error/error", test_driver_action_error_error); diff --git a/tests/vfs0050/device b/tests/vfs0050/device index b66be4bacfc48ac25fb6eed6e7508344bc5aec53..a62692a015c7c986e0f4dc1d0fb5f62addeaa611 100644 --- a/tests/vfs0050/device +++ b/tests/vfs0050/device @@ -81,3 +81,145 @@ A: speed=12 A: tx_lanes=1 A: urbnum=8 A: version= 1.10 + +P: /devices/pci0000:00/0000:00:14.0/usb1 +N: bus/usb/001/001=12010002090001406B1D020011050302010109021900010100E0000904000001090000000705810304000C +E: DEVNAME=/dev/bus/usb/001/001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=1d6b/2/511 +E: TYPE=9/0/1 +E: BUSNUM=001 +E: DEVNUM=001 +E: MAJOR=189 +E: MINOR=0 +E: SUBSYSTEM=usb +E: ID_VENDOR=Linux_5.11.2-arch1-1_xhci-hcd +E: ID_VENDOR_ENC=Linux\x205.11.2-arch1-1\x20xhci-hcd +E: ID_VENDOR_ID=1d6b +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_ID=0002 +E: ID_REVISION=0511 +E: ID_SERIAL=Linux_5.11.2-arch1-1_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_SERIAL_SHORT=0000:00:14.0 +E: ID_BUS=usb +E: ID_USB_INTERFACES=:090000: +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_AUTOSUSPEND=1 +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_PATH=pci-0000:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_FOR_SEAT=usb-pci-0000_00_14_0 +E: TAGS=:seat: +E: CURRENT_TAGS=:seat: +A: authorized=1\n +A: authorized_default=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=09\n +A: bDeviceProtocol=01\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=0mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0511\n +A: bmAttributes=e0\n +A: busnum=1\n +A: configuration= +H: descriptors=12010002090001406B1D020011050302010109021900010100E0000904000001090000000705810304000C +A: dev=189:0\n +A: devnum=1\n +A: devpath=0\n +L: driver=../../../../bus/usb/drivers/usb +L: firmware_node=../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4c/device:4d +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 5.11.2-arch1-1 xhci-hcd\n +A: maxchild=16\n +A: power/active_duration=3289930\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=34389654\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_time=3289845\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=31099805\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=xHCI Host Controller\n +A: quirks=0x0\n +A: removable=unknown\n +A: rx_lanes=1\n +A: serial=0000:00:14.0\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=2355\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0 +E: DRIVER=xhci_hcd +E: PCI_CLASS=C0330 +E: PCI_ID=8086:A12F +E: PCI_SUBSYS_ID=1028:07BE +E: PCI_SLOT_NAME=0000:00:14.0 +E: MODALIAS=pci:v00008086d0000A12Fsv00001028sd000007BEbc0Csc03i30 +E: SUBSYSTEM=pci +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_VENDOR_FROM_DATABASE=Intel Corporation +E: ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=86802FA1060490023130030C000080000400D1ED0000000000000000000000000000000000000000000000002810BE07000000007000000000000000FF010000 +A: consistent_dma_mask_bits=64\n +A: d3cold_allowed=1\n +A: device=0xa12f\n +A: dma_mask_bits=64\n +L: driver=../../../bus/pci/drivers/xhci_hcd +A: driver_override=(null)\n +A: enable=1\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4c +L: iommu=../../virtual/iommu/dmar1 +L: iommu_group=../../../kernel/iommu_groups/4 +A: irq=143\n +A: local_cpulist=0-7\n +A: local_cpus=ff\n +A: modalias=pci:v00008086d0000A12Fsv00001028sd000007BEbc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/143=msi\n +A: numa_node=-1\n +A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 7 10 2112 10\nxHCI ring segments 30 38 4096 38\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\n +A: power/control=on\n +A: power/runtime_active_time=34390988\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/wakeup=enabled\n +A: power/wakeup_abort_count=0\n +A: power/wakeup_active=0\n +A: power/wakeup_active_count=0\n +A: power/wakeup_count=0\n +A: power/wakeup_expire_count=0\n +A: power/wakeup_last_time_ms=0\n +A: power/wakeup_max_time_ms=0\n +A: power/wakeup_total_time_ms=0\n +A: power_state=D0\n +A: resource=0x00000000edd10000 0x00000000edd1ffff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n +A: revision=0x31\n +A: subsystem_device=0x07be\n +A: subsystem_vendor=0x1028\n +A: vendor=0x8086\n + diff --git a/tests/vfs5011/device b/tests/vfs5011/device index e39a74faf09b50527108ebd2120c9578efe6639a..7e660ba170770ab577b874dbd9fb6b975f2042c6 100644 --- a/tests/vfs5011/device +++ b/tests/vfs5011/device @@ -78,3 +78,145 @@ A: speed=12 A: tx_lanes=1 A: urbnum=7 A: version= 1.10 + +P: /devices/pci0000:00/0000:00:14.0/usb2 +N: bus/usb/002/001=12010002090001406B1D020011050302010109021900010100E0000904000001090000000705810304000C +E: DEVNAME=/dev/bus/usb/002/001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=1d6b/2/511 +E: TYPE=9/0/1 +E: BUSNUM=002 +E: DEVNUM=001 +E: MAJOR=189 +E: MINOR=0 +E: SUBSYSTEM=usb +E: ID_VENDOR=Linux_5.11.2-arch1-1_xhci-hcd +E: ID_VENDOR_ENC=Linux\x205.11.2-arch1-1\x20xhci-hcd +E: ID_VENDOR_ID=1d6b +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_ID=0002 +E: ID_REVISION=0511 +E: ID_SERIAL=Linux_5.11.2-arch1-1_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_SERIAL_SHORT=0000:00:14.0 +E: ID_BUS=usb +E: ID_USB_INTERFACES=:090000: +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_AUTOSUSPEND=1 +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_PATH=pci-0000:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_FOR_SEAT=usb-pci-0000_00_14_0 +E: TAGS=:seat: +E: CURRENT_TAGS=:seat: +A: authorized=1\n +A: authorized_default=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=09\n +A: bDeviceProtocol=01\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=0mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0511\n +A: bmAttributes=e0\n +A: busnum=1\n +A: configuration= +H: descriptors=12010002090001406B1D020011050302010109021900010100E0000904000001090000000705810304000C +A: dev=189:0\n +A: devnum=1\n +A: devpath=0\n +L: driver=../../../../bus/usb/drivers/usb +L: firmware_node=../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4c/device:4d +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 5.11.2-arch1-1 xhci-hcd\n +A: maxchild=16\n +A: power/active_duration=3289930\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=34389654\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_time=3289845\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=31099805\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=xHCI Host Controller\n +A: quirks=0x0\n +A: removable=unknown\n +A: rx_lanes=1\n +A: serial=0000:00:14.0\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=2355\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0 +E: DRIVER=xhci_hcd +E: PCI_CLASS=C0330 +E: PCI_ID=8086:A12F +E: PCI_SUBSYS_ID=1028:07BE +E: PCI_SLOT_NAME=0000:00:14.0 +E: MODALIAS=pci:v00008086d0000A12Fsv00001028sd000007BEbc0Csc03i30 +E: SUBSYSTEM=pci +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_VENDOR_FROM_DATABASE=Intel Corporation +E: ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=86802FA1060490023130030C000080000400D1ED0000000000000000000000000000000000000000000000002810BE07000000007000000000000000FF010000 +A: consistent_dma_mask_bits=64\n +A: d3cold_allowed=1\n +A: device=0xa12f\n +A: dma_mask_bits=64\n +L: driver=../../../bus/pci/drivers/xhci_hcd +A: driver_override=(null)\n +A: enable=1\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4c +L: iommu=../../virtual/iommu/dmar1 +L: iommu_group=../../../kernel/iommu_groups/4 +A: irq=143\n +A: local_cpulist=0-7\n +A: local_cpus=ff\n +A: modalias=pci:v00008086d0000A12Fsv00001028sd000007BEbc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/143=msi\n +A: numa_node=-1\n +A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 7 10 2112 10\nxHCI ring segments 30 38 4096 38\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\n +A: power/control=on\n +A: power/runtime_active_time=34390988\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/wakeup=enabled\n +A: power/wakeup_abort_count=0\n +A: power/wakeup_active=0\n +A: power/wakeup_active_count=0\n +A: power/wakeup_count=0\n +A: power/wakeup_expire_count=0\n +A: power/wakeup_last_time_ms=0\n +A: power/wakeup_max_time_ms=0\n +A: power/wakeup_total_time_ms=0\n +A: power_state=D0\n +A: resource=0x00000000edd10000 0x00000000edd1ffff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n +A: revision=0x31\n +A: subsystem_device=0x07be\n +A: subsystem_vendor=0x1028\n +A: vendor=0x8086\n +