Files
libfprint/libfprint/drivers/egismoc/egismoc.c
2025-10-04 10:06:49 +02:00

1631 lines
55 KiB
C

/*
* Driver for Egis Technology (LighTuning) Match-On-Chip sensors
* Originally authored 2023 by Joshua Grisham <josh@joshuagrisham.com>
*
* 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 <stdio.h>
#include <glib.h>
#include <sys/param.h>
#include "drivers_api.h"
#include "fpi-byte-writer.h"
#include "egismoc.h"
struct _FpiDeviceEgisMoc
{
FpDevice parent;
FpiSsm *task_ssm;
FpiSsm *cmd_ssm;
FpiUsbTransfer *cmd_transfer;
GCancellable *interrupt_cancellable;
GPtrArray *enrolled_ids;
gint max_enroll_stages;
};
G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_DEVICE);
static const FpIdEntry egismoc_id_table[] = {
{ .vid = 0x1c7a, .pid = 0x0582, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 },
{ .vid = 0x1c7a, .pid = 0x0583, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 },
{ .vid = 0x1c7a, .pid = 0x0586, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 },
{ .vid = 0x1c7a, .pid = 0x0587, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 },
{ .vid = 0x1c7a, .pid = 0x0588, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 },
{ .vid = 0x1c7a, .pid = 0x05a1, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE2 },
{ .vid = 0, .pid = 0, .driver_data = 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;
/* Interrupt on this device always returns 1 byte short; this is expected */
transfer->short_is_error = FALSE;
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED);
fpi_usb_transfer_submit (g_steal_pointer (&transfer),
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 is going to be freed by completion of SSM */
g_assert (!self->task_ssm || self->task_ssm == ssm);
self->task_ssm = NULL;
g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref);
if (error)
fpi_device_action_error (device, error);
}
static void
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)
{
g_autofree guchar *buffer = NULL;
CommandData *data = userdata;
SynCmdMsgCallback callback;
gssize actual_length;
fp_dbg ("Command receive callback");
if (error)
{
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
if (data == NULL || transfer->actual_length < egismoc_read_prefix_len)
{
fpi_ssm_mark_failed (transfer->ssm,
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
return;
}
/* Let's complete the previous ssm and then handle the callback, so that
* we are sure that we won't start a transfer or a new command while there is
* another one still ongoing
*/
callback = data->callback;
buffer = g_steal_pointer (&transfer->buffer);
actual_length = transfer->actual_length;
fpi_ssm_mark_completed (transfer->ssm);
if (callback)
callback (device, buffer, actual_length, NULL);
}
static void
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,
fpi_device_get_cancellable (device),
fpi_ssm_usb_transfer_cb,
NULL);
break;
}
fpi_ssm_next_state (ssm);
break;
case CMD_GET:
transfer = fpi_usb_transfer_new (device);
transfer->ssm = ssm;
fpi_usb_transfer_fill_bulk (transfer, EGISMOC_EP_CMD_IN,
EGISMOC_USB_IN_RECV_LENGTH);
fpi_usb_transfer_submit (g_steal_pointer (&transfer),
EGISMOC_USB_RECV_TIMEOUT,
fpi_device_get_cancellable (device),
egismoc_cmd_receive_cb,
fpi_ssm_get_data (ssm));
break;
}
}
static void
egismoc_cmd_ssm_done (FpiSsm *ssm,
FpDevice *device,
GError *error)
{
g_autoptr(GError) local_error = error;
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
CommandData *data = fpi_ssm_get_data (ssm);
g_assert (self->cmd_ssm == ssm);
g_assert (!self->cmd_transfer || self->cmd_transfer->ssm == ssm);
self->cmd_ssm = NULL;
self->cmd_transfer = NULL;
if (error && data && data->callback)
data->callback (device, NULL, 0, g_steal_pointer (&local_error));
}
/*
* Derive the 2 "check bytes" for write payloads
* 32-bit big-endian sum of all 16-bit words (including check bytes) MOD 0xFFFF
* should be 0, otherwise the device will reject the payload
*/
static guint16
egismoc_get_check_bytes (FpiByteReader *reader)
{
fp_dbg ("Get check bytes");
size_t sum_values = 0;
guint16 val;
fpi_byte_reader_set_pos (reader, 0);
while (fpi_byte_reader_get_uint16_be (reader, &val))
sum_values += val;
return G_MAXUINT16 - (sum_values % G_MAXUINT16);
}
static void
egismoc_exec_cmd (FpDevice *device,
guchar *cmd,
const gsize cmd_length,
GDestroyNotify cmd_destroy,
SynCmdMsgCallback callback)
{
g_auto(FpiByteWriter) writer = {0};
g_autoptr(FpiUsbTransfer) transfer = NULL;
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
g_autofree CommandData *data = NULL;
gsize buffer_out_length = 0;
gboolean written = TRUE;
guint16 check_value;
fp_dbg ("Execute command and get response");
/*
* buffer_out should be a fully composed command (with prefix, check bytes, etc)
* which looks like this:
* E G I S 00 00 00 01 {cb1} {cb2} {payload}
* where cb1 and cb2 are some check bytes generated by the
* 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;
fpi_byte_writer_init_with_size (&writer, buffer_out_length +
(buffer_out_length % 2 ? 1 : 0), TRUE);
/* Prefix */
written &= fpi_byte_writer_put_data (&writer, egismoc_write_prefix,
egismoc_write_prefix_len);
/* Check Bytes - leave them as 00 for now then later generate and copy over
* the real ones */
written &= fpi_byte_writer_change_pos (&writer, EGISMOC_CHECK_BYTES_LENGTH);
/* Command Payload */
written &= fpi_byte_writer_put_data (&writer, cmd, cmd_length);
/* Now fetch and set the "real" check bytes based on the currently
* assembled payload */
check_value = egismoc_get_check_bytes (FPI_BYTE_READER (&writer));
fpi_byte_writer_set_pos (&writer, egismoc_write_prefix_len);
written &= fpi_byte_writer_put_uint16_be (&writer, check_value);
/* destroy cmd if requested */
if (cmd_destroy)
g_clear_pointer (&cmd, cmd_destroy);
g_assert (self->cmd_ssm == NULL);
self->cmd_ssm = fpi_ssm_new (device,
egismoc_cmd_run_state,
CMD_STATES);
data = g_new0 (CommandData, 1);
data->callback = callback;
fpi_ssm_set_data (self->cmd_ssm, g_steal_pointer (&data), g_free);
if (!written)
{
fpi_ssm_start (self->cmd_ssm, egismoc_cmd_ssm_done);
fpi_ssm_mark_failed (self->cmd_ssm,
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
return;
}
transfer = fpi_usb_transfer_new (device);
transfer->short_is_error = TRUE;
transfer->ssm = self->cmd_ssm;
fpi_usb_transfer_fill_bulk_full (transfer,
EGISMOC_EP_CMD_OUT,
fpi_byte_writer_reset_and_get_data (&writer),
buffer_out_length,
g_free);
g_assert (self->cmd_transfer == NULL);
self->cmd_transfer = g_steal_pointer (&transfer);
fpi_ssm_start (self->cmd_ssm, egismoc_cmd_ssm_done);
}
static void
egismoc_set_print_data (FpPrint *print,
const gchar *device_print_id,
const gchar *user_id)
{
GVariant *print_id_var = NULL;
GVariant *fpi_data = NULL;
g_autofree gchar *fill_user_id = NULL;
if (user_id)
fill_user_id = g_strdup (user_id);
else
fill_user_id = g_strndup (device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE);
fpi_print_fill_from_user_id (print, fill_user_id);
fpi_print_set_type (print, FPI_PRINT_RAW);
fpi_print_set_device_stored (print, TRUE);
g_object_set (print, "description", fill_user_id, NULL);
print_id_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
device_print_id,
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);
if (!self->enrolled_ids)
return g_steal_pointer (&result);
for (guint i = 0; i < self->enrolled_ids->len; i++)
{
FpPrint *print = fp_print_new (device);
egismoc_set_print_data (print, g_ptr_array_index (self->enrolled_ids, i), NULL);
g_ptr_array_add (result, g_object_ref_sink (print));
}
return g_steal_pointer (&result);
}
static void
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;
}
g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref);
self->enrolled_ids = g_ptr_array_new_with_free_func (g_free);
FpiByteReader reader;
gboolean read = TRUE;
fpi_byte_reader_init (&reader, buffer_in, length_in);
read &= fpi_byte_reader_set_pos (&reader, EGISMOC_LIST_RESPONSE_PREFIX_SIZE);
/*
* Each fingerprint ID will be returned in this response as a 32 byte array
* The other stuff in the payload is 16 bytes long, so if there is at least 1
* print then the length should be at least 16+32=48 bytes long
*/
while (read)
{
const guint8 *data;
g_autofree gchar *print_id = NULL;
read &= fpi_byte_reader_get_data (&reader, EGISMOC_FINGERPRINT_DATA_SIZE,
&data);
if (!read)
break;
print_id = g_strndup ((gchar *) data, EGISMOC_FINGERPRINT_DATA_SIZE);
fp_dbg ("Device fingerprint %0d: %.*s", self->enrolled_ids->len + 1,
EGISMOC_FINGERPRINT_DATA_SIZE, print_id);
g_ptr_array_add (self->enrolled_ids, g_steal_pointer (&print_id));
}
fp_info ("Number of currently enrolled fingerprints on the device is %d",
self->enrolled_ids->len);
if (self->task_ssm)
fpi_ssm_next_state (self->task_ssm);
}
static void
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);
g_assert (self->task_ssm == NULL);
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_auto(FpiByteWriter) writer = {0};
g_autoptr(GVariant) print_data = NULL;
g_autoptr(GVariant) print_data_id_var = NULL;
const guchar *print_data_id = NULL;
gsize print_data_id_len = 0;
g_autofree gchar *print_description = NULL;
g_autofree guchar *enrolled_print_id = NULL;
g_autofree guchar *result = NULL;
gboolean written = TRUE;
/*
* The final command body should contain:
* 1) hard-coded 00 00
* 2) 2-byte size indiciator, 20*Number deleted identifiers plus 7 in form of:
* num_to_delete * 0x20 + 0x07
* Since max prints can be higher than 7 then this goes up to 2 bytes
* (e9 + 9 = 109)
* 3) Hard-coded prefix (cmd_delete_prefix)
* 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 7
* (num_to_delete * 0x20)
* 5) All of the currently registered prints to delete in their 32-byte device
* identifiers (enrolled_list)
*/
int num_to_delete = 0;
if (delete_print)
num_to_delete = 1;
else if (self->enrolled_ids)
num_to_delete = self->enrolled_ids->len;
const gsize body_length = sizeof (guchar) * 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 */
fpi_byte_writer_init_with_size (&writer, total_length, TRUE);
/* start with 00 00 (just move starting offset up by 2) */
written &= fpi_byte_writer_set_pos (&writer, 2);
/* Size Counter bytes */
/* "easiest" way to handle 2-bytes size for counter is to hard-code logic for
* when we go to the 2nd byte
* note this will not work in case any model ever supports more than 14 prints
* (assumed max is 10) */
if (num_to_delete > 7)
{
written &= fpi_byte_writer_put_uint8 (&writer, 0x01);
written &= fpi_byte_writer_put_uint8 (&writer, ((num_to_delete - 8) * 0x20) + 0x07);
}
else
{
/* first byte is 0x00, just skip it */
written &= fpi_byte_writer_change_pos (&writer, 1);
written &= fpi_byte_writer_put_uint8 (&writer, (num_to_delete * 0x20) + 0x07);
}
/* command prefix */
written &= fpi_byte_writer_put_data (&writer, cmd_delete_prefix,
cmd_delete_prefix_len);
/* 2-bytes size logic for counter again */
if (num_to_delete > 7)
{
written &= fpi_byte_writer_put_uint8 (&writer, 0x01);
written &= fpi_byte_writer_put_uint8 (&writer, (num_to_delete - 8) * 0x20);
}
else
{
/* first byte is 0x00, just skip it */
written &= fpi_byte_writer_change_pos (&writer, 1);
written &= fpi_byte_writer_put_uint8 (&writer, num_to_delete * 0x20);
}
/* append desired 32-byte fingerprint IDs */
/* if passed a delete_print then fetch its data from the FpPrint */
if (delete_print)
{
g_object_get (delete_print, "description", &print_description, NULL);
g_object_get (delete_print, "fpi-data", &print_data, NULL);
if (!g_variant_check_format_string (print_data, "(@ay)", FALSE))
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID));
return NULL;
}
g_variant_get (print_data, "(@ay)", &print_data_id_var);
print_data_id = g_variant_get_fixed_array (print_data_id_var,
&print_data_id_len, sizeof (guchar));
if (!g_str_has_prefix (print_description, "FP"))
fp_dbg ("Fingerprint '%s' was not created by libfprint; deleting anyway.",
print_description);
fp_info ("Delete fingerprint %s (%s)", print_description, print_data_id);
written &= fpi_byte_writer_put_data (&writer, print_data_id,
EGISMOC_FINGERPRINT_DATA_SIZE);
}
/* Otherwise assume this is a "clear" - just loop through and append all enrolled IDs */
else if (self->enrolled_ids)
{
for (guint i = 0; i < self->enrolled_ids->len && written; i++)
{
written &= fpi_byte_writer_put_data (&writer,
g_ptr_array_index (self->enrolled_ids, i),
EGISMOC_FINGERPRINT_DATA_SIZE);
}
}
g_assert (written);
if (length_out)
*length_out = total_length;
return fpi_byte_writer_reset_and_get_data (&writer);
}
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 successful"));
}
}
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 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);
g_assert (self->task_ssm == NULL);
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);
FpPrint *delete_print = NULL;
fpi_device_get_delete_data (device, &delete_print);
g_assert (self->task_ssm == NULL);
self->task_ssm = fpi_ssm_new (device,
egismoc_delete_run_state,
DELETE_STATES);
/* the print is owned by libfprint during deletion task */
fpi_ssm_set_data (self->task_ssm, delete_print, NULL);
fpi_ssm_start (self->task_ssm, 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,
self->max_enroll_stages);
fpi_device_enroll_progress (device, enroll_print->stage, enroll_print->print, NULL);
break;
case ENROLL_STATUS_COMPLETE:
fp_info ("Enrollment was successful!");
fpi_device_enroll_complete (device, g_object_ref (enroll_print->print), NULL);
break;
default:
if (error)
fpi_ssm_mark_failed (self->task_ssm, error);
else
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Unknown error"));
}
}
static void
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 == self->max_enroll_stages)
fpi_ssm_next_state (self->task_ssm);
else
fpi_ssm_jump_to_state (self->task_ssm, ENROLL_CAPTURE_SENSOR_RESET);
}
static void
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_suffix (buffer_in,
length_in,
rsp_check_not_yet_enrolled_suffix,
rsp_check_not_yet_enrolled_suffix_len))
fpi_ssm_next_state (self->task_ssm);
else
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);
g_auto(FpiByteWriter) writer = {0};
g_autofree guchar *result = NULL;
gboolean written = TRUE;
/*
* The final command body should contain:
* 1) hard-coded 00 00
* 2) 2-byte size indiciator, 20*Number enrolled identifiers plus 9 in form of:
* (enrolled_ids->len + 1) * 0x20 + 0x09
* Since max prints can be higher than 7 then this goes up to 2 bytes
* (e9 + 9 = 109)
* 3) Hard-coded prefix (cmd_check_prefix)
* 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 9
* ((enrolled_ids->len + 1) * 0x20)
* 5) Hard-coded 32 * 0x00 bytes
* 6) All of the currently registered prints in their 32-byte device identifiers
* (enrolled_list)
* 7) Hard-coded suffix (cmd_check_suffix)
*/
g_assert (self->enrolled_ids);
const gsize body_length = sizeof (guchar) * self->enrolled_ids->len *
EGISMOC_FINGERPRINT_DATA_SIZE;
/* prefix length can depend on the type */
const gsize check_prefix_length = (fpi_device_get_driver_data (device) &
EGISMOC_DRIVER_CHECK_PREFIX_TYPE2) ?
cmd_check_prefix_type2_len :
cmd_check_prefix_type1_len;
/* total_length is the 6 various bytes plus all other prefixes/suffixes and
* the body payload */
const gsize total_length = (sizeof (guchar) * 6)
+ check_prefix_length
+ EGISMOC_CMD_CHECK_SEPARATOR_LENGTH
+ body_length
+ cmd_check_suffix_len;
/* pre-fill entire payload with 00s */
fpi_byte_writer_init_with_size (&writer, total_length, TRUE);
/* start with 00 00 (just move starting offset up by 2) */
written &= fpi_byte_writer_set_pos (&writer, 2);
/* Size Counter bytes */
/* "easiest" way to handle 2-bytes size for counter is to hard-code logic for
* when we go to the 2nd byte
* note this will not work in case any model ever supports more than 14 prints
* (assumed max is 10) */
if (self->enrolled_ids->len > 6)
{
written &= fpi_byte_writer_put_uint8 (&writer, 0x01);
written &= fpi_byte_writer_put_uint8 (&writer,
((self->enrolled_ids->len - 7) * 0x20)
+ 0x09);
}
else
{
/* first byte is 0x00, just skip it */
written &= fpi_byte_writer_change_pos (&writer, 1);
written &= fpi_byte_writer_put_uint8 (&writer,
((self->enrolled_ids->len + 1) * 0x20) +
0x09);
}
/* command prefix */
if (fpi_device_get_driver_data (device) & EGISMOC_DRIVER_CHECK_PREFIX_TYPE2)
written &= fpi_byte_writer_put_data (&writer, cmd_check_prefix_type2,
cmd_check_prefix_type2_len);
else
written &= fpi_byte_writer_put_data (&writer, cmd_check_prefix_type1,
cmd_check_prefix_type1_len);
/* 2-bytes size logic for counter again */
if (self->enrolled_ids->len > 6)
{
written &= fpi_byte_writer_put_uint8 (&writer, 0x01);
written &= fpi_byte_writer_put_uint8 (&writer,
(self->enrolled_ids->len - 7) * 0x20);
}
else
{
/* first byte is 0x00, just skip it */
written &= fpi_byte_writer_change_pos (&writer, 1);
written &= fpi_byte_writer_put_uint8 (&writer,
(self->enrolled_ids->len + 1) * 0x20);
}
/* add 00s "separator" to offset position */
written &= fpi_byte_writer_change_pos (&writer,
EGISMOC_CMD_CHECK_SEPARATOR_LENGTH);
for (guint i = 0; i < self->enrolled_ids->len && written; i++)
{
written &= fpi_byte_writer_put_data (&writer,
g_ptr_array_index (self->enrolled_ids, i),
EGISMOC_FINGERPRINT_DATA_SIZE);
}
/* command suffix */
written &= fpi_byte_writer_put_data (&writer, cmd_check_suffix,
cmd_check_suffix_len);
g_assert (written);
if (length_out)
*length_out = total_length;
return fpi_byte_writer_reset_and_get_data (&writer);
}
static void
egismoc_enroll_run_state (FpiSsm *ssm,
FpDevice *device)
{
g_auto(FpiByteWriter) writer = {0};
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 gchar *device_print_id = NULL;
g_autofree gchar *user_id = NULL;
switch (fpi_ssm_get_cur_state (ssm))
{
case ENROLL_GET_ENROLLED_IDS:
/* get enrolled_ids from device for use in check stages below */
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_ids->len >= 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_strndup (user_id, EGISMOC_FINGERPRINT_DATA_SIZE);
egismoc_set_print_data (enroll_print->print, device_print_id, user_id);
fpi_byte_writer_init (&writer);
if (!fpi_byte_writer_put_data (&writer, cmd_new_print_prefix,
cmd_new_print_prefix_len))
{
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
break;
}
if (!fpi_byte_writer_put_data (&writer, (guint8 *) device_print_id,
EGISMOC_FINGERPRINT_DATA_SIZE))
{
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
break;
}
payload_length = fpi_byte_writer_get_size (&writer);
egismoc_exec_cmd (device, fpi_byte_writer_reset_and_get_data (&writer),
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;
g_assert (self->task_ssm == NULL);
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);
gchar 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_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 device_print_id and then see if it matches
* the one indicated
*/
print = fp_print_new (device);
egismoc_set_print_data (print, device_print_id, NULL);
if (!print)
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID,
"Failed to build a print from "
"device response."));
return;
}
fp_info ("Identify successful for: %s", fp_print_get_description (print));
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY)
{
fpi_device_get_identify_data (device, &prints);
found = g_ptr_array_find_with_equal_func (prints,
print,
(GEqualFunc) fp_print_equal,
&index);
if (found)
fpi_device_identify_report (device, g_ptr_array_index (prints, index), print, NULL);
else
fpi_device_identify_report (device, NULL, print, NULL);
}
else
{
fpi_device_get_verify_data (device, &verify_print);
fp_info ("Verifying against: %s", fp_print_get_description (verify_print));
if (fp_print_equal (verify_print, print))
fpi_device_verify_report (device, FPI_MATCH_SUCCESS, print, NULL);
else
fpi_device_verify_report (device, FPI_MATCH_FAIL, print, NULL);
}
}
/* If device was successfully read but it was a "not matched" */
else if (egismoc_validate_response_suffix (buffer_in,
length_in,
rsp_identify_notmatch_suffix,
rsp_identify_notmatch_suffix_len))
{
fp_info ("Print was not identified by the device");
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY)
fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL);
else
fpi_device_identify_report (device, NULL, NULL, NULL);
}
else
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Unrecognized response from device."));
return;
}
fpi_ssm_next_state (self->task_ssm);
}
static void
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 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_ids->len == 0)
{
fpi_ssm_mark_failed (g_steal_pointer (&self->task_ssm),
fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND));
return;
}
fpi_ssm_next_state (ssm);
break;
case IDENTIFY_SENSOR_RESET:
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);
g_assert (self->task_ssm == NULL);
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 gchar *fw_version = NULL;
gsize prefix_length;
guchar *fw_version_start;
gsize fw_version_length;
if (error)
{
fpi_ssm_mark_failed (self->task_ssm, error);
return;
}
/* Check that the read payload indicates "success" */
if (!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_start = buffer_in + prefix_length;
fw_version_length = length_in - prefix_length - rsp_fw_version_suffix_len;
fw_version = g_strndup ((gchar *) fw_version_start, fw_version_length);
fp_info ("Device firmware version is %s", fw_version);
fpi_ssm_next_state (self->task_ssm);
}
static void
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);
egismoc_task_ssm_done (ssm, device, error);
return;
}
egismoc_task_ssm_done (ssm, device, NULL);
fpi_device_open_complete (device, NULL);
}
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);
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);
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);
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);
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);
break;
case DEV_GET_FW_VERSION:
egismoc_exec_cmd (device, cmd_fw_version, cmd_fw_version_len,
NULL, egismoc_fw_version_cb);
return;
default:
g_assert_not_reached ();
}
transfer->ssm = ssm;
transfer->short_is_error = TRUE;
fpi_usb_transfer_submit (g_steal_pointer (&transfer),
EGISMOC_USB_CONTROL_TIMEOUT,
fpi_device_get_cancellable (device),
fpi_ssm_usb_transfer_cb,
NULL);
}
static void
egismoc_probe (FpDevice *device)
{
GUsbDevice *usb_dev;
GError *error = NULL;
g_autofree gchar *serial = NULL;
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
fp_dbg ("%s enter --> ", G_STRFUNC);
/* Claim usb interface */
usb_dev = fpi_device_get_usb_device (device);
if (!g_usb_device_open (usb_dev, &error))
{
fp_dbg ("%s g_usb_device_open failed %s", G_STRFUNC, error->message);
fpi_device_probe_complete (device, NULL, NULL, error);
return;
}
if (!g_usb_device_reset (usb_dev, &error))
{
fp_dbg ("%s g_usb_device_reset failed %s", G_STRFUNC, error->message);
g_usb_device_close (usb_dev, NULL);
fpi_device_probe_complete (device, NULL, NULL, error);
return;
}
if (!g_usb_device_claim_interface (usb_dev, 0, 0, &error))
{
fp_dbg ("%s g_usb_device_claim_interface failed %s", G_STRFUNC, error->message);
g_usb_device_close (usb_dev, NULL);
fpi_device_probe_complete (device, NULL, NULL, error);
return;
}
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
serial = g_strdup ("emulated-device");
else
serial = g_usb_device_get_string_descriptor (usb_dev,
g_usb_device_get_serial_number_index (usb_dev),
&error);
if (error)
{
fp_dbg ("%s g_usb_device_get_string_descriptor failed %s", G_STRFUNC, error->message);
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)),
0, 0, NULL);
g_usb_device_close (usb_dev, NULL);
fpi_device_probe_complete (device, NULL, NULL, error);
return;
}
if (fpi_device_get_driver_data (device) & EGISMOC_DRIVER_MAX_ENROLL_STAGES_20)
self->max_enroll_stages = 20;
else
self->max_enroll_stages = EGISMOC_MAX_ENROLL_STAGES_DEFAULT;
fpi_device_set_nr_enroll_stages (device, self->max_enroll_stages);
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)), 0, 0, NULL);
g_usb_device_close (usb_dev, NULL);
fpi_device_probe_complete (device, serial, NULL, error);
}
static void
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))
{
fpi_device_open_complete (device, error);
return;
}
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device),
0, 0, &error))
{
fpi_device_open_complete (device, error);
return;
}
g_assert (self->task_ssm == NULL);
self->task_ssm = fpi_ssm_new (device, egismoc_dev_init_handler, DEV_INIT_STATES);
fpi_ssm_start (self->task_ssm, egismoc_dev_init_done);
}
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_suspend (FpDevice *device)
{
fp_dbg ("Suspend");
egismoc_cancel (device);
g_cancellable_cancel (fpi_device_get_cancellable (device));
fpi_device_suspend_complete (device, NULL);
}
static void
egismoc_close (FpDevice *device)
{
fp_dbg ("Closing device");
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
GError *error = NULL;
egismoc_cancel (device);
g_clear_object (&self->interrupt_cancellable);
g_usb_device_release_interface (fpi_device_get_usb_device (device),
0, 0, &error);
fpi_device_close_complete (device, error);
}
static void
fpi_device_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_MAX_ENROLL_STAGES_DEFAULT;
/* device should be "always off" unless being used */
dev_class->temp_hot_seconds = 0;
dev_class->probe = egismoc_probe;
dev_class->open = egismoc_open;
dev_class->cancel = egismoc_cancel;
dev_class->suspend = egismoc_suspend;
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;
}