From 79be91831c564f6c8b1af4c0c52c330632923acb Mon Sep 17 00:00:00 2001 From: huan_huang Date: Fri, 4 Aug 2023 10:21:17 +0800 Subject: [PATCH] drivers: add realtek rts5813 driver --- data/autosuspend.hwdb | 5 + libfprint/drivers/realtek/realtek.c | 1220 +++++++++++++++++++++++++++ libfprint/drivers/realtek/realtek.h | 220 +++++ libfprint/meson.build | 2 + meson.build | 1 + tests/meson.build | 1 + tests/realtek/custom.pcapng | Bin 0 -> 29128 bytes tests/realtek/custom.py | 110 +++ tests/realtek/device | 240 ++++++ 9 files changed, 1799 insertions(+) create mode 100644 libfprint/drivers/realtek/realtek.c create mode 100644 libfprint/drivers/realtek/realtek.h create mode 100644 tests/realtek/custom.pcapng create mode 100755 tests/realtek/custom.py create mode 100644 tests/realtek/device diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 35abc907..2b4dc706 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -202,6 +202,11 @@ usb:v298Dp1010* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver realtek +usb:v0BDAp5813* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver synaptics usb:v06CBp00BD* usb:v06CBp00DF* diff --git a/libfprint/drivers/realtek/realtek.c b/libfprint/drivers/realtek/realtek.c new file mode 100644 index 00000000..87b8c805 --- /dev/null +++ b/libfprint/drivers/realtek/realtek.c @@ -0,0 +1,1220 @@ +/* + * Copyright (C) 2022-2023 Realtek Corp. + * + * 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 "realtek" + +#include "drivers_api.h" + +#include "fpi-byte-reader.h" + +#include "realtek.h" + +G_DEFINE_TYPE (FpiDeviceRealtek, fpi_device_realtek, FP_TYPE_DEVICE) + +static const FpIdEntry id_table[] = { + { .vid = 0x0bda, .pid = 0x5813, }, + { .vid = 0, .pid = 0, .driver_data = 0 }, /* terminating entry */ +}; + +static gboolean +parse_print_data (GVariant *data, + guint8 *finger, + const guint8 **user_id, + gsize *user_id_len) +{ + g_autoptr(GVariant) user_id_var = NULL; + + g_return_val_if_fail (data, FALSE); + g_return_val_if_fail (finger, FALSE); + g_return_val_if_fail (user_id, FALSE); + g_return_val_if_fail (user_id_len, FALSE); + + *user_id = NULL; + *user_id_len = 0; + *finger = 0; + + if (!g_variant_check_format_string (data, "(y@ay)", FALSE)) + return FALSE; + + g_variant_get (data, + "(y@ay)", + finger, + &user_id_var); + + *user_id = g_variant_get_fixed_array (user_id_var, user_id_len, 1); + + if (*user_id_len <= 0 || *user_id_len > DEFAULT_UID_LEN) + return FALSE; + + if (*user_id[0] == '\0' || *user_id[0] == ' ') + return FALSE; + + if (*finger != SUB_FINGER_01) + return FALSE; + + return TRUE; +} + +static void +fp_cmd_ssm_done_data_free (CommandData *data) +{ + g_free (data); +} + +/* data callbacks */ + +static void +fp_task_ssm_generic_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fp_finish_capture_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + gint capture_status = buffer_in[0]; + if (capture_status == 0) + { + fpi_device_report_finger_status_changes (FP_DEVICE (self), + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NEEDED); + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_ssm_jump_to_state (self->task_ssm, + fpi_ssm_get_cur_state (self->task_ssm)); + } +} + +static void +fp_accept_sample_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + fpi_device_report_finger_status_changes (FP_DEVICE (self), + FP_FINGER_STATUS_NONE, + FP_FINGER_STATUS_PRESENT); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + gint in_status = buffer_in[0]; + + if (self->fp_purpose != FP_RTK_PURPOSE_ENROLL) + { + /* verify or identify purpose process */ + fpi_ssm_next_state (self->task_ssm); + return; + } + else + { + /* enroll purpose process */ + if (in_status == FP_RTK_CMD_ERR) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Command error!")); + return; + } + + if (self->enroll_stage < self->max_enroll_stage) + { + if (in_status == FP_RTK_SUCCESS) + { + self->enroll_stage++; + fpi_device_enroll_progress (FP_DEVICE (self), self->enroll_stage, NULL, NULL); + fpi_ssm_jump_to_state (self->task_ssm, FP_RTK_ENROLL_CAPTURE); + } + else if (in_status == FP_RTK_MATCH_FAIL) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "InStatus invalid!")); + } + else + { + fpi_device_enroll_progress (FP_DEVICE (self), + self->enroll_stage, + NULL, + fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); + + fpi_ssm_jump_to_state (self->task_ssm, FP_RTK_ENROLL_CAPTURE); + } + return; + } + fpi_ssm_next_state (self->task_ssm); + } +} + +static FpPrint * +fp_print_from_data (FpiDeviceRealtek *self, uint8_t *buffer) +{ + FpPrint *print; + GVariant *data; + GVariant *uid; + guint finger; + gsize userid_len; + g_autofree gchar *userid = NULL; + + userid = g_strndup ((gchar *) buffer + 1, DEFAULT_UID_LEN); + finger = *(buffer); + + print = fp_print_new (FP_DEVICE (self)); + userid_len = MIN (DEFAULT_UID_LEN, strlen (userid)); + + uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + userid, + userid_len, + 1); + + data = g_variant_new ("(y@ay)", + finger, + uid); + + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + g_object_set (print, "fpi-data", data, NULL); + g_object_set (print, "description", userid, NULL); + fpi_print_fill_from_user_id (print, userid); + + return print; +} + +static void +fp_identify_feature_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + FpDevice *device = FP_DEVICE (self); + FpPrint *match = NULL; + FpPrint *print = NULL; + FpiDeviceAction current_action; + + g_autoptr(GPtrArray) templates = NULL; + gboolean found = FALSE; + + current_action = fpi_device_get_current_action (device); + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + gint in_status = buffer_in[0]; + if (in_status == FP_RTK_CMD_ERR) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Command error!")); + return; + } + + if (in_status >= FP_RTK_TOO_HIGH && in_status <= FP_RTK_MERGE_FAILURE) + { + GError *retry_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); + retry_error = fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL); + fpi_ssm_mark_failed (self->task_ssm, retry_error); + return; + } + + if (in_status == FP_RTK_SUCCESS) + { + match = fp_print_from_data (self, buffer_in + 1); + + if (current_action == FPI_DEVICE_ACTION_VERIFY) + { + templates = g_ptr_array_sized_new (1); + fpi_device_get_verify_data (device, &print); + g_ptr_array_add (templates, print); + } + else + { + fpi_device_get_identify_data (device, &templates); + g_ptr_array_ref (templates); + } + + for (gint cnt = 0; cnt < templates->len; cnt++) + { + print = g_ptr_array_index (templates, cnt); + + if (fp_print_equal (print, match)) + { + found = TRUE; + break; + } + } + + if (found) + { + if (current_action == FPI_DEVICE_ACTION_VERIFY) + { + fpi_device_verify_report (device, FPI_MATCH_SUCCESS, match, error); + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_device_identify_report (device, print, match, error); + fpi_ssm_mark_completed (self->task_ssm); + } + return; + } + } + + if (!found) + { + if (current_action == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, error); + else + fpi_device_identify_report (device, NULL, NULL, error); + + fpi_ssm_jump_to_state (self->task_ssm, FP_RTK_VERIFY_NUM_STATES); + } +} + +static void +fp_get_delete_pos_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + FpPrint *print = NULL; + + g_autoptr(GVariant) data = NULL; + gsize user_id_len = 0; + const guint8 *user_id; + guint8 finger; + gboolean found = FALSE; + gchar temp_userid[DEFAULT_UID_LEN + 1] = {0}; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fpi_device_get_delete_data (FP_DEVICE (self), &print); + g_object_get (print, "fpi-data", &data, NULL); + + if (!parse_print_data (data, &finger, &user_id, &user_id_len)) + { + fpi_device_delete_complete (FP_DEVICE (self), + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + + for (gint i = 0; i < self->template_num; i++) + { + if (buffer_in[i * TEMPLATE_LEN] != 0) + { + memcpy (temp_userid, buffer_in + i * TEMPLATE_LEN + UID_OFFSET, DEFAULT_UID_LEN); + if (g_strcmp0 (fp_print_get_description (print), (const char *) temp_userid) == 0) + { + self->pos_index = i; + found = TRUE; + break; + } + } + } + + if (!found) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Get template position failed!")); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fp_get_enroll_num_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + self->template_num = buffer_in[1]; + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fp_get_template_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + gboolean found = FALSE; + + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + for (gint i = 0; i < self->template_num; i++) + { + if (buffer_in[i * TEMPLATE_LEN] == 0) + { + self->pos_index = i; + found = TRUE; + break; + } + } + + if (!found) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "No free template was found!")); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fp_check_duplicate_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + gint in_status = buffer_in[0]; + if (in_status == FP_RTK_CMD_ERR) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Command error!")); + return; + } + + if (in_status == FP_RTK_SUCCESS) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Current fingerprint is duplicate!")); + } + else if (in_status == FP_RTK_MATCH_FAIL) + { + fpi_ssm_next_state (self->task_ssm); + } + else + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "InStatus invalid!")); + } +} + +static void +fp_list_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + gboolean found = FALSE; + + g_autoptr(GPtrArray) list_result = NULL; + + if (error) + { + fpi_device_list_complete (FP_DEVICE (self), NULL, error); + return; + } + + list_result = g_ptr_array_new_with_free_func (g_object_unref); + + for (gint i = 0; i < self->template_num; i++) + { + if (buffer_in[i * TEMPLATE_LEN] != 0) + { + FpPrint *print = NULL; + print = fp_print_from_data (self, buffer_in + i * TEMPLATE_LEN + SUBFACTOR_OFFSET); + g_ptr_array_add (list_result, g_object_ref_sink (print)); + found = TRUE; + } + } + + if (!found) + { + fpi_device_list_complete (FP_DEVICE (self), + g_steal_pointer (&list_result), + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_FULL, + "Database is empty")); + return; + } + + fp_info ("Query templates complete!"); + fpi_device_list_complete (FP_DEVICE (self), + g_steal_pointer (&list_result), + NULL); +} + +static void +fp_clear_storage_cb (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error) +{ + FpDevice *device = FP_DEVICE (self); + + if (error) + { + fpi_device_clear_storage_complete (device, error); + return; + } + + fp_info ("Successfully cleared storage"); + fpi_device_clear_storage_complete (device, NULL); +} + + +static gint +parse_status (guint8 *buffer, gint status_type) +{ + switch (status_type) + { + case FP_RTK_MSG_PLAINTEXT_NO_STATUS: + return 0; + break; + + case FP_RTK_MSG_PLAINTEXT: + return buffer[0]; + break; + + default: + return 1; + break; + } +} + +static void +fp_cmd_receive_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer user_data, + GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + CommandData *data = user_data; + gint ssm_state = 0; + gint status_flag = 1; + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + if (data == NULL) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + ssm_state = fpi_ssm_get_cur_state (transfer->ssm); + + /* skip zero length package */ + if (transfer->actual_length == 0) + { + fpi_ssm_jump_to_state (transfer->ssm, ssm_state); + return; + } + + /* get data */ + if (ssm_state == FP_RTK_CMD_TRANS_DATA) + { + g_autofree guchar *read_buf = NULL; + + read_buf = g_malloc0 (sizeof (guchar) * (self->trans_data_len)); + memcpy (read_buf, transfer->buffer, self->trans_data_len); + self->read_data = g_steal_pointer (&read_buf); + + fpi_ssm_next_state (transfer->ssm); + return; + } + + /* get status */ + status_flag = parse_status (transfer->buffer, self->message_type); + if (status_flag != 0) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "Status check failed")); + return; + } + + if (data->callback) + data->callback (self, self->read_data, NULL); + + if (self->read_data) + g_clear_pointer (&self->read_data, g_free); + + fpi_ssm_mark_completed (transfer->ssm); +} + +static void +fp_cmd_run_state (FpiSsm *ssm, FpDevice *dev) +{ + FpiUsbTransfer *transfer = NULL; + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_CMD_SEND: + if (self->cmd_transfer) + { + self->cmd_transfer->ssm = ssm; + fpi_usb_transfer_submit (g_steal_pointer (&self->cmd_transfer), + CMD_TIMEOUT, + NULL, + fpi_ssm_usb_transfer_cb, + NULL); + } + else + { + fpi_ssm_next_state (ssm); + } + break; + + case FP_RTK_CMD_TRANS_DATA: + if (self->cmd_type == FP_RTK_CMD_ONLY) + { + fpi_ssm_jump_to_state (ssm, FP_RTK_CMD_GET_STATUS); + break; + } + + if (self->cmd_type == FP_RTK_CMD_WRITE) + { + if (self->data_transfer) + { + self->data_transfer->ssm = ssm; + fpi_usb_transfer_submit (g_steal_pointer (&self->data_transfer), + DATA_TIMEOUT, + NULL, + fpi_ssm_usb_transfer_cb, + NULL); + } + else + { + fpi_ssm_next_state (ssm); + } + } + else /* CMD_READ */ + { + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, EP_IN, EP_IN_MAX_BUF_SIZE); + + fpi_usb_transfer_submit (transfer, + self->cmd_cancellable ? 0 : DATA_TIMEOUT, + self->cmd_cancellable ? fpi_device_get_cancellable (dev) : NULL, + fp_cmd_receive_cb, + fpi_ssm_get_data (ssm)); + } + break; + + case FP_RTK_CMD_GET_STATUS: + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, EP_IN, EP_IN_MAX_BUF_SIZE); + fpi_usb_transfer_submit (transfer, + STATUS_TIMEOUT, + NULL, + fp_cmd_receive_cb, + fpi_ssm_get_data (ssm)); + break; + } +} + +static void +fp_cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + CommandData *data = fpi_ssm_get_data (ssm); + + self->cmd_ssm = NULL; + + if (error) + { + if (data->callback) + data->callback (self, NULL, error); + else + g_error_free (error); + } +} + +static FpiUsbTransfer * +prepare_transfer (FpDevice *dev, + guint8 *data, + gsize data_len, + GDestroyNotify free_func) +{ + g_autoptr(FpiUsbTransfer) transfer = NULL; + + g_return_val_if_fail (data || data_len == 0, NULL); + + transfer = fpi_usb_transfer_new (dev); + + fpi_usb_transfer_fill_bulk_full (transfer, + EP_OUT, + data, + data_len, + free_func); + + return g_steal_pointer (&transfer); +} + +static void +realtek_sensor_cmd (FpiDeviceRealtek *self, + guint8 *cmd, + guint8 *trans_data, + FpRtkMsgType message_type, + gboolean bwait_data_delay, + SynCmdMsgCallback callback) +{ + g_autoptr(FpiUsbTransfer) cmd_transfer = NULL; + g_autoptr(FpiUsbTransfer) data_transfer = NULL; + CommandData *data = g_new0 (CommandData, 1); + + self->cmd_type = GET_CMD_TYPE (cmd[0]); + self->message_type = message_type; + self->trans_data_len = GET_TRANS_DATA_LEN (cmd[11], cmd[10]); + self->cmd_cancellable = bwait_data_delay; + + cmd_transfer = prepare_transfer (FP_DEVICE (self), cmd, FP_RTK_CMD_TOTAL_LEN, NULL); + self->cmd_transfer = g_steal_pointer (&cmd_transfer); + + if ((self->cmd_type == FP_RTK_CMD_WRITE) && trans_data) + { + data_transfer = prepare_transfer (FP_DEVICE (self), trans_data, self->trans_data_len, g_free); + self->data_transfer = g_steal_pointer (&data_transfer); + } + + self->cmd_ssm = fpi_ssm_new (FP_DEVICE (self), + fp_cmd_run_state, + FP_RTK_CMD_NUM_STATES); + + data->callback = callback; + fpi_ssm_set_data (self->cmd_ssm, data, (GDestroyNotify) fp_cmd_ssm_done_data_free); + + fpi_ssm_start (self->cmd_ssm, fp_cmd_ssm_done); +} + +static void +fp_verify_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + + fp_info ("Verify complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + if (error && error->domain == FP_DEVICE_RETRY) + { + if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (dev, FPI_MATCH_ERROR, NULL, g_steal_pointer (&error)); + else + fpi_device_identify_report (dev, NULL, NULL, g_steal_pointer (&error)); + } + + if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_complete (dev, error); + else + fpi_device_identify_complete (dev, error); + + self->task_ssm = NULL; +} + +static void +fp_enroll_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + FpPrint *print = NULL; + + fp_info ("Enrollment complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + if (error) + { + fpi_device_enroll_complete (dev, NULL, error); + self->task_ssm = NULL; + return; + } + + fpi_device_get_enroll_data (FP_DEVICE (self), &print); + fpi_device_enroll_complete (FP_DEVICE (self), g_object_ref (print), NULL); + self->task_ssm = NULL; +} + +static void +fp_init_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + + fp_info ("Init complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + fpi_device_open_complete (dev, error); + self->task_ssm = NULL; +} + +static void +fp_delete_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (dev); + + fp_info ("Delete print complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + fpi_device_delete_complete (dev, error); + self->task_ssm = NULL; +} + +static void +fp_verify_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_VERIFY_CAPTURE: + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + + cmd_buf = (guint8 *) &co_start_capture; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_task_ssm_generic_cb); + break; + + case FP_RTK_VERIFY_FINISH_CAPTURE: + cmd_buf = (guint8 *) &co_finish_capture; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_finish_capture_cb); + break; + + case FP_RTK_VERIFY_ACCEPT_SAMPLE: + co_accept_sample.param[0] = self->fp_purpose; + cmd_buf = (guint8 *) &co_accept_sample; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT_NO_STATUS, 1, fp_accept_sample_cb); + break; + + case FP_RTK_VERIFY_INDENTIFY_FEATURE: + cmd_buf = (guint8 *) &tls_identify_feature; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT_NO_STATUS, 0, fp_identify_feature_cb); + break; + + case FP_RTK_VERIFY_UPDATE_TEMPLATE: + cmd_buf = (guint8 *) &co_update_template; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + } +} + +static void +fp_enroll_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + FpPrint *print = NULL; + guint8 *cmd_buf = NULL; + + g_autofree gchar *user_id = NULL; + g_autofree guint8 *payload = NULL; + + GVariant *uid = NULL; + GVariant *data = NULL; + gsize user_id_len; + guint finger; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_ENROLL_GET_TEMPLATE: + g_assert (self->template_num > 0); + + co_get_template.data_len[0] = GET_LEN_L (TEMPLATE_LEN * self->template_num); + co_get_template.data_len[1] = GET_LEN_H (TEMPLATE_LEN * self->template_num); + + cmd_buf = (guint8 *) &co_get_template; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_get_template_cb); + break; + + case FP_RTK_ENROLL_BEGIN_POS: + tls_enroll_begin.param[0] = self->pos_index; + cmd_buf = (guint8 *) &tls_enroll_begin; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + + case FP_RTK_ENROLL_CAPTURE: + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + + cmd_buf = (guint8 *) &co_start_capture; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_task_ssm_generic_cb); + break; + + case FP_RTK_ENROLL_FINISH_CAPTURE: + cmd_buf = (guint8 *) &co_finish_capture; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_finish_capture_cb); + break; + + case FP_RTK_ENROLL_ACCEPT_SAMPLE: + co_accept_sample.param[0] = self->fp_purpose; + cmd_buf = (guint8 *) &co_accept_sample; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT_NO_STATUS, 1, fp_accept_sample_cb); + break; + + case FP_RTK_ENROLL_CHECK_DUPLICATE: + cmd_buf = (guint8 *) &co_check_duplicate; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT_NO_STATUS, 1, fp_check_duplicate_cb); + break; + + case FP_RTK_ENROLL_COMMIT: + guint8 *trans_id = NULL; + + fpi_device_get_enroll_data (device, &print); + user_id = fpi_print_generate_user_id (print); + user_id_len = strlen (user_id); + user_id_len = MIN (DEFAULT_UID_LEN, user_id_len); + + payload = g_malloc0 (UID_PAYLOAD_LEN); + memcpy (payload, user_id, user_id_len); + + trans_id = g_steal_pointer (&payload); + + finger = SUB_FINGER_01; + uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + user_id, + user_id_len, + 1); + data = g_variant_new ("(y@ay)", + finger, + uid); + + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + g_object_set (print, "fpi-data", data, NULL); + g_object_set (print, "description", user_id, NULL); + + g_debug ("user_id: %s, finger: 0x%x", user_id, finger); + + tls_enroll_commit.param[0] = SUB_FINGER_01; + cmd_buf = (guint8 *) &tls_enroll_commit; + realtek_sensor_cmd (self, cmd_buf, trans_id, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + } +} + +static void +fp_init_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_INIT_SELECT_OS: + co_select_system.param[0] = 0x01; + cmd_buf = (guint8 *) &co_select_system; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + + case FP_RTK_INIT_GET_ENROLL_NUM: + cmd_buf = (guint8 *) &co_get_enroll_num; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_get_enroll_num_cb); + break; + } +} + +static void +fp_delete_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_RTK_DELETE_GET_POS: + g_assert (self->template_num > 0); + + co_get_template.data_len[0] = GET_LEN_L (TEMPLATE_LEN * self->template_num); + co_get_template.data_len[1] = GET_LEN_H (TEMPLATE_LEN * self->template_num); + + cmd_buf = (guint8 *) &co_get_template; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_get_delete_pos_cb); + break; + + case FP_RTK_DELETE_PRINT: + co_delete_record.param[0] = self->pos_index; + cmd_buf = (guint8 *) &co_delete_record; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_task_ssm_generic_cb); + break; + } +} + + +static void +identify_verify (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + FpiDeviceAction current_action; + + G_DEBUG_HERE (); + current_action = fpi_device_get_current_action (device); + + g_assert (current_action == FPI_DEVICE_ACTION_VERIFY || + current_action == FPI_DEVICE_ACTION_IDENTIFY); + + if (current_action == FPI_DEVICE_ACTION_IDENTIFY) + self->fp_purpose = FP_RTK_PURPOSE_IDENTIFY; + else + self->fp_purpose = FP_RTK_PURPOSE_VERIFY; + + g_assert (!self->task_ssm); + + self->task_ssm = fpi_ssm_new_full (device, + fp_verify_sm_run_state, + FP_RTK_VERIFY_NUM_STATES, + FP_RTK_VERIFY_NUM_STATES, + "Verify & Identify"); + + fpi_ssm_start (self->task_ssm, fp_verify_ssm_done); +} + +static void +enroll (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + + G_DEBUG_HERE (); + self->enroll_stage = 0; + self->fp_purpose = FP_RTK_PURPOSE_ENROLL; + + g_assert (!self->task_ssm); + + self->task_ssm = fpi_ssm_new_full (device, + fp_enroll_sm_run_state, + FP_RTK_ENROLL_NUM_STATES, + FP_RTK_ENROLL_NUM_STATES, + "Enroll"); + + fpi_ssm_start (self->task_ssm, fp_enroll_ssm_done); +} + +static void +dev_probe (FpDevice *device) +{ + GUsbDevice *usb_dev; + GError *error = NULL; + g_autofree gchar *product = NULL; + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + + G_DEBUG_HERE (); + /* Claim usb interface */ + usb_dev = fpi_device_get_usb_device (device); + if (!g_usb_device_open (usb_dev, &error)) + { + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (!g_usb_device_reset (usb_dev, &error)) + { + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (!g_usb_device_claim_interface (usb_dev, 0, 0, &error)) + { + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + product = g_usb_device_get_string_descriptor (usb_dev, + g_usb_device_get_product_index (usb_dev), + &error); + + if (product) + fp_dbg ("Device name: %s", product); + + self->max_enroll_stage = MAX_ENROLL_SAMPLES; + fpi_device_set_nr_enroll_stages (device, self->max_enroll_stage); + + g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)), 0, 0, NULL); + g_usb_device_close (usb_dev, NULL); + + fpi_device_probe_complete (device, NULL, product, error); +} + +static void +dev_init (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + GError *error = NULL; + + G_DEBUG_HERE (); + if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) + { + fpi_device_open_complete (FP_DEVICE (self), error); + return; + } + + /* Claim usb interface */ + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error)) + { + fpi_device_open_complete (FP_DEVICE (self), error); + return; + } + + g_assert (!self->task_ssm); + + self->task_ssm = fpi_ssm_new_full (device, + fp_init_sm_run_state, + FP_RTK_INIT_NUM_STATES, + FP_RTK_INIT_NUM_STATES, + "Init"); + + fpi_ssm_start (self->task_ssm, fp_init_ssm_done); +} + +static void +dev_exit (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + + g_autoptr(GError) release_error = NULL; + + G_DEBUG_HERE (); + + g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (self)), 0, 0, &release_error); + + fpi_device_close_complete (device, release_error); +} + +static void +delete_print (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + + G_DEBUG_HERE (); + + g_assert (!self->task_ssm); + + self->task_ssm = fpi_ssm_new_full (device, + fp_delete_sm_run_state, + FP_RTK_DELETE_NUM_STATES, + FP_RTK_DELETE_NUM_STATES, + "Delete print"); + + fpi_ssm_start (self->task_ssm, fp_delete_ssm_done); +} + +static void +clear_storage (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + G_DEBUG_HERE (); + co_delete_record.param[0] = 0xff; + cmd_buf = (guint8 *) &co_delete_record; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 0, fp_clear_storage_cb); +} + +static void +list_print (FpDevice *device) +{ + FpiDeviceRealtek *self = FPI_DEVICE_REALTEK (device); + guint8 *cmd_buf = NULL; + + G_DEBUG_HERE (); + g_assert (self->template_num > 0); + + co_get_template.data_len[0] = GET_LEN_L (TEMPLATE_LEN * self->template_num); + co_get_template.data_len[1] = GET_LEN_H (TEMPLATE_LEN * self->template_num); + + cmd_buf = (guint8 *) &co_get_template; + realtek_sensor_cmd (self, cmd_buf, NULL, FP_RTK_MSG_PLAINTEXT, 1, fp_list_cb); +} + +static void +fpi_device_realtek_init (FpiDeviceRealtek *self) +{ +} + +static void +fpi_device_realtek_class_init (FpiDeviceRealtekClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = "Realtek MOC Fingerprint Sensor"; + + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + dev_class->id_table = id_table; + dev_class->nr_enroll_stages = MAX_ENROLL_SAMPLES; + dev_class->temp_hot_seconds = -1; + + dev_class->open = dev_init; + dev_class->close = dev_exit; + dev_class->probe = dev_probe; + dev_class->verify = identify_verify; + dev_class->identify = identify_verify; + dev_class->enroll = enroll; + dev_class->delete = delete_print; + dev_class->clear_storage = clear_storage; + dev_class->list = list_print; + + fpi_device_class_auto_initialize_features (dev_class); +} diff --git a/libfprint/drivers/realtek/realtek.h b/libfprint/drivers/realtek/realtek.h new file mode 100644 index 00000000..803922fe --- /dev/null +++ b/libfprint/drivers/realtek/realtek.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2022-2023 Realtek Corp. + * + * 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" + +#include +#include +#include + +#define EP_IN (2 | FPI_USB_ENDPOINT_IN) +#define EP_OUT (1 | FPI_USB_ENDPOINT_OUT) + +#define EP_IN_MAX_BUF_SIZE 2048 + +#define FP_RTK_CMD_TOTAL_LEN 12 +#define FP_RTK_CMD_LEN 2 +#define FP_RTK_CMD_PARAM_LEN 4 +#define FP_RTK_CMD_ADDR_LEN 4 +#define FP_RTK_CMD_DATA_LEN 2 + +#define TEMPLATE_LEN 35 +#define SUBFACTOR_OFFSET 2 +#define UID_OFFSET 3 +#define UID_PAYLOAD_LEN 32 + +/* Command transfer timeout :ms*/ +#define CMD_TIMEOUT 1000 +#define DATA_TIMEOUT 5000 +#define STATUS_TIMEOUT 2000 + +#define MAX_ENROLL_SAMPLES 8 +#define DEFAULT_UID_LEN 28 +#define SUB_FINGER_01 0xFF + +#define GET_CMD_TYPE(val) ((val & 0xC0) >> 6) +#define GET_TRANS_DATA_LEN(len_h, len_l) ((len_h << 8) | len_l) +#define GET_LEN_L(total_data_len) ((total_data_len) & 0xff) +#define GET_LEN_H(total_data_len) ((total_data_len) >> 8) + +G_DECLARE_FINAL_TYPE (FpiDeviceRealtek, fpi_device_realtek, FPI, DEVICE_REALTEK, FpDevice) + +typedef void (*SynCmdMsgCallback) (FpiDeviceRealtek *self, + uint8_t *buffer_in, + GError *error); + +typedef struct +{ + SynCmdMsgCallback callback; +} CommandData; + +typedef enum { + FP_RTK_CMD_ONLY = 0, + FP_RTK_CMD_READ, + FP_RTK_CMD_WRITE, +} FpRtkCmdType; + +typedef enum { + FP_RTK_MSG_PLAINTEXT = 0, + FP_RTK_MSG_PLAINTEXT_NO_STATUS, +} FpRtkMsgType; + +typedef enum { + FP_RTK_PURPOSE_IDENTIFY = 0x01, /* identify before enroll */ + FP_RTK_PURPOSE_VERIFY = 0x02, + FP_RTK_PURPOSE_ENROLL = 0x04, +} FpRtkPurpose; + +typedef enum { + FP_RTK_SUCCESS = 0x0, + FP_RTK_TOO_HIGH, + FP_RTK_TOO_LOW, + FP_RTK_TOO_LEFT, + FP_RTK_TOO_RIGHT, + FP_RTK_TOO_FAST, + FP_RTK_TOO_SLOW, + FP_RTK_POOR_QUALITY, + FP_RTK_TOO_SKEWED, + FP_RTK_TOO_SHORT, + FP_RTK_MERGE_FAILURE, + FP_RTK_MATCH_FAIL, + FP_RTK_CMD_ERR, +} FpRtkInStatus; + +typedef enum { + FP_RTK_ENROLL_GET_TEMPLATE = 0, + FP_RTK_ENROLL_BEGIN_POS, + FP_RTK_ENROLL_CAPTURE, + FP_RTK_ENROLL_FINISH_CAPTURE, + FP_RTK_ENROLL_ACCEPT_SAMPLE, + FP_RTK_ENROLL_CHECK_DUPLICATE, + FP_RTK_ENROLL_COMMIT, + FP_RTK_ENROLL_NUM_STATES, +} FpRtkEnrollState; + +typedef enum { + FP_RTK_VERIFY_CAPTURE = 0, + FP_RTK_VERIFY_FINISH_CAPTURE, + FP_RTK_VERIFY_ACCEPT_SAMPLE, + FP_RTK_VERIFY_INDENTIFY_FEATURE, + FP_RTK_VERIFY_UPDATE_TEMPLATE, + FP_RTK_VERIFY_NUM_STATES, +} FpRtkVerifyState; + +typedef enum { + FP_RTK_DELETE_GET_POS = 0, + FP_RTK_DELETE_PRINT, + FP_RTK_DELETE_NUM_STATES, +} FpRtkDeleteState; + +typedef enum { + FP_RTK_INIT_SELECT_OS = 0, + FP_RTK_INIT_GET_ENROLL_NUM, + FP_RTK_INIT_NUM_STATES, +} FpRtkInitState; + +typedef enum { + FP_RTK_CMD_SEND = 0, + FP_RTK_CMD_TRANS_DATA, + FP_RTK_CMD_GET_STATUS, + FP_RTK_CMD_NUM_STATES, +} FpRtkCmdState; + +struct _FpiDeviceRealtek +{ + FpDevice parent; + FpiSsm *task_ssm; + FpiSsm *cmd_ssm; + FpiUsbTransfer *cmd_transfer; + FpiUsbTransfer *data_transfer; + gint cmd_type; + FpRtkMsgType message_type; + gboolean cmd_cancellable; + gint enroll_stage; + gint max_enroll_stage; + guchar *read_data; + gsize trans_data_len; + FpRtkPurpose fp_purpose; + gint pos_index; + gint template_num; +}; + +struct realtek_fp_cmd +{ + uint8_t cmd[FP_RTK_CMD_LEN]; + uint8_t param[FP_RTK_CMD_PARAM_LEN]; + uint8_t addr[FP_RTK_CMD_ADDR_LEN]; + uint8_t data_len[FP_RTK_CMD_DATA_LEN]; +}; + +static struct realtek_fp_cmd co_start_capture = { + .cmd = {0x05, 0x05}, +}; + +static struct realtek_fp_cmd co_finish_capture = { + .cmd = {0x45, 0x06}, + .data_len = {0x05}, +}; + +static struct realtek_fp_cmd co_accept_sample = { + .cmd = {0x45, 0x08}, + .data_len = {0x09}, +}; + +static struct realtek_fp_cmd tls_identify_feature = { + .cmd = {0x45, 0x22}, + .data_len = {0x2A}, +}; + +static struct realtek_fp_cmd co_get_enroll_num = { + .cmd = {0x45, 0x0d}, + .data_len = {0x02}, +}; + +static struct realtek_fp_cmd co_get_template = { + .cmd = {0x45, 0x0E}, +}; + +static struct realtek_fp_cmd tls_enroll_begin = { + .cmd = {0x05, 0x20}, +}; + +static struct realtek_fp_cmd co_check_duplicate = { + .cmd = {0x45, 0x10}, + .data_len = {0x22}, +}; + +static struct realtek_fp_cmd tls_enroll_commit = { + .cmd = {0x85, 0x21}, + .data_len = {0x20}, +}; + +static struct realtek_fp_cmd co_update_template = { + .cmd = {0x05, 0x11}, +}; + +static struct realtek_fp_cmd co_delete_record = { + .cmd = {0x05, 0x0F}, +}; + +static struct realtek_fp_cmd co_select_system = { + .cmd = {0x05, 0x13}, +}; \ No newline at end of file diff --git a/libfprint/meson.build b/libfprint/meson.build index 7e3b771f..c2ebd8c1 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -141,6 +141,8 @@ driver_sources = { [ 'drivers/goodixmoc/goodix.c', 'drivers/goodixmoc/goodix_proto.c' ], 'fpcmoc' : [ 'drivers/fpcmoc/fpc.c' ], + 'realtek' : + [ 'drivers/realtek/realtek.c' ], } helper_sources = { diff --git a/meson.build b/meson.build index 0b7569bd..9fc10315 100644 --- a/meson.build +++ b/meson.build @@ -131,6 +131,7 @@ default_drivers = [ 'goodixmoc', 'nb1010', 'fpcmoc', + 'realtek', # SPI 'elanspi', diff --git a/tests/meson.build b/tests/meson.build index 31040d1d..f4f15979 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -52,6 +52,7 @@ drivers_tests = [ 'nb1010', 'egis0570', 'fpcmoc', + 'realtek', ] if get_option('introspection') diff --git a/tests/realtek/custom.pcapng b/tests/realtek/custom.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..0fe6dc7b05d6bdd1008665467bffca83d04835b1 GIT binary patch literal 29128 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 literal 0 HcmV?d00001 diff --git a/tests/realtek/custom.py b/tests/realtek/custom.py new file mode 100755 index 00000000..15a71e2c --- /dev/null +++ b/tests/realtek/custom.py @@ -0,0 +1,110 @@ +#!/usr/bin/python3 + +import traceback +import sys +import gi + +gi.require_version('FPrint', '2.0') +from gi.repository import FPrint, GLib + +# Exit with error on any exception, included those happening in async callbacks +sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1)) + +ctx = GLib.main_context_default() + +c = FPrint.Context() +c.enumerate() +devices = c.get_devices() + +d = devices[0] +del devices + +assert d.get_driver() == "realtek" +assert not d.has_feature(FPrint.DeviceFeature.CAPTURE) +assert d.has_feature(FPrint.DeviceFeature.IDENTIFY) +assert d.has_feature(FPrint.DeviceFeature.VERIFY) +assert not 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) + +d.open_sync() + +# 1. verify clear storage command, 2. make sure later asserts are good +d.clear_storage_sync() + +template = FPrint.Print.new(d) + +def enroll_progress(*args): + # assert d.get_finger_status() & FPrint.FingerStatusFlags.NEEDED + print('enroll progress: ' + str(args)) + +def identify_done(dev, res): + global identified + identified = True + try: + identify_match, identify_print = dev.identify_finish(res) + except gi.repository.GLib.GError as e: + print("Please try again") + else: + print('indentification_done: ', identify_match, identify_print) + assert identify_match.equal(identify_print) + +def start_identify_async(prints): + global identified + print('async identifying') + d.identify(prints, callback=identify_done) + del prints + + while not identified: + ctx.iteration(True) + + identified = False + +# List, enroll, list, verify, identify, delete +print("enrolling") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll done") + +print("listing") +stored = d.list_prints_sync() +print("listing done") +assert len(stored) == 1 +assert stored[0].equal(p) +print("verifying") +try: + assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE + verify_res, verify_print = d.verify_sync(p) + assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +except gi.repository.GLib.GError as e: + print("Please try again") +else: + print("verify done") + del p + 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("deleting") +d.delete_print_sync(p) +print("delete done") + +d.close_sync() + +del d +del c diff --git a/tests/realtek/device b/tests/realtek/device new file mode 100644 index 00000000..c4f1c85e --- /dev/null +++ b/tests/realtek/device @@ -0,0 +1,240 @@ +P: /devices/pci0000:00/0000:00:14.0/usb1/1-4 +N: bus/usb/001/005=12010102EF020140DA0B135801210301020109022E00010104A0FA0904000004FF02000507050102000200070583031000080705840310000807058202000200 +E: DEVNAME=/dev/bus/usb/001/005 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=bda/5813/2101 +E: TYPE=239/2/1 +E: BUSNUM=001 +E: DEVNUM=005 +E: MAJOR=189 +E: MINOR=4 +E: SUBSYSTEM=usb +E: ID_VENDOR=Generic +E: ID_VENDOR_ENC=Generic +E: ID_VENDOR_ID=0bda +E: ID_MODEL=Realtek_USB2.0_Finger_Print_Bridge +E: ID_MODEL_ENC=Realtek\x20USB2.0\x20Finger\x20Print\x20Bridge +E: ID_MODEL_ID=5813 +E: ID_REVISION=2101 +E: ID_SERIAL=Generic_Realtek_USB2.0_Finger_Print_Bridge_201801010001 +E: ID_SERIAL_SHORT=201801010001 +E: ID_BUS=usb +E: ID_USB_INTERFACES=:ff0200: +E: ID_VENDOR_FROM_DATABASE=Realtek Semiconductor Corp. +E: ID_PATH=pci-0000:00:14.0-usb-0:4 +E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_4 +A: authorized=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=ef\n +A: bDeviceProtocol=01\n +A: bDeviceSubClass=02\n +A: bMaxPacketSize0=64\n +A: bMaxPower=500mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=2101\n +A: bmAttributes=a0\n +A: busnum=1\n +A: configuration=Realtek USB2.0 Finger Print Bridge\n +H: descriptors=12010102EF020140DA0B135801210301020109022E00010104A0FA0904000004FF02000507050102000200070583031000080705840310000807058202000200 +A: dev=189:4\n +A: devnum=5\n +A: devpath=4\n +L: driver=../../../../../bus/usb/drivers/usb +L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:4b/device:4c/device:50 +A: idProduct=5813\n +A: idVendor=0bda\n +A: ltm_capable=no\n +A: manufacturer=Generic\n +A: maxchild=0\n +A: physical_location/dock=no\n +A: physical_location/horizontal_position=left\n +A: physical_location/lid=no\n +A: physical_location/panel=top\n +A: physical_location/vertical_position=upper\n +L: port=../1-0:1.0/usb1-port4 +A: power/active_duration=91232868\n +A: power/async=enabled\n +A: power/autosuspend=2\n +A: power/autosuspend_delay_ms=2000\n +A: power/connected_duration=91232868\n +A: power/control=on\n +A: power/level=on\n +A: power/persist=1\n +A: power/runtime_active_kids=0\n +A: power/runtime_active_time=91232594\n +A: power/runtime_enabled=forbidden\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=7\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=Realtek USB2.0 Finger Print Bridge\n +A: quirks=0x0\n +A: removable=removable\n +A: rx_lanes=1\n +A: serial=201801010001\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=15076313\n +A: version= 2.01\n + +P: /devices/pci0000:00/0000:00:14.0/usb1 +N: bus/usb/001/001=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C +E: DEVNAME=/dev/bus/usb/001/001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=1d6b/2/602 +E: TYPE=9/0/1 +E: BUSNUM=001 +E: DEVNUM=001 +E: MAJOR=189 +E: MINOR=0 +E: SUBSYSTEM=usb +E: ID_VENDOR=Linux_6.2.0-35-generic_xhci-hcd +E: ID_VENDOR_ENC=Linux\x206.2.0-35-generic\x20xhci-hcd +E: ID_VENDOR_ID=1d6b +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_ID=0002 +E: ID_REVISION=0602 +E: ID_SERIAL=Linux_6.2.0-35-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_SERIAL_SHORT=0000:00:14.0 +E: ID_BUS=usb +E: ID_USB_INTERFACES=:090000: +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_AUTOSUSPEND=1 +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_PATH=pci-0000:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_FOR_SEAT=usb-pci-0000_00_14_0 +E: TAGS=:seat: +E: CURRENT_TAGS=:seat: +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=1\n +A: configuration= +H: descriptors=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C +A: dev=189:0\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:4b/device:4c +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-35-generic xhci-hcd\n +A: maxchild=16\n +A: power/active_duration=264747968\n +A: power/async=enabled\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=264747968\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_kids=3\n +A: power/runtime_active_time=264747968\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=0\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=xHCI Host Controller\n +A: quirks=0x0\n +A: removable=unknown\n +A: rx_lanes=1\n +A: serial=0000:00:14.0\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=3177\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0 +E: DRIVER=xhci_hcd +E: PCI_CLASS=C0330 +E: PCI_ID=8086:A36D +E: PCI_SUBSYS_ID=1028:085C +E: PCI_SLOT_NAME=0000:00:14.0 +E: MODALIAS=pci:v00008086d0000A36Dsv00001028sd0000085Cbc0Csc03i30 +E: SUBSYSTEM=pci +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_VENDOR_FROM_DATABASE=Intel Corporation +E: ID_MODEL_FROM_DATABASE=Cannon Lake PCH USB 3.1 xHCI Host Controller +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=86806DA3060590021030030C00008000040030D200000000000000000000000000000000000000000000000028105C08000000007000000000000000FF010000FD0134808FC6FF8300000000000000007F6DDC0F000000005919041B00000000316000000000000000000000000000000180C2C108000000000000000000000005908700D802E0FE0000000000000000090014F01000400100000000C10A080000080E00001800008F40020000010000030000000C00000000000000C000000000000000000100003000000000000000030000000C0000000000000000000000000000000000000000000000000000000000000000000000B50F120112000000 +A: consistent_dma_mask_bits=64\n +A: d3cold_allowed=1\n +A: dbc=disabled\n +A: device=0xa36d\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:4b +A: index=4\n +A: irq=125\n +A: label=Onboard - Other\n +A: local_cpulist=0-3\n +A: local_cpus=f\n +A: modalias=pci:v00008086d0000A36Dsv00001028sd0000085Cbc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/125=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 7 2112 7\nxHCI ring segments 24 24 4096 24\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 12 32 128 1\nbuffer-32 0 0 32 0\n +A: power/async=enabled\n +A: power/control=on\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=264748677\n +A: power/runtime_enabled=forbidden\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=1\n +A: power/wakeup=enabled\n +A: power/wakeup_abort_count=0\n +A: power/wakeup_active=0\n +A: power/wakeup_active_count=0\n +A: power/wakeup_count=0\n +A: power/wakeup_expire_count=0\n +A: power/wakeup_last_time_ms=0\n +A: power/wakeup_max_time_ms=0\n +A: power/wakeup_total_time_ms=0\n +A: power_state=D0\n +A: resource=0x00000000d2300000 0x00000000d230ffff 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=0x10\n +A: subsystem_device=0x085c\n +A: subsystem_vendor=0x1028\n +A: vendor=0x8086\n +