TEMP elanmoc2

This commit is contained in:
Davide Depau 2021-09-19 02:52:16 +02:00
parent 40b3923ca6
commit 37f117fc4f
4 changed files with 1033 additions and 0 deletions

View file

@ -0,0 +1,829 @@
/*
* Driver for ELAN Match-On-Chip sensors
* Copyright (C) 2021 Davide Depau
*
* 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 "elanmoc2"
// Library includes
#include <glib.h>
#include <sys/param.h>
// Local includes
#include "drivers_api.h"
#include "elanmoc2.h"
struct _FpiDeviceElanMoC2 {
FpDevice parent;
/* Device properties */
unsigned short dev_type;
/* USB response data */
unsigned char *buffer_in;
gssize buffer_in_len;
/* Command status data */
unsigned char enrolled_num;
// List
GPtrArray *list_result;
// List + clear storage
unsigned char print_index;
unsigned int err_verify_retry_count;
// Enroll
gint enroll_stage;
FpPrint *enroll_print;
};
G_DEFINE_TYPE (FpiDeviceElanMoC2, fpi_device_elanmoc2, FP_TYPE_DEVICE);
static void
elanmoc2_cmd_usb_receive_callback(FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
if (error) {
fpi_ssm_mark_failed(transfer->ssm, error);
} else if (transfer->actual_length > 0 && transfer->buffer[0] != 0x40) {
fpi_ssm_mark_failed(transfer->ssm, g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO,
"Error receiving data from sensor"));
} else {
self->buffer_in = g_memdup(transfer->buffer, transfer->actual_length);
self->buffer_in_len = transfer->actual_length;
fpi_ssm_next_state(transfer->ssm);
}
}
static gboolean
elanmoc2_cmd_send_sync(FpDevice *device, const struct elanmoc2_cmd *cmd, guint8 *buffer_out, GError **error) {
g_autoptr(FpiUsbTransfer) transfer_out = fpi_usb_transfer_new(device);
fpi_usb_transfer_set_short_error(transfer_out, TRUE);
fpi_usb_transfer_fill_bulk_full(transfer_out, ELANMOC2_EP_CMD_OUT, buffer_out, cmd->out_len, g_free);
return fpi_usb_transfer_submit_sync(transfer_out, ELANMOC2_USB_SEND_TIMEOUT, error);
}
static void
elanmoc2_cmd_transceive(FpDevice *device,
FpiSsm *ssm,
const struct elanmoc2_cmd *cmd,
guint8 *buffer_out,
gboolean short_is_error) {
GError *error;
gboolean send_status = elanmoc2_cmd_send_sync(device, cmd, buffer_out, &error);
if (!send_status)
return fpi_ssm_mark_failed(ssm, error);
FpiUsbTransfer *transfer_in = fpi_usb_transfer_new(device);
transfer_in->ssm = ssm;
fpi_usb_transfer_set_short_error(transfer_in, short_is_error);
fpi_usb_transfer_fill_bulk(transfer_in, cmd->ep_in, cmd->in_len);
fpi_usb_transfer_submit(transfer_in,
cmd->cancellable ? ELANMOC2_USB_RECV_CANCELLABLE_TIMEOUT : ELANMOC2_USB_RECV_TIMEOUT,
cmd->cancellable ? fpi_device_get_cancellable(device) : NULL,
elanmoc2_cmd_usb_receive_callback,
NULL);
}
static uint8_t *
elanmoc2_prepare_cmd(FpiDeviceElanMoC2 *self, const struct elanmoc2_cmd *cmd) {
if (cmd->devices != ELANMOC2_ALL_DEV && !(cmd->devices & self->dev_type))
return NULL;
g_autofree uint8_t *buffer = g_malloc0(cmd->out_len);
buffer[0] = 0x40;
memcpy(&buffer[1], cmd->cmd, cmd->short_command ? 1 : 2);
return g_steal_pointer(&buffer);
}
static void
elanmoc2_open(FpDevice *device) {
GError *error = NULL;
FpiDeviceElanMoC2 *self;
G_DEBUG_HERE ();
if (!g_usb_device_reset(fpi_device_get_usb_device(device), &error))
return fpi_device_open_complete(device, error);
if (!g_usb_device_claim_interface(fpi_device_get_usb_device(FP_DEVICE(device)), 0, 0, &error))
return fpi_device_open_complete(device, error);
self = FPI_DEVICE_ELANMOC2(device);
self->dev_type = fpi_device_get_driver_data(FP_DEVICE(device));
fpi_device_open_complete(device, NULL);
}
static void
elanmoc2_close(FpDevice *device) {
GError *error = NULL;
G_DEBUG_HERE ();
g_usb_device_release_interface(fpi_device_get_usb_device(FP_DEVICE(device)), 0, 0, &error);
fpi_device_close_complete(device, error);
}
static void
elanmoc2_ssm_completed_callback(FpiSsm *ssm, FpDevice *device, GError *error) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
if (error)
fpi_device_action_error(device, error);
}
static void
elanmoc2_perform_get_num_enrolled(FpiDeviceElanMoC2 *self, FpiSsm *ssm) {
g_autofree uint8_t *buffer_out = NULL;
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_get_enrolled_count)) == NULL) {
fpi_ssm_next_state(ssm);
return;
}
elanmoc2_cmd_transceive(FP_DEVICE(self), ssm, &cmd_get_enrolled_count, buffer_out, TRUE);
}
/**
* Checks a command status code and, if an error has occurred, creates a new error object.
* Returns whether such error should cause the whole operation to fail.
* @param self FpiDeviceElanMoC2 pointer
* @param error A pointer where the GError will be placed in case of errors
* @return Whether the current action should be completed
*/
static gboolean
elanmoc2_get_finger_error(FpiDeviceElanMoC2 *self, GError **error) {
g_assert(self->buffer_in != NULL);
// Regular status codes never have the most-significant nibble set; errors do
if ((self->buffer_in[1] & 0xF0) == 0) {
*error = NULL;
return FALSE;
}
switch ((unsigned char) self->buffer_in[1]) {
case ELANMOC2_RESP_MOVE_DOWN:
*error = g_error_new(FP_DEVICE_RETRY, FP_DEVICE_RETRY_CENTER_FINGER,
"Move your finger slightly downwards");
return FALSE;
case ELANMOC2_RESP_MOVE_RIGHT:
*error = g_error_new(FP_DEVICE_RETRY, FP_DEVICE_RETRY_CENTER_FINGER,
"Move your finger slightly to the right");
return FALSE;
case ELANMOC2_RESP_MOVE_UP:
*error = g_error_new(FP_DEVICE_RETRY, FP_DEVICE_RETRY_CENTER_FINGER,
"Move your finger slightly upwards");
return FALSE;
case ELANMOC2_RESP_MOVE_LEFT:
*error = g_error_new(FP_DEVICE_RETRY, FP_DEVICE_RETRY_CENTER_FINGER,
"Move your finger slightly to the left");
return FALSE;
case ELANMOC2_RESP_MAX_ENROLLED_REACHED:
*error = g_error_new(FP_DEVICE_RETRY, FP_DEVICE_RETRY_CENTER_FINGER,
"Move your finger slightly to the right");
return FALSE;
case ELANMOC2_RESP_SENSOR_DIRTY:
*error = g_error_new(FP_DEVICE_RETRY, FP_DEVICE_RETRY_REMOVE_FINGER,
"Sensor is dirty or wet");
return FALSE;
case ELANMOC2_RESP_NOT_ENOUGH_SURFACE:
*error = g_error_new(FP_DEVICE_RETRY, FP_DEVICE_RETRY_REMOVE_FINGER,
"Press your finger slightly harder on the sensor");
return FALSE;
case ELANMOC2_RESP_NOT_ENROLLED:
*error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_NOT_FOUND,
"Finger not recognized");
return TRUE;
default:
*error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL,
"Unknown error");
return TRUE;
}
}
static void
elanmoc2_identify_verify_complete(FpDevice *device, GError *error) {
if (fpi_device_get_current_action(device) == FPI_DEVICE_ACTION_IDENTIFY) {
fpi_device_identify_complete(device, error);
} else {
fpi_device_verify_complete(device, error);
}
}
/**
* Calls the correct verify or identify report function based on the input data.
* Returns whether the action should be completed.
* @param device FpDevice
* @param print Identified fingerprint
* @param error Optional error
* @return Whether to complete the action.
*/
static gboolean
elanmoc2_identify_verify_report(FpDevice *device, FpPrint *print, GError *error) {
if (fpi_device_get_current_action(device) == FPI_DEVICE_ACTION_IDENTIFY) {
fpi_device_identify_report(device, print, print, error);
return TRUE;
} else {
FpiMatchResult result = FPI_MATCH_ERROR;
if (print != NULL) {
FpPrint *to_match = fp_print_new(device);
fpi_device_get_verify_data(device, &to_match);
if (fp_print_equal(to_match, print))
result = FPI_MATCH_SUCCESS;
else
result = FPI_MATCH_FAIL;
}
fpi_device_verify_report(device, result, print, error);
return result == FPI_MATCH_FAIL;
}
}
static void
elanmoc2_identify_run_state(FpiSsm *ssm, FpDevice *device) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
g_autofree uint8_t *buffer_out = NULL;
switch (fpi_ssm_get_cur_state(ssm)) {
case IDENTIFY_GET_NUM_ENROLLED: {
elanmoc2_perform_get_num_enrolled(self, ssm);
break;
}
case IDENTIFY_CHECK_NUM_ENROLLED: {
self->enrolled_num = self->buffer_in[1];
if (self->enrolled_num == 0) {
GError *error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_NOT_FOUND,
"Sensor has no enrolled fingerprints");
elanmoc2_identify_verify_complete(device, error);
fpi_ssm_mark_failed(ssm, error);
break;
}
fpi_ssm_next_state(ssm);
}
case IDENTIFY_IDENTIFY: {
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_identify)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
elanmoc2_cmd_transceive(device, ssm, &cmd_identify, buffer_out, TRUE);
fpi_device_report_finger_status(device, FP_FINGER_STATUS_NEEDED);
break;
}
case IDENTIFY_GET_FINGER_INFO: {
fpi_device_report_finger_status(device, FP_FINGER_STATUS_PRESENT);
GError *error = NULL;
gboolean retry = elanmoc2_get_finger_error(self, &error);
if (error != NULL) {
if (retry) {
elanmoc2_identify_verify_report(device, NULL, error);
fpi_ssm_jump_to_state(ssm, IDENTIFY_IDENTIFY);
} else {
elanmoc2_identify_verify_complete(device, error);
fpi_ssm_mark_failed(ssm, error);
}
break;
}
unsigned char finger_id = self->buffer_in[1];
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_finger_info)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
buffer_out[3] = finger_id;
elanmoc2_cmd_transceive(device, ssm, &cmd_finger_info, buffer_out, TRUE);
break;
}
case IDENTIFY_CHECK_FINGER_INFO: {
fpi_device_report_finger_status(device, FP_FINGER_STATUS_NONE);
// Ensure the user ID is a null-terminated string
self->buffer_in[cmd_finger_info.in_len - 1] = '\0';
FpPrint *print = fp_print_new(FP_DEVICE(self));
fpi_print_set_type(print, FPI_PRINT_RAW);
fpi_print_set_device_stored(print, TRUE);
if (!fpi_print_fill_from_user_id(print, &self->buffer_in[2])) {
if (elanmoc2_identify_verify_report(device, NULL, NULL)) {
GError *error = g_error_new(FP_DEVICE_RETRY, FP_DEVICE_ERROR_DATA_NOT_FOUND,
"Finger not recognized by libfprint");
elanmoc2_identify_verify_complete(device, error);
fpi_ssm_mark_failed(ssm, error);
} else {
fpi_ssm_jump_to_state(ssm, IDENTIFY_IDENTIFY);
}
} else {
if (elanmoc2_identify_verify_report(device, print, NULL)) {
elanmoc2_identify_verify_complete(device, NULL);
fpi_ssm_mark_completed(ssm);
} else {
fpi_ssm_jump_to_state(ssm, IDENTIFY_IDENTIFY);
}
}
break;
}
default:
break;
}
g_clear_pointer(&self->buffer_in, g_free);
}
static void
elanmoc2_identify_verify(FpDevice *device) {
FpiSsm *ssm = fpi_ssm_new(device, elanmoc2_identify_run_state, IDENTIFY_NUM_STATES);
fpi_ssm_start(ssm, elanmoc2_ssm_completed_callback);
}
static void
elanmoc2_list_ssm_completed_callback(FpiSsm *ssm, FpDevice *device, GError *error) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
g_clear_pointer(&self->list_result, g_object_unref);
elanmoc2_ssm_completed_callback(ssm, device, error);
}
/**
* Checks and handles errors after a "get finger info" operation that might require finger verification
* @param self FpiDeviceElanMoC2
* @param ssm The sequential state machine
* @param verify_state The state to jump to if verification is required
* @param error The error to report if the operation needs to fail
* @return Whether the current operation should fail
*/
static gboolean
elanmoc2_check_finger_info_error(FpiDeviceElanMoC2 *self, FpiSsm *ssm, int verify_state, GError **error) {
if (self->buffer_in[1] == 0xFF) {
fp_warn("Sensor won't list fingerprints until a finger is verified; performing verification");
self->err_verify_retry_count = 0;
fpi_ssm_jump_to_state(ssm, verify_state);
return FALSE;
} else if (self->buffer_in_len == 2) {
*error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL,
"Failed to get fingerprint info, error: %02X", self->buffer_in[2]);
fpi_ssm_mark_failed(ssm, *error);
return TRUE;
}
}
static gboolean
elanmoc2_handle_check_finger_verification(FpiDeviceElanMoC2 *self, FpiSsm *ssm, int needs_verification_state,
int check_verify_state, int back_to_normal_state, GError **error) {
g_autofree uint8_t *buffer_out = NULL;
gint state = fpi_ssm_get_cur_state(ssm);
if (state == needs_verification_state) {
fpi_device_report_finger_status(FP_DEVICE(self), FP_FINGER_STATUS_NEEDED);
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_identify)) == NULL) {
fpi_ssm_next_state(ssm);
return FALSE;
}
elanmoc2_cmd_transceive(FP_DEVICE(self), ssm, &cmd_identify, buffer_out, TRUE);
} else if (state == check_verify_state) {
fpi_device_report_finger_status(FP_DEVICE(self), FP_FINGER_STATUS_PRESENT);
if ((self->buffer_in[0] & 0xF0) != 0) {
if (++(self->err_verify_retry_count) >= ELANMOC2_LIST_VERIFY_RETRIES) {
*error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_BUSY,
"Unable to verify finger for operation");
fpi_ssm_mark_failed(ssm, *error);
return TRUE;
} else {
fp_warn("Finger not recognized, retrying");
fpi_ssm_jump_to_state(ssm, needs_verification_state);
}
} else {
fpi_ssm_jump_to_state(ssm, back_to_normal_state);
}
}
return FALSE;
}
}
static void
elanmoc2_list_run_state(FpiSsm *ssm, FpDevice *device) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
g_autofree uint8_t *buffer_out = NULL;
switch (fpi_ssm_get_cur_state(ssm)) {
case LIST_GET_NUM_ENROLLED:
elanmoc2_perform_get_num_enrolled(self, ssm);
break;
case LIST_CHECK_NUM_ENROLLED:
self->enrolled_num = self->buffer_in[1];
if (self->enrolled_num == 0) {
fpi_device_list_complete(device, g_steal_pointer(&self->list_result), NULL);
fpi_ssm_mark_completed(ssm);
break;
}
self->print_index = 0;
fpi_ssm_next_state(ssm);
break;
case LIST_GET_FINGER_INFO:
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_finger_info)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
buffer_out[3] = self->print_index;
elanmoc2_cmd_transceive(device, ssm, &cmd_finger_info, buffer_out, FALSE);
break;
case LIST_CHECK_FINGER_INFO:
if (self->buffer_in_len < cmd_finger_info.in_len) {
GError *error = NULL;
if (elanmoc2_check_finger_info_error(self, ssm, LIST_ERR_NEEDS_VERIFICATION, &error))
fpi_device_list_complete(device, NULL, error);
break;
}
fpi_device_report_finger_status(device, FP_FINGER_STATUS_NONE);
// Ensure the user ID is a null-terminated string
self->buffer_in[cmd_finger_info.in_len - 1] = '\0';
FpPrint *print = fp_print_new(device);
fpi_print_set_type(print, FPI_PRINT_RAW);
fpi_print_set_device_stored(print, TRUE);
if (fpi_print_fill_from_user_id(print, (char *) &self->buffer_in[2])) {
g_ptr_array_add(self->list_result, g_object_ref_sink(&print));
} else {
g_object_unref(print);
}
if (++(self->print_index) >= ELANMOC2_MAX_PRINTS) {
fpi_device_list_complete(device, g_steal_pointer(&self->list_result), NULL);
fpi_ssm_mark_completed(ssm);
}
break;
default: {
GError *error = NULL;
if (elanmoc2_handle_check_finger_verification(self, ssm, LIST_ERR_NEEDS_VERIFICATION,
LIST_ERR_CHECK_VERIFICATION, LIST_GET_FINGER_INFO, &error)) {
fpi_device_list_complete(device, g_steal_pointer(&self->list_result), error);
}
break;
}
}
g_clear_pointer(&self->buffer_in, g_free);
}
static void
elanmoc2_list(FpDevice *device) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
FpiSsm *ssm = fpi_ssm_new(device, elanmoc2_list_run_state, LIST_NUM_STATES);
self->list_result = g_ptr_array_new_with_free_func(g_object_unref);
fpi_ssm_start(ssm, elanmoc2_list_ssm_completed_callback);
}
static void
elanmoc2_enroll_ssm_completed_callback(FpiSsm *ssm, FpDevice *device, GError *error) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
g_clear_pointer(&self->enroll_print, g_object_unref);
elanmoc2_ssm_completed_callback(ssm, device, error);
}
static void
elanmoc2_enroll_run_state(FpiSsm *ssm, FpDevice *device) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
g_autofree uint8_t *buffer_out = NULL;
switch (fpi_ssm_get_cur_state(ssm)) {
case ENROLL_GET_NUM_ENROLLED:
elanmoc2_perform_get_num_enrolled(self, ssm);
break;
case ENROLL_CHECK_NUM_ENROLLED:
self->enrolled_num = self->buffer_in[1];
if (self->enrolled_num >= ELANMOC2_MAX_PRINTS) {
GError *error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_FULL,
"Sensor storage is full");
fpi_device_enroll_complete(device, NULL, error);
fpi_ssm_mark_failed(ssm, error);
} else {
fpi_ssm_next_state(ssm);
// Save finger ID, so it can easily be deleted without needing to fetch finger info, which might
// require a finger verification.
GVariant *fpi_data = g_variant_new_byte(self->enrolled_num);
g_object_set(self->enroll_print, "fpi-data", fpi_data, NULL);
}
break;
case ENROLL_QUERY_EXISTING:
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_identify)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
elanmoc2_cmd_transceive(device, ssm, &cmd_identify, buffer_out, TRUE);
fpi_device_report_finger_status(device, FP_FINGER_STATUS_NEEDED);
break;
case ENROLL_CHECK_EXISTING:
fpi_device_report_finger_status(device, FP_FINGER_STATUS_PRESENT);
GError *error = NULL;
if (self->buffer_in[1] == ELANMOC2_RESP_NOT_ENROLLED) {
// Okay
self->enroll_stage = 0;
fpi_ssm_next_state(ssm);
fpi_device_enroll_progress(device, 0, self->enroll_print, NULL);
} else if ((self->buffer_in[1] & 0xF0) == 0) {
// Finger already enrolled
error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_DUPLICATE,
"Fingerprint already registered");
fpi_device_enroll_complete(device, NULL, error);
fpi_ssm_mark_failed(ssm, error);
} else {
// Finger not recognized
gboolean retry = elanmoc2_get_finger_error(self, &error);
g_assert(error != NULL);
if (retry) {
fpi_device_enroll_progress(device, 0, self->enroll_print, error);
fpi_ssm_jump_to_state(ssm, ENROLL_QUERY_EXISTING);
} else {
fpi_device_enroll_complete(device, NULL, error);
fpi_ssm_mark_failed(ssm, error);
}
}
break;
case ENROLL_ENROLL:
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_enroll)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
buffer_out[3] = self->enrolled_num;
buffer_out[4] = ELANMOC2_ENROLL_TIMES;
buffer_out[5] = self->enroll_stage;
buffer_out[6] = 0;
elanmoc2_cmd_transceive(device, ssm, &cmd_enroll, buffer_out, TRUE);
fpi_device_report_finger_status(device, FP_FINGER_STATUS_NEEDED);
break;
case ENROLL_CHECK_ENROLLED:
fpi_device_report_finger_status(device, FP_FINGER_STATUS_PRESENT);
if (self->buffer_in[1] == 0) {
// Stage okay
self->enroll_stage++;
fpi_device_enroll_progress(device, self->enroll_stage, self->enroll_print, NULL);
if (self->enroll_stage >= ELANMOC2_ENROLL_TIMES) {
fpi_ssm_next_state(ssm);
}
} else {
// Detection error
error = NULL;
gboolean retry = elanmoc2_get_finger_error(self, &error);
if (error != NULL) {
if (retry) {
fpi_device_enroll_progress(device, 0, self->enroll_print, error);
} else {
fpi_device_enroll_complete(device, self->enroll_print, error);
fpi_ssm_mark_failed(ssm, error);
}
}
}
fpi_ssm_jump_to_state(ssm, ENROLL_ENROLL);
break;
case ENROLL_UNK_AFTER_ENROLL:
fpi_device_report_finger_status(device, FP_FINGER_STATUS_NONE);
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_unk_after_enroll)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
elanmoc2_cmd_transceive(device, ssm, &cmd_unk_after_enroll, buffer_out, TRUE);
break;
case ENROLL_COMMIT:
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_commit)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
gchar *user_id = fpi_print_generate_user_id(self->enroll_print);
buffer_out[3] = 0xf0 | (self->enrolled_num + 5);
strncpy((char *) &buffer_out[4], user_id, cmd_commit.out_len - 4);
elanmoc2_cmd_transceive(device, ssm, &cmd_commit, buffer_out, TRUE);
break;
case ENROLL_CHECK_COMMITTED:
error = NULL;
if (self->buffer_in[1] != 0) {
error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL,
"Failed to store fingerprint for unknown reasons");
fpi_ssm_mark_failed(ssm, error);
} else {
fpi_ssm_mark_completed(ssm);
}
fpi_device_enroll_complete(device, self->enroll_print, error);
break;
}
g_clear_pointer(&self->buffer_in, g_free);
}
static void
elanmoc2_enroll(FpDevice *device) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
fpi_device_get_enroll_data(device, &self->enroll_print);
fpi_print_set_device_stored(self->enroll_print, TRUE);
fpi_print_set_type(self->enroll_print, FPI_PRINT_RAW);
FpiSsm *ssm = fpi_ssm_new(device, elanmoc2_enroll_run_state, ENROLL_NUM_STATES);
fpi_ssm_start(ssm, elanmoc2_ssm_completed_callback);
}
static void
elanmoc2_delete_run_state(FpiSsm *ssm, FpDevice *device) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
g_autofree uint8_t *buffer_out = NULL;
GError *error = NULL;
switch (fpi_ssm_get_cur_state(ssm)) {
case DELETE_GET_NUM_ENROLLED:
elanmoc2_perform_get_num_enrolled(self, ssm);
break;
case DELETE_DELETE:
self->enrolled_num = self->buffer_in[1];
if (self->enrolled_num == 0) {
error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_NOT_FOUND,
"Sensor storage is empty, nothing to delete");
fpi_device_delete_complete(device, error);
fpi_ssm_mark_failed(ssm, error);
break;
}
FpPrint *print = NULL;
fpi_device_get_delete_data(device, &print);
GVariant *fpi_data = NULL;
g_object_get(print, "fpi-data", &fpi_data, NULL);
g_assert_nonnull(fpi_data);
unsigned char finger_id = g_variant_get_byte(fpi_data);
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_delete)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
buffer_out[3] = 0xf0 | (finger_id + 5);
elanmoc2_cmd_transceive(device, ssm, &cmd_delete, buffer_out, TRUE);
break;
case DELETE_CHECK_DELETED:
error = NULL;
if (self->buffer_in[1] != 0) {
error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_NOT_FOUND,
"Failed to delete fingerprint");
fpi_ssm_mark_failed(ssm, error);
} else {
fpi_ssm_mark_completed(ssm);
}
fpi_device_delete_complete(device, error);
break;
}
g_clear_pointer(&self->buffer_in, g_free);
}
static void
elanmoc2_delete(FpDevice *device) {
FpiSsm *ssm = fpi_ssm_new(device, elanmoc2_delete_run_state, DELETE_NUM_STATES);
fpi_ssm_start(ssm, elanmoc2_ssm_completed_callback);
}
static void
elanmoc2_clear_storage_run_state(FpiSsm *ssm, FpDevice *device) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
g_autofree uint8_t *buffer_out = NULL;
GError *error = NULL;
switch (fpi_ssm_get_cur_state(ssm)) {
case CLEAR_STORAGE_GET_NUM_ENROLLED:
elanmoc2_perform_get_num_enrolled(self, ssm);
break;
case CLEAR_STORAGE_CHECK_NUM_ENROLLED:
self->enrolled_num = self->buffer_in[1];
if (self->enrolled_num == 0) {
fpi_device_clear_storage_complete(device, NULL);
fpi_ssm_mark_completed(ssm);
break;
}
fpi_ssm_next_state(ssm);
break;
case CLEAR_STORAGE_GET_FINGER_INFO:
if (self->print_index >= ELANMOC2_MAX_PRINTS) {
fpi_device_clear_storage_complete(device, NULL);
fpi_ssm_mark_completed(ssm);
break;
}
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_finger_info)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
buffer_out[3] = self->print_index;
elanmoc2_cmd_transceive(device, ssm, &cmd_finger_info, buffer_out, FALSE);
break;
case CLEAR_STORAGE_DELETE:
if (self->buffer_in_len < cmd_finger_info.in_len) {
if (elanmoc2_check_finger_info_error(self, ssm, CLEAR_STORAGE_ERR_NEEDS_VERIFICATION, &error))
fpi_device_clear_storage_complete(device, error);
break;
}
fpi_device_report_finger_status(device, FP_FINGER_STATUS_NONE);
if (self->buffer_in[self->buffer_in_len - 1] == 0xFF) {
// Not enrolled
self->print_index++;
fpi_ssm_jump_to_state(ssm, CLEAR_STORAGE_GET_FINGER_INFO);
break;
}
if ((buffer_out = elanmoc2_prepare_cmd(self, &cmd_delete)) == NULL) {
fpi_ssm_next_state(ssm);
break;
}
buffer_out[3] = 0xf0 | (self->print_index + 5);
memcpy(&buffer_out[4], &self->buffer_in[2], MIN(self->buffer_in_len - 2, cmd_delete.out_len - 4));
elanmoc2_cmd_transceive(device, ssm, &cmd_delete, buffer_out, TRUE);
break;
case CLEAR_STORAGE_CHECK_DELETED:
if (self->buffer_in[1] != 0) {
error = g_error_new(FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL,
"Failed to delete fingerprint");
fpi_device_clear_storage_complete(device, error);
fpi_ssm_mark_failed(ssm, error);
break;
}
self->print_index++;
fpi_ssm_jump_to_state(ssm, CLEAR_STORAGE_GET_FINGER_INFO);
break;
default: {
if (elanmoc2_handle_check_finger_verification(self, ssm,
CLEAR_STORAGE_ERR_NEEDS_VERIFICATION,
CLEAR_STORAGE_ERR_CHECK_VERIFICATION,
CLEAR_STORAGE_GET_FINGER_INFO, &error)) {
fpi_device_clear_storage_complete(device, error);
}
break;
}
}
g_clear_pointer(&self->buffer_in, g_free);
}
static void
elanmoc2_clear_storage(FpDevice *device) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
self->print_index = 0;
FpiSsm *ssm = fpi_ssm_new(device, elanmoc2_clear_storage_run_state, CLEAR_STORAGE_NUM_STATES);
fpi_ssm_start(ssm, elanmoc2_ssm_completed_callback);
}
static void
elanmoc2_cancel(FpDevice *device) {
FpiDeviceElanMoC2 *self = FPI_DEVICE_ELANMOC2(device);
GError *error;
g_autofree uint8_t *buffer_out = NULL;
elanmoc2_prepare_cmd(self, &cmd_abort);
elanmoc2_cmd_send_sync(device, &cmd_abort, buffer_out, &error);
if (error) {
fp_warn("Error while cancelling action: %s", error->message);
g_object_unref(error);
}
}
static void
fpi_device_elanmoc2_init(FpiDeviceElanMoC2 *self) {
G_DEBUG_HERE ();
}
static void
fpi_device_elanmoc2_class_init(FpiDeviceElanMoC2Class *klass) {
FpDeviceClass *dev_class = FP_DEVICE_CLASS(klass);
dev_class->id = FP_COMPONENT;
dev_class->full_name = ELANMOC2_DRIVER_FULLNAME;
dev_class->type = FP_DEVICE_TYPE_USB;
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
dev_class->id_table = elanmoc2_id_table;
dev_class->nr_enroll_stages = ELANMOC2_ENROLL_TIMES;
dev_class->temp_hot_seconds = -1;
dev_class->open = elanmoc2_open;
dev_class->close = elanmoc2_close;
dev_class->identify = elanmoc2_identify_verify;
dev_class->verify = elanmoc2_identify_verify;
dev_class->enroll = elanmoc2_enroll;
dev_class->delete = elanmoc2_delete;
dev_class->clear_storage = elanmoc2_clear_storage;
dev_class->list = elanmoc2_list;
dev_class->cancel = elanmoc2_cancel;
fpi_device_class_auto_initialize_features(dev_class);
}

