libfprint/libfprint/drivers/vfs0050.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

771 lines
20 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 */
FP_GNUC_ACCESS (read_only, 3, 4)
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);
}
/* 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);
}
/* 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_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0 && transfer->actual_length == 0))
{
g_clear_error (&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);
transfer->ssm = ssm;
fpi_usb_transfer_submit (transfer, VFS_USB_ABORT_TIMEOUT, NULL,
async_abort_callback, NULL);
}
/* 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)
{
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);
fpi_ssm_start_subsm (ssm, subsm);
}
static void
send_control_packet_ssm (FpiSsm *ssm, FpDevice *dev)
{
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);
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);
unsigned 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);
/* Capture is done when there is no more data to transfer or device timed out */
if (transfer->actual_length <= 0)
{
fpi_ssm_next_state (transfer->ssm);
}
else
{
self->bytes += transfer->actual_length;
/* Try reading more data */
fpi_ssm_jump_to_state (transfer->ssm,
fpi_ssm_get_cur_state (transfer->ssm));
}
}
/* Main SSM loop */
static void
activate_ssm (FpiSsm *ssm, FpDevice *dev)
{
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);
/* 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,
(guint8 *) 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);
break;
}
case SSM_SUBMIT_IMAGE:
submit_image (self);
clear_data (self);
/* Wait for probable vdev->active changing */
fpi_ssm_next_state_delayed (ssm, VFS_SSM_TIMEOUT);
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_ssm_jump_to_state_delayed (ssm, SSM_TURN_ON, VFS_SSM_ORANGE_TIMEOUT);
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, 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);
}
}
/* 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);
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, GError *error)
{
/* Notify open complete */
fpi_image_device_open_complete (FP_IMAGE_DEVICE (dev), error);
}
/* 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);
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;
}