libfprint/libfprint/drivers/vfs101.c
Benjamin Berg d683b271d4 ssm: Remove delayed action GCancellable integration
Unfortunately, the implementation was not thread safe and was not
sticking to the thread local main context.

In addition to this, it is not entirely clear to me how this API should
behave. The current approach is to simply cancel the transition with the
state machine halting in its current state. Instead, it could also make
sense for cancellation to cause the state machine to return a
G_IO_ERROR_CANCELLED.

As such, simply remove the feature for now. If anyone actually has a
good use-case then we can add it again.
2021-04-28 22:16:37 +02:00

1371 lines
34 KiB
C

/*
* Validity VFS101 driver for libfprint
* Copyright (C) 2011 Sergio Cerlesi <sergio.cerlesi@gmail.com>
*
* 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 "vfs101"
#include "drivers_api.h"
/* Input-Output usb endpoint */
#define EP_IN(n) (n | FPI_USB_ENDPOINT_IN)
#define EP_OUT(n) (n | FPI_USB_ENDPOINT_OUT)
/* Usb bulk timeout */
#define BULK_TIMEOUT 100
/* The device send back the image into block of 16 frames of 292 bytes */
#define VFS_FRAME_SIZE 292
#define VFS_BLOCK_SIZE 16 * VFS_FRAME_SIZE
/* Buffer height */
#define VFS_BUFFER_HEIGHT 5000
/* Buffer size */
#define VFS_BUFFER_SIZE (VFS_BUFFER_HEIGHT * VFS_FRAME_SIZE)
/* Image width */
#define VFS_IMG_WIDTH 200
/* Maximum image height */
#define VFS_IMG_MAX_HEIGHT 1023
/* Minimum image height */
#define VFS_IMG_MIN_HEIGHT 200
/* Scan level threshold */
#define VFS_IMG_SLT_BEGIN 768
#define VFS_IMG_SLT_END 64
#define VFS_IMG_SLT_LINES 4
/* Minimum image level */
#define VFS_IMG_MIN_IMAGE_LEVEL 144
/* Best image contrast */
#define VFS_IMG_BEST_CONTRAST 128
/* Device parameters address */
#define VFS_PAR_000E 0x000e
#define VFS_PAR_0011 0x0011
#define VFS_PAR_THRESHOLD 0x0057
#define VFS_PAR_STATE_3 0x005e
#define VFS_PAR_STATE_5 0x005f
#define VFS_PAR_INFO_RATE 0x0062
#define VFS_PAR_0076 0x0076
#define VFS_PAR_INFO_CONTRAST 0x0077
#define VFS_PAR_0078 0x0078
/* Device regiones address */
#define VFS_REG_IMG_EXPOSURE 0xff500e
#define VFS_REG_IMG_CONTRAST 0xff5038
/* Device settings */
#define VFS_VAL_000E 0x0001
#define VFS_VAL_0011 0x0008
#define VFS_VAL_THRESHOLD 0x0096
#define VFS_VAL_STATE_3 0x0064
#define VFS_VAL_STATE_5 0x00c8
#define VFS_VAL_INFO_RATE 0x0001
#define VFS_VAL_0076 0x0012
#define VFS_VAL_0078 0x2230
#define VFS_VAL_IMG_EXPOSURE 0x21c0
/* Structure for Validity device */
struct _FpDeviceVfs101
{
FpImageDevice parent;
/* Action state */
gboolean active;
gboolean deactivate;
/* Sequential number */
unsigned int seqnum;
/* Buffer for input/output */
unsigned char *buffer;
/* Length of data to send or received */
unsigned int length;
/* Ignore usb error */
int ignore_error;
/* Loop counter */
int counter;
/* Image contrast */
int contrast;
/* Best contrast */
int best_contrast;
/* Best contrast level */
int best_clevel;
/* Bottom line of image */
int bottom;
/* Image height */
int height;
};
G_DECLARE_FINAL_TYPE (FpDeviceVfs101, fpi_device_vfs101, FPI, DEVICE_VFS101,
FpImageDevice);
G_DEFINE_TYPE (FpDeviceVfs101, fpi_device_vfs101, FP_TYPE_IMAGE_DEVICE);
/* Return byte at specified position */
static inline unsigned char
byte (int position, int value)
{
return (value >> (position * 8)) & 0xff;
}
/* Return sequential number */
static inline unsigned short
get_seqnum (int h, int l)
{
return (h << 8) | l;
}
/* Check sequential number */
static inline int
check_seqnum (FpDeviceVfs101 *vdev)
{
if ((byte (0, vdev->seqnum) == vdev->buffer[0]) &&
(byte (1, vdev->seqnum) == vdev->buffer[1]))
return 0;
else
return 1;
}
/* Internal result codes */
enum {
RESULT_RETRY,
RESULT_RETRY_SHORT,
RESULT_RETRY_REMOVE,
RESULT_COUNT,
};
/* Dump buffer for debug */
#define dump_buffer(buf) \
fp_dbg ("%02x %02x %02x %02x %02x %02x %02x %02x", \
buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13] \
)
/* Callback of asynchronous send */
static void
async_send_cb (FpiUsbTransfer *transfer, FpDevice *device,
gpointer user_data, GError *error)
{
FpImageDevice *dev = FP_IMAGE_DEVICE (device);
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
/* Skip error check if ignore_error is set */
if (error)
{
if (!self->ignore_error)
{
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
else
{
g_error_free (error);
fp_dbg ("Ignoring send error: %s", error->message);
}
}
/* Reset ignore_error flag */
self->ignore_error = FALSE;
/* Dump buffer for debug */
dump_buffer (self->buffer);
fpi_ssm_next_state (transfer->ssm);
}
/* Submit asynchronous send */
static void
async_send (FpiSsm *ssm,
FpImageDevice *dev)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
FpiUsbTransfer *transfer;
transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
/* Put sequential number into the buffer */
self->seqnum++;
self->buffer[0] = byte (0, self->seqnum);
self->buffer[1] = byte (1, self->seqnum);
/* Prepare bulk transfer */
fpi_usb_transfer_fill_bulk_full (transfer, EP_OUT (1),
self->buffer, self->length, NULL);
transfer->ssm = ssm;
transfer->short_is_error = TRUE;
fpi_usb_transfer_submit (transfer, BULK_TIMEOUT, NULL,
async_send_cb, NULL);
}
/* Callback of asynchronous recv */
static void
async_recv_cb (FpiUsbTransfer *transfer, FpDevice *device,
gpointer user_data, GError *error)
{
FpImageDevice *dev = FP_IMAGE_DEVICE (device);
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
/* Skip error check if ignore_error is set */
if (!self->ignore_error)
{
if (error)
{
/* Transfer not completed, return IO error */
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
if (check_seqnum (self))
{
/* Sequential number received mismatch, return protocol error */
fp_err ("seqnum mismatch, got %04x, expected %04x",
get_seqnum (self->buffer[1], self->buffer[0]),
self->seqnum);
fpi_ssm_mark_failed (transfer->ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
return;
}
}
g_clear_pointer (&error, g_error_free);
/* Reset ignore_error flag */
self->ignore_error = FALSE;
/* Dump buffer for debug */
dump_buffer (self->buffer);
/* Set length of received data */
self->length = transfer->actual_length;
fpi_ssm_next_state (transfer->ssm);
}
/* Submit asynchronous recv */
static void
async_recv (FpiSsm *ssm,
FpImageDevice *dev)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
FpiUsbTransfer *transfer;
/* Allocation of transfer */
transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
/* Prepare bulk transfer */
fpi_usb_transfer_fill_bulk_full (transfer, EP_IN (1), self->buffer,
0x0f, NULL);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, BULK_TIMEOUT, NULL,
async_recv_cb, NULL);
}
static void async_load (FpiSsm *ssm,
FpImageDevice *dev);
/* Callback of asynchronous load */
static void
async_load_cb (FpiUsbTransfer *transfer, FpDevice *device,
gpointer user_data, GError *error)
{
FpImageDevice *dev = FP_IMAGE_DEVICE (device);
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
/* Skip error check if ignore_error is set */
if (!self->ignore_error)
{
if (error)
{
/* Transfer not completed */
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
if (transfer->actual_length % VFS_FRAME_SIZE)
{
/* Received incomplete frame, return protocol error */
fp_err ("received incomplete frame");
fpi_ssm_mark_failed (transfer->ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
return;
}
}
/* Any error has been ignored. */
g_clear_pointer (&error, g_error_free);
/* Increase image length */
self->length += transfer->actual_length;
if (transfer->actual_length == VFS_BLOCK_SIZE)
{
if ((VFS_BUFFER_SIZE - self->length) < VFS_BLOCK_SIZE)
{
/* Buffer full, image too large, return no memory error */
fp_err ("buffer full, image too large");
fpi_ssm_mark_failed (transfer->ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
return;
}
else
{
/* Image load not completed, submit another asynchronous load */
async_load (transfer->ssm, dev);
}
}
else
{
/* Reset ignore_error flag */
self->ignore_error = FALSE;
/* Image load completed, go to next state */
self->height = self->length / VFS_FRAME_SIZE;
fp_dbg ("image loaded, height = %d", self->height);
fpi_ssm_next_state (transfer->ssm);
}
}
/* Submit asynchronous load */
static void
async_load (FpiSsm *ssm,
FpImageDevice *dev)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
FpiUsbTransfer *transfer;
unsigned char *buffer;
/* Allocation of transfer */
transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
/* Append new data into the buffer */
buffer = self->buffer + self->length;
/* Prepare bulk transfer */
fpi_usb_transfer_fill_bulk_full (transfer, EP_IN (2), buffer,
VFS_BLOCK_SIZE, NULL);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, BULK_TIMEOUT, NULL,
async_load_cb, NULL);
}
/* Swap ssm states */
enum {
M_SWAP_SEND,
M_SWAP_RECV,
M_SWAP_NUM_STATES,
};
/* Exec swap sequential state machine */
static void
m_swap_state (FpiSsm *ssm, FpDevice *dev)
{
switch (fpi_ssm_get_cur_state (ssm))
{
case M_SWAP_SEND:
/* Send data */
async_send (ssm, FP_IMAGE_DEVICE (dev));
break;
case M_SWAP_RECV:
/* Recv response */
async_recv (ssm, FP_IMAGE_DEVICE (dev));
break;
}
}
/* Start swap sequential state machine */
static void
m_swap (FpiSsm *ssm,
FpImageDevice *dev,
unsigned char *data,
size_t length)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
FpiSsm *subsm;
/* Prepare data for sending */
memcpy (self->buffer, data, length);
memset (self->buffer + length, 0, 16 - length);
self->length = length;
/* Start swap ssm */
subsm = fpi_ssm_new (FP_DEVICE (dev), m_swap_state, M_SWAP_NUM_STATES);
fpi_ssm_start_subsm (ssm, subsm);
}
/* Retrieve fingerprint image */
static void
vfs_get_print (FpiSsm *ssm,
FpImageDevice *dev,
unsigned int param,
int type)
{
unsigned char data[2][0x0e] = {
{ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01 },
{ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01 }
};
fp_dbg ("param = %04x, type = %d", param, type);
/* Prepare data for sending */
data[type][6] = byte (0, param);
data[type][7] = byte (1, param);
/* Run swap sequential state machine */
m_swap (ssm, dev, data[type], 0x0e);
}
/* Set a parameter value on the device */
static void
vfs_set_param (FpiSsm *ssm,
FpImageDevice *dev,
unsigned int param,
unsigned int value)
{
unsigned char data[0x0a] = { 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 };
fp_dbg ("param = %04x, value = %04x", param, value);
/* Prepare data for sending */
data[6] = byte (0, param);
data[7] = byte (1, param);
data[8] = byte (0, value);
data[9] = byte (1, value);
/* Run swap sequential state machine */
m_swap (ssm, dev, data, 0x0a);
}
/* Abort previous print */
static void
vfs_abort_print (FpiSsm *ssm,
FpImageDevice *dev)
{
unsigned char data[0x06] = { 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00 };
G_DEBUG_HERE ();
/* Run swap sequential state machine */
m_swap (ssm, dev, data, 0x06);
}
/* Poke a value on a region */
static void
vfs_poke (FpiSsm *ssm,
FpImageDevice *dev,
unsigned int addr,
unsigned int value,
unsigned int size)
{
unsigned char data[0x0f] = { 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
fp_dbg ("addr = %04x, value = %04x", addr, value);
/* Prepare data for sending */
data[6] = byte (0, addr);
data[7] = byte (1, addr);
data[8] = byte (2, addr);
data[9] = byte (3, addr);
data[10] = byte (0, value);
data[11] = byte (1, value);
data[12] = byte (2, value);
data[13] = byte (3, value);
data[14] = byte (0, size);
/* Run swap sequential state machine */
m_swap (ssm, dev, data, 0x0f);
}
/* Get current finger state */
static void
vfs_get_finger_state (FpiSsm *ssm,
FpImageDevice *dev)
{
unsigned char data[0x06] = { 0x00, 0x00, 0x00, 0x00, 0x16, 0x00 };
G_DEBUG_HERE ();
/* Run swap sequential state machine */
m_swap (ssm, dev, data, 0x06);
}
/* Load raw image from reader */
static void
vfs_img_load (FpiSsm *ssm,
FpImageDevice *dev)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
G_DEBUG_HERE ();
/* Reset buffer length */
self->length = 0;
/* Reset image properties */
self->bottom = 0;
self->height = -1;
/* Asynchronous load */
async_load (ssm, dev);
}
#define offset(x, y) ((x) + ((y) * VFS_FRAME_SIZE))
/* Screen image to remove noise and find bottom line and height of image */
static void
img_screen (FpDeviceVfs101 *vdev)
{
int y, x, count, top;
long int level;
int last_line = vdev->height - 1;
fp_dbg ("image height before screen = %d", vdev->height);
count = 0;
/* Image returned from sensor can contain many empty lines,
* for remove these lines compare byte 282-283 (scan level information)
* with two different thresholds, one for the begin of finger image and
* one for the end. To increase stability of the code use a counter
* of lines that satisfy the threshold.
*/
for (y = last_line, top = last_line; y >= 0; y--)
{
/* Take image scan level */
level = vdev->buffer[offset (283, y)] * 256 +
vdev->buffer[offset (282, y)];
fp_dbg ("line = %d, scan level = %ld", y, level);
if (level >= VFS_IMG_SLT_BEGIN && top == last_line)
{
/* Begin threshold satisfied */
if (count < VFS_IMG_SLT_LINES)
{
/* Increase count */
count++;
}
else
{
/* Found top fingerprint line */
top = y + VFS_IMG_SLT_LINES;
count = 0;
}
}
else if ((level < VFS_IMG_SLT_END || level >= 65535) &&
top != last_line)
{
/* End threshold satisfied */
if (count < VFS_IMG_SLT_LINES)
{
/* Increase count */
count++;
}
else
{
/* Found bottom fingerprint line */
vdev->bottom = y + VFS_IMG_SLT_LINES + 1;
break;
}
}
else
{
/* Not threshold satisfied, reset count */
count = 0;
}
}
vdev->height = top - vdev->bottom + 1;
/* Check max height */
if (vdev->height > VFS_IMG_MAX_HEIGHT)
vdev->height = VFS_IMG_MAX_HEIGHT;
fp_dbg ("image height after screen = %d", vdev->height);
/* Scan image and remove noise */
for (y = vdev->bottom; y <= top; y++)
for (x = 6; x < VFS_IMG_WIDTH + 6; x++)
if (vdev->buffer[offset (x, y)] > VFS_IMG_MIN_IMAGE_LEVEL)
vdev->buffer[offset (x, y)] = 255;
};
/* Copy image from reader buffer and put it into image data */
static void
img_copy (FpDeviceVfs101 *self, FpImage *img)
{
unsigned int line;
unsigned char *img_buffer = img->data;
unsigned char *vdev_buffer = self->buffer + (self->bottom * VFS_FRAME_SIZE) + 6;
for (line = 0; line < img->height; line++)
{
/* Copy image line from reader buffer to image data */
memcpy (img_buffer, vdev_buffer, VFS_IMG_WIDTH);
/* Next line of reader buffer */
vdev_buffer = vdev_buffer + VFS_FRAME_SIZE;
/* Next line of image buffer */
img_buffer = img_buffer + VFS_IMG_WIDTH;
}
}
/* Extract fingerpint image from raw data */
static void
img_extract (FpiSsm *ssm,
FpImageDevice *dev)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
FpImage *img;
/* Screen image to remove noise and find top and bottom line */
img_screen (self);
/* Check image height */
if (self->height < VFS_IMG_MIN_HEIGHT)
{
/* Image too short */
self->height = 0;
fpi_image_device_retry_scan (dev, FP_DEVICE_RETRY_TOO_SHORT);
return;
}
/* Create new image */
img = fp_image_new (self->height, VFS_IMG_WIDTH);
img->width = VFS_IMG_WIDTH;
img->height = self->height;
img->flags = FPI_IMAGE_V_FLIPPED;
/* Copy data into image */
img_copy (self, img);
/* Notify image captured */
fpi_image_device_image_captured (dev, img);
};
/* Finger states */
enum {
VFS_FINGER_EMPTY,
VFS_FINGER_PRESENT,
VFS_FINGER_UNKNOWN,
};
/* Return finger state */
static inline int
vfs_finger_state (FpDeviceVfs101 *vdev)
{
/* Check finger state */
switch (vdev->buffer[0x0a])
{
case 0x00:
case 0x01:
/* Finger is empty */
return VFS_FINGER_EMPTY;
break;
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
/* Finger is present */
return VFS_FINGER_PRESENT;
break;
default:
return VFS_FINGER_UNKNOWN;
}
};
/* Check contrast of image */
static void
vfs_check_contrast (FpDeviceVfs101 *vdev)
{
int y;
long int count = 0;
/* Check difference from byte 4 to byte 5 for verify contrast of image */
for (y = 0; y < vdev->height; y++)
count = count + vdev->buffer[offset (5, y)] - vdev->buffer[offset (4, y)];
count = count / vdev->height;
if (count < 16)
{
/* Contrast not valid, retry */
vdev->contrast++;
return;
}
fp_dbg ("contrast = %d, level = %ld", vdev->contrast, count);
if (labs (count - VFS_IMG_BEST_CONTRAST) < abs (vdev->best_clevel - VFS_IMG_BEST_CONTRAST))
{
/* Better contrast found, use it */
vdev->best_contrast = vdev->contrast;
vdev->best_clevel = count;
}
}
/* Loop ssm states */
enum {
/* Step 0 - Scan finger */
M_LOOP_0_GET_PRINT,
M_LOOP_0_SLEEP,
M_LOOP_0_GET_STATE,
M_LOOP_0_LOAD_IMAGE,
M_LOOP_0_EXTRACT_IMAGE,
M_LOOP_0_CHECK_ACTION,
/* Step 1 - Scan failed */
M_LOOP_1_GET_STATE,
M_LOOP_1_CHECK_STATE,
M_LOOP_1_GET_PRINT,
M_LOOP_1_LOAD_IMAGE,
M_LOOP_1_LOOP,
M_LOOP_1_SLEEP,
/* Step 2 - Abort print */
M_LOOP_2_ABORT_PRINT,
M_LOOP_2_LOAD_IMAGE,
/* Step 3 - Wait aborting */
M_LOOP_3_GET_PRINT,
M_LOOP_3_LOAD_IMAGE,
M_LOOP_3_CHECK_IMAGE,
M_LOOP_3_LOOP,
/* Number of states */
M_LOOP_NUM_STATES,
};
/* Exec loop sequential state machine */
static void
m_loop_state (FpiSsm *ssm, FpDevice *_dev)
{
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (_dev);
/* Complete if deactivation was requested */
if (self->deactivate)
{
fpi_ssm_mark_completed (ssm);
return;
}
switch (fpi_ssm_get_cur_state (ssm))
{
case M_LOOP_0_GET_PRINT:
/* Send get print command to the reader */
vfs_get_print (ssm, dev, VFS_BUFFER_HEIGHT, 1);
break;
case M_LOOP_0_SLEEP:
/* Wait fingerprint scanning */
fpi_ssm_next_state_delayed (ssm, 50);
break;
case M_LOOP_0_GET_STATE:
/* Get finger state */
vfs_get_finger_state (ssm, dev);
break;
case M_LOOP_0_LOAD_IMAGE:
/* Check finger state */
switch (vfs_finger_state (self))
{
case VFS_FINGER_EMPTY:
fpi_image_device_report_finger_status (dev, FALSE);
/* Finger isn't present, loop */
fpi_ssm_jump_to_state (ssm, M_LOOP_0_SLEEP);
break;
case VFS_FINGER_PRESENT:
fpi_image_device_report_finger_status (dev, TRUE);
/* Load image from reader */
self->ignore_error = TRUE;
vfs_img_load (ssm, dev);
break;
default:
fpi_image_device_report_finger_status (dev, FALSE);
/* Unknown state */
fp_err ("unknown device state 0x%02x",
self->buffer[0x0a]);
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
break;
}
break;
case M_LOOP_0_EXTRACT_IMAGE:
/* Fingerprint is loaded, extract image from raw data */
img_extract (ssm, dev);
/* Wait handling image */
fpi_ssm_next_state_delayed (ssm, 10);
break;
case M_LOOP_0_CHECK_ACTION:
/* Action not completed */
if (self->height > 0)
/* Continue loop */
fpi_ssm_jump_to_state (ssm, M_LOOP_2_ABORT_PRINT);
else
/* Error found */
fpi_ssm_next_state (ssm);
break;
case M_LOOP_1_GET_STATE:
/* Get finger state */
vfs_get_finger_state (ssm, dev);
break;
case M_LOOP_1_CHECK_STATE:
/* Check finger state */
if (vfs_finger_state (self) == VFS_FINGER_PRESENT)
{
fpi_image_device_report_finger_status (dev, TRUE);
fpi_ssm_next_state_delayed (ssm, 250);
}
else
{
/* Finger not present */
fpi_image_device_report_finger_status (dev, FALSE);
/* Continue */
fpi_ssm_jump_to_state (ssm, M_LOOP_1_SLEEP);
}
break;
case M_LOOP_1_GET_PRINT:
/* Send get print command to the reader */
vfs_get_print (ssm, dev, VFS_BUFFER_HEIGHT, 1);
break;
case M_LOOP_1_LOAD_IMAGE:
/* Load image */
self->ignore_error = TRUE;
vfs_img_load (ssm, dev);
break;
case M_LOOP_1_LOOP:
/* Loop */
fpi_ssm_jump_to_state (ssm, M_LOOP_1_GET_STATE);
break;
case M_LOOP_1_SLEEP:
/* Wait fingerprint scanning */
fpi_ssm_next_state_delayed (ssm, 10);
break;
case M_LOOP_2_ABORT_PRINT:
/* Abort print command */
vfs_abort_print (ssm, dev);
break;
case M_LOOP_2_LOAD_IMAGE:
/* Load abort image */
self->ignore_error = TRUE;
vfs_img_load (ssm, dev);
break;
case M_LOOP_3_GET_PRINT:
/* Get empty image */
vfs_get_print (ssm, dev, 0x000a, 0);
break;
case M_LOOP_3_LOAD_IMAGE:
/* Load abort image */
self->ignore_error = TRUE;
vfs_img_load (ssm, dev);
break;
case M_LOOP_3_CHECK_IMAGE:
if (self->height == 10)
{
/* Image load correctly, jump to step 0 */
self->counter = 0;
fpi_ssm_jump_to_state (ssm, M_LOOP_0_GET_PRINT);
}
else if (self->counter < 10)
{
/* Wait aborting */
self->counter++;
fpi_ssm_next_state_delayed (ssm, 100);
}
else
{
/* reach max loop counter, return protocol error */
fp_err ("waiting abort reach max loop counter");
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
}
break;
case M_LOOP_3_LOOP:
/* Loop */
fpi_ssm_jump_to_state (ssm, M_LOOP_3_GET_PRINT);
break;
}
}
/* Complete loop sequential state machine */
static void
m_loop_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
/* When the loop completes, we have (successfully) deactivated */
if (self->active)
fpi_image_device_deactivate_complete (FP_IMAGE_DEVICE (dev),
error);
self->active = FALSE;
}
/* Init ssm states */
enum {
/* Step 0 - Cleanup device buffer */
M_INIT_0_RECV_DIRTY,
M_INIT_0_ABORT_PRINT,
M_INIT_0_LOAD_IMAGE,
/* Step 1 - Wait aborting */
M_INIT_1_GET_PRINT,
M_INIT_1_LOAD_IMAGE,
M_INIT_1_CHECK_IMAGE,
M_INIT_1_LOOP,
/* Step 2 - Handle unexpected finger presence */
M_INIT_2_GET_STATE,
M_INIT_2_CHECK_STATE,
M_INIT_2_GET_PRINT,
M_INIT_2_LOAD_IMAGE,
M_INIT_2_LOOP,
/* Step 3 - Set parameters */
M_INIT_3_SET_000E,
M_INIT_3_SET_0011,
M_INIT_3_SET_0076,
M_INIT_3_SET_0078,
M_INIT_3_SET_THRESHOLD,
M_INIT_3_SET_STATE3_COUNT,
M_INIT_3_SET_STATE5_COUNT,
M_INIT_3_SET_INFO_CONTRAST,
M_INIT_3_SET_INFO_RATE,
/* Step 4 - Autocalibrate contrast */
M_INIT_4_SET_EXPOSURE,
M_INIT_4_SET_CONTRAST,
M_INIT_4_GET_PRINT,
M_INIT_4_LOAD_IMAGE,
M_INIT_4_CHECK_CONTRAST,
/* Step 5 - Set info line parameters */
M_INIT_5_SET_EXPOSURE,
M_INIT_5_SET_CONTRAST,
M_INIT_5_SET_INFO_CONTRAST,
M_INIT_5_SET_INFO_RATE,
/* Number of states */
M_INIT_NUM_STATES,
};
/* Exec init sequential state machine */
static void
m_init_state (FpiSsm *ssm, FpDevice *_dev)
{
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (_dev);
/* Mark as cancelled when activation collides with deactivation. */
if (self->deactivate)
{
fpi_ssm_mark_failed (ssm,
g_error_new (G_IO_ERROR,
G_IO_ERROR_CANCELLED,
"Initialisation was cancelled"));
return;
}
switch (fpi_ssm_get_cur_state (ssm))
{
case M_INIT_0_RECV_DIRTY:
/* Recv eventually dirty data */
self->ignore_error = TRUE;
async_recv (ssm, dev);
break;
case M_INIT_0_ABORT_PRINT:
/* Abort print command */
vfs_abort_print (ssm, dev);
break;
case M_INIT_0_LOAD_IMAGE:
/* Load abort image */
self->ignore_error = TRUE;
vfs_img_load (ssm, dev);
break;
case M_INIT_1_GET_PRINT:
/* Get empty image */
vfs_get_print (ssm, dev, 0x000a, 0);
break;
case M_INIT_1_LOAD_IMAGE:
/* Load abort image */
self->ignore_error = TRUE;
vfs_img_load (ssm, dev);
break;
case M_INIT_1_CHECK_IMAGE:
if (self->height == 10)
{
/* Image load correctly, jump to step 2 */
self->counter = 0;
fpi_ssm_jump_to_state (ssm, M_INIT_2_GET_STATE);
}
else if (self->counter < 10)
{
/* Wait aborting */
self->counter++;
fpi_ssm_next_state_delayed (ssm, 100);
}
else
{
/* reach max loop counter, return protocol error */
fp_err ("waiting abort reach max loop counter");
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
}
break;
case M_INIT_1_LOOP:
/* Loop */
fpi_ssm_jump_to_state (ssm, M_INIT_1_GET_PRINT);
break;
case M_INIT_2_GET_STATE:
/* Get finger state */
vfs_get_finger_state (ssm, dev);
break;
case M_INIT_2_CHECK_STATE:
/* Check finger state */
if (vfs_finger_state (self) == VFS_FINGER_PRESENT)
{
/* Wait a bit for finger removal; if it doesn't happen, prompt */
if (self->counter < 2)
{
/* Wait removing finger */
self->counter++;
fpi_ssm_next_state_delayed (ssm, 250);
}
else
{
/* The user should remove their finger from the scanner */
fp_warn ("unexpected finger find, remove finger from the scanner");
fpi_ssm_mark_failed (ssm, fpi_device_retry_new (FP_DEVICE_RETRY_REMOVE_FINGER));
}
}
else
{
/* Finger not present */
if (self->counter == 0)
{
/* Continue */
fpi_ssm_jump_to_state (ssm, M_INIT_3_SET_000E);
}
else
{
/* Finger removed, jump to abort */
self->counter = 0;
fpi_ssm_jump_to_state (ssm, M_INIT_0_ABORT_PRINT);
}
}
break;
case M_INIT_2_GET_PRINT:
/* Send get print command to the reader */
vfs_get_print (ssm, dev, VFS_BUFFER_HEIGHT, 1);
break;
case M_INIT_2_LOAD_IMAGE:
/* Load unexpected image */
self->ignore_error = TRUE;
vfs_img_load (ssm, dev);
break;
case M_INIT_2_LOOP:
/* Loop */
fpi_ssm_jump_to_state (ssm, M_INIT_2_GET_STATE);
break;
case M_INIT_3_SET_000E:
/* Set param 0x000e, required for take image */
vfs_set_param (ssm, dev, VFS_PAR_000E, VFS_VAL_000E);
break;
case M_INIT_3_SET_0011:
/* Set param 0x0011, required for take image */
vfs_set_param (ssm, dev, VFS_PAR_0011, VFS_VAL_0011);
break;
case M_INIT_3_SET_0076:
/* Set param 0x0076, required for use info line */
vfs_set_param (ssm, dev, VFS_PAR_0076, VFS_VAL_0076);
break;
case M_INIT_3_SET_0078:
/* Set param 0x0078, required for use info line */
vfs_set_param (ssm, dev, VFS_PAR_0078, VFS_VAL_0078);
break;
case M_INIT_3_SET_THRESHOLD:
/* Set threshold */
vfs_set_param (ssm, dev, VFS_PAR_THRESHOLD, VFS_VAL_THRESHOLD);
break;
case M_INIT_3_SET_STATE3_COUNT:
/* Set state 3 count */
vfs_set_param (ssm, dev, VFS_PAR_STATE_3, VFS_VAL_STATE_3);
break;
case M_INIT_3_SET_STATE5_COUNT:
/* Set state 5 count */
vfs_set_param (ssm, dev, VFS_PAR_STATE_5, VFS_VAL_STATE_5);
break;
case M_INIT_3_SET_INFO_CONTRAST:
/* Set info line contrast */
vfs_set_param (ssm, dev, VFS_PAR_INFO_CONTRAST, 10);
break;
case M_INIT_3_SET_INFO_RATE:
/* Set info line rate */
vfs_set_param (ssm, dev, VFS_PAR_INFO_RATE, 32);
break;
case M_INIT_4_SET_EXPOSURE:
/* Set exposure level of reader */
vfs_poke (ssm, dev, VFS_REG_IMG_EXPOSURE, 0x4000, 0x02);
self->counter = 1;
break;
case M_INIT_4_SET_CONTRAST:
/* Set contrast level of reader */
vfs_poke (ssm, dev, VFS_REG_IMG_CONTRAST, self->contrast, 0x01);
break;
case M_INIT_4_GET_PRINT:
/* Get empty image */
vfs_get_print (ssm, dev, 0x000a, 0);
break;
case M_INIT_4_LOAD_IMAGE:
/* Load empty image */
vfs_img_load (ssm, dev);
break;
case M_INIT_4_CHECK_CONTRAST:
/* Check contrast */
vfs_check_contrast (self);
if (self->contrast <= 6 || self->counter >= 12)
{
/* End contrast scan, continue */
self->contrast = self->best_contrast;
self->counter = 0;
fp_dbg ("use contrast value = %d", self->contrast);
fpi_ssm_next_state (ssm);
}
else
{
/* Continue contrast scan, loop */
self->contrast--;
self->counter++;
fpi_ssm_jump_to_state (ssm, M_INIT_4_SET_CONTRAST);
}
break;
case M_INIT_5_SET_EXPOSURE:
/* Set exposure level of reader */
vfs_poke (ssm, dev, VFS_REG_IMG_EXPOSURE, VFS_VAL_IMG_EXPOSURE, 0x02);
break;
case M_INIT_5_SET_CONTRAST:
/* Set contrast level of reader */
vfs_poke (ssm, dev, VFS_REG_IMG_CONTRAST, self->contrast, 0x01);
break;
case M_INIT_5_SET_INFO_CONTRAST:
/* Set info line contrast */
vfs_set_param (ssm, dev, VFS_PAR_INFO_CONTRAST, self->contrast);
break;
case M_INIT_5_SET_INFO_RATE:
/* Set info line rate */
vfs_set_param (ssm, dev, VFS_PAR_INFO_RATE, VFS_VAL_INFO_RATE);
break;
}
}
/* Complete init sequential state machine */
static void
m_init_complete (FpiSsm *ssm, FpDevice *_dev, GError *error)
{
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
/* Notify activate complete */
fpi_image_device_activate_complete (dev, error);
if (!error)
{
FpiSsm *ssm_loop;
/* Start loop ssm */
ssm_loop = fpi_ssm_new (FP_DEVICE (dev), m_loop_state, M_LOOP_NUM_STATES);
fpi_ssm_start (ssm_loop, m_loop_complete);
}
/* Free sequential state machine */
}
/* Activate device */
static void
dev_activate (FpImageDevice *dev)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
FpiSsm *ssm;
/* Check if already active */
g_assert (!self->active);
/* Set active state */
self->active = TRUE;
self->deactivate = FALSE;
/* Set contrast */
self->contrast = 15;
self->best_clevel = -1;
/* Reset loop counter */
self->counter = 0;
/* Start init ssm */
ssm = fpi_ssm_new (FP_DEVICE (dev), m_init_state, M_INIT_NUM_STATES);
fpi_ssm_start (ssm, m_init_complete);
}
/* Deactivate device */
static void
dev_deactivate (FpImageDevice *dev)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
/* Device already deactivated, likely due to an error */
if (!self->active)
{
fpi_image_device_deactivate_complete (dev, NULL);
return;
}
/* Signal deactivation, deactivation will happen from the SSM
* completion handler. */
self->deactivate = TRUE;
}
/* Open device */
static void
dev_open (FpImageDevice *dev)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
GError *error = NULL;
/* Claim usb interface */
g_usb_device_claim_interface (fpi_device_get_usb_device (FP_DEVICE (dev)), 0, 0, &error);
/* Initialize private structure */
self->seqnum = -1;
self->buffer = g_malloc0 (VFS_BUFFER_SIZE);
/* Notify open complete */
fpi_image_device_open_complete (dev, error);
}
/* Close device */
static void
dev_close (FpImageDevice *dev)
{
FpDeviceVfs101 *self = FPI_DEVICE_VFS101 (dev);
GError *error = NULL;
/* Release usb interface */
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
0, 0, &error);
g_clear_pointer (&self->buffer, g_free);
/* Notify close complete */
fpi_image_device_close_complete (dev, error);
}
/* Usb id table of device */
static const FpIdEntry id_table[] = {
{ .vid = 0x138a, .pid = 0x0001, },
{ .vid = 0, .pid = 0, .driver_data = 0 },
};
static void
fpi_device_vfs101_init (FpDeviceVfs101 *self)
{
}
static void
fpi_device_vfs101_class_init (FpDeviceVfs101Class *klass)
{
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass);
dev_class->id = "vfs101";
dev_class->full_name = "Validity VFS101";
dev_class->type = FP_DEVICE_TYPE_USB;
dev_class->id_table = id_table;
dev_class->scan_type = FP_SCAN_TYPE_SWIPE;
img_class->img_open = dev_open;
img_class->img_close = dev_close;
img_class->activate = dev_activate;
img_class->deactivate = dev_deactivate;
img_class->bz3_threshold = 24;
img_class->img_width = VFS_IMG_WIDTH;
img_class->img_height = -1;
}