View file

@ -0,0 +1,201 @@
/*
* Driver for ELAN Match-On-Chip sensors
* Copyright (C) 2021 Davide Depau
*
* 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
// Stdlib includes
#include <stdbool.h>
// Library includes
#include <libusb.h>
// Local includes
#include "fpi-device.h"
#include "fpi-ssm.h"
#define ELANMOC2_DRIVER_FULLNAME "ELAN Match-on-Chip 2"
#define ELANMOC2_VEND_ID 0x04f3
#define ELANMOC2_ENROLL_TIMES 8
#define ELANMOC2_CMD_MAX_LEN 2
#define ELANMOC2_MAX_PRINTS 10
#define ELANMOC2_LIST_VERIFY_RETRIES 3
// USB parameters
#define ELANMOC2_EP_CMD_OUT (0x1 | LIBUSB_ENDPOINT_OUT)
#define ELANMOC2_EP_CMD_IN (0x3 | LIBUSB_ENDPOINT_IN)
#define ELANMOC2_EP_MOC_CMD_IN (0x4 | LIBUSB_ENDPOINT_IN)
#define ELANMOC2_USB_SEND_TIMEOUT 300
#define ELANMOC2_USB_RECV_TIMEOUT 1000
#define ELANMOC2_USB_RECV_CANCELLABLE_TIMEOUT 5000
// Response codes
#define ELANMOC2_RESP_MOVE_DOWN 0x41
#define ELANMOC2_RESP_MOVE_RIGHT 0x42
#define ELANMOC2_RESP_MOVE_UP 0x43
#define ELANMOC2_RESP_MOVE_LEFT 0x44
#define ELANMOC2_RESP_MAX_ENROLLED_REACHED 0xdd
#define ELANMOC2_RESP_SENSOR_DIRTY 0xfb
#define ELANMOC2_RESP_NOT_ENROLLED 0xfd
#define ELANMOC2_RESP_NOT_ENOUGH_SURFACE 0xfe
// Currently only one device is supported, but I'd like to future-proof this driver for any new contributions.
#define ELANMOC2_ALL_DEV 0
#define ELANMOC2_DEV_0C4C (1 << 0)
G_DECLARE_FINAL_TYPE (FpiDeviceElanMoC2, fpi_device_elanmoc2, FPI, DEVICE_ELANMOC2, FpDevice)
struct elanmoc2_cmd {
unsigned char cmd[ELANMOC2_CMD_MAX_LEN];
gboolean short_command;
int out_len;
int in_len;
int ep_in;
unsigned short devices;
gboolean cancellable;
};
// Cancellable commands
static const struct elanmoc2_cmd cmd_identify = {
.cmd = {0xff, 0x03},
.out_len = 3,
.in_len = 2,
.ep_in = ELANMOC2_EP_MOC_CMD_IN,
.cancellable = true,
};
static const struct elanmoc2_cmd cmd_enroll = {
.cmd = {0xff, 0x01},
.out_len = 7,
.in_len = 2,
.ep_in = ELANMOC2_EP_MOC_CMD_IN,
.cancellable = true,
};
// Not cancellable / quick commands
static const struct elanmoc2_cmd cmd_get_fw_ver = {
.cmd = {0x19},
.short_command = true,
.out_len = 2,
.in_len = 2,
.ep_in = ELANMOC2_EP_CMD_IN,
};
static const struct elanmoc2_cmd cmd_finger_info = {
.cmd = {0xff, 0x12},
.out_len = 4,
.in_len = 64,
.ep_in = ELANMOC2_EP_CMD_IN,
};
static const struct elanmoc2_cmd cmd_get_enrolled_count = {
.cmd = {0xff, 0x04},
.out_len = 3,
.in_len = 2,
.ep_in = ELANMOC2_EP_CMD_IN,
};
static const struct elanmoc2_cmd cmd_abort = {
.cmd = {0xff, 0x02},
.out_len = 3,
.in_len = 2,
.ep_in = ELANMOC2_EP_CMD_IN,
};
static const struct elanmoc2_cmd cmd_commit = {
.cmd = {0xff, 0x02},
.out_len = 72,
.in_len = 2,
.ep_in = ELANMOC2_EP_CMD_IN,
};
static const struct elanmoc2_cmd cmd_unk_after_enroll = {
.cmd = {0xff, 0x10},
.out_len = 3,
.in_len = 3,
.ep_in = ELANMOC2_EP_CMD_IN,
};
static const struct elanmoc2_cmd cmd_delete = {
.cmd = {0xff, 0x13},
.out_len = 72,
.in_len = 2,
.ep_in = ELANMOC2_EP_CMD_IN,
};
enum identify_states {
IDENTIFY_GET_NUM_ENROLLED,
IDENTIFY_CHECK_NUM_ENROLLED,
IDENTIFY_IDENTIFY,
IDENTIFY_GET_FINGER_INFO,
IDENTIFY_CHECK_FINGER_INFO,
IDENTIFY_NUM_STATES
};
enum list_states {
LIST_GET_NUM_ENROLLED,
LIST_CHECK_NUM_ENROLLED,
LIST_GET_FINGER_INFO,
LIST_CHECK_FINGER_INFO,
LIST_ERR_NEEDS_VERIFICATION,
LIST_ERR_CHECK_VERIFICATION,
LIST_NUM_STATES
};
enum enroll_states {
ENROLL_GET_NUM_ENROLLED,
ENROLL_CHECK_NUM_ENROLLED,
ENROLL_QUERY_EXISTING,
ENROLL_CHECK_EXISTING,
ENROLL_ENROLL,
ENROLL_CHECK_ENROLLED,
ENROLL_UNK_AFTER_ENROLL,
ENROLL_COMMIT,
ENROLL_CHECK_COMMITTED,
ENROLL_NUM_STATES
};
enum delete_states {
DELETE_GET_NUM_ENROLLED,
DELETE_DELETE,
DELETE_CHECK_DELETED,
DELETE_NUM_STATES
};
enum clear_storage_states {
CLEAR_STORAGE_GET_NUM_ENROLLED,
CLEAR_STORAGE_CHECK_NUM_ENROLLED,
CLEAR_STORAGE_GET_FINGER_INFO,
CLEAR_STORAGE_DELETE,
CLEAR_STORAGE_CHECK_DELETED,
CLEAR_STORAGE_ERR_NEEDS_VERIFICATION,
CLEAR_STORAGE_ERR_CHECK_VERIFICATION,
CLEAR_STORAGE_NUM_STATES
};
static const FpIdEntry elanmoc2_id_table[] = {
{.vid = ELANMOC2_VEND_ID, .pid = 0x0c4c, .driver_data = ELANMOC2_ALL_DEV},
{.vid = 0, .pid = 0, .driver_data = ELANMOC2_DEV_0C4C}
};

View file

@ -125,6 +125,8 @@ driver_sources = {
[ 'drivers/elan.c' ],
'elanmoc' :
[ 'drivers/elanmoc/elanmoc.c' ],
'elanmoc2' :
[ 'drivers/elanmoc2/elanmoc2.c' ],
'elanspi' :
[ 'drivers/elanspi.c' ],
'nb1010' :

View file

@ -118,6 +118,7 @@ default_drivers = [
'synaptics',
'elan',
'elanmoc',
'elanmoc2',
'uru4000',
'upektc',
'upeksonly',