libfprint/libfprint/drivers/vfs101.c
Bastien Nocera 197edac702 lib: Generate driver IDs, instead of hard-coding them
Instead of adding driver IDs by hand to a header file, generate the
driver ID from the (hopefully unique) name of the driver.

This means one file less for driver authors to modify, and one possible
source of merge conflicts less as well.

However, this means that already enrolled fingerprints will need to be
enrolled again, as the driver IDs will have changed compared to their
old on-disk value.
2019-06-12 16:50:01 +02:00

1547 lines
36 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 | LIBUSB_ENDPOINT_IN)
#define EP_OUT(n) (n | LIBUSB_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 thresold */
#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 vfs101_dev
{
/* Action state */
int active;
/* Sequential number */
unsigned int seqnum;
/* Usb transfer */
struct libusb_transfer *transfer;
/* Buffer for input/output */
unsigned char buffer[VFS_BUFFER_SIZE];
/* Length of data to send or received */
unsigned int length;
/* Ignore usb error */
int ignore_error;
/* Loop counter */
int counter;
/* Number of enroll stage */
int enroll_stage;
/* 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;
};
/* 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(struct vfs101_dev *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,
};
/* Enroll result codes */
static int result_codes[2][RESULT_COUNT] =
{
{
FP_ENROLL_RETRY,
FP_ENROLL_RETRY_TOO_SHORT,
FP_ENROLL_RETRY_REMOVE_FINGER,
},
{
FP_VERIFY_RETRY,
FP_VERIFY_RETRY_TOO_SHORT,
FP_VERIFY_RETRY_REMOVE_FINGER,
},
};
/* Return result code based on current action */
static int result_code(struct fp_img_dev *dev, int result)
{
/* Check result value */
if (result < 0 || result >= RESULT_COUNT)
return result;
/* Return result code */
if (fpi_imgdev_get_action(dev) == IMG_ACTION_ENROLL)
return result_codes[0][result];
else
return result_codes[1][result];
};
/* 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(struct libusb_transfer *transfer)
{
fpi_ssm *ssm = transfer->user_data;
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
/* Cleanup transfer */
vdev->transfer = NULL;
/* Skip error check if ignore_error is set */
if (!vdev->ignore_error)
{
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
{
/* Transfer not completed, return IO error */
fp_err("transfer not completed, status = %d", transfer->status);
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
goto out;
}
if (transfer->length != transfer->actual_length)
{
/* Data sended mismatch with expected, return protocol error */
fp_err("length mismatch, got %d, expected %d",
transfer->actual_length, transfer->length);
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
goto out;
}
}
else
/* Reset ignore_error flag */
vdev->ignore_error = FALSE;
/* Dump buffer for debug */
dump_buffer(vdev->buffer);
fpi_ssm_next_state(ssm);
out:
libusb_free_transfer(transfer);
}
/* Submit asynchronous send */
static void
async_send(fpi_ssm *ssm,
struct fp_img_dev *dev)
{
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
int r;
/* Allocation of transfer */
vdev->transfer = fpi_usb_alloc();
/* Put sequential number into the buffer */
vdev->seqnum++;
vdev->buffer[0] = byte(0, vdev->seqnum);
vdev->buffer[1] = byte(1, vdev->seqnum);
/* Prepare bulk transfer */
libusb_fill_bulk_transfer(vdev->transfer, fpi_dev_get_usb_dev(FP_DEV(dev)), EP_OUT(1), vdev->buffer, vdev->length, async_send_cb, ssm, BULK_TIMEOUT);
/* Submit transfer */
r = libusb_submit_transfer(vdev->transfer);
if (r != 0)
{
/* Submission of transfer failed, return IO error */
libusb_free_transfer(vdev->transfer);
fp_err("submit of usb transfer failed");
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
return;
}
}
/* Callback of asynchronous recv */
static void async_recv_cb(struct libusb_transfer *transfer)
{
fpi_ssm *ssm = transfer->user_data;
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
/* Cleanup transfer */
vdev->transfer = NULL;
/* Skip error check if ignore_error is set */
if (!vdev->ignore_error)
{
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
{
/* Transfer not completed, return IO error */
fp_err("transfer not completed, status = %d", transfer->status);
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
goto out;
}
if (check_seqnum(vdev))
{
/* Sequential number received mismatch, return protocol error */
fp_err("seqnum mismatch, got %04x, expected %04x",
get_seqnum(vdev->buffer[1], vdev->buffer[0]), vdev->seqnum);
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
goto out;
}
}
else
/* Reset ignore_error flag */
vdev->ignore_error = FALSE;
/* Dump buffer for debug */
dump_buffer(vdev->buffer);
/* Set length of received data */
vdev->length = transfer->actual_length;
fpi_ssm_next_state(ssm);
out:
libusb_free_transfer(transfer);
}
/* Submit asynchronous recv */
static void
async_recv(fpi_ssm *ssm,
struct fp_img_dev *dev)
{
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
int r;
/* Allocation of transfer */
vdev->transfer = fpi_usb_alloc();
/* Prepare bulk transfer */
libusb_fill_bulk_transfer(vdev->transfer, fpi_dev_get_usb_dev(FP_DEV(dev)), EP_IN(1), vdev->buffer, 0x0f, async_recv_cb, ssm, BULK_TIMEOUT);
/* Submit transfer */
r = libusb_submit_transfer(vdev->transfer);
if (r != 0)
{
/* Submission of transfer failed, free transfer and return IO error */
libusb_free_transfer(vdev->transfer);
fp_err("submit of usb transfer failed");
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
return;
}
}
static void async_load(fpi_ssm *ssm, struct fp_img_dev *dev);
/* Callback of asynchronous load */
static void async_load_cb(struct libusb_transfer *transfer)
{
fpi_ssm *ssm = transfer->user_data;
struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm);
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
/* Cleanup transfer */
vdev->transfer = NULL;
/* Skip error check if ignore_error is set */
if (!vdev->ignore_error)
{
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
{
/* Transfer not completed */
fp_err("transfer not completed, status = %d, length = %d", transfer->status, vdev->length);
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
goto out;
}
if (transfer->actual_length % VFS_FRAME_SIZE)
{
/* Received incomplete frame, return protocol error */
fp_err("received incomplete frame");
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
goto out;
}
}
/* Increase image length */
vdev->length += transfer->actual_length;
if (transfer->actual_length == VFS_BLOCK_SIZE)
{
if ((VFS_BUFFER_SIZE - vdev->length) < VFS_BLOCK_SIZE)
{
/* Buffer full, image too large, return no memory error */
fp_err("buffer full, image too large");
fpi_imgdev_session_error(dev, -ENOMEM);
fpi_ssm_mark_failed(ssm, -ENOMEM);
goto out;
}
else
/* Image load not completed, submit another asynchronous load */
async_load(ssm, dev);
}
else
{
/* Reset ignore_error flag */
if (vdev->ignore_error)
vdev->ignore_error = FALSE;
/* Image load completed, go to next state */
vdev->height = vdev->length / VFS_FRAME_SIZE;
fp_dbg("image loaded, height = %d", vdev->height);
fpi_ssm_next_state(ssm);
}
out:
libusb_free_transfer(transfer);
}
/* Submit asynchronous load */
static void
async_load(fpi_ssm *ssm,
struct fp_img_dev *dev)
{
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
unsigned char *buffer;
int r;
/* Allocation of transfer */
vdev->transfer = fpi_usb_alloc();
/* Append new data into the buffer */
buffer = vdev->buffer + vdev->length;
/* Prepare bulk transfer */
libusb_fill_bulk_transfer(vdev->transfer, fpi_dev_get_usb_dev(FP_DEV(dev)), EP_IN(2), buffer, VFS_BLOCK_SIZE, async_load_cb, ssm, BULK_TIMEOUT);
/* Submit transfer */
r = libusb_submit_transfer(vdev->transfer);
if (r != 0)
{
/* Submission of transfer failed, return IO error */
libusb_free_transfer(vdev->transfer);
fp_err("submit of usb transfer failed");
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
return;
}
}
/* Submit asynchronous sleep */
static void
async_sleep(unsigned int msec,
fpi_ssm *ssm,
struct fp_img_dev *dev)
{
if (fpi_timeout_add(msec, fpi_ssm_next_state_timeout_cb, FP_DEV(dev), ssm) == NULL)
{
/* Failed to add timeout */
fp_err("failed to add timeout");
fpi_imgdev_session_error(dev, -ETIME);
fpi_ssm_mark_failed(ssm, -ETIME);
}
}
/* Swap ssm states */
enum
{
M_SWAP_SEND,
M_SWAP_RECV,
M_SWAP_NUM_STATES,
};
/* Exec swap sequential state machine */
static void m_swap_state(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
switch (fpi_ssm_get_cur_state(ssm))
{
case M_SWAP_SEND:
/* Send data */
async_send(ssm, user_data);
break;
case M_SWAP_RECV:
/* Recv response */
async_recv(ssm, user_data);
break;
}
}
/* Start swap sequential state machine */
static void
m_swap(fpi_ssm *ssm,
struct fp_img_dev *dev,
unsigned char *data,
size_t length)
{
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
fpi_ssm *subsm;
/* Prepare data for sending */
memcpy(vdev->buffer, data, length);
memset(vdev->buffer + length, 0, 16 - length);
vdev->length = length;
/* Start swap ssm */
subsm = fpi_ssm_new(FP_DEV(dev), m_swap_state, M_SWAP_NUM_STATES, dev);
fpi_ssm_start_subsm(ssm, subsm);
}
/* Retrieve fingerprint image */
static void
vfs_get_print(fpi_ssm *ssm,
struct fp_img_dev *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(fpi_ssm *ssm,
struct fp_img_dev *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(fpi_ssm *ssm,
struct fp_img_dev *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(fpi_ssm *ssm,
struct fp_img_dev *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(fpi_ssm *ssm,
struct fp_img_dev *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(fpi_ssm *ssm,
struct fp_img_dev *dev)
{
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
G_DEBUG_HERE();
/* Reset buffer length */
vdev->length = 0;
/* Reset image properties */
vdev->bottom = 0;
vdev->height = -1;
/* Asynchronous load */
async_load(ssm, dev);
}
/* Check if action is completed */
static int action_completed(struct fp_img_dev *dev)
{
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
if ((fpi_imgdev_get_action(dev) == IMG_ACTION_ENROLL) &&
(vdev->enroll_stage < fp_dev_get_nr_enroll_stages(FP_DEV(dev))))
/* Enroll not completed, return false */
return FALSE;
else if (vdev->enroll_stage < 1)
/* Action not completed, return false */
return FALSE;
/* Action completed, return true */
return TRUE;
}
#define offset(x, y) ((x) + ((y) * VFS_FRAME_SIZE))
/* Screen image to remove noise and find bottom line and height od image */
static void img_screen(struct vfs101_dev *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 differents threshold, 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;
/* Checkk 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(struct vfs101_dev *vdev, struct fp_img *img)
{
unsigned int line;
unsigned char *img_buffer = img->data;
unsigned char *vdev_buffer = vdev->buffer + (vdev->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(fpi_ssm *ssm,
struct fp_img_dev *dev)
{
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
struct fp_img *img;
/* Screen image to remove noise and find top and bottom line */
img_screen(vdev);
/* Check image height */
if (vdev->height < VFS_IMG_MIN_HEIGHT)
{
/* Image too short */
vdev->height = 0;
return;
}
/* Fingerprint is present, load image from reader */
fpi_imgdev_report_finger_status(dev, TRUE);
/* Create new image */
img = fpi_img_new(vdev->height * VFS_IMG_WIDTH);
img->width = VFS_IMG_WIDTH;
img->height = vdev->height;
img->flags = FP_IMG_V_FLIPPED;
/* Copy data into image */
img_copy(vdev, img);
/* Notify image captured */
fpi_imgdev_image_captured(dev, img);
/* FIXME
* What is this for? The action result, and the enroll stages should
* already be handled in fpi_imgdev_image_captured()
*/
/* Check captured result */
if (fpi_imgdev_get_action_result(dev) >= 0 &&
fpi_imgdev_get_action_result(dev) != FP_ENROLL_RETRY &&
fpi_imgdev_get_action_result(dev) != FP_VERIFY_RETRY)
{
/* Image captured, increase enroll stage */
vdev->enroll_stage++;
/* Check if action is completed */
if (!action_completed(dev))
fpi_imgdev_set_action_result(dev, FP_ENROLL_PASS);
}
else
{
/* Image capture failed */
if (fpi_imgdev_get_action(dev) == IMG_ACTION_ENROLL)
/* Return retry */
fpi_imgdev_set_action_result(dev, result_code(dev, RESULT_RETRY));
else
{
/* Return no match */
vdev->enroll_stage++;
fpi_imgdev_set_action_result(dev, FP_VERIFY_NO_MATCH);
}
}
/* Fingerprint is removed from reader */
fpi_imgdev_report_finger_status(dev, FALSE);
};
/* Finger states */
enum
{
VFS_FINGER_EMPTY,
VFS_FINGER_PRESENT,
VFS_FINGER_UNKNOWN,
};
/* Return finger state */
static inline int vfs_finger_state(struct vfs101_dev *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(struct vfs101_dev *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(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
struct vfs101_dev *vdev = FP_INSTANCE_DATA(_dev);
/* Check action state */
if (!vdev->active)
{
/* Action not active, mark sequential state machine completed */
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 */
async_sleep(50, ssm, dev);
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(vdev))
{
case VFS_FINGER_EMPTY:
/* Finger isn't present, loop */
fpi_ssm_jump_to_state(ssm, M_LOOP_0_SLEEP);
break;
case VFS_FINGER_PRESENT:
/* Load image from reader */
vdev->ignore_error = TRUE;
vfs_img_load(ssm, dev);
break;
default:
/* Unknown state */
fp_err("unknown device state 0x%02x", vdev->buffer[0x0a]);
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
break;
}
break;
case M_LOOP_0_EXTRACT_IMAGE:
/* Check if image is loaded */
if (vdev->height > 0)
/* Fingerprint is loaded, extract image from raw data */
img_extract(ssm, dev);
/* Wait handling image */
async_sleep(10, ssm, dev);
break;
case M_LOOP_0_CHECK_ACTION:
/* Check if action is completed */
if (action_completed(dev))
/* Action completed */
fpi_ssm_mark_completed(ssm);
else
/* Action not completed */
if (vdev->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(vdev) == VFS_FINGER_PRESENT)
{
if (vdev->counter < 20)
{
if (vdev->counter == 1)
{
/* The user should remove their finger from the scanner */
fp_warn("finger present after scan, remove it");
fpi_imgdev_session_error(dev, result_code(dev, RESULT_RETRY_REMOVE));
}
/* Wait removing finger */
vdev->counter++;
async_sleep(250, ssm, dev);
}
else
{
/* reach max loop counter, return protocol error */
fp_err("finger not removed from the scanner");
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
}
}
else
{
/* Finger not present */
if (vdev->counter == 0)
{
/* Check image height */
if (vdev->height == 0)
{
/* Return retry to short */
fp_warn("image too short, retry");
fpi_imgdev_session_error(dev, result_code(dev, RESULT_RETRY_SHORT));
}
else
{
/* Return retry result */
fp_warn("load image failed, retry");
fpi_imgdev_session_error(dev, result_code(dev, RESULT_RETRY));
}
}
/* Continue */
vdev->counter = 0;
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 */
vdev->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 */
async_sleep(10, ssm, dev);
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 */
vdev->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 */
vdev->ignore_error = TRUE;
vfs_img_load(ssm, dev);
break;
case M_LOOP_3_CHECK_IMAGE:
if (vdev->height == 10)
{
/* Image load correctly, jump to step 0 */
vdev->counter = 0;
fpi_ssm_jump_to_state(ssm, M_LOOP_0_GET_PRINT);
}
else if (vdev->counter < 10)
{
/* Wait aborting */
vdev->counter++;
async_sleep(100, ssm, dev);
}
else
{
/* reach max loop counter, return protocol error */
fp_err("waiting abort reach max loop counter");
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
}
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(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
/* Free sequential state machine */
fpi_ssm_free(ssm);
}
/* 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(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
struct vfs101_dev *vdev = FP_INSTANCE_DATA(_dev);
/* Check action state */
if (!vdev->active)
{
/* Action not active, mark sequential state machine completed */
fpi_ssm_mark_completed(ssm);
return;
}
switch (fpi_ssm_get_cur_state(ssm))
{
case M_INIT_0_RECV_DIRTY:
/* Recv eventualy dirty data */
vdev->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 */
vdev->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 */
vdev->ignore_error = TRUE;
vfs_img_load(ssm, dev);
break;
case M_INIT_1_CHECK_IMAGE:
if (vdev->height == 10)
{
/* Image load correctly, jump to step 2 */
vdev->counter = 0;
fpi_ssm_jump_to_state(ssm, M_INIT_2_GET_STATE);
}
else if (vdev->counter < 10)
{
/* Wait aborting */
vdev->counter++;
async_sleep(100, ssm, dev);
}
else
{
/* reach max loop counter, return protocol error */
fp_err("waiting abort reach max loop counter");
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
}
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(vdev) == VFS_FINGER_PRESENT)
{
if (vdev->counter < 20)
{
if (vdev->counter == 2)
{
/* The user should remove their finger from the scanner */
fp_warn("unexpected finger find, remove finger from the scanner");
fpi_imgdev_session_error(dev, result_code(dev, RESULT_RETRY_REMOVE));
}
/* Wait removing finger */
vdev->counter++;
async_sleep(250, ssm, dev);
}
else
{
/* reach max loop counter, return protocol error */
fp_err("finger not removed from the scanner");
fpi_imgdev_session_error(dev, -EIO);
fpi_ssm_mark_failed(ssm, -EIO);
}
}
else
{
/* Finger not present */
if (vdev->counter == 0)
/* Continue */
fpi_ssm_jump_to_state(ssm, M_INIT_3_SET_000E);
else
{
/* Finger removed, jump to abort */
vdev->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 */
vdev->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);
vdev->counter = 1;
break;
case M_INIT_4_SET_CONTRAST:
/* Set contrast level of reader */
vfs_poke(ssm, dev, VFS_REG_IMG_CONTRAST, vdev->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(vdev);
if (vdev->contrast <= 6 || vdev->counter >= 12)
{
/* End contrast scan, continue */
vdev->contrast = vdev->best_contrast;
vdev->counter = 0;
fp_dbg("use contrast value = %d", vdev->contrast);
fpi_ssm_next_state(ssm);
}
else
{
/* Continue contrast scan, loop */
vdev->contrast--;
vdev->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, vdev->contrast, 0x01);
break;
case M_INIT_5_SET_INFO_CONTRAST:
/* Set info line contrast */
vfs_set_param(ssm, dev, VFS_PAR_INFO_CONTRAST, vdev->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(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
struct vfs101_dev *vdev = FP_INSTANCE_DATA(_dev);
fpi_ssm *ssm_loop;
if (!fpi_ssm_get_error(ssm) && vdev->active)
{
/* Notify activate complete */
fpi_imgdev_activate_complete(dev, 0);
/* Start loop ssm */
ssm_loop = fpi_ssm_new(FP_DEV(dev), m_loop_state, M_LOOP_NUM_STATES, dev);
fpi_ssm_start(ssm_loop, m_loop_complete);
}
/* Free sequential state machine */
fpi_ssm_free(ssm);
}
/* Activate device */
static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state)
{
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
fpi_ssm *ssm;
/* Check if already active */
if (vdev->active)
{
fp_err("device already activated");
fpi_imgdev_session_error(dev, -EBUSY);
return 1;
}
/* Set active state */
vdev->active = TRUE;
/* Set contrast */
vdev->contrast = 15;
vdev->best_clevel = -1;
/* Reset loop counter and enroll stage */
vdev->counter = 0;
vdev->enroll_stage = 0;
/* Start init ssm */
ssm = fpi_ssm_new(FP_DEV(dev), m_init_state, M_INIT_NUM_STATES, dev);
fpi_ssm_start(ssm, m_init_complete);
return 0;
}
/* Deactivate device */
static void dev_deactivate(struct fp_img_dev *dev)
{
struct vfs101_dev *vdev = FP_INSTANCE_DATA(FP_DEV(dev));
/* Reset active state */
vdev->active = FALSE;
/* Handle eventualy existing events */
while (vdev->transfer)
fp_handle_events();
/* Notify deactivate complete */
fpi_imgdev_deactivate_complete(dev);
}
/* Open device */
static int dev_open(struct fp_img_dev *dev, unsigned long driver_data)
{
struct vfs101_dev *vdev = NULL;
int r;
/* Claim usb interface */
r = libusb_claim_interface(fpi_dev_get_usb_dev(FP_DEV(dev)), 0);
if (r < 0)
{
/* Interface not claimed, return error */
fp_err("could not claim interface 0: %s", libusb_error_name(r));
return r;
}
/* Initialize private structure */
vdev = g_malloc0(sizeof(struct vfs101_dev));
vdev->seqnum = -1;
fp_dev_set_instance_data(FP_DEV(dev), vdev);
/* Notify open complete */
fpi_imgdev_open_complete(dev, 0);
return 0;
}
/* Close device */
static void dev_close(struct fp_img_dev *dev)
{
struct vfs101_dev *vdev;
/* Release private structure */
vdev = FP_INSTANCE_DATA(FP_DEV(dev));
g_free(vdev);
/* Release usb interface */
libusb_release_interface(fpi_dev_get_usb_dev(FP_DEV(dev)), 0);
/* Notify close complete */
fpi_imgdev_close_complete(dev);
}
/* Usb id table of device */
static const struct usb_id id_table[] =
{
{ .vendor = 0x138a, .product = 0x0001 },
{ 0, 0, 0, },
};
/* Device driver definition */
struct fp_img_driver vfs101_driver =
{
/* Driver specification */
.driver =
{
.name = FP_COMPONENT,
.full_name = "Validity VFS101",
.id_table = id_table,
.scan_type = FP_SCAN_TYPE_SWIPE,
},
/* Image specification */
.flags = 0,
.img_width = VFS_IMG_WIDTH,
.img_height = -1,
.bz3_threshold = 24,
/* Routine specification */
.open = dev_open,
.close = dev_close,
.activate = dev_activate,
.deactivate = dev_deactivate,
};