From ada5a59e1435d34e7063cbdcb4b7781ad28ecb60 Mon Sep 17 00:00:00 2001 From: Jason Huang Date: Wed, 28 Jan 2026 21:06:48 +0800 Subject: [PATCH] egis_etu905: add 1c7a:05ae fingerprint reader --- data/autosuspend.hwdb | 1 + libfprint/drivers/egismoc/egis_etu905.c | 1663 +++++++++++++++++++++++ libfprint/drivers/egismoc/egis_etu905.h | 217 +++ libfprint/meson.build | 2 + meson.build | 1 + 5 files changed, 1884 insertions(+) create mode 100644 libfprint/drivers/egismoc/egis_etu905.c create mode 100644 libfprint/drivers/egismoc/egis_etu905.h diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 9d1fd351..c09c3153 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -85,6 +85,7 @@ usb:v1C7Ap0586* usb:v1C7Ap0587* usb:v1C7Ap0588* usb:v1C7Ap05A1* +usb:v1C7Ap05AE* ID_AUTOSUSPEND=1 ID_PERSIST=0 diff --git a/libfprint/drivers/egismoc/egis_etu905.c b/libfprint/drivers/egismoc/egis_etu905.c new file mode 100644 index 00000000..59f81607 --- /dev/null +++ b/libfprint/drivers/egismoc/egis_etu905.c @@ -0,0 +1,1663 @@ +/* + * 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 = 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; +} CommandData; + +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); + + g_return_if_fail (transfer->ssm); + if (error) + fpi_ssm_mark_failed (transfer->ssm, error); + else + fpi_ssm_next_state (transfer->ssm); +} + +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) +{ + fp_dbg ("Task SSM done"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + /* 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 next state 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); +} + +static void +egis_etu905_task_ssm_next_state_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Task SSM next state callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + 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) +{ + g_autofree guchar *buffer = NULL; + CommandData *data = userdata; + SynCmdMsgCallback callback; + gssize actual_length; + + 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; + } + + /* Let's complete the previous ssm and then handle the callback, so that + * we are sure that we won't start a transfer or a new command while there is + * another one still ongoing + */ + callback = data->callback; + buffer = g_steal_pointer (&transfer->buffer); + actual_length = transfer->actual_length; + + fpi_ssm_mark_completed (transfer->ssm); + + if (callback) + callback (device, buffer, actual_length, NULL); +} + +static void +egis_etu905_cmd_run_state (FpiSsm *ssm, + FpDevice *device) +{ + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + g_autoptr(FpiUsbTransfer) transfer = NULL; + + 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; + } +} + +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 (error && data && data->callback) + data->callback (device, NULL, 0, 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), g_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) +{ + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + g_autoptr(GPtrArray) result = g_ptr_array_new_with_free_func (g_object_unref); + + 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) +{ + fp_dbg ("List callback"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + + 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_strndup ((gchar *) data, EGIS_ETU905_FINGERPRINT_DATA_SIZE); + fp_dbg ("Device fingerprint %0d: %.*s", self->enrolled_ids->len + 1, + EGIS_ETU905_FINGERPRINT_DATA_SIZE, print_id); + 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 (%s)", print_description, print_data_id); + + 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_check_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Enroll 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_next_state (self->task_ssm); + 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; + 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); + + const guint8 *data = NULL; + g_autofree gchar *print_id = NULL; + + read &= fpi_byte_reader_get_data (&reader, EGIS_ETU905_FINGERPRINT_DATA_SIZE, + &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}; + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + 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_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 ENROLL_CHECK_ENROLLED_NUM: + if (self->enrolled_ids->len >= EGIS_ETU905_MAX_ENROLL_NUM) + { + egis_etu905_enroll_status_report (device, enroll_print, ENROLL_STATUS_DEVICE_FULL, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_FULL)); + return; + } + fpi_ssm_next_state (ssm); + break; + + case ENROLL_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_SENSOR_ENROLL: + egis_etu905_exec_cmd (device, cmd_sensor_enroll, cmd_sensor_enroll_len, + NULL, egis_etu905_task_ssm_next_state_cb); + break; + + case ENROLL_WAIT_FINGER: + egis_etu905_wait_finger_on_sensor (ssm, device); + break; + + case ENROLL_SENSOR_CHECK: + egis_etu905_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, + NULL, egis_etu905_task_ssm_next_state_cb); + break; + + case ENROLL_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_enroll_check_cb); + break; + + 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_COMMIT_START: + egis_etu905_exec_cmd (device, cmd_commit_starting, cmd_commit_starting_len, + NULL, egis_etu905_commit_start_cb); + break; + + case ENROLL_COMMIT: + 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, self->sid, EGIS_ETU905_FINGERPRINT_DATA_SIZE); + + egis_etu905_set_print_data (enroll_print->print, (const gchar *)&sid_data.reserve_para_4, NULL); + 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) +{ + fp_dbg ("Enroll"); + FpiDeviceEgisEtu905 *self = FPI_DEVICE_EGIS_ETU905 (device); + EnrollPrint *enroll_print = g_new0 (EnrollPrint, 1); + + 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 = fpi_usb_transfer_new (device); + + 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->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); + GError *error = NULL; + + self->interrupt_cancellable = g_cancellable_new (); + + if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) + { + fpi_device_open_complete (device, error); + return; + } + + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), + 0, 0, &error)) + { + fpi_device_open_complete (device, 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) +{ + 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, 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; + /* device should be "always off" unless being used */ + dev_class->temp_hot_seconds = 0; + + 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..09d3f217 --- /dev/null +++ b/libfprint/drivers/egismoc/egis_etu905.h @@ -0,0 +1,217 @@ +/* + * 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 60000 + +#define EGIS_ETU905_USB_IN_RECV_LENGTH 4096 +#define EGIS_ETU905_USB_INTERRUPT_IN_RECV_LENGTH 64 + +#define EGIS_ETU905_MAX_ENROLL_STAGES_DEFAULT 11 +#define EGIS_ETU905_MAX_ENROLL_NUM 11 +#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_sensor_enroll[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x17, 0x01, 0x00}; +static gsize cmd_sensor_enroll_len = sizeof (cmd_sensor_enroll) / sizeof (cmd_sensor_enroll[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 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_GET_ENROLLED_IDS, + ENROLL_CHECK_ENROLLED_NUM, + ENROLL_SENSOR_RESET, + ENROLL_SENSOR_ENROLL, + ENROLL_WAIT_FINGER, + ENROLL_SENSOR_CHECK, + ENROLL_CHECK, + ENROLL_START, + ENROLL_CAPTURE_SENSOR_RESET, + ENROLL_CAPTURE_SENSOR_START_CAPTURE, + ENROLL_CAPTURE_WAIT_FINGER, + ENROLL_CAPTURE_READ_RESPONSE, + 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..97b98e66 100644 --- a/meson.build +++ b/meson.build @@ -131,6 +131,7 @@ default_drivers = [ 'etes603', 'egis0570', 'egismoc', + 'egis_etu905', 'vcom5s', 'synaptics', 'elan',