/* * Validity VFS101 driver for libfprint * Copyright (C) 2011 Sergio Cerlesi * * 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; }