libfprint/libfprint/drivers/vfs0050.c
2019-11-20 20:38:06 +01:00

789 lines
21 KiB
C

/*
* Validity VFS0050 driver for libfprint
* Copyright (C) 2015-2016 Konstantin Semenov <zemen17@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 "vfs0050"
#include "drivers_api.h"
#include "vfs0050.h"
G_DEFINE_TYPE (FpDeviceVfs0050, fpi_device_vfs0050, FP_TYPE_IMAGE_DEVICE)
/* USB functions */
/* Callback for async_write */
static void
async_write_callback (FpiUsbTransfer *transfer, FpDevice *device,
gpointer user_data, GError *error)
{
if (error)
{
fp_err ("USB write transfer: %s", error->message);
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
fpi_ssm_next_state (transfer->ssm);
}
/* Send data to EP1, the only out endpoint */
static void
async_write (FpiSsm *ssm,
FpDevice *dev,
void *data,
int len)
{
FpiUsbTransfer *transfer;
transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
fpi_usb_transfer_fill_bulk_full (transfer, 0x01, data, len, NULL);
transfer->ssm = ssm;
transfer->short_is_error = TRUE;
fpi_usb_transfer_submit (transfer, VFS_USB_TIMEOUT, NULL,
async_write_callback, NULL);
fpi_usb_transfer_unref (transfer);
}
/* Callback for async_read */
static void
async_read_callback (FpiUsbTransfer *transfer, FpDevice *device,
gpointer user_data, GError *error)
{
int ep = transfer->endpoint;
if (error)
{
fp_err ("USB read transfer on endpoint %d: %s", ep - 0x80,
error->message);
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
fpi_ssm_next_state (transfer->ssm);
}
/* Receive data from the given ep and either discard or fill the given buffer */
static void
async_read (FpiSsm *ssm,
FpDevice *dev,
int ep,
void *data,
int len)
{
FpiUsbTransfer *transfer;
GDestroyNotify free_func = NULL;
ep |= FPI_USB_ENDPOINT_IN;
transfer = fpi_usb_transfer_new (FP_DEVICE (dev));
transfer->ssm = ssm;
transfer->short_is_error = TRUE;
if (data == NULL)
{
data = g_malloc0 (len);
free_func = g_free;
}
/* 0x83 is the only interrupt endpoint */
if (ep == EP3_IN)
fpi_usb_transfer_fill_interrupt_full (transfer, ep, data, len, free_func);
else
fpi_usb_transfer_fill_bulk_full (transfer, ep, data, len, free_func);
fpi_usb_transfer_submit (transfer, VFS_USB_TIMEOUT, NULL,
async_read_callback, NULL);
fpi_usb_transfer_unref (transfer);
}
/* Callback for async_abort */
static void
async_abort_callback (FpiUsbTransfer *transfer, FpDevice *device,
gpointer user_data, GError *error)
{
int ep = transfer->endpoint;
/* In normal case endpoint is empty */
if (g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT))
{
g_free (error);
fpi_ssm_next_state (transfer->ssm);
return;
}
if (error)
{
fp_err ("USB write transfer: %s", error->message);
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
/* Don't stop process, only print warning */
fp_warn ("Endpoint %d had extra %zd bytes readable", ep - 0x80,
transfer->actual_length);
fpi_ssm_jump_to_state (transfer->ssm,
fpi_ssm_get_cur_state (transfer->ssm));
}
/* Receive data from the given ep; continues to the next state once no
* more data is available. Otherwise the current state is repeated. */
static void
async_abort (FpDevice *dev, FpiSsm *ssm, int ep)
{
FpiUsbTransfer *transfer;
ep |= FPI_USB_ENDPOINT_IN;
transfer = fpi_usb_transfer_new (dev);
/* 0x83 is the only interrupt endpoint */
if (ep == EP3_IN)
fpi_usb_transfer_fill_interrupt (transfer, ep, VFS_USB_BUFFER_SIZE);
else
fpi_usb_transfer_fill_bulk (transfer, ep, VFS_USB_BUFFER_SIZE);
fpi_usb_transfer_submit (transfer, VFS_USB_ABORT_TIMEOUT, NULL,
async_abort_callback, NULL);
fpi_usb_transfer_unref (transfer);
}
/* Image processing functions */
/* Pixel getter for fpi_assemble_lines */
static unsigned char
vfs0050_get_pixel (struct fpi_line_asmbl_ctx *ctx,
GSList * line, unsigned int x)
{
return ((struct vfs_line *) line->data)->data[x];
}
/* Deviation getter for fpi_assemble_lines */
static int
vfs0050_get_difference (struct fpi_line_asmbl_ctx *ctx,
GSList * line_list_1, GSList * line_list_2)
{
struct vfs_line *line1 = line_list_1->data;
struct vfs_line *line2 = line_list_2->data;
const int shift = (VFS_IMAGE_WIDTH - VFS_NEXT_LINE_WIDTH) / 2 - 1;
int res = 0;
for (int i = 0; i < VFS_NEXT_LINE_WIDTH; ++i)
{
int x =
(int) line1->next_line_part[i] - (int) line2->data[shift + i];
res += x * x;
}
return res;
}
#define VFS_NOISE_THRESHOLD 40
/* Checks whether line is noise or not using hardware parameters */
static char
is_noise (struct vfs_line *line)
{
int val1 = line->noise_hash_1;
int val2 = line->noise_hash_2;
if (val1 > VFS_NOISE_THRESHOLD &&
val1 < 256 - VFS_NOISE_THRESHOLD &&
val2 > VFS_NOISE_THRESHOLD && val2 < 256 - VFS_NOISE_THRESHOLD)
return 1;
return 0;
}
/* Parameters for fpi_assemble_lines */
static struct fpi_line_asmbl_ctx assembling_ctx = {
.line_width = VFS_IMAGE_WIDTH,
.max_height = VFS_MAX_HEIGHT,
.resolution = 10,
.median_filter_size = 25,
.max_search_offset = 100,
.get_deviation = vfs0050_get_difference,
.get_pixel = vfs0050_get_pixel,
};
/* Processes image before submitting */
static FpImage *
prepare_image (FpDeviceVfs0050 *vdev)
{
int height = vdev->bytes / VFS_LINE_SIZE;
/* Noise cleaning. IMHO, it works pretty well
I've not detected cases when it doesn't work or cuts a part of the finger
Noise arises at the end of scan when some water remains on the scanner */
while (height > 0)
{
if (!is_noise (vdev->lines_buffer + height - 1))
break;
--height;
}
if (height > VFS_MAX_HEIGHT)
height = VFS_MAX_HEIGHT;
/* If image is not good enough */
if (height < VFS_IMAGE_WIDTH)
return NULL;
/* Building GSList */
GSList *lines = NULL;
for (int i = height - 1; i >= 0; --i)
lines = g_slist_prepend (lines, vdev->lines_buffer + i);
/* Perform line assembling */
FpImage *img = fpi_assemble_lines (&assembling_ctx, lines, height);
g_slist_free (lines);
return img;
}
/* Processes and submits image after fingerprint received */
static void
submit_image (FpDeviceVfs0050 *self)
{
FpImageDevice *idev = FP_IMAGE_DEVICE (self);
/* We were not asked to submit image actually */
if (!self->active)
return;
FpImage *img = prepare_image (self);
if (!img)
fpi_image_device_retry_scan (idev, FP_DEVICE_RETRY_TOO_SHORT);
else
fpi_image_device_image_captured (idev, img);
/* Finger not on the scanner */
fpi_image_device_report_finger_status (idev, FALSE);
}
/* Proto functions */
/* SSM loop for clear_ep2 */
static void
clear_ep2_ssm (FpiSsm *ssm, FpDevice *dev, void *user_data)
{
char command04 = 0x04;
switch (fpi_ssm_get_cur_state (ssm))
{
case SUBSM1_COMMAND_04:
async_write (ssm, dev, &command04, sizeof (command04));
break;
case SUBSM1_RETURN_CODE:
async_read (ssm, dev, 1, NULL, 2);
break;
case SUBSM1_ABORT_2:
async_abort (dev, ssm, 2);
break;
default:
fp_err ("Unknown SUBSM1 state");
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
}
}
/* Send command to clear EP2 */
static void
clear_ep2 (FpDevice *dev,
FpiSsm *ssm)
{
FpiSsm *subsm =
fpi_ssm_new (dev, clear_ep2_ssm, SUBSM1_STATES, NULL);
fpi_ssm_start_subsm (ssm, subsm);
}
static void
send_control_packet_ssm (FpiSsm *ssm, FpDevice *dev,
void *user_data)
{
FpDeviceVfs0050 *self = FPI_DEVICE_VFS0050 (dev);
switch (fpi_ssm_get_cur_state (ssm))
{
case SUBSM2_SEND_CONTROL:
async_write (ssm, dev, self->control_packet,
VFS_CONTROL_PACKET_SIZE);
break;
case SUBSM2_RETURN_CODE:
async_read (ssm, dev, 1, NULL, 2);
break;
case SUBSM2_SEND_COMMIT:
/* next_receive_* packets could be sent only in pair */
if (self->control_packet == next_receive_1)
{
self->control_packet = next_receive_2;
fpi_ssm_jump_to_state (ssm, SUBSM2_SEND_CONTROL);
break;
}
/* commit_out in Windows differs in each commit, but I send the same each time */
async_write (ssm, dev, commit_out, sizeof (commit_out));
break;
case SUBSM2_COMMIT_RESPONSE:
async_read (ssm, dev, 1, NULL, VFS_COMMIT_RESPONSE_SIZE);
break;
case SUBSM2_READ_EMPTY_INTERRUPT:
/* I don't know how to check result, it could be different
* NOTE: I guess this comment relates to the above read. */
async_read (ssm, dev, 3, self->interrupt, VFS_INTERRUPT_SIZE);
break;
case SUBSM2_ABORT_3:
/* Check that interrupt is empty */
if (memcmp
(self->interrupt, empty_interrupt, VFS_INTERRUPT_SIZE))
{
fp_err ("Unknown SUBSM2 state");
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
break;
}
async_abort (dev, ssm, 3);
break;
case SUBSM2_CLEAR_EP2:
/* After turn_on Windows doesn't clear EP2 */
if (self->control_packet != turn_on)
clear_ep2 (dev, ssm);
else
fpi_ssm_next_state (ssm);
break;
default:
fp_err ("Unknown SUBSM2 state");
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
}
}
/* Send device state control packet */
static void
send_control_packet (FpiSsm *ssm,
FpDevice *dev)
{
FpiSsm *subsm =
fpi_ssm_new (dev, send_control_packet_ssm,
SUBSM2_STATES, NULL);
fpi_ssm_start_subsm (ssm, subsm);
}
/* Clears all fprint data */
static void
clear_data (FpDeviceVfs0050 *vdev)
{
g_free (vdev->lines_buffer);
vdev->lines_buffer = NULL;
vdev->memory = vdev->bytes = 0;
}
/* After receiving interrupt from EP3 */
static void
interrupt_callback (FpiUsbTransfer *transfer, FpDevice *device,
gpointer user_data, GError *error)
{
FpDeviceVfs0050 *self = FPI_DEVICE_VFS0050 (device);
char *interrupt = transfer->buffer;
/* we expect a cancellation error when the device is deactivating
* go into the SSM_CLEAR_EP2 state in that case. */
if (!self->active && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_error_free (error);
fpi_ssm_jump_to_state (transfer->ssm, SSM_CLEAR_EP2);
return;
}
if (error)
{
fp_err ("USB read interrupt transfer: %s",
error->message);
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
/* Standard interrupts */
if (memcmp (interrupt, interrupt1, VFS_INTERRUPT_SIZE) == 0 ||
memcmp (interrupt, interrupt2, VFS_INTERRUPT_SIZE) == 0 ||
memcmp (interrupt, interrupt3, VFS_INTERRUPT_SIZE) == 0)
{
/* Go to the next ssm stage */
fpi_ssm_next_state (transfer->ssm);
return;
}
/* When finger is on the scanner before turn_on */
if (interrupt[0] == 0x01)
{
fp_warn ("Finger is already on the scanner");
/* Go to the next ssm stage */
fpi_ssm_next_state (transfer->ssm);
return;
}
/* Unknown interrupt; abort the session */
fp_err ("Unknown interrupt '%02x:%02x:%02x:%02x:%02x'!",
interrupt[0] & 0xff, interrupt[1] & 0xff, interrupt[2] & 0xff,
interrupt[3] & 0xff, interrupt[4] & 0xff);
/* Abort ssm */
fpi_ssm_mark_failed (transfer->ssm,
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
}
static void
receive_callback (FpiUsbTransfer *transfer, FpDevice *device,
gpointer user_data, GError *error)
{
FpDeviceVfs0050 *self = FPI_DEVICE_VFS0050 (device);
if (error && !g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT))
{
fp_err ("USB read transfer: %s", error->message);
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
if (error)
g_error_free (error);
/* Check if fingerprint data is over */
if (transfer->actual_length == 0)
{
fpi_ssm_next_state (transfer->ssm);
}
else
{
self->bytes += transfer->actual_length;
/* We need more data */
fpi_ssm_jump_to_state (transfer->ssm,
fpi_ssm_get_cur_state (transfer->ssm));
}
}
/* SSM stub to prepare device to another scan after orange light was on */
static void
another_scan (FpDevice *dev,
void *data)
{
FpiSsm *ssm = data;
fpi_ssm_jump_to_state (ssm, SSM_TURN_ON);
}
/* Main SSM loop */
static void
activate_ssm (FpiSsm *ssm, FpDevice *dev, void *user_data)
{
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
FpDeviceVfs0050 *self = FPI_DEVICE_VFS0050 (dev);
switch (fpi_ssm_get_cur_state (ssm))
{
case SSM_INITIAL_ABORT_1:
async_abort (dev, ssm, 1);
break;
case SSM_INITIAL_ABORT_2:
async_abort (dev, ssm, 2);
break;
case SSM_INITIAL_ABORT_3:
async_abort (dev, ssm, 3);
break;
case SSM_CLEAR_EP2:
clear_ep2 (dev, ssm);
break;
case SSM_TURN_OFF:
/* Set control_packet argument */
self->control_packet = turn_off;
send_control_packet (ssm, dev);
break;
case SSM_TURN_ON:
if (!self->active)
{
/* The only correct exit */
fpi_ssm_mark_completed (ssm);
if (self->need_report)
{
fpi_image_device_deactivate_complete (idev,
NULL);
self->need_report = 0;
}
break;
}
/* Set control_packet argument */
self->control_packet = turn_on;
send_control_packet (ssm, dev);
break;
case SSM_ASK_INTERRUPT: {
FpiUsbTransfer *transfer;
/* Activated, light must be blinking now */
/* If we first time here, report that activate completed */
if (self->need_report)
{
fpi_image_device_activate_complete (idev, NULL);
self->need_report = 0;
}
/* Asynchronously enquire an interrupt */
transfer = fpi_usb_transfer_new (dev);
transfer->ssm = ssm;
transfer->short_is_error = TRUE;
fpi_usb_transfer_fill_interrupt (transfer, 0x83, VFS_INTERRUPT_SIZE);
fpi_usb_transfer_submit (transfer,
0,
fpi_device_get_cancellable (dev),
interrupt_callback, NULL);
fpi_usb_transfer_unref (transfer);
/* I've put it here to be sure that data is cleared */
clear_data (self);
fpi_ssm_next_state (ssm);
break;
}
case SSM_WAIT_INTERRUPT:
/* TODO: This state is unused at this point. When we
* are in this state, then a user cancellation will
* cause deactivation. In that case, the USB transfer
* is cancelled and the device is set to not be active.
* We then go into SSM_CLEAR_EP2 based on the
* cancellation. */
break;
case SSM_RECEIVE_FINGER: {
FpiUsbTransfer *transfer;
if (self->memory == 0)
{
/* Initialize fingerprint buffer */
g_free (self->lines_buffer);
self->memory = VFS_USB_BUFFER_SIZE;
self->lines_buffer = g_malloc (self->memory);
self->bytes = 0;
/* Finger is on the scanner */
fpi_image_device_report_finger_status (idev, TRUE);
}
/* Increase buffer size while it's insufficient */
while (self->bytes + VFS_USB_BUFFER_SIZE > self->memory)
{
self->memory <<= 1;
self->lines_buffer =
(struct vfs_line *) g_realloc (self->lines_buffer,
self->memory);
}
/* Receive chunk of data */
transfer = fpi_usb_transfer_new (dev);
fpi_usb_transfer_fill_bulk_full (transfer, 0x82,
(void *) self->lines_buffer + self->bytes,
VFS_USB_BUFFER_SIZE, NULL);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, VFS_USB_TIMEOUT, NULL,
receive_callback, NULL);
fpi_usb_transfer_unref (transfer);
break;
}
case SSM_SUBMIT_IMAGE:
submit_image (self);
clear_data (self);
/* Wait for probable vdev->active changing */
fpi_device_add_timeout (dev, VFS_SSM_TIMEOUT,
fpi_ssm_next_state_timeout_cb, ssm);
break;
case SSM_NEXT_RECEIVE:
if (!self->active)
{
/* It's the last scan */
fpi_ssm_jump_to_state (ssm, SSM_CLEAR_EP2);
break;
}
/* Set control_packet argument */
self->control_packet = next_receive_1;
send_control_packet (ssm, dev);
break;
case SSM_WAIT_ANOTHER_SCAN:
/* Orange light is on now */
fpi_device_add_timeout (dev, VFS_SSM_ORANGE_TIMEOUT,
another_scan, ssm);
break;
default:
fp_err ("Unknown state");
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
}
}
/* Driver functions */
/* Callback for dev_activate ssm */
static void
dev_activate_callback (FpiSsm *ssm, FpDevice *dev,
void *user_data, GError *error)
{
FpDeviceVfs0050 *self = FPI_DEVICE_VFS0050 (dev);
self->ssm_active = 0;
if (error)
{
g_warning ("Unhandled device activation error: %s", error->message);
g_error_free (error);
}
fpi_ssm_free (ssm);
}
/* Activate device */
static void
dev_activate (FpImageDevice *idev)
{
FpDeviceVfs0050 *self = FPI_DEVICE_VFS0050 (idev);
/* Initialize flags */
self->active = 1;
self->need_report = 1;
self->ssm_active = 1;
FpiSsm *ssm = fpi_ssm_new (FP_DEVICE (idev), activate_ssm, SSM_STATES,
idev);
fpi_ssm_start (ssm, dev_activate_callback);
}
/* Deactivate device */
static void
dev_deactivate (FpImageDevice *idev)
{
FpDeviceVfs0050 *self = FPI_DEVICE_VFS0050 (idev);
if (!self->ssm_active)
{
fpi_image_device_deactivate_complete (idev, NULL);
return;
}
/* Initialize flags */
self->active = 0;
self->need_report = 1;
}
/* Callback for dev_open ssm */
static void
dev_open_callback (FpiSsm *ssm, FpDevice *dev, void *user_data,
GError *error)
{
/* Notify open complete */
fpi_image_device_open_complete (FP_IMAGE_DEVICE (dev), error);
fpi_ssm_free (ssm);
}
/* Open device */
static void
dev_open (FpImageDevice *idev)
{
GError *error = NULL;
/* Claim usb interface */
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (FP_DEVICE (idev)), 0, 0, &error))
{
fpi_image_device_open_complete (idev, error);
return;
}
/* Clearing previous device state */
FpiSsm *ssm = fpi_ssm_new (FP_DEVICE (idev), activate_ssm, SSM_STATES, NULL);
fpi_ssm_start (ssm, dev_open_callback);
}
/* Close device */
static void
dev_close (FpImageDevice *idev)
{
GError *error = NULL;
FpDeviceVfs0050 *self = FPI_DEVICE_VFS0050 (idev);
clear_data (self);
/* Release usb interface */
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (idev)),
0, 0, &error);
/* Notify close complete */
fpi_image_device_close_complete (idev, error);
}
/* Usb id table of device */
static const FpIdEntry id_table[] = {
{.vid = 0x138a, .pid = 0x0050, },
{.vid = 0, .pid = 0, .driver_data = 0},
};
static void
fpi_device_vfs0050_init (FpDeviceVfs0050 *self)
{
}
static void
fpi_device_vfs0050_class_init (FpDeviceVfs0050Class *klass)
{
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass);
dev_class->id = "vfs0050";
dev_class->full_name = "Validity VFS0050";
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_IMAGE_WIDTH;
img_class->img_height = -1;
}