diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 55b5cb4c..a3ff00af 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -77,6 +77,12 @@ usb:v1C7Ap0571* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver egis_etu905 +usb:v1C7Ap05AE* +usb:v1C7Ap9201* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver egismoc usb:v1C7Ap0582* usb:v1C7Ap0583* diff --git a/libfprint/drivers/egismoc/egis_etu905.c b/libfprint/drivers/egismoc/egis_etu905.c new file mode 100644 index 00000000..be0c6e05 --- /dev/null +++ b/libfprint/drivers/egismoc/egis_etu905.c @@ -0,0 +1,1635 @@ +/* + * Driver for Egis Technology (LighTuning) Match-On-Chip sensors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define FP_COMPONENT "egis_etu905" + +#include +#include +#include + +#include "drivers_api.h" +#include "fpi-byte-writer.h" + +#include "egis_etu905.h" + +struct _FpiDeviceEgisEtu905 +{ + FpDevice parent; + FpiSsm *task_ssm; + FpiSsm *cmd_ssm; + FpiUsbTransfer *cmd_transfer; + GCancellable *interrupt_cancellable; + GPtrArray *enrolled_ids; + gint max_enroll_stages; + guint8 sid[EGIS_ETU905_FINGERPRINT_DATA_SIZE]; +}; + +G_DEFINE_TYPE (FpiDeviceEgisEtu905, fpi_device_egis_etu905, FP_TYPE_DEVICE); + +static const FpIdEntry egis_etu905_id_table[] = { + { .vid = 0x1c7a, .pid = 0x05ae, .driver_data = EGIS_ETU905_DRIVER_CHECK_PREFIX_TYPE1 }, + { .vid = 0x1c7a, .pid = 0x9201, .driver_data = EGIS_ETU905_DRIVER_CHECK_PREFIX_TYPE1 }, + { .vid = 0, .pid = 0, .driver_data = 0 } +}; + +typedef void (*SynCmdMsgCallback) (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error); + +typedef struct egis_etu905_command_data +{ + SynCmdMsgCallback callback; + guchar *buffer_in; + gsize length_in; +} CommandData; + +static void +egis_etu905_command_data_free (CommandData *data) +{ + g_free (data->buffer_in); + g_free (data); +} + +typedef struct egis_etu905_enroll_print +{ + FpPrint *print; + int stage; +} EnrollPrint; + +static void +egis_etu905_finger_on_sensor_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer userdata, + GError *error) +{ + fp_dbg ("Finger on sensor callback"); + fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); + + fpi_ssm_usb_transfer_cb (transfer, device, userdata, error); +} + +static void +egis_etu905_wait_finger_on_sensor (FpiSsm *ssm, + FpDevice *device) +{ + fp_dbg ("Wait for finger on sensor"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device); + + fpi_usb_transfer_fill_interrupt (transfer, EGIS_ETU905_EP_CMD_INTERRUPT_IN, + EGIS_ETU905_USB_INTERRUPT_IN_RECV_LENGTH); + transfer->ssm = ssm; + /* Interrupt on this device always returns 1 byte short; this is expected */ + transfer->short_is_error = FALSE; + + fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED); + + fpi_usb_transfer_submit (g_steal_pointer (&transfer), + EGIS_ETU905_USB_INTERRUPT_TIMEOUT, + self->interrupt_cancellable, + egis_etu905_finger_on_sensor_cb, + NULL); +} + +static gboolean +egis_etu905_validate_response_prefix (const guchar *buffer_in, + const gsize buffer_in_len, + const guchar *valid_prefix, + const gsize valid_prefix_len) +{ + const gboolean result = memcmp (buffer_in + + (egis_etu905_read_prefix_len + + EGIS_ETU905_CHECK_BYTES_LENGTH), + valid_prefix, + valid_prefix_len) == 0; + + fp_dbg ("Response prefix valid: %s", result ? "yes" : "NO"); + return result; +} + +static gboolean +egis_etu905_validate_response_suffix (const guchar *buffer_in, + const gsize buffer_in_len, + const guchar *valid_suffix, + const gsize valid_suffix_len) +{ + const gboolean result = memcmp (buffer_in + (buffer_in_len - valid_suffix_len), + valid_suffix, + valid_suffix_len) == 0; + + fp_dbg ("Response suffix valid: %s", result ? "yes" : "NO"); + return result; +} + +static void +egis_etu905_task_ssm_done (FpiSsm *ssm, + FpDevice *device, + GError *error) +{ + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + fp_dbg ("Task SSM done"); + + /* task_ssm is going to be freed by completion of SSM */ + g_assert (!self->task_ssm || self->task_ssm == ssm); + self->task_ssm = NULL; + + g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref); + + if (error) + fpi_device_action_error (device, error); +} + +static void +egis_etu905_commit_start_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Task SSM commit start callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + if (error) + fpi_ssm_mark_failed (self->task_ssm, error); + else + fpi_ssm_jump_to_state (self->task_ssm, ENROLL_COMMIT); +} + +/* + * Generic callback for commands that don't need special handling on success. + * Advances the task SSM to the next state on success, or marks it failed on error. + */ +static void +egis_etu905_task_ssm_next_state_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + fp_dbg ("Task SSM next state callback"); + + if (error) + fpi_ssm_mark_failed (self->task_ssm, error); + else + fpi_ssm_next_state (self->task_ssm); +} + +static void +egis_etu905_cmd_receive_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer userdata, + GError *error) +{ + CommandData *data = userdata; + + fp_dbg ("Command receive callback"); + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + if (data == NULL || transfer->actual_length < egis_etu905_read_prefix_len) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + /* Store the response data and let the cmd_ssm_done callback invoke + * the actual callback with the stored data */ + data->buffer_in = g_steal_pointer (&transfer->buffer); + data->length_in = transfer->actual_length; + + fpi_ssm_mark_completed (transfer->ssm); +} + +static void +egis_etu905_cmd_run_state (FpiSsm *ssm, + FpDevice *device) +{ + g_autoptr(FpiUsbTransfer) transfer = NULL; + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case CMD_SEND: + if (self->cmd_transfer) + { + self->cmd_transfer->ssm = ssm; + fpi_usb_transfer_submit (g_steal_pointer (&self->cmd_transfer), + EGIS_ETU905_USB_SEND_TIMEOUT, + fpi_device_get_cancellable (device), + fpi_ssm_usb_transfer_cb, + NULL); + break; + } + + fpi_ssm_next_state (ssm); + break; + + case CMD_GET: + transfer = fpi_usb_transfer_new (device); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, EGIS_ETU905_EP_CMD_IN, + EGIS_ETU905_USB_IN_RECV_LENGTH); + fpi_usb_transfer_submit (g_steal_pointer (&transfer), + EGIS_ETU905_USB_RECV_TIMEOUT, + fpi_device_get_cancellable (device), + egis_etu905_cmd_receive_cb, + fpi_ssm_get_data (ssm)); + break; + } +} + +/* + * Command SSM done callback. + * Always invokes the callback with stored data on success or error. + */ +static void +egis_etu905_cmd_ssm_done (FpiSsm *ssm, + FpDevice *device, + GError *error) +{ + g_autoptr(GError) local_error = error; + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + CommandData *data = fpi_ssm_get_data (ssm); + + g_assert (self->cmd_ssm == ssm); + g_assert (!self->cmd_transfer || self->cmd_transfer->ssm == ssm); + + self->cmd_ssm = NULL; + self->cmd_transfer = NULL; + + if (data && data->callback) + { + data->callback (device, + g_steal_pointer (&data->buffer_in), + data->length_in, + g_steal_pointer (&local_error)); + } +} + +/* + * Derive the 2 "check bytes" for write payloads + * 32-bit big-endian sum of all 16-bit words (including check bytes) MOD 0xFFFF + * should be 0, otherwise the device will reject the payload + */ +static guint16 +egis_etu905_get_check_bytes (FpiByteReader *reader) +{ + fp_dbg ("Get check bytes"); + size_t sum_values = 0; + guint16 val; + + fpi_byte_reader_set_pos (reader, 0); + + while (fpi_byte_reader_get_uint16_be (reader, &val)) + sum_values += val; + + return G_MAXUINT16 - (sum_values % G_MAXUINT16); +} + +static void +egis_etu905_exec_cmd (FpDevice *device, + guchar *cmd, + const gsize cmd_length, + GDestroyNotify cmd_destroy, + SynCmdMsgCallback callback) +{ + g_auto(FpiByteWriter) writer = {0}; + g_autoptr(FpiUsbTransfer) transfer = NULL; + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + g_autofree CommandData *data = NULL; + gsize buffer_out_length = 0; + gboolean written = TRUE; + guint16 check_value; + + fp_dbg ("Execute command and get response"); + + /* + * buffer_out should be a fully composed command (with prefix, check bytes, etc) + * which looks like this: + * E G I S 00 00 00 01 {cb1} {cb2} {payload} + * where cb1 and cb2 are some check bytes generated by the + * egis_etu905_get_check_bytes() method and payload is what is passed via the cmd + * parameter + */ + buffer_out_length = egis_etu905_write_prefix_len + + EGIS_ETU905_CHECK_BYTES_LENGTH + + cmd_length; + + fpi_byte_writer_init_with_size (&writer, buffer_out_length + + (buffer_out_length % 2 ? 1 : 0), TRUE); + + /* Prefix */ + written &= fpi_byte_writer_put_data (&writer, egis_etu905_write_prefix, + egis_etu905_write_prefix_len); + + /* Check Bytes - leave them as 00 for now then later generate and copy over + * the real ones */ + written &= fpi_byte_writer_change_pos (&writer, EGIS_ETU905_CHECK_BYTES_LENGTH); + + /* Command Payload */ + written &= fpi_byte_writer_put_data (&writer, cmd, cmd_length); + + /* Now fetch and set the "real" check bytes based on the currently + * assembled payload */ + check_value = egis_etu905_get_check_bytes (FPI_BYTE_READER (&writer)); + fpi_byte_writer_set_pos (&writer, egis_etu905_write_prefix_len); + written &= fpi_byte_writer_put_uint16_be (&writer, check_value); + + /* destroy cmd if requested */ + if (cmd_destroy) + g_clear_pointer (&cmd, cmd_destroy); + + g_assert (self->cmd_ssm == NULL); + self->cmd_ssm = fpi_ssm_new (device, + egis_etu905_cmd_run_state, + CMD_STATES); + + data = g_new0 (CommandData, 1); + data->callback = callback; + fpi_ssm_set_data (self->cmd_ssm, g_steal_pointer (&data), + (GDestroyNotify) egis_etu905_command_data_free); + + if (!written) + { + fpi_ssm_start (self->cmd_ssm, egis_etu905_cmd_ssm_done); + fpi_ssm_mark_failed (self->cmd_ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + transfer = fpi_usb_transfer_new (device); + transfer->short_is_error = TRUE; + transfer->ssm = self->cmd_ssm; + + fpi_usb_transfer_fill_bulk_full (transfer, + EGIS_ETU905_EP_CMD_OUT, + fpi_byte_writer_reset_and_get_data (&writer), + buffer_out_length, + g_free); + + g_assert (self->cmd_transfer == NULL); + self->cmd_transfer = g_steal_pointer (&transfer); + fpi_ssm_start (self->cmd_ssm, egis_etu905_cmd_ssm_done); +} + +static void +egis_etu905_set_print_data (FpPrint *print, + const gchar *device_print_id, + const gchar *user_id) +{ + GVariant *print_id_var = NULL; + GVariant *fpi_data = NULL; + g_autofree gchar *fill_user_id = NULL; + + if (user_id) + fill_user_id = g_strdup (user_id); + else + fill_user_id = g_strndup (device_print_id, EGIS_ETU905_FINGERPRINT_DATA_SIZE); + + fpi_print_fill_from_user_id (print, fill_user_id); + + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + + g_object_set (print, "description", fill_user_id, NULL); + + print_id_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + device_print_id, + EGIS_ETU905_FINGERPRINT_DATA_SIZE, + sizeof (guchar)); + fpi_data = g_variant_new ("(@ay)", print_id_var); + g_object_set (print, "fpi-data", fpi_data, NULL); +} + +static GPtrArray * +egis_etu905_get_enrolled_prints (FpDevice *device) +{ + g_autoptr(GPtrArray) result = g_ptr_array_new_with_free_func (g_object_unref); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + if (!self->enrolled_ids) + return g_steal_pointer (&result); + + for (guint i = 0; i < self->enrolled_ids->len; i++) + { + FpPrint *print = fp_print_new (device); + egis_etu905_set_print_data (print, g_ptr_array_index (self->enrolled_ids, i), NULL); + g_ptr_array_add (result, g_object_ref_sink (print)); + } + + return g_steal_pointer (&result); +} + +static void +egis_etu905_list_fill_enrolled_ids_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + fp_dbg ("List callback"); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref); + self->enrolled_ids = g_ptr_array_new_with_free_func (g_free); + + FpiByteReader reader; + gboolean read = TRUE; + + fpi_byte_reader_init (&reader, buffer_in, length_in); + + read &= fpi_byte_reader_set_pos (&reader, EGIS_ETU905_LIST_RESPONSE_PREFIX_SIZE); + + /* + * Each fingerprint ID will be returned in this response as a 32 byte array + * The other stuff in the payload is 16 bytes long, so if there is at least 1 + * print then the length should be at least 16+32=48 bytes long + */ + while (read) + { + const guint8 *data; + g_autofree gchar *print_id = NULL; + + read &= fpi_byte_reader_get_data (&reader, EGIS_ETU905_FINGERPRINT_DATA_SIZE, + &data); + if (!read) + break; + + print_id = g_memdup2 (data, EGIS_ETU905_FINGERPRINT_DATA_SIZE); + g_ptr_array_add (self->enrolled_ids, g_steal_pointer (&print_id)); + } + + fp_info ("Number of currently enrolled fingerprints on the device is %d", + self->enrolled_ids->len); + + if (self->task_ssm) + fpi_ssm_next_state (self->task_ssm); +} + +static void +egis_etu905_list_run_state (FpiSsm *ssm, + FpDevice *device) +{ + g_autoptr(GPtrArray) enrolled_prints = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case LIST_GET_ENROLLED_IDS: + egis_etu905_exec_cmd (device, cmd_list, cmd_list_len, NULL, + egis_etu905_list_fill_enrolled_ids_cb); + break; + + case LIST_RETURN_ENROLLED_PRINTS: + enrolled_prints = egis_etu905_get_enrolled_prints (device); + fpi_device_list_complete (device, g_steal_pointer (&enrolled_prints), NULL); + fpi_ssm_next_state (ssm); + break; + } +} + +static void +egis_etu905_list (FpDevice *device) +{ + fp_dbg ("List"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + g_assert (self->task_ssm == NULL); + self->task_ssm = fpi_ssm_new (device, + egis_etu905_list_run_state, + LIST_STATES); + fpi_ssm_start (self->task_ssm, egis_etu905_task_ssm_done); +} + +static guchar * +egis_etu905_get_delete_cmd (FpDevice *device, + FpPrint *delete_print, + gsize *length_out) +{ + fp_dbg ("Get delete command"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + g_auto(FpiByteWriter) writer = {0}; + g_autoptr(GVariant) print_data = NULL; + g_autoptr(GVariant) print_data_id_var = NULL; + const guchar *print_data_id = NULL; + gsize print_data_id_len = 0; + g_autofree gchar *print_description = NULL; + g_autofree guchar *enrolled_print_id = NULL; + g_autofree guchar *result = NULL; + gboolean written = TRUE; + + /* + * The final command body should contain: + * 1) hard-coded 00 00 + * 2) 2-byte size indiciator, 20*Number deleted identifiers plus 7 in form of: + * num_to_delete * 0x20 + 0x07 + * Since max prints can be higher than 7 then this goes up to 2 bytes + * (e9 + 9 = 109) + * 3) Hard-coded prefix (cmd_delete_prefix) + * 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 7 + * (num_to_delete * 0x20) + * 5) All of the currently registered prints to delete in their 32-byte device + * identifiers (enrolled_list) + */ + + int num_to_delete = 0; + if (delete_print) + num_to_delete = 1; + else if (self->enrolled_ids) + num_to_delete = self->enrolled_ids->len; + + const gsize body_length = sizeof (guchar) * EGIS_ETU905_FINGERPRINT_DATA_SIZE * + num_to_delete; + /* total_length is the 6 various bytes plus prefix and body payload */ + const gsize total_length = (sizeof (guchar) * 6) + cmd_delete_prefix_len + + body_length; + + /* pre-fill entire payload with 00s */ + fpi_byte_writer_init_with_size (&writer, total_length, TRUE); + + /* start with 00 00 (just move starting offset up by 2) */ + written &= fpi_byte_writer_set_pos (&writer, 2); + + /* Size Counter bytes */ + /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for + * when we go to the 2nd byte + * note this will not work in case any model ever supports more than 14 prints + * (assumed max is 10) */ + if (num_to_delete > 7) + { + written &= fpi_byte_writer_put_uint8 (&writer, 0x01); + written &= fpi_byte_writer_put_uint8 (&writer, ((num_to_delete - 8) * 0x20) + 0x07); + } + else + { + /* first byte is 0x00, just skip it */ + written &= fpi_byte_writer_change_pos (&writer, 1); + written &= fpi_byte_writer_put_uint8 (&writer, (num_to_delete * 0x20) + 0x07); + } + + /* command prefix */ + written &= fpi_byte_writer_put_data (&writer, cmd_delete_prefix, + cmd_delete_prefix_len); + + /* 2-bytes size logic for counter again */ + if (num_to_delete > 7) + { + written &= fpi_byte_writer_put_uint8 (&writer, 0x01); + written &= fpi_byte_writer_put_uint8 (&writer, (num_to_delete - 8) * 0x20); + } + else + { + /* first byte is 0x00, just skip it */ + written &= fpi_byte_writer_change_pos (&writer, 1); + written &= fpi_byte_writer_put_uint8 (&writer, num_to_delete * 0x20); + } + + /* append desired 32-byte fingerprint IDs */ + /* if passed a delete_print then fetch its data from the FpPrint */ + if (delete_print) + { + g_object_get (delete_print, "description", &print_description, NULL); + g_object_get (delete_print, "fpi-data", &print_data, NULL); + + if (!g_variant_check_format_string (print_data, "(@ay)", FALSE)) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return NULL; + } + + g_variant_get (print_data, "(@ay)", &print_data_id_var); + print_data_id = g_variant_get_fixed_array (print_data_id_var, + &print_data_id_len, sizeof (guchar)); + + if (!g_str_has_prefix (print_description, "FP")) + fp_dbg ("Fingerprint '%s' was not created by libfprint; deleting anyway.", + print_description); + + fp_info ("Delete fingerprint %s", print_description); + + written &= fpi_byte_writer_put_data (&writer, print_data_id, + EGIS_ETU905_FINGERPRINT_DATA_SIZE); + } + /* Otherwise assume this is a "clear" - just loop through and append all enrolled IDs */ + else if (self->enrolled_ids) + { + for (guint i = 0; i < self->enrolled_ids->len && written; i++) + { + written &= fpi_byte_writer_put_data (&writer, + g_ptr_array_index (self->enrolled_ids, i), + EGIS_ETU905_FINGERPRINT_DATA_SIZE); + } + } + + g_assert (written); + + if (length_out) + *length_out = total_length; + + return fpi_byte_writer_reset_and_get_data (&writer); +} + +static void +egis_etu905_delete_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Delete callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "success" with the delete */ + if (egis_etu905_validate_response_prefix (buffer_in, + length_in, + rsp_delete_success_prefix, + rsp_delete_success_prefix_len)) + { + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_CLEAR_STORAGE) + { + fpi_device_clear_storage_complete (device, NULL); + fpi_ssm_next_state (self->task_ssm); + } + else if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE) + { + fpi_device_delete_complete (device, NULL); + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Unsupported delete action.")); + } + } + else + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Delete print was not successful")); + } +} + +static void +egis_etu905_delete_run_state (FpiSsm *ssm, + FpDevice *device) +{ + g_autofree guchar *payload = NULL; + gsize payload_length = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case DELETE_GET_ENROLLED_IDS: + /* get enrolled_ids from device for use building delete payload below */ + egis_etu905_exec_cmd (device, cmd_list, cmd_list_len, NULL, + egis_etu905_list_fill_enrolled_ids_cb); + break; + + case DELETE_DELETE: + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE) + payload = egis_etu905_get_delete_cmd (device, fpi_ssm_get_data (ssm), + &payload_length); + else + payload = egis_etu905_get_delete_cmd (device, NULL, &payload_length); + + egis_etu905_exec_cmd (device, g_steal_pointer (&payload), payload_length, + g_free, egis_etu905_delete_cb); + break; + } +} + +static void +egis_etu905_clear_storage (FpDevice *device) +{ + fp_dbg ("Clear storage"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + g_assert (self->task_ssm == NULL); + self->task_ssm = fpi_ssm_new (device, + egis_etu905_delete_run_state, + DELETE_STATES); + fpi_ssm_start (self->task_ssm, egis_etu905_task_ssm_done); +} + +static void +egis_etu905_delete (FpDevice *device) +{ + fp_dbg ("Delete"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + FpPrint *delete_print = NULL; + + fpi_device_get_delete_data (device, &delete_print); + + g_assert (self->task_ssm == NULL); + self->task_ssm = fpi_ssm_new (device, + egis_etu905_delete_run_state, + DELETE_STATES); + /* the print is owned by libfprint during deletion task */ + fpi_ssm_set_data (self->task_ssm, delete_print, NULL); + fpi_ssm_start (self->task_ssm, egis_etu905_task_ssm_done); +} + +static void +egis_etu905_enroll_status_report (FpDevice *device, + EnrollPrint *enroll_print, + EnrollStatus status, + GError *error) +{ + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + switch (status) + { + case ENROLL_STATUS_DEVICE_FULL: + case ENROLL_STATUS_DUPLICATE: + fpi_ssm_mark_failed (self->task_ssm, error); + break; + + case ENROLL_STATUS_RETRY: + fpi_device_enroll_progress (device, enroll_print->stage, NULL, error); + break; + + case ENROLL_STATUS_PARTIAL_OK: + enroll_print->stage++; + fp_info ("Partial capture successful. Please touch the sensor again (%d/%d)", + enroll_print->stage, + self->max_enroll_stages); + fpi_device_enroll_progress (device, enroll_print->stage, enroll_print->print, NULL); + break; + + case ENROLL_STATUS_COMPLETE: + fp_info ("Enrollment was successful!"); + fpi_device_enroll_complete (device, g_object_ref (enroll_print->print), NULL); + break; + + default: + if (error) + fpi_ssm_mark_failed (self->task_ssm, error); + else + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "Unknown error")); + } +} + +static void +egis_etu905_read_capture_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Read capture callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + EnrollPrint *enroll_print = fpi_ssm_get_data (self->task_ssm); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "success" */ + if (egis_etu905_validate_response_prefix (buffer_in, + length_in, + rsp_read_success_prefix, + rsp_read_success_prefix_len) && + egis_etu905_validate_response_suffix (buffer_in, + length_in, + rsp_read_success_suffix, + rsp_read_success_suffix_len)) + { + egis_etu905_enroll_status_report (device, enroll_print, + ENROLL_STATUS_PARTIAL_OK, NULL); + } + else + { + /* If not success then the sensor can either report "off center" or "sensor is dirty" */ + + /* "Off center" */ + if (egis_etu905_validate_response_prefix (buffer_in, + length_in, + rsp_read_offcenter_prefix, + rsp_read_offcenter_prefix_len) && + egis_etu905_validate_response_suffix (buffer_in, + length_in, + rsp_read_offcenter_suffix, + rsp_read_offcenter_suffix_len)) + error = fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER); + + /* "Sensor is dirty" */ + else if (egis_etu905_validate_response_prefix (buffer_in, + length_in, + rsp_read_dirty_prefix, + rsp_read_dirty_prefix_len)) + error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, + "Your device is having trouble recognizing you. " + "Make sure your sensor is clean."); + + else + error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER, + "Unknown failure trying to read your finger. " + "Please try again."); + + egis_etu905_enroll_status_report (device, enroll_print, ENROLL_STATUS_RETRY, error); + } + + if (enroll_print->stage == self->max_enroll_stages) + fpi_ssm_next_state (self->task_ssm); + else + fpi_ssm_jump_to_state (self->task_ssm, ENROLL_CAPTURE_SENSOR_RESET); +} + +static void +egis_etu905_enroll_duplicate_check_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Task SSM enroll duplicate check callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload reports "not yet enrolled" */ + if (egis_etu905_validate_response_suffix (buffer_in, + length_in, + rsp_check_not_yet_enrolled_suffix, + rsp_check_not_yet_enrolled_suffix_len)) + fpi_ssm_jump_to_state (self->task_ssm, ENROLL_COMMIT_START); + else + egis_etu905_enroll_status_report (device, NULL, ENROLL_STATUS_DUPLICATE, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_DUPLICATE)); +} + +static void +egis_etu905_enroll_begin_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Enroll begin callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + FpiByteReader reader; + + fpi_byte_reader_init (&reader, buffer_in, length_in); + + fpi_byte_reader_set_pos (&reader, EGIS_ETU905_LIST_RESPONSE_PREFIX_SIZE); + + const guint8 *data = NULL; + + fpi_byte_reader_get_data (&reader, EGIS_ETU905_FINGERPRINT_DATA_SIZE, + &data); + + if (data) + memcpy (self->sid, data, EGIS_ETU905_FINGERPRINT_DATA_SIZE); + + fpi_ssm_next_state (self->task_ssm); +} + +/* + * Builds the full "check" payload which includes identifiers for all + * fingerprints which currently should exist on the storage. This payload is + * used during both enrollment and verify actions. + */ +static guchar * +egis_etu905_get_check_cmd (FpDevice *device, + gsize *length_out) +{ + fp_dbg ("Get check command"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + g_auto(FpiByteWriter) writer = {0}; + g_autofree guchar *result = NULL; + gboolean written = TRUE; + + /* + * The final command body should contain: + * 1) hard-coded 00 00 + * 2) 2-byte size indiciator, 20*Number enrolled identifiers plus 9 in form of: + * (enrolled_ids->len + 1) * 0x20 + 0x09 + * Since max prints can be higher than 7 then this goes up to 2 bytes + * (e9 + 9 = 109) + * 3) Hard-coded prefix (cmd_check_prefix) + * 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 9 + * ((enrolled_ids->len + 1) * 0x20) + * 5) Hard-coded 32 * 0x00 bytes + * 6) All of the currently registered prints in their 32-byte device identifiers + * (enrolled_list) + * 7) Hard-coded suffix (cmd_check_suffix) + */ + + g_assert (self->enrolled_ids); + const gsize body_length = sizeof (guchar) * self->enrolled_ids->len * + EGIS_ETU905_FINGERPRINT_DATA_SIZE; + + /* prefix length can depend on the type */ + const gsize check_prefix_length = (fpi_device_get_driver_data (device) & + EGIS_ETU905_DRIVER_CHECK_PREFIX_TYPE2) ? + cmd_check_prefix_type2_len : + cmd_check_prefix_type1_len; + + /* total_length is the 6 various bytes plus all other prefixes/suffixes and + * the body payload */ + const gsize total_length = (sizeof (guchar) * 6) + + check_prefix_length + + EGIS_ETU905_CMD_CHECK_SEPARATOR_LENGTH + + body_length + + cmd_check_suffix_len; + + /* pre-fill entire payload with 00s */ + fpi_byte_writer_init_with_size (&writer, total_length, TRUE); + + /* start with 00 00 (just move starting offset up by 2) */ + written &= fpi_byte_writer_set_pos (&writer, 2); + + /* Size Counter bytes */ + /* "easiest" way to handle 2-bytes size for counter is to hard-code logic for + * when we go to the 2nd byte + * note this will not work in case any model ever supports more than 14 prints + * (assumed max is 10) */ + if (self->enrolled_ids->len > 6) + { + written &= fpi_byte_writer_put_uint8 (&writer, 0x01); + written &= fpi_byte_writer_put_uint8 (&writer, + ((self->enrolled_ids->len - 7) * 0x20) + + 0x09); + } + else + { + /* first byte is 0x00, just skip it */ + written &= fpi_byte_writer_change_pos (&writer, 1); + written &= fpi_byte_writer_put_uint8 (&writer, + ((self->enrolled_ids->len + 1) * 0x20) + + 0x09); + } + + /* command prefix */ + if (fpi_device_get_driver_data (device) & EGIS_ETU905_DRIVER_CHECK_PREFIX_TYPE2) + written &= fpi_byte_writer_put_data (&writer, cmd_check_prefix_type2, + cmd_check_prefix_type2_len); + else + written &= fpi_byte_writer_put_data (&writer, cmd_check_prefix_type1, + cmd_check_prefix_type1_len); + + /* 2-bytes size logic for counter again */ + if (self->enrolled_ids->len > 6) + { + written &= fpi_byte_writer_put_uint8 (&writer, 0x01); + written &= fpi_byte_writer_put_uint8 (&writer, + (self->enrolled_ids->len - 7) * 0x20); + } + else + { + /* first byte is 0x00, just skip it */ + written &= fpi_byte_writer_change_pos (&writer, 1); + written &= fpi_byte_writer_put_uint8 (&writer, + (self->enrolled_ids->len + 1) * 0x20); + } + + /* add 00s "separator" to offset position */ + written &= fpi_byte_writer_change_pos (&writer, + EGIS_ETU905_CMD_CHECK_SEPARATOR_LENGTH); + + for (guint i = 0; i < self->enrolled_ids->len && written; i++) + { + written &= fpi_byte_writer_put_data (&writer, + g_ptr_array_index (self->enrolled_ids, i), + EGIS_ETU905_FINGERPRINT_DATA_SIZE); + } + + /* command suffix */ + written &= fpi_byte_writer_put_data (&writer, cmd_check_suffix, + cmd_check_suffix_len); + g_assert (written); + + if (length_out) + *length_out = total_length; + + return fpi_byte_writer_reset_and_get_data (&writer); +} + +static void +egis_etu905_enroll_run_state (FpiSsm *ssm, + FpDevice *device) +{ + g_auto(FpiByteWriter) writer = {0}; + EgismocSidData sid_data = {0}; + EnrollPrint *enroll_print = fpi_ssm_get_data (ssm); + g_autofree guchar *payload = NULL; + gsize payload_length = 0; + g_autofree gchar *device_print_id = NULL; + g_autofree gchar *user_id = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case ENROLL_START: + egis_etu905_exec_cmd (device, cmd_enroll_starting, cmd_enroll_starting_len, + NULL, egis_etu905_enroll_begin_cb); + break; + + case ENROLL_CAPTURE_SENSOR_RESET: + egis_etu905_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, + NULL, egis_etu905_task_ssm_next_state_cb); + break; + + case ENROLL_CAPTURE_SENSOR_START_CAPTURE: + egis_etu905_exec_cmd (device, cmd_sensor_start_capture, cmd_sensor_start_capture_len, + NULL, + egis_etu905_task_ssm_next_state_cb); + break; + + case ENROLL_CAPTURE_WAIT_FINGER: + egis_etu905_wait_finger_on_sensor (ssm, device); + break; + + case ENROLL_CAPTURE_READ_RESPONSE: + egis_etu905_exec_cmd (device, cmd_read_capture, cmd_read_capture_len, + NULL, egis_etu905_read_capture_cb); + break; + + case ENROLL_DUPLICATE_CHECK: + egis_etu905_exec_cmd (device, cmd_duplicate_check, cmd_duplicate_check_len, + NULL, egis_etu905_enroll_duplicate_check_cb); + break; + + case ENROLL_COMMIT_START: + egis_etu905_exec_cmd (device, cmd_commit_starting, cmd_commit_starting_len, + NULL, egis_etu905_commit_start_cb); + break; + + case ENROLL_COMMIT: + user_id = fpi_print_generate_user_id (enroll_print->print); + fp_dbg ("New fingerprint ID: %s", user_id); + + sid_data.reserve_para_1 = EGIS_ETU905_PARA_1_VALUE; + sid_data.reserve_para_2 = EGIS_ETU905_PARA_2_VALUE; + sid_data.reserve_para_3 = EGIS_ETU905_PARA_3_VALUE; + memcpy (sid_data.reserve_para_4, user_id, MIN (EGIS_ETU905_FINGERPRINT_DATA_SIZE, strlen (user_id))); + egis_etu905_set_print_data (enroll_print->print, (const gchar *) &sid_data.reserve_para_4, user_id); + fpi_byte_writer_init (&writer); + if (!fpi_byte_writer_put_data (&writer, cmd_new_print_prefix_type2, + cmd_new_print_prefix_type2_len)) + { + fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + break; + } + if (!fpi_byte_writer_put_data (&writer, (guint8 *) &sid_data, + sizeof (EgismocSidData))) + { + fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + break; + } + + payload_length = fpi_byte_writer_get_size (&writer); + egis_etu905_exec_cmd (device, fpi_byte_writer_reset_and_get_data (&writer), + payload_length, + g_free, egis_etu905_task_ssm_next_state_cb); + break; + + case ENROLL_COMMIT_SENSOR_RESET: + egis_etu905_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, + NULL, egis_etu905_task_ssm_next_state_cb); + break; + + case ENROLL_COMPLETE: + egis_etu905_enroll_status_report (device, enroll_print, ENROLL_STATUS_COMPLETE, NULL); + fpi_ssm_next_state (ssm); + break; + } +} + +static void +egis_etu905_enroll (FpDevice *device) +{ + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + EnrollPrint *enroll_print = g_new0 (EnrollPrint, 1); + + fp_dbg ("Enroll"); + + fpi_device_get_enroll_data (device, &enroll_print->print); + enroll_print->stage = 0; + + g_assert (self->task_ssm == NULL); + self->task_ssm = fpi_ssm_new (device, egis_etu905_enroll_run_state, ENROLL_STATES); + fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&enroll_print), g_free); + fpi_ssm_start (self->task_ssm, egis_etu905_task_ssm_done); +} + +static void +egis_etu905_identify_check_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Identify check callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + gchar device_print_id[EGIS_ETU905_FINGERPRINT_DATA_SIZE]; + FpPrint *print = NULL; + FpPrint *verify_print = NULL; + GPtrArray *prints; + gboolean found = FALSE; + guint index; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "match" */ + if (egis_etu905_validate_response_suffix (buffer_in, + length_in, + rsp_identify_match_suffix, + rsp_identify_match_suffix_len)) + { + /* + On success, there is a 32 byte array of "something"(?) in chars 14-45 + and then the 32 byte array ID of the matched print comes as chars 46-77 + */ + memcpy (device_print_id, + buffer_in + EGIS_ETU905_IDENTIFY_RESPONSE_PRINT_ID_OFFSET, + EGIS_ETU905_FINGERPRINT_DATA_SIZE); + + /* Create a new print from this device_print_id and then see if it matches + * the one indicated + */ + print = fp_print_new (device); + egis_etu905_set_print_data (print, device_print_id, NULL); + + if (!print) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "Failed to build a print from " + "device response.")); + return; + } + + fp_info ("Identify successful for: %s", fp_print_get_description (print)); + + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY) + { + fpi_device_get_identify_data (device, &prints); + found = g_ptr_array_find_with_equal_func (prints, + print, + (GEqualFunc) fp_print_equal, + &index); + + if (found) + fpi_device_identify_report (device, g_ptr_array_index (prints, index), print, NULL); + else + fpi_device_identify_report (device, NULL, print, NULL); + } + else + { + fpi_device_get_verify_data (device, &verify_print); + fp_info ("Verifying against: %s", fp_print_get_description (verify_print)); + + if (fp_print_equal (verify_print, print)) + fpi_device_verify_report (device, FPI_MATCH_SUCCESS, print, NULL); + else + fpi_device_verify_report (device, FPI_MATCH_FAIL, print, NULL); + } + } + /* If device was successfully read but it was a "not matched" */ + else if (egis_etu905_validate_response_suffix (buffer_in, + length_in, + rsp_identify_notmatch_suffix, + rsp_identify_notmatch_suffix_len)) + { + fp_info ("Print was not identified by the device"); + + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL); + else + fpi_device_identify_report (device, NULL, NULL, NULL); + } + else + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Unrecognized response from device.")); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +egis_etu905_identify_run_state (FpiSsm *ssm, + FpDevice *device) +{ + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + g_autofree guchar *payload = NULL; + gsize payload_length = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case IDENTIFY_GET_ENROLLED_IDS: + /* get enrolled_ids from device for use in check stages below */ + egis_etu905_exec_cmd (device, cmd_list, cmd_list_len, + NULL, egis_etu905_list_fill_enrolled_ids_cb); + break; + + case IDENTIFY_CHECK_ENROLLED_NUM: + if (self->enrolled_ids->len == 0) + { + fpi_ssm_mark_failed (g_steal_pointer (&self->task_ssm), + fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); + return; + } + fpi_ssm_next_state (ssm); + break; + + case IDENTIFY_SENSOR_RESET: + egis_etu905_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, + NULL, egis_etu905_task_ssm_next_state_cb); + break; + + case IDENTIFY_SENSOR_IDENTIFY: + egis_etu905_exec_cmd (device, cmd_sensor_identify, cmd_sensor_identify_len, + NULL, egis_etu905_task_ssm_next_state_cb); + break; + + case IDENTIFY_WAIT_FINGER: + egis_etu905_wait_finger_on_sensor (ssm, device); + break; + + case IDENTIFY_SENSOR_CHECK: + egis_etu905_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, + NULL, egis_etu905_task_ssm_next_state_cb); + break; + + case IDENTIFY_CHECK: + payload = egis_etu905_get_check_cmd (device, &payload_length); + egis_etu905_exec_cmd (device, g_steal_pointer (&payload), payload_length, + g_free, egis_etu905_identify_check_cb); + break; + + case IDENTIFY_COMPLETE_SENSOR_RESET: + egis_etu905_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, + NULL, egis_etu905_task_ssm_next_state_cb); + break; + + /* + * In Windows, the driver seems at this point to then immediately take + * another read from the sensor; this is suspected to be an on-chip + * "verify". However, because the user's finger is still on the sensor from + * the identify, then it seems to always return positive. We will consider + * this extra step unnecessary and just skip it in this driver. This driver + * will instead handle matching of the FpPrint from the gallery in the + * "verify" case of the callback egis_etu905_identify_check_cb. + */ + case IDENTIFY_COMPLETE: + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY) + fpi_device_identify_complete (device, NULL); + else + fpi_device_verify_complete (device, NULL); + + fpi_ssm_mark_completed (ssm); + break; + } +} + +static void +egis_etu905_identify_verify (FpDevice *device) +{ + fp_dbg ("Identify or Verify"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + g_assert (self->task_ssm == NULL); + self->task_ssm = fpi_ssm_new (device, egis_etu905_identify_run_state, IDENTIFY_STATES); + fpi_ssm_start (self->task_ssm, egis_etu905_task_ssm_done); +} + +static void +egis_etu905_fw_version_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Firmware version callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + g_autofree gchar *fw_version = NULL; + gsize prefix_length; + guchar *fw_version_start; + gsize fw_version_length; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "success" */ + if (!egis_etu905_validate_response_suffix (buffer_in, + length_in, + rsp_fw_version_suffix, + rsp_fw_version_suffix_len)) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Device firmware response " + "was not valid.")); + return; + } + + /* + * FW Version is 12 bytes: a carriage return (0x0d) plus the version string + * itself. Always skip [the read prefix] + [2 * check bytes] + [3 * 0x00] that + * come with every payload Then we will also skip the carriage return and take + * all but the last 2 bytes as the FW Version + */ + prefix_length = egis_etu905_read_prefix_len + 2 + 3 + 1; + fw_version_start = buffer_in + prefix_length; + fw_version_length = length_in - prefix_length - rsp_fw_version_suffix_len; + fw_version = g_strndup ((gchar *) fw_version_start, fw_version_length); + + fp_info ("Device firmware version is %s", fw_version); + + fpi_ssm_next_state (self->task_ssm); +} + +static void +egis_etu905_cmd_init_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("cmd init callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "success" */ + if (!egis_etu905_validate_response_suffix (buffer_in, + length_in, + rsp_fw_version_suffix, + rsp_fw_version_suffix_len)) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "cmd init response " + "was not valid.")); + return; + } + fpi_ssm_next_state (self->task_ssm); +} + +static void +egis_etu905_dev_init_done (FpiSsm *ssm, + FpDevice *device, + GError *error) +{ + if (error) + { + g_usb_device_release_interface ( + fpi_device_get_usb_device (device), 0, 0, NULL); + egis_etu905_task_ssm_done (ssm, device, error); + return; + } + + egis_etu905_task_ssm_done (ssm, device, NULL); + fpi_device_open_complete (device, NULL); +} + +static void +egis_etu905_dev_init_handler (FpiSsm *ssm, + FpDevice *device) +{ + g_autoptr(FpiUsbTransfer) transfer = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case DEV_GET_FW_VERSION: + egis_etu905_exec_cmd (device, cmd_fw_version, cmd_fw_version_len, + NULL, egis_etu905_fw_version_cb); + return; + + case DEV_INIT_CONTROL: + egis_etu905_exec_cmd (device, cmd_init, cmd_init_len, + NULL, egis_etu905_cmd_init_cb); + return; + + default: + g_assert_not_reached (); + } + + transfer = fpi_usb_transfer_new (device); + transfer->ssm = ssm; + transfer->short_is_error = TRUE; + fpi_usb_transfer_submit (g_steal_pointer (&transfer), + EGIS_ETU905_USB_CONTROL_TIMEOUT, + fpi_device_get_cancellable (device), + fpi_ssm_usb_transfer_cb, + NULL); +} + +static void +egis_etu905_probe (FpDevice *device) +{ + GUsbDevice *usb_dev; + GError *error = NULL; + g_autofree gchar *serial = NULL; + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + fp_dbg ("%s enter --> ", G_STRFUNC); + + /* Claim usb interface */ + usb_dev = fpi_device_get_usb_device (device); + if (!g_usb_device_open (usb_dev, &error)) + { + fp_dbg ("%s g_usb_device_open failed %s", G_STRFUNC, error->message); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (!g_usb_device_reset (usb_dev, &error)) + { + fp_dbg ("%s g_usb_device_reset failed %s", G_STRFUNC, error->message); + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (!g_usb_device_claim_interface (usb_dev, 0, 0, &error)) + { + fp_dbg ("%s g_usb_device_claim_interface failed %s", G_STRFUNC, error->message); + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) + serial = g_strdup ("emulated-device"); + else + serial = g_usb_device_get_string_descriptor (usb_dev, + g_usb_device_get_serial_number_index (usb_dev), + &error); + + if (error) + { + fp_dbg ("%s g_usb_device_get_string_descriptor failed %s", G_STRFUNC, error->message); + g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)), + 0, 0, NULL); + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (fpi_device_get_driver_data (device) & EGIS_ETU905_DRIVER_MAX_ENROLL_STAGES_20) + self->max_enroll_stages = 20; + else + self->max_enroll_stages = EGIS_ETU905_MAX_ENROLL_STAGES_DEFAULT; + + fpi_device_set_nr_enroll_stages (device, self->max_enroll_stages); + + g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)), 0, 0, NULL); + g_usb_device_close (usb_dev, NULL); + + fpi_device_probe_complete (device, serial, NULL, error); +} + +static void +egis_etu905_open (FpDevice *device) +{ + fp_dbg ("Opening device"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + g_autoptr(GError) error = NULL; + + if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) + { + fpi_device_open_complete (device, g_steal_pointer (&error)); + return; + } + + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), + 0, 0, &error)) + { + fpi_device_open_complete (device, g_steal_pointer (&error)); + return; + } + + g_assert (self->task_ssm == NULL); + self->task_ssm = fpi_ssm_new (device, egis_etu905_dev_init_handler, DEV_INIT_STATES); + fpi_ssm_start (self->task_ssm, egis_etu905_dev_init_done); +} + +static void +egis_etu905_cancel (FpDevice *device) +{ + fp_dbg ("Cancel"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + g_cancellable_cancel (self->interrupt_cancellable); + g_clear_object (&self->interrupt_cancellable); + self->interrupt_cancellable = g_cancellable_new (); +} + +static void +egis_etu905_suspend (FpDevice *device) +{ + fp_dbg ("Suspend"); + + egis_etu905_cancel (device); + g_cancellable_cancel (fpi_device_get_cancellable (device)); + fpi_device_suspend_complete (device, NULL); +} + +static void +egis_etu905_close (FpDevice *device) +{ + g_autoptr(GError) error = NULL; + fp_dbg ("Closing device"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + GError *error = NULL; + + egis_etu905_cancel (device); + g_clear_object (&self->interrupt_cancellable); + + g_usb_device_release_interface (fpi_device_get_usb_device (device), + 0, 0, &error); + fpi_device_close_complete (device, g_steal_pointer (&error)); +} + +static void +fpi_device_egis_etu905_init (FpiDeviceEgisEtu905 *self) +{ + G_DEBUG_HERE (); +} + +static void +fpi_device_egis_etu905_class_init (FpiDeviceEgisEtu905Class *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = EGIS_ETU905_DRIVER_FULLNAME; + + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + dev_class->id_table = egis_etu905_id_table; + dev_class->nr_enroll_stages = EGIS_ETU905_MAX_ENROLL_STAGES_DEFAULT; + dev_class->temp_hot_seconds = -1; + + dev_class->probe = egis_etu905_probe; + dev_class->open = egis_etu905_open; + dev_class->cancel = egis_etu905_cancel; + dev_class->suspend = egis_etu905_suspend; + dev_class->close = egis_etu905_close; + dev_class->identify = egis_etu905_identify_verify; + dev_class->verify = egis_etu905_identify_verify; + dev_class->enroll = egis_etu905_enroll; + dev_class->delete = egis_etu905_delete; + dev_class->clear_storage = egis_etu905_clear_storage; + dev_class->list = egis_etu905_list; + + fpi_device_class_auto_initialize_features (dev_class); + dev_class->features |= FP_DEVICE_FEATURE_DUPLICATES_CHECK; +} diff --git a/libfprint/drivers/egismoc/egis_etu905.h b/libfprint/drivers/egismoc/egis_etu905.h new file mode 100644 index 00000000..e931deca --- /dev/null +++ b/libfprint/drivers/egismoc/egis_etu905.h @@ -0,0 +1,214 @@ +/* + * Driver for Egis Technology (LighTuning) Match-On-Chip sensors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "fpi-device.h" +#include "fpi-ssm.h" + +G_DECLARE_FINAL_TYPE (FpiDeviceEgisEtu905, fpi_device_egis_etu905, FPI, DEVICE_EGIS_ETU905, FpDevice) + +#define EGIS_ETU905_DRIVER_FULLNAME "Egis Technology (LighTuning) Match-on-Chip" + +#define EGIS_ETU905_DRIVER_CHECK_PREFIX_TYPE1 (1 << 0) +#define EGIS_ETU905_DRIVER_CHECK_PREFIX_TYPE2 (1 << 1) +#define EGIS_ETU905_DRIVER_MAX_ENROLL_STAGES_20 (1 << 2) + +#define EGIS_ETU905_EP_CMD_OUT (0x02 | FPI_USB_ENDPOINT_OUT) +#define EGIS_ETU905_EP_CMD_IN (0x81 | FPI_USB_ENDPOINT_IN) +#define EGIS_ETU905_EP_CMD_INTERRUPT_IN 0x83 + +#define EGIS_ETU905_USB_CONTROL_TIMEOUT 5000 +#define EGIS_ETU905_USB_SEND_TIMEOUT 5000 +#define EGIS_ETU905_USB_RECV_TIMEOUT 5000 +#define EGIS_ETU905_USB_INTERRUPT_TIMEOUT 0 /* No timeout for waiting for finger down */ + +#define EGIS_ETU905_USB_IN_RECV_LENGTH 4096 +#define EGIS_ETU905_USB_INTERRUPT_IN_RECV_LENGTH 64 + +#define EGIS_ETU905_MAX_ENROLL_STAGES_DEFAULT 12 +#define EGIS_ETU905_MAX_ENROLL_NUM 12 +#define EGIS_ETU905_FINGERPRINT_DATA_SIZE 32 +#define EGIS_ETU905_LIST_RESPONSE_PREFIX_SIZE 14 +#define EGIS_ETU905_LIST_RESPONSE_SUFFIX_SIZE 2 + +#define EGIS_ETU905_PARA_4_SIZE 68 + +#define EGIS_ETU905_PARA_1_VALUE 0 +#define EGIS_ETU905_PARA_2_VALUE 0 +#define EGIS_ETU905_PARA_3_VALUE 32 + +#pragma pack(push, 1) +typedef struct +{ + guchar reserve_para_1; + gushort reserve_para_2; + gushort reserve_para_3; + guchar reserve_para_4[EGIS_ETU905_PARA_4_SIZE]; +} EgismocSidData; +#pragma pack(pop) + +/* standard prefixes for all read/writes */ + +static guchar egis_etu905_write_prefix[] = {'E', 'G', 'I', 'S', 0x00, 0x00, 0x00, 0x01}; +static gsize egis_etu905_write_prefix_len = sizeof (egis_etu905_write_prefix) / sizeof (egis_etu905_write_prefix[0]); + +static guchar egis_etu905_read_prefix[] = {'S', 'I', 'G', 'E', 0x00, 0x00, 0x00, 0x01}; +static gsize egis_etu905_read_prefix_len = sizeof (egis_etu905_read_prefix) / sizeof (egis_etu905_read_prefix[0]); + + +/* hard-coded command payloads */ + +static guchar cmd_fw_version[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x0c}; +static gsize cmd_fw_version_len = sizeof (cmd_fw_version) / sizeof (cmd_fw_version[0]); +static guchar rsp_fw_version_suffix[] = {0x90, 0x00}; +static gsize rsp_fw_version_suffix_len = sizeof (rsp_fw_version_suffix) / sizeof (rsp_fw_version_suffix[0]); + +static guchar cmd_init[] = {0x00, 0x00, 0x00, 0x29, 0x50, 0x81, 0x80, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x12, 0x00, 0x45, 0x67, 0x69, 0x73, + 0x46, 0x50, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x51, 0xc6, 0xbd, 0x71}; +static gsize cmd_init_len = sizeof (cmd_init) / sizeof (cmd_init[0]); + +static guchar cmd_list[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x19, 0x04, 0x00, 0x00, 0x01, 0x40}; +static gsize cmd_list_len = sizeof (cmd_list) / sizeof (cmd_list[0]); + +static guchar cmd_sensor_reset[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x1a, 0x00, 0x00}; +static gsize cmd_sensor_reset_len = sizeof (cmd_sensor_reset) / sizeof (cmd_sensor_reset[0]); + +static guchar cmd_sensor_check[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x17, 0x02, 0x00}; +static gsize cmd_sensor_check_len = sizeof (cmd_sensor_check) / sizeof (cmd_sensor_check[0]); + +static guchar cmd_sensor_identify[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x17, 0x01, 0x01}; +static gsize cmd_sensor_identify_len = sizeof (cmd_sensor_identify) / sizeof (cmd_sensor_identify[0]); + +static guchar rsp_identify_match_suffix[] = {0x90, 0x00}; +static gsize rsp_identify_match_suffix_len = sizeof (rsp_identify_match_suffix) / sizeof (rsp_identify_match_suffix[0]); + +static guchar rsp_identify_notmatch_suffix[] = {0x90, 0x04}; +static gsize rsp_identify_notmatch_suffix_len = sizeof (rsp_identify_notmatch_suffix) / sizeof (rsp_identify_notmatch_suffix[0]); + +static guchar cmd_enroll_starting[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x16, 0x01, 0x00, 0x00, 0x00, 0x20}; +static gsize cmd_enroll_starting_len = sizeof (cmd_enroll_starting) / sizeof (cmd_enroll_starting[0]); + +static guchar cmd_sensor_start_capture[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x16, 0x02, 0x01}; +static gsize cmd_sensor_start_capture_len = sizeof (cmd_sensor_start_capture) / sizeof (cmd_sensor_start_capture[0]); + +static guchar cmd_read_capture[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x16, 0x02, 0x02, 0x00, 0x00, 0x02}; +static gsize cmd_read_capture_len = sizeof (cmd_read_capture) / sizeof (cmd_read_capture[0]); + +static guchar cmd_duplicate_check[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x16, 0x05, 0x00}; +static gsize cmd_duplicate_check_len = sizeof (cmd_duplicate_check) / sizeof (cmd_duplicate_check[0]); + +static guchar rsp_read_success_prefix[] = {0x00, 0x00, 0x00, 0x04}; +static gsize rsp_read_success_prefix_len = sizeof (rsp_read_success_prefix) / sizeof (rsp_read_success_prefix[0]); +static guchar rsp_read_success_suffix[] = {0x90, 0x00}; +static gsize rsp_read_success_suffix_len = sizeof (rsp_read_success_suffix) / sizeof (rsp_read_success_suffix[0]); +static guchar rsp_read_offcenter_prefix[] = {0x00, 0x00, 0x00, 0x04}; +static gsize rsp_read_offcenter_prefix_len = sizeof (rsp_read_offcenter_prefix) / sizeof (rsp_read_offcenter_prefix[0]); +static guchar rsp_read_offcenter_suffix[] = {0x64, 0x91}; +static gsize rsp_read_offcenter_suffix_len = sizeof (rsp_read_offcenter_suffix) / sizeof (rsp_read_offcenter_suffix[0]); +static guchar rsp_read_dirty_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x64}; +static gsize rsp_read_dirty_prefix_len = sizeof (rsp_read_dirty_prefix) / sizeof (rsp_read_dirty_prefix[0]); + +static guchar cmd_commit_starting[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x16, 0x05, 0x00, 0x00, 0x00, 0x20}; +static gsize cmd_commit_starting_len = sizeof (cmd_commit_starting) / sizeof (cmd_commit_starting[0]); + +/* prefixes/suffixes and other things for dynamically created command payloads */ + +#define EGIS_ETU905_CHECK_BYTES_LENGTH 2 +#define EGIS_ETU905_IDENTIFY_RESPONSE_PRINT_ID_OFFSET 46 +#define EGIS_ETU905_CMD_CHECK_SEPARATOR_LENGTH 32 + +static guchar cmd_new_print_prefix_type2[] = {0x00, 0x00, 0x00, 0x50, 0x50, 0x16, 0x03, 0x00, 0x00, 0x00, 0x49}; +static gsize cmd_new_print_prefix_type2_len = sizeof (cmd_new_print_prefix_type2) / sizeof (cmd_new_print_prefix_type2[0]); + +static guchar cmd_delete_prefix[] = {0x50, 0x18, 0x04, 0x00, 0x00}; +static gsize cmd_delete_prefix_len = sizeof (cmd_delete_prefix) / sizeof (cmd_delete_prefix[0]); +static guchar rsp_delete_success_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x90, 0x00}; +static gsize rsp_delete_success_prefix_len = sizeof (rsp_delete_success_prefix) / sizeof (rsp_delete_success_prefix[0]); + +static guchar cmd_check_prefix_type1[] = {0x50, 0x17, 0x03, 0x00, 0x00}; +static gsize cmd_check_prefix_type1_len = sizeof (cmd_check_prefix_type1) / sizeof (cmd_check_prefix_type1[0]); +static guchar cmd_check_prefix_type2[] = {0x50, 0x17, 0x03, 0x80, 0x00}; +static gsize cmd_check_prefix_type2_len = sizeof (cmd_check_prefix_type2) / sizeof (cmd_check_prefix_type2[0]); +static guchar cmd_check_suffix[] = {0x00, 0x40}; +static gsize cmd_check_suffix_len = sizeof (cmd_check_suffix) / sizeof (cmd_check_suffix[0]); +static guchar rsp_check_not_yet_enrolled_suffix[] = {0x90, 0x04}; +static gsize rsp_check_not_yet_enrolled_suffix_len = sizeof (rsp_check_not_yet_enrolled_suffix) / sizeof (rsp_check_not_yet_enrolled_suffix[0]); + + +/* SSM task states and various status enums */ + +typedef enum { + CMD_SEND, + CMD_GET, + CMD_STATES, +} CommandStates; + +typedef enum { + DEV_GET_FW_VERSION, + DEV_INIT_CONTROL, + DEV_INIT_STATES, +} DeviceInitStates; + +typedef enum { + IDENTIFY_GET_ENROLLED_IDS, + IDENTIFY_CHECK_ENROLLED_NUM, + IDENTIFY_SENSOR_RESET, + IDENTIFY_SENSOR_IDENTIFY, + IDENTIFY_WAIT_FINGER, + IDENTIFY_SENSOR_CHECK, + IDENTIFY_CHECK, + IDENTIFY_COMPLETE_SENSOR_RESET, + IDENTIFY_COMPLETE, + IDENTIFY_STATES, +} IdentifyStates; + +typedef enum { + ENROLL_START, + ENROLL_CAPTURE_SENSOR_RESET, + ENROLL_CAPTURE_SENSOR_START_CAPTURE, + ENROLL_CAPTURE_WAIT_FINGER, + ENROLL_CAPTURE_READ_RESPONSE, + ENROLL_DUPLICATE_CHECK, + ENROLL_COMMIT_START, + ENROLL_COMMIT, + ENROLL_COMMIT_SENSOR_RESET, + ENROLL_COMPLETE, + ENROLL_STATES, +} EnrollStates; + +typedef enum { + ENROLL_STATUS_DEVICE_FULL, + ENROLL_STATUS_DUPLICATE, + ENROLL_STATUS_PARTIAL_OK, + ENROLL_STATUS_RETRY, + ENROLL_STATUS_COMPLETE, +} EnrollStatus; + +typedef enum { + LIST_GET_ENROLLED_IDS, + LIST_RETURN_ENROLLED_PRINTS, + LIST_STATES, +} ListStates; + +typedef enum { + DELETE_GET_ENROLLED_IDS, + DELETE_DELETE, + DELETE_STATES, +} DeleteStates; diff --git a/libfprint/meson.build b/libfprint/meson.build index ae0f6e24..081b18de 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -127,6 +127,8 @@ driver_sources = { [ 'drivers/egis0570.c' ], 'egismoc' : [ 'drivers/egismoc/egismoc.c' ], + 'egis_etu905' : + [ 'drivers/egismoc/egis_etu905.c' ], 'vfs0050' : [ 'drivers/vfs0050.c' ], 'elan' : diff --git a/meson.build b/meson.build index 14fb11f2..02b43e4f 100644 --- a/meson.build +++ b/meson.build @@ -131,6 +131,7 @@ default_drivers = [ 'etes603', 'egis0570', 'egismoc', + 'egis_etu905', 'vcom5s', 'synaptics', 'elan', @@ -165,6 +166,7 @@ endian_independent_drivers = virtual_drivers + [ 'aes4000', 'egis0570', 'egismoc', + 'egis_etu905', 'elanmoc', 'etes603', 'focaltech_moc', diff --git a/tests/egis_etu905/custom.pcapng b/tests/egis_etu905/custom.pcapng new file mode 100644 index 00000000..b21c4622 Binary files /dev/null and b/tests/egis_etu905/custom.pcapng differ diff --git a/tests/egis_etu905/custom.py b/tests/egis_etu905/custom.py new file mode 100644 index 00000000..a842f26e --- /dev/null +++ b/tests/egis_etu905/custom.py @@ -0,0 +1,109 @@ +#!/usr/bin/python3 +import traceback + +import sys +import gi + +gi.require_version('FPrint', '2.0') +from gi.repository import FPrint, GLib + +# Exit with error on any exception, included those happening in async callbacks +sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1)) + +ctx = GLib.main_context_default() + +c = FPrint.Context() +c.enumerate() +devices = c.get_devices() + +d = devices[0] +del devices + +assert d.get_driver() == "egis_etu905" +assert not d.has_feature(FPrint.DeviceFeature.CAPTURE) +assert d.has_feature(FPrint.DeviceFeature.IDENTIFY) +assert d.has_feature(FPrint.DeviceFeature.VERIFY) +assert d.has_feature(FPrint.DeviceFeature.STORAGE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_LIST) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) + +d.open_sync() + +def enroll_progress(*args): + assert d.get_finger_status() == FPrint.FingerStatusFlags.PRESENT + print("enroll progress stage: " + str(args[1])) + +def identify_done(dev, res): + global identified + identified = True + identify_match, identify_print = dev.identify_finish(res) + print("MATCH FOUND!" if identify_match else "NO MATCH FOUND") + assert identify_match.equal(identify_print) + +# List +print("--- LISTING ---") +stored = d.list_prints_sync() +prints1 = len(stored) +print(f"--- LIST DONE: Found {prints1} prints before enroll---") + +# Enroll +print("--- ENROLLING ---") +template = FPrint.Print.new(d) + +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("--- ENROLL DONE ---") +del template + +# List +print("--- LISTING ---") +stored = d.list_prints_sync() +prints2 = len(stored) +print(f"--- LIST DONE: Found {prints2} prints after enroll---") +assert (prints2 - prints1) == 1 + +# Verify +print("--- VERIFYING ---") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +verify_res, verify_print = d.verify_sync(p) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print(f"--- VERIFY DONE: Result {verify_res} ---") + +# Identify +print("--- ASYNC IDENTIFYING ---") +identified = False +deserialized_prints = [] +for p in stored: + deserialized_prints.append(FPrint.Print.deserialize(p.serialize())) + assert deserialized_prints[-1].equal(p) + +d.identify(deserialized_prints, callback=identify_done) +del deserialized_prints + +while not identified: + ctx.iteration(True) +print("--- IDENTIFY DONE ---") + +# Delete +print("--- DELETING ---") +p_to_delete = next((sp for sp in stored if sp.equal(p)), None) +if p_to_delete: + d.delete_print_sync(p_to_delete) + print("--- DELETE DONE ---") +del p_to_delete +del p + +# List +print("--- LISTING ---") +stored = d.list_prints_sync() +prints3 = len(stored) +print(f"--- LIST DONE: Found {prints3} prints after deleting---") +assert (prints2 - prints3) == 1 +del stored + +d.close_sync() + +del d +del c diff --git a/tests/egis_etu905/device b/tests/egis_etu905/device new file mode 100644 index 00000000..4b750a79 --- /dev/null +++ b/tests/egis_etu905/device @@ -0,0 +1,348 @@ +P: /devices/pci0000:00/0000:00:08.1/0000:05:00.4/usb3/3-3 +N: bus/usb/003/002=12010002FF0000407A1CAE0542530102030109022700010100A0320904000003FFFFFF00070581024000000705020240000007058303400001 +E: BUSNUM=003 +E: DEVNAME=/dev/bus/usb/003/002 +E: DEVNUM=002 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_BUS=usb +E: ID_MODEL=ETU905Axx-E +E: ID_MODEL_ENC=ETU905Axx-E +E: ID_MODEL_ID=05ae +E: ID_PATH=pci-0000:05:00.4-usb-0:3 +E: ID_PATH_TAG=pci-0000_05_00_4-usb-0_3 +E: ID_PATH_WITH_USB_REVISION=pci-0000:05:00.4-usbv2-0:3 +E: ID_REVISION=5342 +E: ID_SERIAL=EGIS_ETU905Axx-E_0A8606PNA357 +E: ID_SERIAL_SHORT=0A8606PNA357 +E: ID_USB_INTERFACES=:ffffff: +E: ID_USB_MODEL=ETU905Axx-E +E: ID_USB_MODEL_ENC=ETU905Axx-E +E: ID_USB_MODEL_ID=05ae +E: ID_USB_REVISION=5342 +E: ID_USB_SERIAL=EGIS_ETU905Axx-E_0A8606PNA357 +E: ID_USB_SERIAL_SHORT=0A8606PNA357 +E: ID_USB_VENDOR=EGIS +E: ID_USB_VENDOR_ENC=EGIS +E: ID_USB_VENDOR_ID=1c7a +E: ID_VENDOR=EGIS +E: ID_VENDOR_ENC=EGIS +E: ID_VENDOR_FROM_DATABASE=LighTuning Technology Inc. +E: ID_VENDOR_ID=1c7a +E: MAJOR=189 +E: MINOR=257 +E: PRODUCT=1c7a/5ae/5342 +E: SUBSYSTEM=usb +E: TYPE=255/0/0 +A: authorized=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=ff\n +A: bDeviceProtocol=00\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=100mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=5342\n +A: bmAttributes=a0\n +A: busnum=3\n +A: configuration= +H: descriptors=12010002FF0000407A1CAE0542530102030109022700010100A0320904000003FFFFFF00070581024000000705020240000007058303400001 +A: dev=189:257\n +A: devnum=2\n +A: devpath=3\n +L: driver=../../../../../../bus/usb/drivers/usb +L: firmware_node=../../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0c/device:1b/device:1c/device:1f +A: idProduct=05ae\n +A: idVendor=1c7a\n +A: ltm_capable=no\n +A: manufacturer=EGIS\n +A: maxchild=0\n +A: physical_location/dock=no\n +A: physical_location/horizontal_position=left\n +A: physical_location/lid=no\n +A: physical_location/panel=top\n +A: physical_location/vertical_position=upper\n +L: port=../3-0:1.0/usb3-port3 +A: power/active_duration=93136758\n +A: power/async=enabled\n +A: power/autosuspend=2\n +A: power/autosuspend_delay_ms=2000\n +A: power/connected_duration=93139621\n +A: power/control=on\n +A: power/level=on\n +A: power/persist=1\n +A: power/runtime_active_kids=0\n +A: power/runtime_active_time=93138826\n +A: power/runtime_enabled=forbidden\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=1\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=ETU905Axx-E\n +A: quirks=0x0\n +A: removable=fixed\n +A: rx_lanes=1\n +A: serial=0A8606PNA357\n +A: speed=12\n +A: tx_lanes=1\n +A: urbnum=5855\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:08.1/0000:05:00.4/usb3 +N: bus/usb/003/001=12010002090001406B1D020014060302010109021900010100E0000904000001090000000705810304000C +E: BUSNUM=003 +E: CURRENT_TAGS=:seat: +E: DEVNAME=/dev/bus/usb/003/001 +E: DEVNUM=001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_AUTOSUSPEND=1 +E: ID_BUS=usb +E: ID_FOR_SEAT=usb-pci-0000_05_00_4 +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_MODEL_ID=0002 +E: ID_PATH=pci-0000:05:00.4 +E: ID_PATH_TAG=pci-0000_05_00_4 +E: ID_REVISION=0614 +E: ID_SERIAL=Linux_6.14.0-37-generic_xhci-hcd_xHCI_Host_Controller_0000:05:00.4 +E: ID_SERIAL_SHORT=0000:05:00.4 +E: ID_USB_INTERFACES=:090000: +E: ID_USB_MODEL=xHCI_Host_Controller +E: ID_USB_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_USB_MODEL_ID=0002 +E: ID_USB_REVISION=0614 +E: ID_USB_SERIAL=Linux_6.14.0-37-generic_xhci-hcd_xHCI_Host_Controller_0000:05:00.4 +E: ID_USB_SERIAL_SHORT=0000:05:00.4 +E: ID_USB_VENDOR=Linux_6.14.0-37-generic_xhci-hcd +E: ID_USB_VENDOR_ENC=Linux\x206.14.0-37-generic\x20xhci-hcd +E: ID_USB_VENDOR_ID=1d6b +E: ID_VENDOR=Linux_6.14.0-37-generic_xhci-hcd +E: ID_VENDOR_ENC=Linux\x206.14.0-37-generic\x20xhci-hcd +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_VENDOR_ID=1d6b +E: MAJOR=189 +E: MINOR=256 +E: PRODUCT=1d6b/2/614 +E: SUBSYSTEM=usb +E: TAGS=:seat: +E: TYPE=9/0/1 +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=0614\n +A: bmAttributes=e0\n +A: busnum=3\n +A: configuration= +H: descriptors=12010002090001406B1D020014060302010109021900010100E0000904000001090000000705810304000C +A: dev=189:256\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:0c/device:1b/device:1c +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 6.14.0-37-generic xhci-hcd\n +A: maxchild=4\n +A: power/active_duration=93137993\n +A: power/async=enabled\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=93139758\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=93139206\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=0\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:05:00.4\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=989\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:08.1/0000:05:00.4 +E: DRIVER=xhci_hcd +E: ID_MODEL_FROM_DATABASE=Renoir/Cezanne USB 3.1 +E: ID_PATH=pci-0000:05:00.4 +E: ID_PATH_TAG=pci-0000_05_00_4 +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_VENDOR_FROM_DATABASE=Advanced Micro Devices, Inc. [AMD] +E: MODALIAS=pci:v00001022d00001639sv000017AAsd0000382Fbc0Csc03i30 +E: PCI_CLASS=C0330 +E: PCI_ID=1022:1639 +E: PCI_SLOT_NAME=0000:05:00.4 +E: PCI_SUBSYS_ID=17AA:382F +E: SUBSYSTEM=pci +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=22103916070410000030030C10008000040040D0000000000000000000000000000000000000000000000000AA172F38000000004800000000000000FF010000000000000000000009500800AA172F38016403C80000000000000000000000003120000010A00200A18F000010290000030D400040000311000000000000000000000000000000001F007100000000001E00800100000100000000000000000005C08600000000000000000000000000000000000000000000000000000000001100078000E00F0000F00F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B00012A010001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000D000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +A: consistent_dma_mask_bits=64\n +A: current_link_speed=8.0 GT/s PCIe\n +A: current_link_width=16\n +A: d3cold_allowed=1\n +A: dbc=disabled\n +A: dbc_bInterfaceProtocol=01\n +A: dbc_bcdDevice=0010\n +A: dbc_idProduct=0010\n +A: dbc_idVendor=1d6b\n +A: dbc_poll_interval_ms=64\n +A: device=0x1639\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:0c/device:1b +L: iommu=../../0000:00:00.2/iommu/ivhd0 +L: iommu_group=../../../../kernel/iommu_groups/20 +A: irq=44\n +A: link/l0s_aspm=0\n +A: link/l1_aspm=0\n +A: local_cpulist=0-11\n +A: local_cpus=fff\n +A: max_link_speed=8.0 GT/s PCIe\n +A: max_link_width=16\n +A: modalias=pci:v00001022d00001639sv000017AAsd0000382Fbc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/45=msix\n +A: msi_irqs/46=msix\n +A: msi_irqs/47=msix\n +A: msi_irqs/48=msix\n +A: msi_irqs/49=msix\n +A: msi_irqs/50=msix\n +A: msi_irqs/51=msix\n +A: msi_irqs/52=msix\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 4 5 2112 5\nxHCI ring segments 23 23 4096 23\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/async=enabled\n +A: power/control=on\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=93139523\n +A: power/runtime_enabled=forbidden\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=1\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: reset_method=pm\n +A: resource=0x00000000d0400000 0x00000000d04fffff 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=0x00\n +A: subsystem_device=0x382f\n +A: subsystem_vendor=0x17aa\n +A: vendor=0x1022\n + +P: /devices/pci0000:00/0000:00:08.1 +E: DRIVER=pcieport +E: ID_MODEL_FROM_DATABASE=Renoir Internal PCIe GPP Bridge to Bus +E: ID_PATH=pci-0000:00:08.1 +E: ID_PATH_TAG=pci-0000_00_08_1 +E: ID_PCI_CLASS_FROM_DATABASE=Bridge +E: ID_PCI_INTERFACE_FROM_DATABASE=Normal decode +E: ID_PCI_SUBCLASS_FROM_DATABASE=PCI bridge +E: ID_VENDOR_FROM_DATABASE=Advanced Micro Devices, Inc. [AMD] +E: MODALIAS=pci:v00001022d00001635sv00001022sd00001635bc06sc04i00 +E: PCI_CLASS=60400 +E: PCI_ID=1022:1635 +E: PCI_SLOT_NAME=0000:00:08.1 +E: PCI_SUBSYS_ID=1022:1635 +E: SUBSYSTEM=pci +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x060400\n +H: config=221035160704100000000406100081000000000000000000000505001111000030D060D001E011F0FC000000FC000000000000005000000000000000FF01020000000000000000000000000000000000015803C80000000010A042002280000010290000030D7000400C03310000000000004000180001000000000000003100000000001E00800143001F00000000000000000000000000000000000000000005C081000000E0FE0000000000000000000000000000000000000000000000000D00000022103516000000000000000000000000000000000000000000000000000000007CA006000000000000000000000000000000000000000000000000000B000127010001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001900012A00000000000000007F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F000000000D0001405F001D000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000250001410100008001000080000000002600014400000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2700010000000000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C0000389C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +A: consistent_dma_mask_bits=32\n +A: current_link_speed=8.0 GT/s PCIe\n +A: current_link_width=16\n +A: d3cold_allowed=1\n +A: device=0x1635\n +A: dma_mask_bits=32\n +L: driver=../../../bus/pci/drivers/pcieport +A: driver_override=(null)\n +A: enable=2\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:0c +L: iommu=../0000:00:00.2/iommu/ivhd0 +L: iommu_group=../../../kernel/iommu_groups/8 +A: irq=33\n +A: local_cpulist=0-11\n +A: local_cpus=fff\n +A: max_link_speed=8.0 GT/s PCIe\n +A: max_link_width=16\n +A: modalias=pci:v00001022d00001635sv00001022sd00001635bc06sc04i00\n +A: msi_bus=1\n +A: msi_irqs/33=msi\n +A: numa_node=-1\n +A: power/async=enabled\n +A: power/autosuspend_delay_ms=100\n +A: power/control=auto\n +A: power/runtime_active_kids=5\n +A: power/runtime_active_time=93139537\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=0\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: power_state=D0\n +A: reset_method=pm\n +A: resource=0x0000000000000000 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\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000001000 0x0000000000001fff 0x0000000000000101\n0x00000000d0300000 0x00000000d06fffff 0x0000000000000200\n0x000000fce0000000 0x000000fcf01fffff 0x0000000000102201\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n +A: revision=0x00\n +A: secondary_bus_number=5\n +A: subordinate_bus_number=5\n +A: subsystem_device=0x1635\n +A: subsystem_vendor=0x1022\n +A: vendor=0x1022\n + diff --git a/tests/meson.build b/tests/meson.build index 07c924be..b6e8bf95 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -52,6 +52,7 @@ drivers_tests = [ 'nb1010', 'egis0570', 'egismoc', + 'egis_etu905', 'egismoc-05a1', 'egismoc-0586', 'egismoc-0587',