From 3d5e73b49d441d2f8f88d9686eba760a683ef320 Mon Sep 17 00:00:00 2001 From: mikee512 Date: Sun, 25 May 2025 14:52:05 -0400 Subject: [PATCH] Add clear before verify and make state machines callable --- libfprint/drivers/crfpmoc/crfpmoc.c | 596 +++++++++++++++++++++++----- libfprint/drivers/crfpmoc/crfpmoc.h | 55 ++- tests/crfpmoc/custom.ioctl | 10 +- tests/crfpmoc/device | 76 ++-- 4 files changed, 589 insertions(+), 148 deletions(-) diff --git a/libfprint/drivers/crfpmoc/crfpmoc.c b/libfprint/drivers/crfpmoc/crfpmoc.c index a16564de..b6285dd0 100644 --- a/libfprint/drivers/crfpmoc/crfpmoc.c +++ b/libfprint/drivers/crfpmoc/crfpmoc.c @@ -35,16 +35,37 @@ struct _FpiDeviceCrfpMoc GCancellable *interrupt_cancellable; int fd; int emul_fd; - int poll_source; }; G_DEFINE_TYPE (FpiDeviceCrfpMoc, fpi_device_crfpmoc, FP_TYPE_DEVICE) -/* Data for the enroll state machine */ +/* State machine private data. All state machines get this and it is + * cleared/freed at the completion of the state machine. + */ +struct crfpmoc_ssm_data +{ + FpiSsm *ssm; + FpiDeviceCrfpMoc *self; + int timeout; + int timeout_state; + int poll; +}; + +/* Data for the enroll state machine + */ struct crfpmoc_enroll_data { - FpPrint *print; /* print template to be completed and returned */ - int stage; /* stage of enrollment */ + struct crfpmoc_ssm_data parent; + FpPrint *print; /* print template to be completed and returned */ + int stage; /* stage of enrollment */ +}; + +/* Data for the verify state machine + */ +struct crfpmoc_verify_data +{ + struct crfpmoc_ssm_data parent; + FpPrint *match; /* print matched */ }; static const FpIdEntry crfpmoc_id_table[] = { @@ -53,12 +74,69 @@ static const FpIdEntry crfpmoc_id_table[] = { }; /* Define the variant data format and offsets for FpPrint */ -#define CRFPMOC_PRINT_DATA_VARIANT_FMT "(@ay@ayii)" +#define CRFPMOC_PRINT_DATA_VARIANT_FMT "(@ay@ayqq)" #define CRFPMOC_PRINT_DATA_VARIANT_PRINT_ID 0 #define CRFPMOC_PRINT_DATA_VARIANT_TEMPLATE 1 #define CRFPMOC_PRINT_DATA_VARIANT_MAX_OUTSIZE 2 #define CRFPMOC_PRINT_DATA_VARIANT_MAX_TEMPLATES 3 +/* Clear and free the state machine private data + */ +static void +crfpmoc_ssm_data_free (void *arg) +{ + struct crfpmoc_ssm_data *data = (struct crfpmoc_ssm_data *) arg; + + /* Cancel any poll source */ + if (data->poll) + { + fp_dbg ("crfpmoc_ssm_data_free: remove poll source %d", data->poll); + g_source_remove (data->poll); + } + + /* Cancel any timeout source */ + if (data->timeout) + { + fp_dbg ("crfpmoc_ssm_data_free: remove timeout source %d", data->timeout); + g_source_remove (data->timeout); + } + + g_free (data); +} + +/* Wrapper to start a sub-state machine after adding the private data. The data + * can be supplied and is required to be crfpmoc_ssm_data or contain it as its + * first member. + */ +static void +crfpmoc_ssm_start_subsm (FpiSsm *parent, FpiSsm *ssm, FpiDeviceCrfpMoc *self, + struct crfpmoc_ssm_data *data) +{ + if (data == NULL) + data = g_malloc0 (sizeof (*data)); + data->self = self; + data->ssm = ssm; + fpi_ssm_set_data (ssm, data, crfpmoc_ssm_data_free); + fpi_ssm_start_subsm (parent, ssm); +} + +/* Wrapper to start a state machine after adding the private data. The data + * can be supplied and is required to be crfpmoc_ssm_data or contain it as its + * first member. + */ +static void +crfpmoc_ssm_start (FpiSsm *ssm, FpiDeviceCrfpMoc *self, + struct crfpmoc_ssm_data *data, + FpiSsmCompletedCallback cb) +{ + if (data == NULL) + data = g_malloc0 (sizeof (*data)); + data->self = self; + data->ssm = ssm; + fpi_ssm_set_data (ssm, data, crfpmoc_ssm_data_free); + fpi_ssm_start (ssm, cb); +} + static const gchar *const crfpmoc_meanings[] = { "SUCCESS", "INVALID_COMMAND", @@ -222,7 +300,7 @@ crfpmoc_get_print_data (FpPrint *print, guint8 **template, CRFPMOC_PRINT_DATA_VARIANT_MAX_OUTSIZE); if (variant_ec_max) { - *ec_max_outsize = g_variant_get_int32 (variant_ec_max); + *ec_max_outsize = g_variant_get_uint16 (variant_ec_max); fp_dbg ("Extracted ec_max_outsize: %d", *ec_max_outsize); } else @@ -238,7 +316,7 @@ crfpmoc_get_print_data (FpPrint *print, guint8 **template, CRFPMOC_PRINT_DATA_VARIANT_MAX_TEMPLATES); if (variant_max_templates) { - *max_templates = g_variant_get_int32 (variant_max_templates); + *max_templates = g_variant_get_uint16 (variant_max_templates); fp_dbg ("Extracted max_templates: %d", *max_templates); } else @@ -267,6 +345,11 @@ crfpmoc_get_print_data (FpPrint *print, guint8 **template, } } +/* Self test uses umockdev for replay of a captured sequence. At this + * time umockdev supports cros_ec only for replay. We need to create + * the sequence. Record all the ioctls for replay in the format + * umockdev requires. + */ static void crfpmoc_umockdev_record (FpiDeviceCrfpMoc *self, int res, int cmd, void *arg) @@ -289,11 +372,29 @@ crfpmoc_umockdev_record (FpiDeviceCrfpMoc *self, int res, len = snprintf (buffer, bufsiz, "CROS_EC_DEV_IOCXCMD_V2 %u ", s_cmd->insize); g_assert (len < 48); - while (size > 0) + + /* Send the command */ + while (size > s_cmd->insize) { len += snprintf (&buffer[len], bufsiz - len, "%02X", *ptr++); size--; } + + /* Send the data. If this is a frame suppress the actual data for privacy */ + if (s_cmd->command == CRFPMOC_EC_CMD_FP_FRAME) + { + memset (&buffer[len], '0', size * 2); + len += size * 2; + size = 0; + } + else + { + while (size > 0) + { + len += snprintf (&buffer[len], bufsiz - len, "%02X", *ptr++); + size--; + } + } g_assert (len < bufsiz); buffer[len++] = '\n'; if (write (self->emul_fd, buffer, len) != len) @@ -363,19 +464,35 @@ crfpmoc_ec_command (FpiDeviceCrfpMoc *self, int command, return r; } +/* Completion callback for event reporting. Get the event and decode + * for now. The function may be used with timeout support and if so + * the timeout needs to be cancelled here. The state machine that + * requested the poll is passed as an argument. Use the private data + * to see if a timeout is active. + */ static gboolean crfpmoc_read_bytes (gint fd, GIOCondition condition, gpointer user_data) { - FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (user_data); + // FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (user_data); + FpiSsm *ssm = (FpiSsm *) user_data; + struct crfpmoc_ssm_data *data = fpi_ssm_get_data (ssm); int rv; gint event; gboolean has_more; struct crfpmoc_ec_response_get_next_event_v1 buffer = {0}; fp_dbg ("crfpmoc_read_bytes: called"); - if (fd != self->fd) - goto one_shot_out; + // if (fd != self->fd) + // goto one_shot_out; + + /* If there is an active timeout on this state machine remove it */ + if (data->timeout) + { + fp_dbg ("crfpmoc_read_bytes: remove timeout source %d", data->timeout); + g_source_remove (data->timeout); + data->timeout = 0; + } rv = read (fd, &buffer, sizeof (buffer)); fp_dbg ("crfpmoc_read_bytes: read %d bytes", rv); @@ -383,14 +500,14 @@ crfpmoc_read_bytes (gint fd, GIOCondition condition, if (rv == 0) { fp_warn ("Timeout waiting for MKBP event"); - fpi_ssm_mark_failed (self->task_ssm, + fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); goto one_shot_out; } else if (rv < 0) { fp_warn ("Error polling for MKBP event"); - fpi_ssm_mark_failed (self->task_ssm, + fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); goto one_shot_out; } @@ -404,6 +521,9 @@ crfpmoc_read_bytes (gint fd, GIOCondition condition, { case CRFPMOC_EC_MKBP_EVENT_FINGERPRINT: fp_dbg ("crfpmoc_read_bytes: fingerprint 0x%08x", buffer.data.fp_events); + + /* Enroll mode + */ if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_ENROLL) { guint error = CRFPMOC_EC_MKBP_FP_ERRCODE (buffer.data.fp_events); @@ -431,6 +551,9 @@ crfpmoc_read_bytes (gint fd, GIOCondition condition, } fp_dbg (" status: %d (%s)", error, reason ? reason : ""); } + + /* Match mode + */ if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_MATCH) { guint error = CRFPMOC_EC_MKBP_FP_ERRCODE (buffer.data.fp_events); @@ -478,33 +601,47 @@ crfpmoc_read_bytes (gint fd, GIOCondition condition, } fp_dbg (" status: %d (%s)", error, reason ? reason : ""); } + + /* Finger down mode + */ if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_FINGER_DOWN) fp_dbg (" Finger down"); + + /* Finger up mode + */ if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_FINGER_UP) fp_dbg (" Finger up"); + + /* ?? + */ if (buffer.data.fp_events & CRFPMOC_EC_MKBP_FP_IMAGE_READY) fp_dbg (" Image ready"); break; } - fpi_ssm_next_state (self->task_ssm); + /* Make sure we do not run in the callback context */ + fpi_ssm_next_state_delayed (ssm, 1); one_shot_out: /* The call of this function with FALSE return will complete * the poll source. No need to remove it on close or cancel. */ - self->poll_source = 0; + data->poll = 0; + fp_dbg ("crfpmoc_read_bytes: return FALSE"); return FALSE; #ifdef LATER rearm_out: /* Keep the poll source active */ + fp_dbg ("crfpmoc_read_bytes: return TRUE"); return TRUE; #endif } static void -crfpmoc_ec_pollevent (FpiDeviceCrfpMoc *self, unsigned long mask) +crfpmoc_ec_pollevent (FpiSsm *ssm, unsigned long mask) { + struct crfpmoc_ssm_data *data = fpi_ssm_get_data (ssm); + FpiDeviceCrfpMoc *self = data->self; int rv; rv = ioctl (self->fd, CRFPMOC_CROS_EC_DEV_IOCEVENTMASK_V2, mask); @@ -515,8 +652,8 @@ crfpmoc_ec_pollevent (FpiDeviceCrfpMoc *self, unsigned long mask) return; } - self->poll_source = g_unix_fd_add (self->fd, G_IO_IN, crfpmoc_read_bytes, self); - fp_dbg ("crfpmoc_ec_pollevent: created poll source %d", self->poll_source); + data->poll = g_unix_fd_add (self->fd, G_IO_IN, crfpmoc_read_bytes, ssm); + fp_dbg ("crfpmoc_ec_pollevent: created poll source %d", data->poll); } static gboolean @@ -569,6 +706,8 @@ crfpmoc_cmd_fp_seed (FpiDeviceCrfpMoc *self, const char *seed, return TRUE; } +// TODO: should this be converted to a sub state machine instead of +// the sync completion ? static gboolean crfpmoc_fp_set_context (FpiDeviceCrfpMoc *self, const char *context, GError **error) @@ -795,9 +934,28 @@ crfmoc_cmd_fp_ensure_seed (FpiDeviceCrfpMoc *self, return TRUE; } +static void +crfpmoc_show_proto_info (struct crfpmoc_ec_response_protocol_get_info *protocol_info) +{ + gsize len = 0; + gsize buflen = 256; + g_autofree gchar *buffer = g_malloc (buflen); + + for (int i = 0; i < 32; i++) + if (protocol_info->protocol_versions & (1ULL << i)) + len += snprintf (&buffer[len], buflen - len, "%s%d", len == 0 ? "" : ",", i); + + buffer[buflen - 1] = '\0'; + fp_dbg ("crfpmoc_ec_max_outsize: vers %s, max_out %d, max_in %d, flags 0x%08x", + buffer, + protocol_info->max_request_packet_size, + protocol_info->max_response_packet_size, + protocol_info->flags); +} + static gboolean crfpmoc_ec_max_insize (FpiDeviceCrfpMoc *self, - guint32 *max_insize, GError **error) + guint16 *max_insize, GError **error) { struct crfpmoc_ec_response_protocol_get_info protocol_info; gboolean rv; @@ -807,6 +965,7 @@ crfpmoc_ec_max_insize (FpiDeviceCrfpMoc *self, if (rv != EC_RES_SUCCESS) return FALSE; + crfpmoc_show_proto_info (&protocol_info); *max_insize = protocol_info.max_response_packet_size - sizeof (struct crfpmoc_ec_host_response); @@ -825,6 +984,7 @@ crfpmoc_ec_max_outsize (FpiDeviceCrfpMoc *self, if (rv != EC_RES_SUCCESS) return FALSE; + crfpmoc_show_proto_info (&protocol_info); *max_outsize = protocol_info.max_request_packet_size - sizeof (struct crfpmoc_ec_host_request); @@ -869,7 +1029,7 @@ crfpmoc_fp_download_template (FpiDeviceCrfpMoc *self, gsize rsize = sizeof (*info); const gint max_attempts = 3; gint num_attempts; - guint32 ec_max_insize; + guint16 ec_max_insize; gint template_idx = index + CRFPMOC_FP_FRAME_INDEX_TEMPLATE; guint16 sum = 0; @@ -1003,13 +1163,15 @@ crfpmoc_fp_upload_template (FpiDeviceCrfpMoc *self, } static void -crfpmoc_cmd_wait_event_fingerprint (FpiDeviceCrfpMoc *self) +crfpmoc_cmd_wait_event_fingerprint (FpiSsm *ssm) { long event_type = CRFPMOC_EC_MKBP_EVENT_FINGERPRINT; - crfpmoc_ec_pollevent (self, 1 << event_type); + crfpmoc_ec_pollevent (ssm, 1 << event_type); } +/* Generic task completion callback to report any errors + */ static void crfpmoc_task_ssm_done (FpiSsm *ssm, FpDevice *device, GError *error) @@ -1042,6 +1204,7 @@ crfpmoc_open (FpDevice *device) { g_set_error (&err, G_IO_ERROR, g_io_error_from_errno (errno), "unable to open misc device"); + fp_dbg ("crfpmoc_open: open_complete"); fpi_device_open_complete (device, err); return; } @@ -1054,6 +1217,17 @@ crfpmoc_open (FpDevice *device) if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) self->emul_fd = creat ("/tmp/crfpmoc.ioctl", 0777); + /* Just for debug messages */ + guint16 ec_max_outsize; + if (!crfpmoc_ec_max_outsize (self, &ec_max_outsize, &err)) + fp_err ("Failed to get max outsize"); + + /* Just for debug messages */ + guint16 ec_max_insize; + if (!crfpmoc_ec_max_insize (self, &ec_max_insize, &err)) + fp_err ("Failed to get max insize"); + + fp_dbg ("crfpmoc_open: open_complete"); fpi_device_open_complete (device, NULL); } @@ -1079,14 +1253,6 @@ crfpmoc_cancel (FpDevice *device) fp_dbg ("Cancel"); FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device); - /* Cancel any poll source */ - if (self->poll_source) - { - fp_dbg ("crfpmoc_cancel: remove poll source %d", self->poll_source); - g_source_remove (self->poll_source); - self->poll_source = 0; - } - /* Cancel the running state machine, if any */ if (self->task_ssm != NULL) fpi_ssm_mark_failed (self->task_ssm, @@ -1107,6 +1273,7 @@ crfpmoc_suspend (FpDevice *device) crfpmoc_cancel (device); g_cancellable_cancel (fpi_device_get_cancellable (device)); + fp_dbg ("crfpmoc_suspend: suspend_complete"); fpi_device_suspend_complete (device, NULL); } @@ -1129,6 +1296,7 @@ crfpmoc_close (FpDevice *device) close (self->emul_fd); self->emul_fd = -1; } + fp_dbg ("crfpmoc_close: close_complete"); fpi_device_close_complete (device, NULL); } @@ -1152,8 +1320,68 @@ static void handle_enroll_wait_enroll_complete (FpDevice *device, FpiDeviceCrfpMoc *self) { - fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED); - crfpmoc_cmd_wait_event_fingerprint (self); + + /* Sensor awaits the finger down + */ + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + crfpmoc_cmd_wait_event_fingerprint (self->task_ssm); +} + +static gint +crfpmoc_ssm_timeout (gpointer user_data) +{ + struct crfpmoc_ssm_data *data = (struct crfpmoc_ssm_data *) user_data; + + fp_dbg ("crfpmoc_ssm_timeout: timeout %d, move to state %d", + data->timeout, data->timeout_state); + + /* Timeout should be cancelled, make sure cleanup does not see it */ + data->timeout = 0; + + /* If there is an active poller on this state machine remove it */ + if (data->poll) + { + fp_dbg ("crfpmoc_ssm_timeout: remove poll source %d", data->poll); + g_source_remove (data->poll); + data->poll = 0; + } + + /* Can't call the state machine from here, make sure we exit first */ + fpi_ssm_jump_to_state_delayed (data->ssm, data->timeout_state, 1); + return FALSE; +} + +static void +handle_xxx_wait_finger_up (FpDevice *device, + FpiDeviceCrfpMoc *self, + FpiSsm *ssm, + int state, + int timeout) +{ + GError *error = NULL; + guint32 mode; + struct crfpmoc_ssm_data *data = fpi_ssm_get_data (ssm); + + data->timeout_state = state; + data->timeout = g_timeout_add (timeout, crfpmoc_ssm_timeout, data); + + fp_dbg ("handle_xxx_wait_finger_up: start, added timeout %d", data->timeout); + + /* Tell the upper layers the used should remove finger. This does + * not currently seem to do much. To actually get the upper layers + * to prompt the user it seems a retry error needs to be returned + * in a report. + */ + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NONE, + FP_FINGER_STATUS_NEEDED); + + if (!crfpmoc_cmd_fp_mode (self, CRFPMOC_FP_MODE_FINGER_UP, &mode, &error)) + fpi_ssm_mark_failed (self->task_ssm, error); + + crfpmoc_cmd_wait_event_fingerprint (ssm); } static void @@ -1180,7 +1408,9 @@ handle_enroll_sensor_check (FpiSsm *ssm, FpDevice *device, } else { - fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NONE); enroll_data->stage++; fp_info ("Partial capture successful (%d/%d).", enroll_data->stage, @@ -1193,13 +1423,17 @@ handle_enroll_sensor_check (FpiSsm *ssm, FpDevice *device, } else if (mode == 0) { - fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NONE); fpi_ssm_next_state (ssm); } else { - fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NONE); fpi_device_enroll_progress (device, enroll_data->stage, NULL, fpi_device_retry_new_msg (FP_DEVICE_RETRY_GENERAL, @@ -1263,7 +1497,7 @@ handle_enroll_commit (FpiSsm *ssm, FpDevice *device, info.template_max); } - fp_info ("Enrollment was successful!"); + fp_info ("handle_enroll_commit: enroll_complete"); fpi_device_enroll_complete (device, g_object_ref (enroll_data->print), NULL); fpi_ssm_mark_completed (ssm); @@ -1274,25 +1508,40 @@ crfpmoc_enroll_run_state (FpiSsm *ssm, FpDevice *device) { FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device); struct crfpmoc_enroll_data *enroll_data = fpi_ssm_get_data (ssm); + int state = fpi_ssm_get_cur_state (ssm); - switch (fpi_ssm_get_cur_state (ssm)) + switch (state) { - case ENROLL_SENSOR_ENROLL: + case ENROLL_SENSOR_ENROLL: /* State 0 */ handle_enroll_sensor_enroll (ssm, self); break; - case ENROLL_WAIT_ENROLL_COMPLETE: + case ENROLL_WAIT_ENROLL_COMPLETE: /* State 1 */ handle_enroll_wait_enroll_complete (device, self); break; - case ENROLL_SENSOR_CHECK: +#if 0 + /* During self test recording following an enroll with a verify + * without lifting the finger seems to randomly fail. This + * solves the issue but leaves the default enroll screen waiting + * for the finger up with no user prompt. It will only complete + * when the finger is removed. Seems to be a poor user interaction. + */ + case ENROLL_WAIT_FINGER_UP: /* State 2 */ + handle_xxx_wait_finger_up (device, self, ssm); + break; +#endif + + case ENROLL_SENSOR_CHECK: /* State 3 */ handle_enroll_sensor_check (ssm, device, self, enroll_data); break; - case ENROLL_COMMIT: + case ENROLL_COMMIT: /* State 4 */ handle_enroll_commit (ssm, device, self, enroll_data); break; } + fp_dbg ("crfpmoc_enroll_run_state: state %d return", + state); } static void @@ -1306,9 +1555,9 @@ crfpmoc_enroll (FpDevice *device) g_malloc0 (sizeof (struct crfpmoc_enroll_data)); r = crfpmoc_set_keys (self, &error); - if (!r) { + fp_dbg ("crfpmoc_enroll: enroll_complete"); fpi_device_enroll_complete (device, NULL, error); return; } @@ -1319,8 +1568,9 @@ crfpmoc_enroll (FpDevice *device) g_assert (self->task_ssm == NULL); self->task_ssm = fpi_ssm_new (device, crfpmoc_enroll_run_state, ENROLL_STATES); - fpi_ssm_set_data (self->task_ssm, enroll_data, g_free); - fpi_ssm_start (self->task_ssm, crfpmoc_task_ssm_done); + crfpmoc_ssm_start (self->task_ssm, self, + (struct crfpmoc_ssm_data *) enroll_data, + crfpmoc_task_ssm_done); } static void @@ -1439,8 +1689,13 @@ static void handle_verify_wait_match_complete (FpDevice *device, FpiDeviceCrfpMoc *self) { - fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED); - crfpmoc_cmd_wait_event_fingerprint (self); + + /* Sensor awaits the finger down + */ + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + crfpmoc_cmd_wait_event_fingerprint (self->task_ssm); } static void @@ -1464,12 +1719,16 @@ handle_verify_sensor_check (FpiSsm *ssm, FpDevice *device, } else if (mode == 0) { - fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NONE); fpi_ssm_next_state (ssm); } else { - fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NONE); fpi_ssm_mark_failed (ssm, fpi_device_retry_new_msg (FP_DEVICE_RETRY_GENERAL, "FP mode: (0x%x)", mode)); @@ -1477,6 +1736,47 @@ handle_verify_sensor_check (FpiSsm *ssm, FpDevice *device, } } +/* Verify completion + * report (only retry errors can be reported) + * FPI_MATCH_SUCCESS + * error must be NULL + * print same as suppplied + * FPI_MATCH_FAIL + * error must be NULL + * print must be NULL + * FPI_MATCH_ERROR (retry) + * error must be valid + * print must be NULL + * complete + * FPI_MATCH_SUCCESS + * FPI_MATCH_FAIL + * error must be NULL + * FPI_MATCH_ERROR + * error must be same as report for retry, otherwise no report + * and non retry error + * + * Identify completion + * report (only retry errors can be reported) + * Successful match + * match from gallery that matched + * print from gallery that matched (we have no storage so cant + * match anything else) + * error must be NULL + * No match + * match must be NULL + * print must be NULL (we have no ability to return a print without enroll) + * error must be NULL + * Error + * match must be NULL + * print must be NULL (we have no ability to return a print without enroll) + * complete + * Successful match + * No match + * error must be NULL + * Error + * error must be same as report for retry, otherwise no report + * and non retry error + */ static void handle_verify_check (FpiSsm *ssm, FpDevice *device, FpiDeviceCrfpMoc *self) @@ -1529,6 +1829,8 @@ handle_verify_check (FpiSsm *ssm, FpDevice *device, } else { + // This should not occur as we can only match prints + // from the gallery fp_warn ("Template index %d is out of range", template_idx); fpi_device_identify_report (device, NULL, NULL, NULL); } @@ -1541,9 +1843,15 @@ handle_verify_check (FpiSsm *ssm, FpDevice *device, } if (is_identify) - fpi_device_identify_complete (device, NULL); + { + fp_dbg ("handle_verify_check: identify_complete"); + fpi_device_identify_complete (device, NULL); + } else - fpi_device_verify_complete (device, NULL); + { + fp_dbg ("handle_verify_check: verify_complete"); + fpi_device_verify_complete (device, NULL); + } fpi_ssm_mark_completed (ssm); } @@ -1564,36 +1872,42 @@ crfpmoc_cmd_wait_for_it (FpiSsm *ssm, FpDevice *device) } static void -crfpmoc_verify_run_state (FpiSsm *ssm, FpDevice *device) +crfpmoc_finger_up_run_state (FpiSsm *ssm, FpDevice *device) { FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device); + int state = fpi_ssm_get_cur_state (ssm); - switch (fpi_ssm_get_cur_state (ssm)) + switch (state) { - case VERIFY_UPLOAD_TEMPLATE: - handle_verify_upload_template (ssm, device, self); + case FINGER_UP_START: /* State 0 */ + handle_xxx_wait_finger_up (device, self, ssm, + FINGER_UP_TIMEOUT, + 5000); break; - case VERIFY_SENSOR_MATCH: - handle_verify_sensor_match (ssm, self); + case FINGER_UP_DONE: /* State 1 */ + fpi_ssm_mark_completed (ssm); break; - case VERIFY_WAIT_MATCH_COMPLETE: - handle_verify_wait_match_complete (device, self); - break; - - case VERIFY_SENSOR_CHECK: - handle_verify_sensor_check (ssm, device, self); - break; - - case VERIFY_CHECK: - handle_verify_check (ssm, device, self); - break; - - default: - fpi_ssm_next_state (ssm); + /* User has not lifted finger. Send a retry error to prompt them + */ + case FINGER_UP_TIMEOUT: /* State 2 */ + fpi_ssm_mark_failed (ssm, + fpi_device_retry_new (FP_DEVICE_RETRY_REMOVE_FINGER)); break; } + fp_dbg ("crfpmoc_finger_up_run_state: state %d return", + state); +} + +static void +handle_verify_finger_up (FpiSsm *ssm, FpDevice *device, + FpiDeviceCrfpMoc *self) +{ + FpiSsm *sub = fpi_ssm_new (device, crfpmoc_finger_up_run_state, + FINGER_UP_STATES); + + crfpmoc_ssm_start_subsm (self->task_ssm, sub, self, NULL); } static void @@ -1603,8 +1917,9 @@ crfpmoc_clear_storage_run_state (FpiSsm *ssm, FpDevice *device) gboolean r; guint32 mode; GError *error = NULL; + int state = fpi_ssm_get_cur_state (ssm); - switch (fpi_ssm_get_cur_state (ssm)) + switch (state) { case CLEAR_STORAGE_SENSOR_RESET: fp_dbg ("crfpmoc_clear_storage_run_state: reset"); @@ -1631,31 +1946,134 @@ crfpmoc_clear_storage_run_state (FpiSsm *ssm, FpDevice *device) case CLEAR_STORAGE_SENSOR_DONE: if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_CLEAR_STORAGE) - fpi_device_clear_storage_complete (device, NULL); + { + fp_dbg ("crfpmoc_clear_storage_run_state: clear_storage_complete"); + fpi_device_clear_storage_complete (device, NULL); + } fpi_ssm_mark_completed (ssm); break; } + fp_dbg ("crfpmoc_clear_storage_run_state: state %d return", + state); } static void -crfpmoc_ident_clear_done (FpiSsm *ssm, FpDevice *device, - GError *error) +handle_verify_clear_storage (FpiSsm *ssm, FpDevice *device, + FpiDeviceCrfpMoc *self) +{ + FpiSsm *sub = fpi_ssm_new (device, crfpmoc_clear_storage_run_state, + CLEAR_STORAGE_STATES); + + crfpmoc_ssm_start_subsm (self->task_ssm, sub, self, NULL); +} + +static void +handle_verify_set_seed (FpiSsm *ssm, FpDevice *device, + FpiDeviceCrfpMoc *self) +{ + GError *error = NULL; + + if (!crfpmoc_set_keys (self, &error)) + fpi_ssm_mark_failed (ssm, error); + else + fpi_ssm_next_state (ssm); +} + +static void +crfpmoc_verify_run_state (FpiSsm *ssm, FpDevice *device) { FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device); - gboolean r; + int state = fpi_ssm_get_cur_state (ssm); - crfpmoc_task_ssm_done (ssm, device, error); - - r = crfpmoc_set_keys (self, &error); - if (!r) + switch (state) { - fpi_device_identify_complete (device, error); - return; - } + /* Make sure no residual templates are left in the sensor + */ + case VERIFY_CLEAR_STORAGE: + handle_verify_clear_storage (ssm, device, self); + break; - g_assert (self->task_ssm == NULL); - self->task_ssm = fpi_ssm_new (device, crfpmoc_verify_run_state, VERIFY_STATES); - fpi_ssm_start (self->task_ssm, crfpmoc_task_ssm_done); + /* Make sure the finger is actually up + */ + case VERIFY_FINGER_UP: + handle_verify_finger_up (ssm, device, self); + break; + + /* Set the seed and the context + */ + case VERIFY_SET_SEED: + handle_verify_set_seed (ssm, device, self); + break; + + /* Upload the supplied template(s) for match + */ + case VERIFY_UPLOAD_TEMPLATE: + handle_verify_upload_template (ssm, device, self); + break; + + /* Switch to match mode + */ + case VERIFY_SENSOR_MATCH: + handle_verify_sensor_match (ssm, self); + break; + + /* Wait for the match to report completion event + */ + case VERIFY_WAIT_MATCH_COMPLETE: + handle_verify_wait_match_complete (device, self); + break; + + /* Wait for the match mode to indicate complete + */ + case VERIFY_SENSOR_CHECK: + handle_verify_sensor_check (ssm, device, self); + break; + + /* Get the match status and report it + */ + case VERIFY_CHECK: + handle_verify_check (ssm, device, self); + break; + } + fp_dbg ("crfpmoc_verify_run_state: state %d return", + state); +} + +static void +crfpmoc_verify_ssm_done (FpiSsm *ssm, FpDevice *device, + GError *error) +{ + fp_dbg ("crfpmoc_verify_ssm_done"); + FpiDeviceCrfpMoc *self = FPI_DEVICE_CRFPMOC (device); + + // g_assert (!self->task_ssm || self->task_ssm == ssm); + self->task_ssm = NULL; + + /* If the state machine failed the report and completion was not + * sent. Send it here. + */ + if (error) + { + if (error->domain == FP_DEVICE_RETRY) + { + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (device, FPI_MATCH_ERROR, NULL, g_steal_pointer (&error)); + else + fpi_device_identify_report (device, NULL, NULL, g_steal_pointer (&error)); + } + + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) + { + fp_dbg ("crfpmoc_verify_ssm_done: verify_complete"); + fpi_device_verify_complete (device, error); + } + else + { + fp_dbg ("crfpmoc_verify_ssm_done: identify_complete"); + fpi_device_identify_complete (device, error); + } + } + crfpmoc_cancel (device); } static void @@ -1666,9 +2084,8 @@ crfpmoc_identify_verify (FpDevice *device) fp_dbg ("Identify or Verify"); g_assert (self->task_ssm == NULL); - self->task_ssm = fpi_ssm_new (device, crfpmoc_clear_storage_run_state, - CLEAR_STORAGE_STATES); - fpi_ssm_start (self->task_ssm, crfpmoc_ident_clear_done); + self->task_ssm = fpi_ssm_new (device, crfpmoc_verify_run_state, VERIFY_STATES); + crfpmoc_ssm_start (self->task_ssm, self, NULL, crfpmoc_verify_ssm_done); } static void @@ -1680,7 +2097,7 @@ crfpmoc_clear_storage (FpDevice *device) g_assert (self->task_ssm == NULL); self->task_ssm = fpi_ssm_new (device, crfpmoc_clear_storage_run_state, CLEAR_STORAGE_STATES); - fpi_ssm_start (self->task_ssm, crfpmoc_task_ssm_done); + crfpmoc_ssm_start (self->task_ssm, self, NULL, crfpmoc_task_ssm_done); } static void @@ -1688,7 +2105,6 @@ fpi_device_crfpmoc_init (FpiDeviceCrfpMoc *self) { self->fd = -1; self->emul_fd = -1; - self->poll_source = 0; } static void diff --git a/libfprint/drivers/crfpmoc/crfpmoc.h b/libfprint/drivers/crfpmoc/crfpmoc.h index 24711e88..011ca046 100644 --- a/libfprint/drivers/crfpmoc/crfpmoc.h +++ b/libfprint/drivers/crfpmoc/crfpmoc.h @@ -448,26 +448,45 @@ enum crfpmoc_ec_status { /* SSM task states and various status enums */ -typedef enum { - ENROLL_SENSOR_ENROLL, - ENROLL_WAIT_ENROLL_COMPLETE, - ENROLL_SENSOR_CHECK, - ENROLL_COMMIT, +/* Enroll state machine states + */ +enum { + ENROLL_SENSOR_ENROLL, /* Enter enroll mode */ + ENROLL_WAIT_ENROLL_COMPLETE, /* Wait for command completion event */ + ENROLL_SENSOR_CHECK, /* Verify mode has indicated completion */ + // ENROLL_WAIT_FINGER_UP, /* Wait for finger to be removed */ + ENROLL_COMMIT, /* Download template and return print */ ENROLL_STATES, -} EnrollStates; +}; -typedef enum { - VERIFY_UPLOAD_TEMPLATE, - VERIFY_SENSOR_MATCH, - VERIFY_WAIT_MATCH_COMPLETE, - VERIFY_SENSOR_CHECK, - VERIFY_CHECK, +/* Verify/Identify state machine states + */ +enum { + VERIFY_CLEAR_STORAGE, /* Clear all stored prints */ + VERIFY_FINGER_UP, /* Make sure the finger is not on the sensor */ + VERIFY_SET_SEED, /* Setup the seed and context */ + VERIFY_UPLOAD_TEMPLATE, /* Upload all gallery templates */ + VERIFY_SENSOR_MATCH, /* Enter match mode */ + VERIFY_WAIT_MATCH_COMPLETE, /* Wait for match complete event */ + VERIFY_SENSOR_CHECK, /* Verify mode has indicated completion */ + VERIFY_CHECK, /* Get the match completion status */ VERIFY_STATES, -} VerifyStates; +}; -typedef enum { - CLEAR_STORAGE_SENSOR_RESET, - CLEAR_STORAGE_SENSOR_WAIT, - CLEAR_STORAGE_SENSOR_DONE, +/* Clear storage state machine states + */ +enum { + CLEAR_STORAGE_SENSOR_RESET, /* Enter reset sensor mode */ + CLEAR_STORAGE_SENSOR_WAIT, /* Wait for mode to indicate completion */ + CLEAR_STORAGE_SENSOR_DONE, /* Return results */ CLEAR_STORAGE_STATES, -} ClearStorageStates; +}; + +/* Clear storage state machine states + */ +enum { + FINGER_UP_START, + FINGER_UP_DONE, + FINGER_UP_TIMEOUT, + FINGER_UP_STATES, +}; diff --git a/tests/crfpmoc/custom.ioctl b/tests/crfpmoc/custom.ioctl index 15a758de..b0d3428a 100644 --- a/tests/crfpmoc/custom.ioctl +++ b/tests/crfpmoc/custom.ioctl @@ -1,4 +1,6 @@ @DEV /dev/cros_fp +CROS_EC_DEV_IOCXCMD_V2 12 000000000B000000000000000C00000000000000080000002002200201000000 +CROS_EC_DEV_IOCXCMD_V2 12 000000000B000000000000000C00000000000000080000002002200201000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 @@ -48,6 +50,7 @@ CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000 +CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000004000000 CROS_EC_DEV_IOCXCMD_V2 8 00000000090400000000000008000000000000000100000001000000 CROS_EC_DEV_IOCXCMD_V2 0 0100000006040000240000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 0 0100000006040000240000000000000010000000 @@ -77,12 +80,14 @@ CROS_EC_DEV_IOCXCMD_V2 0 00000000050400007C0000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000040000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 48 010000000304000000000000300000000000000046504320090000001F020000010000009466000047524559A000A0000800FF0324140000050001000100000004000000 -CROS_EC_DEV_IOCXCMD_V2 22 00000000070400000000000016000000000000002471010024E802005C5E04003CD54A503A0000000000 +CROS_EC_DEV_IOCXCMD_V2 22 00000000070400000000000016000000000000002471010098C40200D03A0400E81B920D4D0000000000 +CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000 +CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000004000000 CROS_EC_DEV_IOCXCMD_V2 8 00000000090400000000000008000000000000000100000001000000 CROS_EC_DEV_IOCXCMD_V2 0 0100000006040000240000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 0 0100000006040000240000000000000010000000 @@ -112,7 +117,8 @@ CROS_EC_DEV_IOCXCMD_V2 0 00000000050400007C0000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000040000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 48 010000000304000000000000300000000000000046504320090000001F020000010000009466000047524559A000A0000800FF0324140000050001000100000004000000 -CROS_EC_DEV_IOCXCMD_V2 22 000000000704000000000000160000000000000024710100E0EA020018610400342F58503A0000000000 +CROS_EC_DEV_IOCXCMD_V2 22 00000000070400000000000016000000000000002471010078C10200783804007C52A90D4D0000000000 +CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000000000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 CROS_EC_DEV_IOCXCMD_V2 4 000000000204000004000000040000000000000080000000 diff --git a/tests/crfpmoc/device b/tests/crfpmoc/device index c12ab82a..a226e6bf 100644 --- a/tests/crfpmoc/device +++ b/tests/crfpmoc/device @@ -52,39 +52,39 @@ A: power/runtime_usage=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=856\n +A: power/wakeup_active_count=1742\n A: power/wakeup_count=0\n A: power/wakeup_expire_count=0\n -A: power/wakeup_last_time_ms=247138441\n +A: power/wakeup_last_time_ms=288328363\n A: power/wakeup_max_time_ms=0\n A: power/wakeup_total_time_ms=0\n -A: statistics/bytes=5478464\n -A: statistics/bytes_rx=5478464\n -A: statistics/bytes_tx=1240159\n +A: statistics/bytes=7992753\n +A: statistics/bytes_rx=7992753\n +A: statistics/bytes_tx=1847815\n A: statistics/errors=0\n -A: statistics/messages=194027\n +A: statistics/messages=275116\n A: statistics/spi_async=0\n -A: statistics/spi_sync=194027\n -A: statistics/spi_sync_immediate=194027\n +A: statistics/spi_sync=275116\n +A: statistics/spi_sync_immediate=275116\n A: statistics/timedout=0\n -A: statistics/transfer_bytes_histo_0-1=39778\n +A: statistics/transfer_bytes_histo_0-1=52216\n A: statistics/transfer_bytes_histo_1024-2047=0\n -A: statistics/transfer_bytes_histo_128-255=718\n -A: statistics/transfer_bytes_histo_16-31=1581\n +A: statistics/transfer_bytes_histo_128-255=1468\n +A: statistics/transfer_bytes_histo_16-31=2867\n A: statistics/transfer_bytes_histo_16384-32767=0\n -A: statistics/transfer_bytes_histo_2-3=422\n +A: statistics/transfer_bytes_histo_2-3=852\n A: statistics/transfer_bytes_histo_2048-4095=0\n -A: statistics/transfer_bytes_histo_256-511=3907\n -A: statistics/transfer_bytes_histo_32-63=109157\n +A: statistics/transfer_bytes_histo_256-511=5797\n +A: statistics/transfer_bytes_histo_32-63=163370\n A: statistics/transfer_bytes_histo_32768-65535=0\n -A: statistics/transfer_bytes_histo_4-7=1567\n +A: statistics/transfer_bytes_histo_4-7=2016\n A: statistics/transfer_bytes_histo_4096-8191=0\n A: statistics/transfer_bytes_histo_512-1023=711\n -A: statistics/transfer_bytes_histo_64-127=724\n +A: statistics/transfer_bytes_histo_64-127=759\n A: statistics/transfer_bytes_histo_65536+=0\n -A: statistics/transfer_bytes_histo_8-15=35462\n +A: statistics/transfer_bytes_histo_8-15=45060\n A: statistics/transfer_bytes_histo_8192-16383=0\n -A: statistics/transfers=194027\n +A: statistics/transfers=275116\n A: statistics/transfers_split_maxsize=0\n P: /devices/pci0000:00/0000:00:1e.3/pxa2xx-spi.8/spi_master/spi1 @@ -100,33 +100,33 @@ A: power/runtime_status=unsupported\n A: power/runtime_suspended_time=0\n A: power/runtime_usage=0\n L: software_node=../../../../../../kernel/software_nodes/node3 -A: statistics/bytes=5478464\n -A: statistics/bytes_rx=5478464\n -A: statistics/bytes_tx=1240159\n +A: statistics/bytes=7992753\n +A: statistics/bytes_rx=7992753\n +A: statistics/bytes_tx=1847815\n A: statistics/errors=0\n -A: statistics/messages=194027\n +A: statistics/messages=275116\n A: statistics/spi_async=0\n -A: statistics/spi_sync=194027\n -A: statistics/spi_sync_immediate=194027\n +A: statistics/spi_sync=275116\n +A: statistics/spi_sync_immediate=275116\n A: statistics/timedout=0\n -A: statistics/transfer_bytes_histo_0-1=39778\n +A: statistics/transfer_bytes_histo_0-1=52216\n A: statistics/transfer_bytes_histo_1024-2047=0\n -A: statistics/transfer_bytes_histo_128-255=718\n -A: statistics/transfer_bytes_histo_16-31=1581\n +A: statistics/transfer_bytes_histo_128-255=1468\n +A: statistics/transfer_bytes_histo_16-31=2867\n A: statistics/transfer_bytes_histo_16384-32767=0\n -A: statistics/transfer_bytes_histo_2-3=422\n +A: statistics/transfer_bytes_histo_2-3=852\n A: statistics/transfer_bytes_histo_2048-4095=0\n -A: statistics/transfer_bytes_histo_256-511=3907\n -A: statistics/transfer_bytes_histo_32-63=109157\n +A: statistics/transfer_bytes_histo_256-511=5797\n +A: statistics/transfer_bytes_histo_32-63=163370\n A: statistics/transfer_bytes_histo_32768-65535=0\n -A: statistics/transfer_bytes_histo_4-7=1567\n +A: statistics/transfer_bytes_histo_4-7=2016\n A: statistics/transfer_bytes_histo_4096-8191=0\n A: statistics/transfer_bytes_histo_512-1023=711\n -A: statistics/transfer_bytes_histo_64-127=724\n +A: statistics/transfer_bytes_histo_64-127=759\n A: statistics/transfer_bytes_histo_65536+=0\n -A: statistics/transfer_bytes_histo_8-15=35462\n +A: statistics/transfer_bytes_histo_8-15=45060\n A: statistics/transfer_bytes_histo_8192-16383=0\n -A: statistics/transfers=194027\n +A: statistics/transfers=275116\n A: statistics/transfers_split_maxsize=0\n A: waiting_for_supplier=0\n @@ -145,10 +145,10 @@ A: power/async=disabled\n A: power/autosuspend_delay_ms=50\n A: power/control=auto\n A: power/runtime_active_kids=0\n -A: power/runtime_active_time=1738748\n +A: power/runtime_active_time=2111536\n A: power/runtime_enabled=enabled\n A: power/runtime_status=suspended\n -A: power/runtime_suspended_time=245399566\n +A: power/runtime_suspended_time=286222682\n A: power/runtime_usage=0\n L: software_node=../../../../kernel/software_nodes/node3 @@ -191,10 +191,10 @@ A: power/async=enabled\n A: power/control=auto\n A: power/pm_qos_latency_tolerance_us=auto\n A: power/runtime_active_kids=0\n -A: power/runtime_active_time=2432781\n +A: power/runtime_active_time=2944140\n A: power/runtime_enabled=enabled\n A: power/runtime_status=suspended\n -A: power/runtime_suspended_time=244708518\n +A: power/runtime_suspended_time=285393064\n A: power/runtime_usage=0\n A: power_state=D3hot\n A: resource=0x000000007ff55000 0x000000007ff55fff 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