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 00000000..fcd8119e Binary files /dev/null and b/tests/egismoc/custom.pcapng differ 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',