From 0b9a64331f01e16a13d6e47effeceb842014eec7 Mon Sep 17 00:00:00 2001 From: Joshua Grisham <18266314+joshuagrisham@users.noreply.github.com> Date: Sun, 8 Oct 2023 14:52:55 +0200 Subject: [PATCH] Initial commit of egismoc driver --- libfprint/drivers/egismoc/egismoc.c | 1443 +++++++++++++++++++++++++++ libfprint/drivers/egismoc/egismoc.h | 231 +++++ libfprint/meson.build | 2 + meson.build | 1 + tests/egismoc/custom.pcapng | Bin 0 -> 73752 bytes tests/egismoc/custom.py | 156 +++ tests/egismoc/device | 262 +++++ tests/meson.build | 1 + 8 files changed, 2096 insertions(+) create mode 100644 libfprint/drivers/egismoc/egismoc.c create mode 100644 libfprint/drivers/egismoc/egismoc.h create mode 100644 tests/egismoc/custom.pcapng create mode 100755 tests/egismoc/custom.py create mode 100644 tests/egismoc/device diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c new file mode 100644 index 00000000..0b249c44 --- /dev/null +++ b/libfprint/drivers/egismoc/egismoc.c @@ -0,0 +1,1443 @@ +/* + * Driver for Egis Technology (LighTuning) Match-On-Chip sensors + * Originally authored 2023 by Joshua Grisham + * + * Portions of code and logic inspired from the elanmoc libfprint driver + * which is copyright (C) 2021 Elan Microelectronics Inc (see elanmoc.c) + * + * Based on original reverse-engineering work by Joshua Grisham. The protocol has + * been reverse-engineered from captures of the official Windows driver, and by + * testing commands on the sensor with a multiplatform Python prototype driver: + * https://github.com/joshuagrisham/galaxy-book2-pro-linux/tree/main/fingerprint/ + * + * 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 "egismoc" + +#include +#include +#include + +#include "drivers_api.h" + +#include "egismoc.h" + +G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_DEVICE); + +static const FpIdEntry egismoc_id_table[] = { + { .vid = 0x1c7a, .pid = 0x0582 }, + { .vid = 0, .pid = 0 } +}; + +typedef void (*SynCmdMsgCallback) (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error); + +typedef struct egismoc_command_data +{ + SynCmdMsgCallback callback; +} CommandData; + +typedef struct egismoc_enroll_print +{ + FpPrint *print; + int stage; +} EnrollPrint; + +static void +egismoc_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 +egismoc_wait_finger_on_sensor (FpiSsm *ssm, + FpDevice *device) +{ + fp_dbg ("Wait for finger on sensor"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device); + + fpi_usb_transfer_fill_interrupt (transfer, EGISMOC_EP_CMD_INTERRUPT_IN, EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH); + transfer->ssm = ssm; + transfer->short_is_error = FALSE; /* Interrupt on this device always returns 1 byte short; this is expected */ + + fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED); + + fpi_usb_transfer_submit (g_steal_pointer (&transfer), + EGISMOC_USB_INTERRUPT_TIMEOUT, + self->interrupt_cancellable, + egismoc_finger_on_sensor_cb, + NULL); +} + +static gboolean +egismoc_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 + (egismoc_read_prefix_len + EGISMOC_CHECK_BYTES_LENGTH), + valid_prefix, + valid_prefix_len) == 0; + + fp_dbg ("Response prefix valid: %s", result ? "yes" : "NO"); + return result; +} + +static gboolean +egismoc_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 +egismoc_task_ssm_done (FpiSsm *ssm, + FpDevice *device, + GError *error) +{ + fp_dbg ("Task SSM done"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + /* task_ssm already freed by completion of SSM */ + self->task_ssm = NULL; + + if (self->enrolled_ids) + g_ptr_array_free (self->enrolled_ids, TRUE); + self->enrolled_ids = NULL; + self->enrolled_num = -1; + + if (error) + fpi_device_action_error (device, error); +} + +static void +egismoc_task_ssm_next_state_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Task SSM next state callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + if (error) + fpi_ssm_mark_failed (self->task_ssm, error); + else + fpi_ssm_next_state (self->task_ssm); +} + +static void +egismoc_cmd_receive_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer userdata, + GError *error) +{ + fp_dbg ("Command receive callback"); + CommandData *data = userdata; + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + if (data == NULL || transfer->actual_length < egismoc_read_prefix_len) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + if (data->callback) + data->callback (device, transfer->buffer, transfer->actual_length, NULL); + + fpi_ssm_mark_completed (transfer->ssm); +} + +static void +egismoc_cmd_run_state (FpiSsm *ssm, + FpDevice *device) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (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), + EGISMOC_USB_SEND_TIMEOUT, + NULL, + fpi_ssm_usb_transfer_cb, + NULL); + } + else + { + fpi_ssm_next_state (ssm); + } + break; + + case CMD_GET: + transfer = fpi_usb_transfer_new (device); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, EGISMOC_EP_CMD_IN, EGISMOC_USB_IN_RECV_LENGTH); + fpi_usb_transfer_submit (g_steal_pointer (&transfer), + EGISMOC_USB_RECV_TIMEOUT, + NULL, + egismoc_cmd_receive_cb, + fpi_ssm_get_data (ssm)); + break; + } +} + +static void +egismoc_cmd_ssm_done (FpiSsm *ssm, + FpDevice *device, + GError *error) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + CommandData *data = fpi_ssm_get_data (ssm); + + self->cmd_ssm = NULL; + self->cmd_transfer = NULL; + + if (error) + { + if (data->callback) + data->callback (device, NULL, 0, error); + else + g_error_free (error); + } +} + +typedef union egismoc_check_bytes +{ + unsigned short check_short; + guchar check_bytes[EGISMOC_CHECK_BYTES_LENGTH]; +} EgisMocCheckBytes; + +/* + 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 EgisMocCheckBytes +egismoc_get_check_bytes (const guchar *value, + const gsize value_length) +{ + fp_dbg ("Get check bytes"); + EgisMocCheckBytes check_bytes; + unsigned short value_bigendian_shorts[(int) ((value_length + 1) / 2)]; + int s = 0; + + for (int i = 0; i < value_length; i = i + 2, s++) + { + if (i + 1 < value_length) + value_bigendian_shorts[s] = (((short) value[i + 1]) << 8) | (0x00ff & value[i]); + else + value_bigendian_shorts[s] = (((short) 0x00) << 8) | (0x00ff & value[i]); + } + unsigned long sum_shorts = 0; + + for (int i = 0; i < s; i++) + sum_shorts += value_bigendian_shorts[i]; + + /* + derive the "first possible occurence" of check bytes as: + `0xFFFF - (sum_of_32bit_words % 0xFFFF) + */ + check_bytes.check_short = 0xffff - (sum_shorts % 0xffff); + return check_bytes; +} + +static void +egismoc_exec_cmd (FpDevice *device, + guchar *cmd, + const gsize cmd_length, + GDestroyNotify cmd_destroy, + SynCmdMsgCallback callback) +{ + fp_dbg ("Execute command and get response"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + EgisMocCheckBytes check_bytes; + g_autofree guchar *buffer_out = NULL; + gsize buffer_out_length = 0; + + g_autoptr(FpiUsbTransfer) transfer = NULL; + CommandData *data = g_new0 (CommandData, 1); + + self->cmd_ssm = fpi_ssm_new (device, + egismoc_cmd_run_state, + CMD_STATES); + + transfer = fpi_usb_transfer_new (device); + transfer->short_is_error = TRUE; + + /* + 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 egismoc_get_check_bytes method + and payload is what is passed via the cmd parameter + */ + buffer_out_length = egismoc_write_prefix_len + + EGISMOC_CHECK_BYTES_LENGTH + + cmd_length; + buffer_out = g_malloc0 (buffer_out_length); + + /* Prefix */ + memcpy (buffer_out, egismoc_write_prefix, egismoc_write_prefix_len); + + /* Check Bytes - leave them as 00 for now then later generate and copy over the real ones */ + + /* Command Payload */ + memcpy (buffer_out + egismoc_write_prefix_len + EGISMOC_CHECK_BYTES_LENGTH, cmd, cmd_length); + + /* destroy cmd if requested */ + if (cmd_destroy) + cmd_destroy (cmd); + + /* Now fetch and set the "real" check bytes based on the currently assembled payload */ + check_bytes = egismoc_get_check_bytes (buffer_out, buffer_out_length); + memcpy (buffer_out + egismoc_write_prefix_len, check_bytes.check_bytes, EGISMOC_CHECK_BYTES_LENGTH); + + fpi_usb_transfer_fill_bulk_full (transfer, + EGISMOC_EP_CMD_OUT, + g_steal_pointer (&buffer_out), + buffer_out_length, + g_free); + transfer->ssm = self->cmd_ssm; + self->cmd_transfer = g_steal_pointer (&transfer); + data->callback = callback; + + fpi_ssm_set_data (self->cmd_ssm, data, g_free); + fpi_ssm_start (self->cmd_ssm, egismoc_cmd_ssm_done); +} + +static void +egismoc_set_print_data (FpPrint *print, + const guchar *device_print_id) +{ + g_autofree gchar *user_id = g_malloc (EGISMOC_FINGERPRINT_DATA_SIZE + 1); + GVariant *print_id_var = NULL; + GVariant *fpi_data = NULL; + + memcpy (user_id, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); + memset (user_id + EGISMOC_FINGERPRINT_DATA_SIZE, '\0', sizeof (gchar)); + + fpi_print_fill_from_user_id (print, user_id); + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + + g_object_set (print, "description", user_id, NULL); + + print_id_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + device_print_id, + EGISMOC_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 * +egismoc_get_enrolled_prints (FpDevice *device) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + g_autoptr(GPtrArray) result = g_ptr_array_new_with_free_func (g_object_unref); + FpPrint *print = NULL; + + for (int i = 0; i < self->enrolled_num; i++) + { + print = fp_print_new (device); + egismoc_set_print_data (print, g_ptr_array_index (self->enrolled_ids, i)); + g_ptr_array_add (result, g_object_ref_sink (print)); + } + + return g_steal_pointer (&result); +} + +static void +egismoc_list_fill_enrolled_ids_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("List callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + if (self->enrolled_ids) + g_ptr_array_free (self->enrolled_ids, TRUE); + self->enrolled_ids = g_ptr_array_new_with_free_func (g_free); + self->enrolled_num = 0; + + /* + 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 + */ + for (int pos = EGISMOC_LIST_RESPONSE_PREFIX_SIZE; + pos < length_in - EGISMOC_LIST_RESPONSE_SUFFIX_SIZE; + pos += EGISMOC_FINGERPRINT_DATA_SIZE, self->enrolled_num++) + { + g_autofree guchar *print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE); + memcpy (print_id, buffer_in + pos, EGISMOC_FINGERPRINT_DATA_SIZE); + fp_dbg ("Device fingerprint %0d: %.*s%c", self->enrolled_num, EGISMOC_FINGERPRINT_DATA_SIZE, print_id, '\0'); + 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_num); + + if (self->task_ssm) + fpi_ssm_next_state (self->task_ssm); +} + +static void +egismoc_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: + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + break; + + case LIST_RETURN_ENROLLED_PRINTS: + enrolled_prints = egismoc_get_enrolled_prints (device); + fpi_device_list_complete (device, g_steal_pointer (&enrolled_prints), NULL); + fpi_ssm_next_state (ssm); + break; + } +} + +static void +egismoc_list (FpDevice *device) +{ + fp_dbg ("List"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + self->task_ssm = fpi_ssm_new (device, + egismoc_list_run_state, + LIST_STATES); + fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); +} + +static guchar * +egismoc_get_delete_cmd (FpDevice *device, + FpPrint *delete_print, + gsize *length_out) +{ + fp_dbg ("Get delete command"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + g_autofree const gchar *print_description = NULL; + + 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 guchar *enrolled_print_id = NULL; + guchar *result = NULL; + gsize pos = 0; + + /* + 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) + */ + + const int num_to_delete = (!delete_print) ? self->enrolled_num : 1; + const gsize body_length = sizeof (guchar) * EGISMOC_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 */ + result = g_malloc0 (total_length); + + /* start with 00 00 (just move starting offset up by 2) */ + pos = 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) + { + memset (result + pos, 0x01, sizeof (guchar)); + pos += sizeof (guchar); + memset (result + pos, ((num_to_delete - 8) * 0x20) + 0x07, sizeof (guchar)); + pos += sizeof (guchar); + } + else + { + /* first byte is 0x00, just skip it */ + pos += sizeof (guchar); + memset (result + pos, (num_to_delete * 0x20) + 0x07, sizeof (guchar)); + pos += sizeof (guchar); + } + + /* command prefix */ + memcpy (result + pos, cmd_delete_prefix, cmd_delete_prefix_len); + pos += cmd_delete_prefix_len; + + /* 2-bytes size logic for counter again */ + if (num_to_delete > 7) + { + memset (result + pos, 0x01, sizeof (guchar)); + pos += sizeof (guchar); + memset (result + pos, ((num_to_delete - 8) * 0x20), sizeof (guchar)); + pos += sizeof (guchar); + } + else + { + /* first byte is 0x00, just skip it */ + pos += sizeof (guchar); + memset (result + pos, (num_to_delete * 0x20), sizeof (guchar)); + pos += sizeof (guchar); + } + + /* 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); + + memcpy (result + pos, print_data_id, EGISMOC_FINGERPRINT_DATA_SIZE); + } + /* Otherwise assume this is a "clear" - just loop through and append all enrolled IDs */ + else + { + for (int i = 0; i < self->enrolled_ids->len; i++) + memcpy (result + pos + (EGISMOC_FINGERPRINT_DATA_SIZE * i), + g_ptr_array_index (self->enrolled_ids, i), + EGISMOC_FINGERPRINT_DATA_SIZE); + } + pos += body_length; + + if (length_out) + *length_out = total_length; + + return g_steal_pointer (&result); +} + +static void +egismoc_delete_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Delete callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "success" with the delete */ + if (egismoc_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 successfull")); + } +} + +static void +egismoc_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 and enrolled_num from device for use building delete payload below */ + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + break; + + case DELETE_DELETE: + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE) + payload = egismoc_get_delete_cmd (device, fpi_ssm_get_data (ssm), &payload_length); + else + payload = egismoc_get_delete_cmd (device, NULL, &payload_length); + + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_delete_cb); + break; + } +} + +static void +egismoc_clear_storage (FpDevice *device) +{ + fp_dbg ("Clear storage"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + self->task_ssm = fpi_ssm_new (device, + egismoc_delete_run_state, + DELETE_STATES); + fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); +} + +static void +egismoc_delete (FpDevice *device) +{ + fp_dbg ("Delete"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + g_autoptr(FpPrint) delete_print = NULL; + + fpi_device_get_delete_data (device, &delete_print); + + self->task_ssm = fpi_ssm_new (device, + egismoc_delete_run_state, + DELETE_STATES); + fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&delete_print), NULL); /* todo leak or cleared by libfprint ? */ + fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); +} + +static void +egismoc_enroll_status_report (FpDevice *device, + EnrollPrint *enroll_print, + EnrollStatus status, + GError *error) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (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, + EGISMOC_MAX_ENROLL_NUM); + 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 +egismoc_read_capture_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Read capture callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (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 (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_read_success_prefix, + rsp_read_success_prefix_len) && + egismoc_validate_response_suffix (buffer_in, + length_in, + rsp_read_success_suffix, + rsp_read_success_suffix_len)) + { + egismoc_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 (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_read_offcenter_prefix, + rsp_read_offcenter_prefix_len) && + egismoc_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 (egismoc_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."); + + egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_RETRY, error); + } + + if (enroll_print->stage == EGISMOC_ENROLL_TIMES) + fpi_ssm_next_state (self->task_ssm); + else + fpi_ssm_jump_to_state (self->task_ssm, ENROLL_CAPTURE_SENSOR_RESET); +} + +static void +egismoc_enroll_check_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Enroll check callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload reports "not yet enrolled" */ + if (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_check_not_yet_enrolled_prefix, + rsp_check_not_yet_enrolled_prefix_len)) + fpi_ssm_next_state (self->task_ssm); + else + egismoc_enroll_status_report (device, NULL, ENROLL_STATUS_DUPLICATE, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_DUPLICATE)); +} + +/* + 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 * +egismoc_get_check_cmd (FpDevice *device, + gsize *length_out) +{ + fp_dbg ("Get check command"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + guchar *device_print_id = NULL; + guchar *result = NULL; + gsize pos = 0; + + /* + 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_num + 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_num + 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) + */ + + const gsize body_length = sizeof (guchar) * self->enrolled_num * EGISMOC_FINGERPRINT_DATA_SIZE; + + /* total_length is the 6 various bytes plus all other prefixes/suffixes and the body payload */ + const gsize total_length = (sizeof (guchar) * 6) + + cmd_check_prefix_len + + EGISMOC_CMD_CHECK_SEPARATOR_LENGTH + + body_length + + cmd_check_suffix_len; + + /* pre-fill entire payload with 00s */ + result = g_malloc0 (total_length); + + /* start with 00 00 (just move starting offset up by 2) */ + pos = 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_num > 6) + { + memset (result + pos, 0x01, sizeof (guchar)); + pos += sizeof (guchar); + memset (result + pos, ((self->enrolled_num - 7) * 0x20) + 0x09, sizeof (guchar)); + pos += sizeof (guchar); + } + else + { + /* first byte is 0x00, just skip it */ + pos += sizeof (guchar); + memset (result + pos, ((self->enrolled_num + 1) * 0x20) + 0x09, sizeof (guchar)); + pos += sizeof (guchar); + } + + /* command prefix */ + memcpy (result + pos, cmd_check_prefix, cmd_check_prefix_len); + pos += cmd_check_prefix_len; + + /* 2-bytes size logic for counter again */ + if (self->enrolled_num > 6) + { + memset (result + pos, 0x01, sizeof (guchar)); + pos += sizeof (guchar); + memset (result + pos, (self->enrolled_num - 7) * 0x20, sizeof (guchar)); + pos += sizeof (guchar); + } + else + { + /* first byte is 0x00, just skip it */ + pos += sizeof (guchar); + memset (result + pos, (self->enrolled_num + 1) * 0x20, sizeof (guchar)); + pos += sizeof (guchar); + } + + /* add 00s "separator" to offset position */ + pos += EGISMOC_CMD_CHECK_SEPARATOR_LENGTH; + + /* append all currently registered 32-byte fingerprint IDs */ + const gsize print_id_length = sizeof (guchar) * EGISMOC_FINGERPRINT_DATA_SIZE; + + for (int i = 0; i < self->enrolled_num; i++) + { + device_print_id = g_ptr_array_index (self->enrolled_ids, i); + memcpy (result + pos + (print_id_length * i), device_print_id, print_id_length); + } + pos += body_length; + + /* command suffix */ + memcpy (result + pos, cmd_check_suffix, cmd_check_suffix_len); + pos += cmd_check_suffix_len; + + if (length_out) + *length_out = total_length; + + return g_steal_pointer (&result); +} + +static void +egismoc_enroll_run_state (FpiSsm *ssm, + FpDevice *device) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + EnrollPrint *enroll_print = fpi_ssm_get_data (ssm); + g_autofree guchar *payload = NULL; + gsize payload_length = 0; + g_autofree guchar *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 and enrolled_num from device for use in check stages below */ + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + break; + + case ENROLL_CHECK_ENROLLED_NUM: + if (self->enrolled_num >= EGISMOC_MAX_ENROLL_NUM) + { + egismoc_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: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_SENSOR_ENROLL: + egismoc_exec_cmd (device, cmd_sensor_enroll, cmd_sensor_enroll_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_WAIT_FINGER: + egismoc_wait_finger_on_sensor (ssm, device); + break; + + case ENROLL_SENSOR_CHECK: + egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_CHECK: + payload = egismoc_get_check_cmd (device, &payload_length); + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_enroll_check_cb); + break; + + case ENROLL_START: + egismoc_exec_cmd (device, cmd_enroll_starting, cmd_enroll_starting_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_CAPTURE_SENSOR_RESET: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_CAPTURE_SENSOR_START_CAPTURE: + egismoc_exec_cmd (device, cmd_sensor_start_capture, cmd_sensor_start_capture_len, NULL, + egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_CAPTURE_WAIT_FINGER: + egismoc_wait_finger_on_sensor (ssm, device); + break; + + case ENROLL_CAPTURE_READ_RESPONSE: + egismoc_exec_cmd (device, cmd_read_capture, cmd_read_capture_len, NULL, egismoc_read_capture_cb); + break; + + case ENROLL_COMMIT_START: + egismoc_exec_cmd (device, cmd_commit_starting, cmd_commit_starting_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_COMMIT: + user_id = fpi_print_generate_user_id (enroll_print->print); + fp_dbg ("New fingerprint ID: %s", user_id); + + device_print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE); + memcpy (device_print_id, user_id, MIN (strlen (user_id), EGISMOC_FINGERPRINT_DATA_SIZE)); + egismoc_set_print_data (enroll_print->print, device_print_id); + + /* create new dynamic payload of cmd_new_print_prefix + device_print_id */ + payload_length = cmd_new_print_prefix_len + EGISMOC_FINGERPRINT_DATA_SIZE; + payload = g_malloc0 (payload_length); + memcpy (payload, cmd_new_print_prefix, cmd_new_print_prefix_len); + memcpy (payload + cmd_new_print_prefix_len, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE); + + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_COMMIT_SENSOR_RESET: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case ENROLL_COMPLETE: + egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_COMPLETE, NULL); + fpi_ssm_next_state (ssm); + break; + } +} + +static void +egismoc_enroll (FpDevice *device) +{ + fp_dbg ("Enroll"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + EnrollPrint *enroll_print = g_new0 (EnrollPrint, 1); + + fpi_device_get_enroll_data (device, &enroll_print->print); + enroll_print->stage = 0; + + self->task_ssm = fpi_ssm_new (device, egismoc_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, egismoc_task_ssm_done); +} + +static void +egismoc_identify_check_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Identify check callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + guchar device_print_id[EGISMOC_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 (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_identify_match_prefix, + rsp_identify_match_prefix_len) && + egismoc_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 + EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET, + EGISMOC_FINGERPRINT_DATA_SIZE); + + /* Create a new print from this ID and then see if it matches the one indicated */ + print = fp_print_new (device); + egismoc_set_print_data (print, device_print_id); + + 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); + + fpi_ssm_next_state (self->task_ssm); + } + 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); + + fpi_ssm_next_state (self->task_ssm); + } + } + /* If device was successfully read but it was a "not matched" */ + else if (egismoc_validate_response_prefix (buffer_in, + length_in, + rsp_identify_notmatch_prefix, + rsp_identify_notmatch_prefix_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); + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_device_identify_report (device, NULL, NULL, 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, + "Unrecognized response from device.")); + } +} + +static void +egismoc_identify_run_state (FpiSsm *ssm, + FpDevice *device) +{ + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (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 and enrolled_num from device for use in check stages below */ + egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb); + break; + + case IDENTIFY_CHECK_ENROLLED_NUM: + if (self->enrolled_num == 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: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case IDENTIFY_SENSOR_IDENTIFY: + egismoc_exec_cmd (device, cmd_sensor_identify, cmd_sensor_identify_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case IDENTIFY_WAIT_FINGER: + egismoc_wait_finger_on_sensor (ssm, device); + break; + + case IDENTIFY_SENSOR_CHECK: + egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, NULL, egismoc_task_ssm_next_state_cb); + break; + + case IDENTIFY_CHECK: + payload = egismoc_get_check_cmd (device, &payload_length); + egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_identify_check_cb); + break; + + case IDENTIFY_COMPLETE_SENSOR_RESET: + egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_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 egismoc_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 +egismoc_identify_verify (FpDevice *device) +{ + fp_dbg ("Identify or Verify"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + self->task_ssm = fpi_ssm_new (device, egismoc_identify_run_state, IDENTIFY_STATES); + fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done); +} + +static void +egismoc_fw_version_cb (FpDevice *device, + guchar *buffer_in, + gsize length_in, + GError *error) +{ + fp_dbg ("Firmware version callback"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + g_autofree guchar *fw_version = NULL; + gsize prefix_length; + gsize fw_version_length; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + /* Check that the read payload indicates "success" */ + if (!egismoc_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 = egismoc_read_prefix_len + 2 + 3 + 1; + fw_version_length = length_in - prefix_length - rsp_fw_version_suffix_len; + fw_version = g_malloc0 (fw_version_length + 1); + + memcpy (fw_version, + buffer_in + prefix_length, + length_in - prefix_length - rsp_fw_version_suffix_len); + *(fw_version + fw_version_length) = '\0'; + + fp_info ("Device firmware version is %s", fw_version); + + fpi_ssm_next_state (self->task_ssm); +} + +static void +egismoc_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); + + fpi_device_open_complete (device, error); +} + +static void +egismoc_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_INIT_CONTROL1: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 32, 0x0000, 4, 16); + goto send_control; + break; + + case DEV_INIT_CONTROL2: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 32, 0x0000, 4, 40); + goto send_control; + break; + + case DEV_INIT_CONTROL3: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_STANDARD, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0, 0x0000, 0, 2); + goto send_control; + break; + + case DEV_INIT_CONTROL4: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_STANDARD, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0, 0x0000, 0, 2); + goto send_control; + break; + + case DEV_INIT_CONTROL5: + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 82, 0x0000, 0, 8); + goto send_control; + break; + + case DEV_GET_FW_VERSION: + egismoc_exec_cmd (device, cmd_fw_version, cmd_fw_version_len, NULL, egismoc_fw_version_cb); + break; + } + + return; + +send_control: + transfer->ssm = ssm; + transfer->short_is_error = TRUE; + fpi_usb_transfer_submit (g_steal_pointer (&transfer), + EGISMOC_USB_CONTROL_TIMEOUT, + NULL, + fpi_ssm_usb_transfer_cb, + NULL); +} + +static void +egismoc_open (FpDevice *device) +{ + fp_dbg ("Opening device"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + GError *error = NULL; + + self->interrupt_cancellable = g_cancellable_new (); + + if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) + goto error; + + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error)) + goto error; + + self->task_ssm = fpi_ssm_new (device, egismoc_dev_init_handler, DEV_INIT_STATES); + fpi_ssm_start (self->task_ssm, egismoc_dev_init_done); + return; + +error: + return fpi_device_open_complete (device, error); +} + +static void +egismoc_cancel (FpDevice *device) +{ + fp_dbg ("Cancel"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + + g_cancellable_cancel (self->interrupt_cancellable); + g_clear_object (&self->interrupt_cancellable); + self->interrupt_cancellable = g_cancellable_new (); +} + +static void +egismoc_close (FpDevice *device) +{ + fp_dbg ("Closing device"); + FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device); + GError *error = NULL; + + egismoc_cancel (device); + + g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error); + fpi_device_close_complete (device, error); + + if (self->task_ssm) + fpi_ssm_free (self->task_ssm); + self->task_ssm = NULL; + + if (self->cmd_ssm) + fpi_ssm_free (self->cmd_ssm); + self->cmd_ssm = NULL; + + self->cmd_transfer = NULL; + + g_clear_object (&self->interrupt_cancellable); + + if (self->enrolled_ids) + g_ptr_array_free (self->enrolled_ids, TRUE); + self->enrolled_ids = NULL; + self->enrolled_num = -1; +} + +static void +fpi_device_egismoc_init (FpiDeviceEgisMoc *self) +{ + G_DEBUG_HERE (); +} + +static void +fpi_device_egismoc_class_init (FpiDeviceEgisMocClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = EGISMOC_DRIVER_FULLNAME; + + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + dev_class->id_table = egismoc_id_table; + dev_class->nr_enroll_stages = EGISMOC_ENROLL_TIMES; + dev_class->temp_hot_seconds = 0; /* device should be "always off" unless being used */ + + dev_class->open = egismoc_open; + dev_class->cancel = egismoc_cancel; + dev_class->suspend = egismoc_cancel; + dev_class->close = egismoc_close; + dev_class->identify = egismoc_identify_verify; + dev_class->verify = egismoc_identify_verify; + dev_class->enroll = egismoc_enroll; + dev_class->delete = egismoc_delete; + dev_class->clear_storage = egismoc_clear_storage; + dev_class->list = egismoc_list; + + fpi_device_class_auto_initialize_features (dev_class); + dev_class->features |= FP_DEVICE_FEATURE_DUPLICATES_CHECK; +} diff --git a/libfprint/drivers/egismoc/egismoc.h b/libfprint/drivers/egismoc/egismoc.h new file mode 100644 index 00000000..c30bac1b --- /dev/null +++ b/libfprint/drivers/egismoc/egismoc.h @@ -0,0 +1,231 @@ +/* + * Driver for Egis Technology (LighTuning) Match-On-Chip sensors + * Originally authored 2023 by Joshua Grisham + * + * Portions of code and logic inspired from the elanmoc libfprint driver + * which is copyright (C) 2021 Elan Microelectronics Inc (see elanmoc.c) + * + * Based on original reverse-engineering work by Joshua Grisham. The protocol has + * been reverse-engineered from captures of the official Windows driver, and by + * testing commands on the sensor with a multiplatform Python prototype driver: + * https://github.com/joshuagrisham/galaxy-book2-pro-linux/tree/main/fingerprint/ + * + * 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 (FpiDeviceEgisMoc, fpi_device_egismoc, FPI, DEVICE_EGISMOC, FpDevice) + +#define EGISMOC_DRIVER_FULLNAME "Egis Technology (LighTuning) Match-on-Chip" +#define EGISMOC_EP_CMD_OUT (0x02 | FPI_USB_ENDPOINT_OUT) +#define EGISMOC_EP_CMD_IN (0x81 | FPI_USB_ENDPOINT_IN) +#define EGISMOC_EP_CMD_INTERRUPT_IN 0x83 + +#define EGISMOC_USB_CONTROL_TIMEOUT 5000 +#define EGISMOC_USB_SEND_TIMEOUT 5000 +#define EGISMOC_USB_RECV_TIMEOUT 5000 +#define EGISMOC_USB_INTERRUPT_TIMEOUT 0 + +#define EGISMOC_USB_IN_RECV_LENGTH 4096 +#define EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH 64 + +#define EGISMOC_ENROLL_TIMES 10 +#define EGISMOC_MAX_ENROLL_NUM 10 +#define EGISMOC_FINGERPRINT_DATA_SIZE 32 +#define EGISMOC_LIST_RESPONSE_PREFIX_SIZE 14 +#define EGISMOC_LIST_RESPONSE_SUFFIX_SIZE 2 + +struct _FpiDeviceEgisMoc +{ + FpDevice parent; + FpiSsm *task_ssm; + FpiSsm *cmd_ssm; + FpiUsbTransfer *cmd_transfer; + GCancellable *interrupt_cancellable; + + int enrolled_num; + GPtrArray *enrolled_ids; +}; + + +/* standard prefixes for all read/writes */ + +static guchar egismoc_write_prefix[] = {'E', 'G', 'I', 'S', 0x00, 0x00, 0x00, 0x01}; +static gsize egismoc_write_prefix_len = sizeof (egismoc_write_prefix) / sizeof (egismoc_write_prefix[0]); + +static guchar egismoc_read_prefix[] = {'S', 'I', 'G', 'E', 0x00, 0x00, 0x00, 0x01}; +static gsize egismoc_read_prefix_len = sizeof (egismoc_read_prefix) / sizeof (egismoc_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_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_prefix[] = {0x00, 0x00, 0x00, 0x42}; +static gsize rsp_identify_match_prefix_len = sizeof (rsp_identify_match_prefix) / sizeof (rsp_identify_match_prefix[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_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x90, 0x04}; +static gsize rsp_identify_notmatch_prefix_len = sizeof (rsp_identify_notmatch_prefix) / sizeof (rsp_identify_notmatch_prefix[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[] = {0x0a, 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[] = {0x0a, 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]); + + +/* commands which exist on the device but are currently not used */ +/* + static guchar cmd_sensor_cancel[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x16, 0x04, 0x00}; + static gsize cmd_sensor_cancel_len = sizeof(cmd_sensor_cancel) / sizeof(cmd_sensor_cancel[0]); + + static guchar cmd_sensor_verify[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x04, 0x01, 0x00}; + static gsize cmd_sensor_verify_len = sizeof(cmd_sensor_verify) / sizeof(cmd_sensor_verify[0]); + + static guchar cmd_read_verify[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x04, 0x02, 0x00}; + static gsize cmd_read_verify_len = sizeof(cmd_read_verify) / sizeof(cmd_read_verify[0]); + */ + + +/* prefixes/suffixes and other things for dynamically created command payloads */ + +#define EGISMOC_CHECK_BYTES_LENGTH 2 +#define EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET 46 +#define EGISMOC_CMD_CHECK_SEPARATOR_LENGTH 32 + +static guchar cmd_new_print_prefix[] = {0x00, 0x00, 0x00, 0x27, 0x50, 0x16, 0x03, 0x00, 0x00, 0x00, 0x20}; +static gsize cmd_new_print_prefix_len = sizeof (cmd_new_print_prefix) / sizeof (cmd_new_print_prefix[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[] = {0x50, 0x17, 0x03, 0x00, 0x00}; +static gsize cmd_check_prefix_len = sizeof (cmd_check_prefix) / sizeof (cmd_check_prefix[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_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x90}; +static gsize rsp_check_not_yet_enrolled_prefix_len = sizeof (rsp_check_not_yet_enrolled_prefix) / sizeof (rsp_check_not_yet_enrolled_prefix[0]); + + +/* SSM task states and various status enums */ + +typedef enum { + CMD_SEND, + CMD_GET, + CMD_STATES, +} CommandStates; + +typedef enum { + DEV_INIT_CONTROL1, + DEV_INIT_CONTROL2, + DEV_INIT_CONTROL3, + DEV_INIT_CONTROL4, + DEV_INIT_CONTROL5, + DEV_GET_FW_VERSION, + 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 da8285e5..8eed4dd5 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -119,6 +119,8 @@ driver_sources = { [ 'drivers/etes603.c' ], 'egis0570' : [ 'drivers/egis0570.c' ], + 'egismoc' : + [ 'drivers/egismoc/egismoc.c' ], 'vfs0050' : [ 'drivers/vfs0050.c' ], 'elan' : diff --git a/meson.build b/meson.build index aeef6911..10d17e75 100644 --- a/meson.build +++ b/meson.build @@ -120,6 +120,7 @@ default_drivers = [ 'vfs0050', 'etes603', 'egis0570', + 'egismoc', 'vcom5s', 'synaptics', 'elan', diff --git a/tests/egismoc/custom.pcapng b/tests/egismoc/custom.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..fcd8119ed5643faaa19e43c62c678d40ad71abb0 GIT binary patch literal 73752 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 literal 0 HcmV?d00001 diff --git a/tests/egismoc/custom.py b/tests/egismoc/custom.py new file mode 100755 index 00000000..3a662380 --- /dev/null +++ b/tests/egismoc/custom.py @@ -0,0 +1,156 @@ +#!/usr/bin/python3 + +import traceback +import sys +import time +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 + +d.open_sync() + +assert d.get_driver() == "egismoc" +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.DUPLICATES_CHECK) +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) + +def enroll_progress(*args): + print("finger status: ", d.get_finger_status()) + print('enroll progress: ' + str(args)) + +def identify_done(dev, res): + global identified + identified = True + identify_match, identify_print = dev.identify_finish(res) + print('indentification_done: ', identify_match, identify_print) + assert identify_match.equal(identify_print) + +# Beginning with list and clear assumes you begin with >0 prints enrolled before capturing + +print("listing - device should have prints") +stored = d.list_prints_sync() +assert len(stored) > 0 +del stored + +print("clear device storage") +d.clear_storage_sync() +print("clear done") + +print("listing - device should be empty") +stored = d.list_prints_sync() +assert len(stored) == 0 +del stored + +print("enrolling") +template = FPrint.Print.new(d) +template.set_finger(FPrint.Finger.LEFT_INDEX) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p1 = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll done") +del template + +print("listing - device should have 1 print") +stored = d.list_prints_sync() +assert len(stored) == 1 +assert stored[0].equal(p1) + +print("verifying") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +verify_res, verify_print = d.verify_sync(p1) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("verify done") +assert verify_res == True + +identified = False +deserialized_prints = [] +for p in stored: + deserialized_prints.append(FPrint.Print.deserialize(p.serialize())) + assert deserialized_prints[-1].equal(p) +del stored + +print('async identifying') +d.identify(deserialized_prints, callback=identify_done) +del deserialized_prints + +while not identified: + ctx.iteration(True) + +print("try to enroll duplicate") +template = FPrint.Print.new(d) +template.set_finger(FPrint.Finger.RIGHT_INDEX) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +try: + d.enroll_sync(template, None, enroll_progress, None) +except GLib.Error as error: + assert error.matches(FPrint.DeviceError.quark(), + FPrint.DeviceError.DATA_DUPLICATE) +except Exception as exc: + raise +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("duplicate enroll attempt done") + +print("listing - device should still only have 1 print") +stored = d.list_prints_sync() +assert len(stored) == 1 +assert stored[0].equal(p1) +del stored + +print("enroll new finger") +template = FPrint.Print.new(d) +template.set_finger(FPrint.Finger.RIGHT_INDEX) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p2 = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll new finger done") +del template + +print("listing - device should have 2 prints") +stored = d.list_prints_sync() +assert len(stored) == 2 +assert (stored[0].equal(p1) and stored[1].equal(p2)) or (stored[0].equal(p2) and stored[1].equal(p1)) +del stored + +print("deleting first print") +d.delete_print_sync(p1) +print("delete done") +del p1 + +print("listing - device should only have second print") +stored = d.list_prints_sync() +assert len(stored) == 1 +assert stored[0].equal(p2) +del stored +del p2 + +print("clear device storage") +d.clear_storage_sync() +print("clear done") + +print("listing - device should be empty") +stored = d.list_prints_sync() +assert len(stored) == 0 +del stored + +d.close_sync() + +del d +del c diff --git a/tests/egismoc/device b/tests/egismoc/device new file mode 100644 index 00000000..6bd912a0 --- /dev/null +++ b/tests/egismoc/device @@ -0,0 +1,262 @@ +P: /devices/pci0000:00/0000:00:14.0/usb3/3-5 +N: bus/usb/003/012=12010002FF0000407A1C820581110102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005 +E: BUSNUM=003 +E: CURRENT_TAGS=:snap_cups_ippeveprinter:snap_cups_cupsd: +E: DEVNAME=/dev/bus/usb/003/012 +E: DEVNUM=012 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: ID_BUS=usb +E: ID_MODEL=ETU905A80-E +E: ID_MODEL_ENC=ETU905A80-E +E: ID_MODEL_ID=0582 +E: ID_PATH=pci-0000:00:14.0-usb-0:5 +E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_5 +E: ID_REVISION=1181 +E: ID_SERIAL=EGIS_ETU905A80-E_0E7828PBS393 +E: ID_SERIAL_SHORT=0E7828PBS393 +E: ID_USB_INTERFACES=:ff0000: +E: ID_USB_MODEL=ETU905A80-E +E: ID_USB_MODEL_ENC=ETU905A80-E +E: ID_USB_MODEL_ID=0582 +E: ID_USB_REVISION=1181 +E: ID_USB_SERIAL=EGIS_ETU905A80-E_0E7828PBS393 +E: ID_USB_SERIAL_SHORT=0E7828PBS393 +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=267 +E: PRODUCT=1c7a/582/1181 +E: SUBSYSTEM=usb +E: TAGS=:snap_cups_ippeveprinter:snap_cups_cupsd: +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=1181\n +A: bmAttributes=a0\n +A: busnum=3\n +A: configuration= +H: descriptors=12010002FF0000407A1C820581110102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005 +A: dev=189:267\n +A: devnum=12\n +A: devpath=5\n +L: driver=../../../../../bus/usb/drivers/usb +L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:51/device:52/device:57 +A: idProduct=0582\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=center\n +A: physical_location/lid=no\n +A: physical_location/panel=unknown\n +A: physical_location/vertical_position=center\n +L: port=../3-0:1.0/usb3-port5 +A: power/active_duration=1425996\n +A: power/async=enabled\n +A: power/autosuspend=2\n +A: power/autosuspend_delay_ms=2000\n +A: power/connected_duration=1426656\n +A: power/control=on\n +A: power/level=on\n +A: power/persist=0\n +A: power/runtime_active_kids=0\n +A: power/runtime_active_time=1426124\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=ETU905A80-E\n +A: quirks=0x0\n +A: removable=fixed\n +A: rx_lanes=1\n +A: serial=0E7828PBS393\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=2803\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0/usb3 +N: bus/usb/003/001=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C +E: BUSNUM=003 +E: CURRENT_TAGS=:seat:snap_cups_cupsd:snap_cups_ippeveprinter: +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_00_14_0 +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:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_REVISION=0602 +E: ID_SERIAL=Linux_6.2.0-34-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_SERIAL_SHORT=0000:00:14.0 +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=0602 +E: ID_USB_SERIAL=Linux_6.2.0-34-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_USB_SERIAL_SHORT=0000:00:14.0 +E: ID_USB_VENDOR=Linux_6.2.0-34-generic_xhci-hcd +E: ID_USB_VENDOR_ENC=Linux\x206.2.0-34-generic\x20xhci-hcd +E: ID_USB_VENDOR_ID=1d6b +E: ID_VENDOR=Linux_6.2.0-34-generic_xhci-hcd +E: ID_VENDOR_ENC=Linux\x206.2.0-34-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/602 +E: SUBSYSTEM=usb +E: TAGS=:snap_cups_cupsd:seat:snap_cups_ippeveprinter: +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=0602\n +A: bmAttributes=e0\n +A: busnum=3\n +A: configuration= +H: descriptors=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C +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:51/device:52 +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 6.2.0-34-generic xhci-hcd\n +A: maxchild=12\n +A: power/active_duration=337953872\n +A: power/async=enabled\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=337978524\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=337962424\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=616\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:00:14.0\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=4969\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0 +E: DRIVER=xhci_hcd +E: ID_MODEL_FROM_DATABASE=Alder Lake PCH USB 3.2 xHCI Host Controller +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=Intel Corporation +E: MODALIAS=pci:v00008086d000051EDsv0000144Dsd0000C870bc0Csc03i30 +E: PCI_CLASS=C0330 +E: PCI_ID=8086:51ED +E: PCI_SLOT_NAME=0000:00:14.0 +E: PCI_SUBSYS_ID=144D:C870 +E: SUBSYSTEM=pci +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=8680ED51060490020130030C000080000400161D6000000000000000000000000000000000000000000000004D1470C8000000007000000000000000FF010000FD0134A089C27F8000000000000000003F6DD80F000000000000000000000000316000000000000000000000000000000180C2C1080000000000000000000000059087007805E0FE000000000000000009B014F01000400100000000C10A080000080E00001800008F50020000010000090000018680C00009001014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B50F010112000000 +A: consistent_dma_mask_bits=64\n +A: d3cold_allowed=1\n +A: dbc=disabled\n +A: device=0x51ed\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:51 +A: index=7\n +L: iommu=../../virtual/iommu/dmar1 +L: iommu_group=../../../kernel/iommu_groups/8 +A: irq=145\n +A: label=Onboard - Other\n +A: local_cpulist=0-15\n +A: local_cpus=ffff\n +A: modalias=pci:v00008086d000051EDsv0000144Dsd0000C870bc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/145=msi\n +A: numa_node=-1\n +A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 6 9 2112 9\nxHCI ring segments 26 34 4096 34\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 32 128 1\nbuffer-32 0 0 32 0\n +A: power/async=enabled\n +A: power/control=auto\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=337964621\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=438\n +A: power/runtime_usage=0\n +A: power/wakeup=enabled\n +A: power/wakeup_abort_count=0\n +A: power/wakeup_active=0\n +A: power/wakeup_active_count=7\n +A: power/wakeup_count=0\n +A: power/wakeup_expire_count=7\n +A: power/wakeup_last_time_ms=336554844\n +A: power/wakeup_max_time_ms=105\n +A: power/wakeup_total_time_ms=721\n +A: power_state=D0\n +A: resource=0x000000601d160000 0x000000601d16ffff 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=0x01\n +A: subsystem_device=0xc870\n +A: subsystem_vendor=0x144d\n +A: vendor=0x8086\n + diff --git a/tests/meson.build b/tests/meson.build index 199500d2..c919ec6e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -51,6 +51,7 @@ drivers_tests = [ 'goodixmoc', 'nb1010', 'egis0570', + 'egismoc', 'fpcmoc', 'realtek', 'focaltech_moc',