1072 lines
30 KiB
C
1072 lines
30 KiB
C
/*
|
|
* Validity Sensors, Inc. VFS7552 Fingerprint Reader driver for libfprint
|
|
* Copyright (C) 2013 Arseniy Lartsev <arseniy@chalmers.se>
|
|
* AceLan Kao <acelan.kao@canonical.com>
|
|
* 2018 Mark Harfouche <mark.harfouche@gmail.com>
|
|
* 2020 Julius Piso <julius@piso.at>
|
|
*
|
|
* 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 "vfs7552"
|
|
|
|
#include "drivers_api.h"
|
|
#include "vfs7552_proto.h"
|
|
|
|
#define VFS7552_CONTROL_PIXELS (8)
|
|
#define VFS7552_LINE_SIZE (VFS7552_IMAGE_WIDTH + VFS7552_CONTROL_PIXELS)
|
|
#define VFS7552_IMAGE_CHUNKS (3)
|
|
|
|
#define CAPTURE_VARIANCE_THRESHOLD 1200
|
|
#define FINGER_OFF_VARIANCE_THRESHOLD 100
|
|
#define NOISE_VARIANCE_THRESHOLD 4000
|
|
|
|
/* =================== sync/async USB transfer sequence ==================== */
|
|
|
|
enum {
|
|
ACTION_SEND,
|
|
ACTION_RECEIVE,
|
|
};
|
|
|
|
struct usb_action
|
|
{
|
|
int type;
|
|
const char *name;
|
|
int endpoint;
|
|
int size;
|
|
unsigned char *data;
|
|
int correct_reply_size;
|
|
};
|
|
|
|
#define SEND(ENDPOINT, COMMAND) \
|
|
{ \
|
|
.type = ACTION_SEND, \
|
|
.endpoint = ENDPOINT, \
|
|
.name = #COMMAND, \
|
|
.size = sizeof (COMMAND), \
|
|
.data = COMMAND \
|
|
},
|
|
|
|
#define RECV(ENDPOINT, SIZE) \
|
|
{ \
|
|
.type = ACTION_RECEIVE, \
|
|
.endpoint = ENDPOINT, \
|
|
.size = SIZE, \
|
|
.data = NULL \
|
|
},
|
|
|
|
#define RECV_CHECK(ENDPOINT, SIZE, EXPECTED) \
|
|
{ \
|
|
.type = ACTION_RECEIVE, \
|
|
.endpoint = ENDPOINT, \
|
|
.size = SIZE, \
|
|
.data = EXPECTED, \
|
|
.correct_reply_size = sizeof (EXPECTED) \
|
|
},
|
|
|
|
#define RECV_CHECK_SIZE(ENDPOINT, SIZE, EXPECTED) \
|
|
{ \
|
|
.type = ACTION_RECEIVE, \
|
|
.endpoint = ENDPOINT, \
|
|
.size = SIZE, \
|
|
.data = NULL, \
|
|
.correct_reply_size = sizeof (EXPECTED) \
|
|
},
|
|
|
|
struct usbexchange_data
|
|
{
|
|
int stepcount;
|
|
struct usb_action *actions;
|
|
FpiUsbTransfer *last_recv;
|
|
int timeout;
|
|
};
|
|
|
|
/* ================== Class Definition =================== */
|
|
|
|
struct _FpDeviceVfs7552
|
|
{
|
|
FpImageDevice parent;
|
|
|
|
FpiImageDeviceState dev_state;
|
|
FpiImageDeviceState dev_state_next;
|
|
gboolean background_captured;
|
|
unsigned char background[VFS7552_IMAGE_SIZE];
|
|
unsigned char image[VFS7552_IMAGE_SIZE];
|
|
gint lines_captured;
|
|
|
|
gboolean deactivating;
|
|
gboolean loop_running;
|
|
struct usbexchange_data init_sequence;
|
|
FpiUsbTransfer *flying_transfer;
|
|
};
|
|
|
|
G_DECLARE_FINAL_TYPE (FpDeviceVfs7552, fpi_device_vfs7552, FPI, DEVICE_VFS7552,
|
|
FpImageDevice);
|
|
G_DEFINE_TYPE (FpDeviceVfs7552, fpi_device_vfs7552, FP_TYPE_IMAGE_DEVICE);
|
|
|
|
/* ======================= States ======================== */
|
|
|
|
enum open_states {
|
|
DEV_OPEN_START,
|
|
DEV_OPEN_NUM_STATES
|
|
};
|
|
|
|
enum activate_states {
|
|
ACTIVATE_INIT,
|
|
ACTIVATE_INTERRUPT_QUERY,
|
|
ACTIVATE_INTERRUPT_CHECK,
|
|
ACTIVATE_FINALIZE,
|
|
ACTIVATE_NUM_STATES
|
|
};
|
|
|
|
enum capture_states {
|
|
CAPTURE_QUERY_DATA_READY,
|
|
CAPTURE_CHECK_DATA_READY,
|
|
CAPTURE_REQUEST_CHUNK,
|
|
CAPTURE_READ_CHUNK,
|
|
CAPTURE_COMPLETE,
|
|
CAPTURE_FINALIZE,
|
|
CAPTURE_NUM_STATES
|
|
};
|
|
|
|
enum deactivate_states {
|
|
DEACTIVATE_ENTER,
|
|
DEACTIVATE_DISABLE_SENSOR,
|
|
DEACTIVATE_NUM_STATES
|
|
};
|
|
|
|
/* ============== USB Sequence Definitions =============== */
|
|
|
|
struct usb_action vfs7552_initialization[] = {
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_cmd_01)
|
|
RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 64, vfs7552_cmd_01_recv)
|
|
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_cmd_19)
|
|
RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 128, vfs7552_cmd_19_recv)
|
|
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_00)
|
|
RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY)
|
|
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_01)
|
|
RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY)
|
|
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_02)
|
|
RECV_CHECK (VFS7552_IN_ENDPOINT, 64, vfs7552_init_02_recv)
|
|
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_03)
|
|
RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 64, vfs7552_init_03_recv)
|
|
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_04)
|
|
RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY)
|
|
|
|
/*
|
|
* Windows driver does this and it works
|
|
* But in this driver this call never returns...
|
|
* RECV(VFS7552_IN_ENDPOINT_CTRL2, 8)
|
|
*/
|
|
};
|
|
|
|
struct usb_action vfs7552_stop_capture[] = {
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_cmd_04)
|
|
RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY)
|
|
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_cmd_52)
|
|
RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY)
|
|
};
|
|
|
|
struct usb_action vfs7552_initiate_capture[] = {
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_image_start)
|
|
RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 2048, vfs7552_image_start_resp)
|
|
};
|
|
|
|
struct usb_action vfs7552_wait_finger_init[] = {
|
|
RECV_CHECK_SIZE (VFS7552_INTERRUPT_ENDPOINT, 8, interrupt_ok)
|
|
};
|
|
|
|
struct usb_action vfs7552_data_ready_query[] = {
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_is_image_ready)
|
|
RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 64, vfs7552_is_image_ready_resp_ready)
|
|
};
|
|
|
|
struct usb_action vfs7552_request_chunk[] = {
|
|
SEND (VFS7552_OUT_ENDPOINT, vfs7552_read_image_chunk)
|
|
};
|
|
|
|
/* ================== USB Communication ================== */
|
|
|
|
static void
|
|
async_send_cb (FpiUsbTransfer *transfer, FpDevice *device,
|
|
gpointer user_data, GError *error)
|
|
{
|
|
struct usbexchange_data *data = fpi_ssm_get_data (transfer->ssm);
|
|
struct usb_action *action;
|
|
|
|
g_assert (!(fpi_ssm_get_cur_state (transfer->ssm) >= data->stepcount));
|
|
|
|
action = &data->actions[fpi_ssm_get_cur_state (transfer->ssm)];
|
|
g_assert (!(action->type != ACTION_SEND));
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (transfer->ssm, error);
|
|
return;
|
|
}
|
|
|
|
/* success */
|
|
fpi_ssm_next_state (transfer->ssm);
|
|
}
|
|
|
|
static void
|
|
async_recv_cb (FpiUsbTransfer *transfer, FpDevice *device,
|
|
gpointer user_data, GError *error)
|
|
{
|
|
struct usbexchange_data *data = fpi_ssm_get_data (transfer->ssm);
|
|
struct usb_action *action;
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (transfer->ssm, error);
|
|
return;
|
|
}
|
|
|
|
g_assert (!(fpi_ssm_get_cur_state (transfer->ssm) >= data->stepcount));
|
|
|
|
action = &data->actions[fpi_ssm_get_cur_state (transfer->ssm)];
|
|
g_assert (!(action->type != ACTION_RECEIVE));
|
|
|
|
if (action->data != NULL)
|
|
{
|
|
if (transfer->actual_length != action->correct_reply_size)
|
|
{
|
|
fpi_ssm_mark_failed (transfer->ssm,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
|
"Got %d bytes instead of %d",
|
|
(gint) transfer->actual_length,
|
|
action->correct_reply_size));
|
|
return;
|
|
}
|
|
if (memcmp (transfer->buffer, action->data,
|
|
action->correct_reply_size) != 0)
|
|
{
|
|
fpi_ssm_mark_failed (transfer->ssm,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
|
"Received a wrong reply from the driver."));
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fp_dbg ("Got %d bytes out of %d",
|
|
(gint) transfer->actual_length,
|
|
(gint) transfer->length);
|
|
}
|
|
|
|
fpi_ssm_next_state (transfer->ssm);
|
|
}
|
|
|
|
static void
|
|
usbexchange_loop (FpiSsm *ssm, FpDevice *_dev)
|
|
{
|
|
struct usbexchange_data *data = fpi_ssm_get_data (ssm);
|
|
struct usb_action *action = &data->actions[fpi_ssm_get_cur_state (ssm)];
|
|
FpiUsbTransfer *transfer;
|
|
|
|
g_assert (fpi_ssm_get_cur_state (ssm) < data->stepcount);
|
|
|
|
switch (action->type)
|
|
{
|
|
case ACTION_SEND:
|
|
fp_dbg ("Sending %s", action->name);
|
|
transfer = fpi_usb_transfer_new (_dev);
|
|
fpi_usb_transfer_fill_bulk_full (transfer, action->endpoint,
|
|
action->data, action->size,
|
|
NULL);
|
|
transfer->ssm = ssm;
|
|
transfer->short_is_error = TRUE;
|
|
fpi_usb_transfer_submit (transfer, data->timeout, NULL,
|
|
async_send_cb, NULL);
|
|
break;
|
|
|
|
case ACTION_RECEIVE:
|
|
fp_dbg ("Receiving %d bytes", action->size);
|
|
transfer = fpi_usb_transfer_new (_dev);
|
|
fpi_usb_transfer_fill_bulk (transfer, action->endpoint,
|
|
action->size);
|
|
transfer->ssm = ssm;
|
|
fpi_usb_transfer_submit (transfer, data->timeout, NULL,
|
|
async_recv_cb, NULL);
|
|
|
|
g_clear_pointer (&data->last_recv, fpi_usb_transfer_unref);
|
|
data->last_recv = fpi_usb_transfer_ref (transfer);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
usb_exchange_async (FpiSsm *ssm,
|
|
struct usbexchange_data *data,
|
|
const char *exchange_name)
|
|
{
|
|
FpiSsm *subsm = fpi_ssm_new_full (fpi_ssm_get_device (ssm),
|
|
usbexchange_loop,
|
|
data->stepcount,
|
|
exchange_name);
|
|
|
|
fpi_ssm_set_data (subsm, data, NULL);
|
|
fpi_ssm_start_subsm (ssm, subsm);
|
|
}
|
|
|
|
/* =========== Image Capturing and Processing ============ */
|
|
|
|
enum {
|
|
CHUNK_READ_FINISHED,
|
|
CHUNK_READ_NEED_MORE,
|
|
CHUNK_READ_ERROR
|
|
};
|
|
|
|
static int
|
|
clean_image (FpDeviceVfs7552 *self)
|
|
{
|
|
fp_dbg ("Cleaning image");
|
|
unsigned int sum = 0;
|
|
|
|
for (int i = 0; i < VFS7552_IMAGE_SIZE; i++)
|
|
{
|
|
if (self->background[i] < self->image[i])
|
|
self->image[i] = 0;
|
|
else
|
|
self->image[i] = self->background[i] - self->image[i];
|
|
if ((int) (self->image[i]) * 4 > 255)
|
|
self->image[i] = 255;
|
|
else
|
|
self->image[i] *= 4;
|
|
|
|
sum += self->image[i];
|
|
}
|
|
|
|
if (sum == 0)
|
|
{
|
|
fp_dbg ("frame darker than background; finger present during calibration?");
|
|
// Retake an image of the background at the next opportunity.
|
|
self->background_captured = FALSE;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
process_chunk (FpDeviceVfs7552 *self, FpiUsbTransfer *transfer)
|
|
{
|
|
unsigned char *ptr;
|
|
int n_bytes_in_chunk;
|
|
int n_lines;
|
|
int i;
|
|
|
|
if (transfer->actual_length < 6)
|
|
return CHUNK_READ_ERROR;
|
|
|
|
ptr = transfer->buffer;
|
|
n_bytes_in_chunk = ptr[2] + ptr[3] * 256;
|
|
|
|
if (transfer->actual_length < 6 + n_bytes_in_chunk)
|
|
return CHUNK_READ_ERROR;
|
|
|
|
ptr = ptr + 6;
|
|
n_lines = n_bytes_in_chunk / VFS7552_LINE_SIZE;
|
|
if (n_lines + self->lines_captured > VFS7552_IMAGE_HEIGHT)
|
|
{
|
|
g_warning ("Device sent more lines that were expected! Aborting.");
|
|
return CHUNK_READ_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < n_lines; i++)
|
|
{
|
|
ptr = ptr + VFS7552_CONTROL_PIXELS;
|
|
memcpy (&self->image[self->lines_captured * VFS7552_IMAGE_WIDTH], ptr, VFS7552_IMAGE_WIDTH);
|
|
ptr = ptr + VFS7552_IMAGE_WIDTH;
|
|
self->lines_captured += 1;
|
|
}
|
|
|
|
if (self->lines_captured == VFS7552_IMAGE_HEIGHT)
|
|
return CHUNK_READ_FINISHED;
|
|
else
|
|
return CHUNK_READ_NEED_MORE;
|
|
}
|
|
|
|
static void
|
|
chunk_capture_callback (FpiUsbTransfer *transfer, FpDevice *device,
|
|
gpointer user_data, GError *error)
|
|
{
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (device);
|
|
|
|
if (error)
|
|
{
|
|
if (!self->deactivating)
|
|
{
|
|
fp_err ("Failed to capture data");
|
|
fpi_ssm_mark_failed (transfer->ssm, error);
|
|
}
|
|
else
|
|
{
|
|
// Clear the cancel error, because we are handling deactivation separately
|
|
g_error_free (error);
|
|
fpi_ssm_mark_completed (transfer->ssm);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (process_chunk (self, transfer))
|
|
{
|
|
case CHUNK_READ_FINISHED:
|
|
fpi_ssm_next_state (transfer->ssm);
|
|
break;
|
|
|
|
case CHUNK_READ_NEED_MORE:
|
|
fpi_ssm_jump_to_state (transfer->ssm, CAPTURE_REQUEST_CHUNK);
|
|
break;
|
|
|
|
case CHUNK_READ_ERROR:
|
|
fpi_ssm_mark_failed (transfer->ssm,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
|
"Failed to decode image packet of length %d",
|
|
(int) transfer->actual_length));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
capture_chunk_async (FpiSsm *ssm, FpDevice *_dev, guint timeout)
|
|
{
|
|
FpiUsbTransfer *transfer;
|
|
|
|
transfer = fpi_usb_transfer_new (_dev);
|
|
|
|
fpi_usb_transfer_fill_bulk (transfer, VFS7552_IN_ENDPOINT,
|
|
VFS7552_RECEIVE_BUF_SIZE);
|
|
|
|
transfer->ssm = ssm;
|
|
fpi_usb_transfer_submit (transfer, timeout, NULL,
|
|
chunk_capture_callback, NULL);
|
|
}
|
|
|
|
static void
|
|
reset_state (FpDeviceVfs7552 *self)
|
|
{
|
|
self->lines_captured = 0;
|
|
}
|
|
|
|
/* ================ Run States ================= */
|
|
|
|
static void
|
|
deactivate_run_state (FpiSsm *ssm, FpDevice *_dev)
|
|
{
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case DEACTIVATE_ENTER:
|
|
fpi_ssm_next_state_delayed (ssm, 10, NULL);
|
|
break;
|
|
|
|
case DEACTIVATE_DISABLE_SENSOR:
|
|
self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_stop_capture);
|
|
self->init_sequence.actions = vfs7552_stop_capture;
|
|
self->init_sequence.timeout = 1000;
|
|
usb_exchange_async (ssm, &self->init_sequence, "STOP CAPTURE");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
capture_run_state (FpiSsm *ssm, FpDevice *_dev)
|
|
{
|
|
FpDeviceVfs7552 *self;
|
|
unsigned char *receive_buf;
|
|
int variance_before;
|
|
int variance_after;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
if (self->deactivating)
|
|
{
|
|
fp_dbg ("deactivating, marking completed");
|
|
fpi_ssm_mark_completed (ssm);
|
|
return;
|
|
}
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case CAPTURE_QUERY_DATA_READY:
|
|
self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_data_ready_query);
|
|
self->init_sequence.actions = vfs7552_data_ready_query;
|
|
self->init_sequence.timeout = 0; // Do not time out
|
|
usb_exchange_async (ssm, &self->init_sequence, "QUERY DATA READY");
|
|
break;
|
|
|
|
case CAPTURE_CHECK_DATA_READY:
|
|
receive_buf = ((unsigned char *) self->init_sequence.last_recv->buffer);
|
|
|
|
if (receive_buf[0] == vfs7552_is_image_ready_resp_not_ready[0])
|
|
{
|
|
fpi_ssm_jump_to_state (ssm, CAPTURE_QUERY_DATA_READY);
|
|
}
|
|
else if (receive_buf[0] == vfs7552_is_image_ready_resp_ready[0])
|
|
{
|
|
reset_state (self);
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
else if (receive_buf[0] == vfs7552_is_image_ready_resp_finger_off[0])
|
|
{
|
|
fp_dbg ("finger off response received");
|
|
if (self->dev_state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF)
|
|
{
|
|
reset_state (self);
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
else
|
|
{
|
|
fpi_ssm_jump_to_state (ssm, CAPTURE_FINALIZE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
|
"Unknown response 0x%02x",
|
|
receive_buf[0]));
|
|
}
|
|
break;
|
|
|
|
case CAPTURE_REQUEST_CHUNK:
|
|
self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_request_chunk);
|
|
self->init_sequence.actions = vfs7552_request_chunk;
|
|
self->init_sequence.timeout = 1000;
|
|
usb_exchange_async (ssm, &self->init_sequence, "REQUEST CHUNK");
|
|
break;
|
|
|
|
case CAPTURE_READ_CHUNK:
|
|
capture_chunk_async (ssm, _dev, 1000);
|
|
break;
|
|
|
|
case CAPTURE_COMPLETE:
|
|
// Store the image as a background reference, if the variance is below the finger off threshold.
|
|
if (!self->background_captured)
|
|
{
|
|
// Calculate the variance of the captured image
|
|
variance_before = fpi_std_sq_dev (self->image, VFS7552_IMAGE_SIZE);
|
|
fp_dbg ("variance_before = %d\n", variance_before);
|
|
self->background_captured = TRUE;
|
|
memcpy (self->background, self->image, VFS7552_IMAGE_SIZE);
|
|
fp_dbg ("background stored");
|
|
fpi_ssm_jump_to_state (ssm, CAPTURE_QUERY_DATA_READY);
|
|
break;
|
|
}
|
|
|
|
clean_image (self);
|
|
variance_after = fpi_std_sq_dev (self->image, VFS7552_IMAGE_SIZE);
|
|
fp_dbg ("variance_after = %d\n", variance_after);
|
|
|
|
if (self->dev_state == FPI_IMAGE_DEVICE_STATE_CAPTURE ||
|
|
self->dev_state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON)
|
|
{
|
|
// If the finger is placed on the sensor, the variance should ideally increase above a certain
|
|
// threshold. Otherwise request a new image and test again. Additionally we want to ensure
|
|
// that we don't capture prints with a way too high noise level (this sometimes happens).
|
|
if (variance_after > CAPTURE_VARIANCE_THRESHOLD && variance_after < NOISE_VARIANCE_THRESHOLD)
|
|
fpi_ssm_mark_completed (ssm);
|
|
else
|
|
fpi_ssm_jump_to_state (ssm, CAPTURE_QUERY_DATA_READY);
|
|
}
|
|
else if (self->dev_state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF)
|
|
{
|
|
// If the finger is removed from the sensor, the variance should ideally drop below a certain
|
|
// threshold. Otherwise request a new image and test again.
|
|
if (variance_after < FINGER_OFF_VARIANCE_THRESHOLD)
|
|
fpi_ssm_mark_completed (ssm);
|
|
else
|
|
fpi_ssm_jump_to_state (ssm, CAPTURE_QUERY_DATA_READY);
|
|
}
|
|
break;
|
|
|
|
case CAPTURE_FINALIZE:
|
|
fpi_ssm_mark_completed (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
activate_run_state (FpiSsm *ssm, FpDevice *_dev)
|
|
{
|
|
FpDeviceVfs7552 *self;
|
|
unsigned char *receive_buf;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
if (self->deactivating)
|
|
{
|
|
fp_dbg ("deactivating, marking completed");
|
|
fpi_ssm_mark_completed (ssm);
|
|
return;
|
|
}
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case ACTIVATE_INIT:
|
|
// This sequence prepares the sensor for capturing the image.
|
|
self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_initiate_capture);
|
|
self->init_sequence.actions = vfs7552_initiate_capture;
|
|
self->init_sequence.timeout = VFS7552_DEFAULT_WAIT_TIMEOUT;
|
|
usb_exchange_async (ssm, &self->init_sequence, "ACTIVATE INIT");
|
|
break;
|
|
|
|
case ACTIVATE_INTERRUPT_QUERY:
|
|
// This sequence configures the sensor to listen to finger placement events.
|
|
self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_wait_finger_init);
|
|
self->init_sequence.actions = vfs7552_wait_finger_init;
|
|
self->init_sequence.timeout = 0; // Do not time out
|
|
usb_exchange_async (ssm, &self->init_sequence, "ACTIVATE INTERRUPT QUERY");
|
|
break;
|
|
|
|
case ACTIVATE_INTERRUPT_CHECK:
|
|
receive_buf = ((unsigned char *) self->init_sequence.last_recv->buffer);
|
|
if (receive_buf[0] == interrupt_ok[0])
|
|
{
|
|
// This seems to mean: "Sensor is all good"
|
|
fpi_ssm_jump_to_state (ssm, ACTIVATE_INTERRUPT_QUERY);
|
|
}
|
|
else if (receive_buf[0] == interrupt_ready[0])
|
|
{
|
|
// This seems to mean: "We detected a finger"
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
else if (receive_buf[0] == interrupt_dont_ask[0])
|
|
{
|
|
// This seems to mean: "We already told you we detected a finger, stop asking us"
|
|
// It will not respond to another request on the interrupt endpoint
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
else
|
|
{
|
|
fp_dbg ("Unknown response 0x%02x", receive_buf[0]);
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case ACTIVATE_FINALIZE:
|
|
fpi_ssm_mark_completed (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
open_run_state (FpiSsm *ssm, FpDevice *_dev)
|
|
{
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case DEV_OPEN_START:
|
|
self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_initialization);
|
|
self->init_sequence.actions = vfs7552_initialization;
|
|
self->init_sequence.timeout = VFS7552_DEFAULT_WAIT_TIMEOUT;
|
|
usb_exchange_async (ssm, &self->init_sequence, "DEVICE OPEN");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ============= SSM Finalization Functions ============== */
|
|
|
|
static void
|
|
deactivation_complete (FpiSsm *ssm, FpDevice *_dev, GError *error)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref);
|
|
|
|
self->dev_state = FPI_IMAGE_DEVICE_STATE_INACTIVE;
|
|
self->loop_running = FALSE;
|
|
|
|
if (self->deactivating)
|
|
{
|
|
self->deactivating = FALSE;
|
|
fpi_image_device_deactivate_complete (dev, error);
|
|
return;
|
|
}
|
|
|
|
if (!error)
|
|
fpi_image_device_report_finger_status (dev, FALSE);
|
|
else
|
|
fpi_image_device_session_error (dev, error);
|
|
}
|
|
|
|
static void
|
|
start_deactivation (FpDevice *_dev)
|
|
{
|
|
FpDeviceVfs7552 *self = FPI_DEVICE_VFS7552 (_dev);
|
|
FpiSsm *ssm;
|
|
|
|
self->loop_running = TRUE;
|
|
self->dev_state = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF;
|
|
ssm = fpi_ssm_new (_dev, deactivate_run_state, DEACTIVATE_NUM_STATES);
|
|
fpi_ssm_start (ssm, deactivation_complete);
|
|
}
|
|
|
|
static void
|
|
report_finger_off (FpiSsm *ssm, FpDevice *_dev, GError *error)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref);
|
|
|
|
if (!error)
|
|
start_deactivation (_dev);
|
|
else
|
|
fpi_image_device_session_error (dev, error);
|
|
}
|
|
|
|
static void
|
|
start_report_finger_off (FpDevice *_dev)
|
|
{
|
|
FpDeviceVfs7552 *self = FPI_DEVICE_VFS7552 (_dev);
|
|
FpiSsm *ssm;
|
|
|
|
self->loop_running = TRUE;
|
|
ssm = fpi_ssm_new (_dev, capture_run_state, CAPTURE_NUM_STATES);
|
|
fpi_ssm_start (ssm, report_finger_off);
|
|
}
|
|
|
|
static void
|
|
capture_complete (FpiSsm *ssm, FpDevice *_dev, GError *error)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref);
|
|
|
|
if (!self->deactivating && !error)
|
|
{
|
|
FpImage *img;
|
|
img = fp_image_new (2 * VFS7552_IMAGE_WIDTH,
|
|
2 * VFS7552_IMAGE_HEIGHT);
|
|
// Scale the image
|
|
for (int j = 0; j < VFS7552_IMAGE_HEIGHT; j++)
|
|
{
|
|
for (int i = 0; i < VFS7552_IMAGE_WIDTH; i++)
|
|
{
|
|
int ref = j * VFS7552_IMAGE_WIDTH + i;
|
|
int ref_new = 4 * j * VFS7552_IMAGE_WIDTH + 2 * i;
|
|
img->data[ref_new] = self->image[ref];
|
|
img->data[ref_new + 1] = self->image[ref];
|
|
img->data[ref_new + 2 * VFS7552_IMAGE_WIDTH] = self->image[ref];
|
|
img->data[ref_new + 2 * VFS7552_IMAGE_WIDTH + 1] = self->image[ref];
|
|
}
|
|
}
|
|
|
|
fp_dbg ("Image captured");
|
|
fpi_image_device_image_captured (dev, img);
|
|
}
|
|
self->loop_running = FALSE;
|
|
|
|
if (self->deactivating)
|
|
start_deactivation (_dev);
|
|
else if (error)
|
|
fpi_image_device_session_error (dev, error);
|
|
}
|
|
|
|
static void
|
|
start_capture (FpDevice *_dev)
|
|
{
|
|
FpDeviceVfs7552 *self = FPI_DEVICE_VFS7552 (_dev);
|
|
FpiSsm *ssm;
|
|
|
|
self->loop_running = TRUE;
|
|
ssm = fpi_ssm_new (_dev, capture_run_state, CAPTURE_NUM_STATES);
|
|
fpi_ssm_start (ssm, capture_complete);
|
|
}
|
|
|
|
static void
|
|
report_finger_on_complete (FpiSsm *ssm, FpDevice *_dev, GError *error)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref);
|
|
|
|
if (!self->deactivating && !error)
|
|
fpi_image_device_report_finger_status (dev, TRUE);
|
|
self->loop_running = FALSE;
|
|
|
|
if (self->deactivating)
|
|
start_deactivation (_dev);
|
|
else if (error)
|
|
fpi_image_device_session_error (dev, error);
|
|
}
|
|
|
|
static void
|
|
activate_complete (FpiSsm *ssm, FpDevice *_dev, GError *error)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref);
|
|
|
|
if (!self->deactivating && !error)
|
|
{
|
|
ssm = fpi_ssm_new (_dev, capture_run_state, CAPTURE_NUM_STATES);
|
|
fpi_ssm_start (ssm, report_finger_on_complete);
|
|
return;
|
|
}
|
|
|
|
if (self->deactivating)
|
|
{
|
|
start_deactivation (_dev);
|
|
}
|
|
else if (error)
|
|
{
|
|
self->loop_running = FALSE;
|
|
fpi_image_device_session_error (dev, error);
|
|
}
|
|
}
|
|
|
|
static void
|
|
start_report_finger_on (FpDevice *_dev)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (dev);
|
|
FpiSsm *ssm;
|
|
|
|
self->deactivating = FALSE;
|
|
self->loop_running = TRUE;
|
|
ssm = fpi_ssm_new (_dev, activate_run_state, ACTIVATE_NUM_STATES);
|
|
fpi_ssm_start (ssm, activate_complete);
|
|
}
|
|
|
|
static void
|
|
validity_change_state (FpDevice *_dev, void *data)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (dev);
|
|
FpiImageDeviceState next_state = self->dev_state_next;
|
|
|
|
if (self->dev_state == next_state)
|
|
{
|
|
fp_dbg ("already in %d", next_state);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
fp_dbg ("changing to %d", next_state);
|
|
}
|
|
|
|
switch (next_state)
|
|
{
|
|
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON:
|
|
self->dev_state = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON;
|
|
start_report_finger_on (_dev);
|
|
break;
|
|
|
|
case FPI_IMAGE_DEVICE_STATE_CAPTURE:
|
|
self->dev_state = FPI_IMAGE_DEVICE_STATE_CAPTURE;
|
|
start_capture (_dev);
|
|
break;
|
|
|
|
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF:
|
|
self->dev_state = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF;
|
|
start_report_finger_off (_dev);
|
|
break;
|
|
|
|
/* Ignored States */
|
|
case FPI_IMAGE_DEVICE_STATE_INACTIVE:
|
|
case FPI_IMAGE_DEVICE_STATE_ACTIVATING:
|
|
case FPI_IMAGE_DEVICE_STATE_DEACTIVATING:
|
|
case FPI_IMAGE_DEVICE_STATE_IDLE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
open_complete (FpiSsm *ssm, FpDevice *_dev, GError *error)
|
|
{
|
|
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (_dev);
|
|
g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref);
|
|
|
|
fpi_image_device_open_complete (dev, error);
|
|
}
|
|
|
|
/* ================== Driver Entrypoints =================== */
|
|
|
|
static void
|
|
dev_close (FpImageDevice *dev)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
|
|
0, 0, &error);
|
|
|
|
fpi_image_device_close_complete (dev, error);
|
|
}
|
|
|
|
static void
|
|
dev_deactivate (FpImageDevice *dev)
|
|
{
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (dev);
|
|
|
|
if (self->loop_running)
|
|
self->deactivating = TRUE;
|
|
else
|
|
fpi_image_device_deactivate_complete (dev, NULL);
|
|
}
|
|
|
|
static void
|
|
delayed_change_state (FpDevice *_dev, FpiImageDeviceState state)
|
|
{
|
|
GSource *timeout;
|
|
char *name;
|
|
|
|
// schedule state change instead of calling it directly to allow all actions
|
|
// related to the previous state to complete
|
|
timeout = fpi_device_add_timeout (_dev, 50,
|
|
validity_change_state,
|
|
NULL, NULL);
|
|
|
|
name = g_strdup_printf ("dev_change_state to %d", state);
|
|
g_source_set_name (timeout, name);
|
|
g_free (name);
|
|
}
|
|
|
|
static void
|
|
dev_change_state (FpImageDevice *dev, FpiImageDeviceState state)
|
|
{
|
|
FpDeviceVfs7552 *self;
|
|
|
|
self = FPI_DEVICE_VFS7552 (dev);
|
|
|
|
switch (state)
|
|
{
|
|
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON:
|
|
self->dev_state_next = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON;
|
|
delayed_change_state (FP_DEVICE (dev), state);
|
|
break;
|
|
|
|
case FPI_IMAGE_DEVICE_STATE_CAPTURE:
|
|
self->dev_state_next = FPI_IMAGE_DEVICE_STATE_CAPTURE;
|
|
delayed_change_state (FP_DEVICE (dev), state);
|
|
break;
|
|
|
|
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF:
|
|
self->dev_state_next = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF;
|
|
delayed_change_state (FP_DEVICE (dev), state);
|
|
break;
|
|
|
|
/* Ignored States */
|
|
case FPI_IMAGE_DEVICE_STATE_INACTIVE:
|
|
case FPI_IMAGE_DEVICE_STATE_ACTIVATING:
|
|
case FPI_IMAGE_DEVICE_STATE_DEACTIVATING:
|
|
case FPI_IMAGE_DEVICE_STATE_IDLE:
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
dev_activate (FpImageDevice *dev)
|
|
{
|
|
fpi_image_device_activate_complete (dev, NULL);
|
|
}
|
|
|
|
static void
|
|
dev_open (FpImageDevice *dev)
|
|
{
|
|
FpiSsm *ssm;
|
|
GError *error = NULL;
|
|
|
|
// First we need to reset the device, otherwise opening will fail at state 13
|
|
if (!g_usb_device_reset (fpi_device_get_usb_device (FP_DEVICE (dev)), &error))
|
|
{
|
|
fpi_image_device_open_complete (dev, error);
|
|
return;
|
|
}
|
|
|
|
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (FP_DEVICE (dev)), 0, 0, &error))
|
|
{
|
|
fpi_image_device_open_complete (dev, error);
|
|
return;
|
|
}
|
|
|
|
ssm = fpi_ssm_new (FP_DEVICE (dev), open_run_state, DEV_OPEN_NUM_STATES);
|
|
fpi_ssm_start (ssm, open_complete);
|
|
}
|
|
|
|
static const FpIdEntry id_table[] = {
|
|
/* Validity device from some Dell XPS laptops (9560, 9360 at least) */
|
|
{ .vid = 0x138a, .pid = 0x0091 },
|
|
{ .vid = 0x0, .pid = 0x0 },
|
|
};
|
|
|
|
static void
|
|
fpi_device_vfs7552_init (FpDeviceVfs7552 *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fpi_device_vfs7552_class_init (FpDeviceVfs7552Class *klass)
|
|
{
|
|
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
|
|
FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass);
|
|
|
|
dev_class->id = "vfs7552";
|
|
dev_class->full_name = "Validity VFS7552";
|
|
dev_class->type = FP_DEVICE_TYPE_USB;
|
|
dev_class->id_table = id_table;
|
|
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
|
|
|
|
img_class->img_close = dev_close;
|
|
img_class->deactivate = dev_deactivate;
|
|
img_class->change_state = dev_change_state;
|
|
img_class->activate = dev_activate;
|
|
img_class->img_open = dev_open;
|
|
|
|
img_class->bz3_threshold = 20;
|
|
|
|
img_class->img_width = VFS7552_IMAGE_WIDTH;
|
|
img_class->img_height = VFS7552_IMAGE_HEIGHT;
|
|
}
|