7c7402a697
Ported to the new internal libfprint API. https://bugzilla.redhat.com/show_bug.cgi?id=499732
1131 lines
27 KiB
C
1131 lines
27 KiB
C
/*
|
||
* AuthenTec AES1610 driver for libfprint
|
||
* Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org>
|
||
* Copyright (C) 2007 Cyrille Bagard
|
||
* Copyright (C) 2007 Vasily Khoruzhick
|
||
* Copyright (C) 2009 Guido Grazioli <guido.grazioli@gmail.com>
|
||
*
|
||
* Based on code from libfprint aes2501 driver.
|
||
*
|
||
* 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 "aes1610"
|
||
|
||
#include <errno.h>
|
||
#include <string.h>
|
||
|
||
#include <libusb.h>
|
||
|
||
#include <aeslib.h>
|
||
#include <fp_internal.h>
|
||
|
||
static void start_capture(struct fp_img_dev *dev);
|
||
static void complete_deactivation(struct fp_img_dev *dev);
|
||
static int adjust_gain(unsigned char *buffer, int status);
|
||
|
||
#define FIRST_AES1610_REG 0x1B
|
||
#define LAST_AES1610_REG 0xFF
|
||
|
||
#define GAIN_STATUS_FIRST 1
|
||
#define GAIN_STATUS_NORMAL 2
|
||
|
||
/* FIXME these need checking */
|
||
#define EP_IN (1 | LIBUSB_ENDPOINT_IN)
|
||
#define EP_OUT (2 | LIBUSB_ENDPOINT_OUT)
|
||
|
||
#define BULK_TIMEOUT 4000
|
||
|
||
/*
|
||
* The AES1610 is an imaging device using a swipe-type sensor. It samples
|
||
* the finger at preprogrammed intervals, sending a 128x8 frame to the
|
||
* computer.
|
||
* Unless the user is scanning their finger unreasonably fast, the frames
|
||
* *will* overlap. The implementation below detects this overlap and produces
|
||
* a contiguous image as the end result.
|
||
* The fact that the user determines the length of the swipe (and hence the
|
||
* number of useful frames) and also the fact that overlap varies means that
|
||
* images returned from this driver vary in height.
|
||
*/
|
||
|
||
#define FRAME_WIDTH 128
|
||
#define FRAME_HEIGHT 8
|
||
#define FRAME_SIZE (FRAME_WIDTH * FRAME_HEIGHT)
|
||
/* maximum number of frames to read during a scan */
|
||
/* FIXME reduce substantially */
|
||
#define MAX_FRAMES 350
|
||
|
||
/****** GENERAL FUNCTIONS ******/
|
||
|
||
struct aes1610_dev {
|
||
uint8_t read_regs_retry_count;
|
||
GSList *strips;
|
||
size_t strips_len;
|
||
gboolean deactivating;
|
||
uint8_t blanks_count;
|
||
};
|
||
|
||
typedef void (*aes1610_read_regs_cb)(struct fp_img_dev *dev, int status,
|
||
unsigned char *regs, void *user_data);
|
||
|
||
struct aes1610_read_regs {
|
||
struct fp_img_dev *dev;
|
||
aes1610_read_regs_cb callback;
|
||
struct aes_regwrite *regwrite;
|
||
void *user_data;
|
||
};
|
||
|
||
/* FIXME: what to do here? */
|
||
static void stub_capture_stop_cb(struct fp_img_dev *dev, int result, void *user_data)
|
||
{
|
||
|
||
}
|
||
|
||
|
||
/* check that read succeeded but ignore all data */
|
||
static void generic_ignore_data_cb(struct libusb_transfer *transfer)
|
||
{
|
||
struct fpi_ssm *ssm = transfer->user_data;
|
||
|
||
if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
|
||
fpi_ssm_mark_aborted(ssm, -EIO);
|
||
else if (transfer->length != transfer->actual_length)
|
||
fpi_ssm_mark_aborted(ssm, -EPROTO);
|
||
else
|
||
fpi_ssm_next_state(ssm);
|
||
|
||
g_free(transfer->buffer);
|
||
libusb_free_transfer(transfer);
|
||
}
|
||
|
||
|
||
static void read_regs_data_cb(struct libusb_transfer *transfer)
|
||
{
|
||
struct aes1610_read_regs *rdata = transfer->user_data;
|
||
unsigned char *retdata = NULL;
|
||
int r;
|
||
|
||
if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
|
||
r = -EIO;
|
||
} else if (transfer->length != transfer->actual_length) {
|
||
r = -EPROTO;
|
||
} else {
|
||
r = 0;
|
||
retdata = transfer->buffer;
|
||
}
|
||
|
||
rdata->callback(rdata->dev, r, retdata, rdata->user_data);
|
||
g_free(rdata);
|
||
g_free(transfer->buffer);
|
||
libusb_free_transfer(transfer);
|
||
}
|
||
|
||
static void read_regs_rq_cb(struct fp_img_dev *dev, int result, void *user_data)
|
||
{
|
||
struct aes1610_read_regs *rdata = user_data;
|
||
struct libusb_transfer *transfer;
|
||
unsigned char *data;
|
||
int r;
|
||
|
||
g_free(rdata->regwrite);
|
||
if (result != 0)
|
||
goto err;
|
||
|
||
transfer = libusb_alloc_transfer(0);
|
||
if (!transfer) {
|
||
result = -ENOMEM;
|
||
goto err;
|
||
}
|
||
|
||
data = g_malloc(126);
|
||
libusb_fill_bulk_transfer(transfer, dev->udev, EP_IN, data, 126,
|
||
read_regs_data_cb, rdata, BULK_TIMEOUT);
|
||
|
||
r = libusb_submit_transfer(transfer);
|
||
if (r < 0) {
|
||
g_free(data);
|
||
libusb_free_transfer(transfer);
|
||
result = -EIO;
|
||
goto err;
|
||
}
|
||
|
||
return;
|
||
err:
|
||
rdata->callback(dev, result, NULL, rdata->user_data);
|
||
g_free(rdata);
|
||
}
|
||
|
||
|
||
// XXX: this comes from aes2501 driver but it is unused here
|
||
static void read_regs(struct fp_img_dev *dev, aes1610_read_regs_cb callback,
|
||
void *user_data)
|
||
{
|
||
/* FIXME: regwrite is dynamic because of asynchronity. is this really
|
||
* required? */
|
||
struct aes_regwrite *regwrite = g_malloc(sizeof(*regwrite));
|
||
struct aes1610_read_regs *rdata = g_malloc(sizeof(*rdata));
|
||
|
||
fp_dbg("");
|
||
//regwrite->reg = AES1610_REG_CTRL2;
|
||
//regwrite->value = AES1610_CTRL2_READ_REGS;
|
||
rdata->dev = dev;
|
||
rdata->callback = callback;
|
||
rdata->user_data = user_data;
|
||
rdata->regwrite = regwrite;
|
||
|
||
//aes_write_regv(dev, (const struct aes_regwrite *) regwrite, 1,
|
||
// read_regs_rq_cb, rdata);
|
||
}
|
||
|
||
/* Read the value of a specific register from a register dump */
|
||
static int regval_from_dump(unsigned char *data, uint8_t target)
|
||
{
|
||
if (*data != FIRST_AES1610_REG) {
|
||
fp_err("not a register dump");
|
||
return -EILSEQ;
|
||
}
|
||
|
||
if (!(FIRST_AES1610_REG <= target && target <= LAST_AES1610_REG)) {
|
||
fp_err("out of range");
|
||
return -EINVAL;
|
||
}
|
||
|
||
target -= FIRST_AES1610_REG;
|
||
target *= 2;
|
||
return data[target + 1];
|
||
}
|
||
|
||
static void generic_write_regv_cb(struct fp_img_dev *dev, int result,
|
||
void *user_data)
|
||
{
|
||
struct fpi_ssm *ssm = user_data;
|
||
if (result == 0)
|
||
fpi_ssm_next_state(ssm);
|
||
else
|
||
fpi_ssm_mark_aborted(ssm, result);
|
||
}
|
||
|
||
|
||
|
||
/* read the specified number of bytes from the IN endpoint but throw them
|
||
* away, then increment the SSM */
|
||
static void generic_read_ignore_data(struct fpi_ssm *ssm, size_t bytes)
|
||
{
|
||
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
|
||
unsigned char *data;
|
||
int r;
|
||
|
||
if (!transfer) {
|
||
fpi_ssm_mark_aborted(ssm, -ENOMEM);
|
||
return;
|
||
}
|
||
|
||
data = g_malloc(bytes);
|
||
libusb_fill_bulk_transfer(transfer, ssm->dev->udev, EP_IN, data, bytes,
|
||
generic_ignore_data_cb, ssm, BULK_TIMEOUT);
|
||
|
||
r = libusb_submit_transfer(transfer);
|
||
if (r < 0) {
|
||
g_free(data);
|
||
libusb_free_transfer(transfer);
|
||
fpi_ssm_mark_aborted(ssm, r);
|
||
}
|
||
}
|
||
|
||
/****** IMAGE PROCESSING ******/
|
||
|
||
static int sum_histogram_values(unsigned char *data, uint8_t threshold)
|
||
{
|
||
int r = 0;
|
||
int i;
|
||
uint16_t *histogram = (uint16_t *)(data + 1);
|
||
|
||
if (*data != 0xde)
|
||
return -EILSEQ;
|
||
|
||
if (threshold > 0x0f)
|
||
return -EINVAL;
|
||
|
||
/* FIXME endianness */
|
||
for (i = threshold; i < 16; i++)
|
||
r += histogram[i];
|
||
|
||
return r;
|
||
}
|
||
|
||
/* find overlapping parts of frames */
|
||
static unsigned int find_overlap(unsigned char *first_frame,
|
||
unsigned char *second_frame, unsigned int *min_error)
|
||
{
|
||
unsigned int dy;
|
||
unsigned int not_overlapped_height = 0;
|
||
*min_error = 255 * FRAME_SIZE;
|
||
for (dy = 0; dy < FRAME_HEIGHT; dy++) {
|
||
/* Calculating difference (error) between parts of frames */
|
||
unsigned int i;
|
||
unsigned int error = 0;
|
||
for (i = 0; i < FRAME_WIDTH * (FRAME_HEIGHT - dy); i++) {
|
||
/* Using ? operator to avoid abs function */
|
||
error += first_frame[i] > second_frame[i] ?
|
||
(first_frame[i] - second_frame[i]) :
|
||
(second_frame[i] - first_frame[i]);
|
||
}
|
||
|
||
/* Normalize error */
|
||
error *= 15;
|
||
error /= i;
|
||
if (error < *min_error) {
|
||
*min_error = error;
|
||
not_overlapped_height = dy;
|
||
}
|
||
first_frame += FRAME_WIDTH;
|
||
}
|
||
|
||
return not_overlapped_height;
|
||
}
|
||
|
||
/* assemble a series of frames into a single image */
|
||
static unsigned int assemble(struct aes1610_dev *aesdev, unsigned char *output,
|
||
gboolean reverse, unsigned int *errors_sum)
|
||
{
|
||
uint8_t *assembled = output;
|
||
int frame;
|
||
uint32_t image_height = FRAME_HEIGHT;
|
||
unsigned int min_error;
|
||
size_t num_strips = aesdev->strips_len;
|
||
GSList *list_entry = aesdev->strips;
|
||
*errors_sum = 0;
|
||
|
||
if (num_strips < 1)
|
||
return 0;
|
||
|
||
/* Rotating given data by 90 degrees
|
||
* Taken from document describing aes1610 image format
|
||
* TODO: move reversing detection here */
|
||
|
||
if (reverse)
|
||
output += (num_strips - 1) * FRAME_SIZE;
|
||
for (frame = 0; frame < num_strips; frame++) {
|
||
aes_assemble_image(list_entry->data, FRAME_WIDTH, FRAME_HEIGHT, output);
|
||
|
||
if (reverse)
|
||
output -= FRAME_SIZE;
|
||
else
|
||
output += FRAME_SIZE;
|
||
list_entry = g_slist_next(list_entry);
|
||
}
|
||
|
||
/* Detecting where frames overlaped */
|
||
output = assembled;
|
||
for (frame = 1; frame < num_strips; frame++) {
|
||
int not_overlapped;
|
||
|
||
output += FRAME_SIZE;
|
||
not_overlapped = find_overlap(assembled, output, &min_error);
|
||
*errors_sum += min_error;
|
||
image_height += not_overlapped;
|
||
assembled += FRAME_WIDTH * not_overlapped;
|
||
memcpy(assembled, output, FRAME_SIZE);
|
||
}
|
||
return image_height;
|
||
}
|
||
|
||
static void assemble_and_submit_image(struct fp_img_dev *dev)
|
||
{
|
||
struct aes1610_dev *aesdev = dev->priv;
|
||
size_t final_size;
|
||
struct fp_img *img;
|
||
unsigned int errors_sum, r_errors_sum;
|
||
|
||
fp_dbg("");
|
||
|
||
BUG_ON(aesdev->strips_len == 0);
|
||
|
||
/* reverse list */
|
||
aesdev->strips = g_slist_reverse(aesdev->strips);
|
||
|
||
/* create buffer big enough for max image */
|
||
img = fpi_img_new(aesdev->strips_len * FRAME_SIZE);
|
||
|
||
img->flags = FP_IMG_COLORS_INVERTED;
|
||
img->height = assemble(aesdev, img->data, FALSE, &errors_sum);
|
||
img->height = assemble(aesdev, img->data, TRUE, &r_errors_sum);
|
||
|
||
if (r_errors_sum > errors_sum) {
|
||
img->height = assemble(aesdev, img->data, FALSE, &errors_sum);
|
||
img->flags |= FP_IMG_V_FLIPPED | FP_IMG_H_FLIPPED;
|
||
fp_dbg("normal scan direction");
|
||
} else {
|
||
fp_dbg("reversed scan direction");
|
||
}
|
||
|
||
/* now that overlap has been removed, resize output image buffer */
|
||
final_size = img->height * FRAME_WIDTH;
|
||
img = fpi_img_resize(img, final_size);
|
||
/* FIXME: ugly workaround */
|
||
if (img->height < 12)
|
||
img->height = 12;
|
||
fpi_imgdev_image_captured(dev, img);
|
||
|
||
/* free strips and strip list */
|
||
g_slist_foreach(aesdev->strips, (GFunc) g_free, NULL);
|
||
g_slist_free(aesdev->strips);
|
||
aesdev->strips = NULL;
|
||
aesdev->strips_len = 0;
|
||
aesdev->blanks_count = 0;
|
||
}
|
||
|
||
|
||
/****** FINGER PRESENCE DETECTION ******/
|
||
|
||
|
||
static const struct aes_regwrite finger_det_reqs[] = {
|
||
{ 0x80, 0x01 },
|
||
{ 0x80, 0x12 },
|
||
{ 0x85, 0x00 },
|
||
{ 0x8A, 0x00 },
|
||
{ 0x8B, 0x0E },
|
||
{ 0x8C, 0x90 },
|
||
{ 0x8D, 0x83 },
|
||
{ 0x8E, 0x07 },
|
||
{ 0x8F, 0x07 },
|
||
{ 0x96, 0x00 },
|
||
{ 0x97, 0x48 },
|
||
{ 0xA1, 0x00 },
|
||
{ 0xA2, 0x50 },
|
||
{ 0xA6, 0xE4 },
|
||
{ 0xAD, 0x08 },
|
||
{ 0xAE, 0x5B },
|
||
{ 0xAF, 0x54 },
|
||
{ 0xB1, 0x28 },
|
||
{ 0xB5, 0xAB },
|
||
{ 0xB6, 0x0E },
|
||
{ 0x1B, 0x2D },
|
||
{ 0x81, 0x04 }
|
||
};
|
||
|
||
static const struct aes_regwrite finger_det_none[] = {
|
||
{ 0x80, 0x01 },
|
||
{ 0x82, 0x00 },
|
||
{ 0x86, 0x00 },
|
||
{ 0xB1, 0x28 },
|
||
{ 0x1D, 0x00 }
|
||
};
|
||
|
||
|
||
static void start_finger_detection(struct fp_img_dev *dev);
|
||
|
||
static void finger_det_data_cb(struct libusb_transfer *transfer)
|
||
{
|
||
struct fp_img_dev *dev = transfer->user_data;
|
||
unsigned char *data = transfer->buffer;
|
||
int i;
|
||
int sum = 0;
|
||
|
||
if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
|
||
fpi_imgdev_session_error(dev, -EIO);
|
||
goto out;
|
||
} else if (transfer->length != transfer->actual_length) {
|
||
fpi_imgdev_session_error(dev, -EPROTO);
|
||
goto out;
|
||
}
|
||
|
||
/* examine histogram to determine finger presence */
|
||
for (i = 3; i < 17; i++)
|
||
sum += (data[i] & 0xf) + (data[i] >> 4);
|
||
if (sum > 20) {
|
||
/* reset default gain */
|
||
adjust_gain(data,GAIN_STATUS_FIRST);
|
||
/* finger present, start capturing */
|
||
fpi_imgdev_report_finger_status(dev, TRUE);
|
||
start_capture(dev);
|
||
} else {
|
||
/* no finger, poll for a new histogram */
|
||
start_finger_detection(dev);
|
||
}
|
||
|
||
out:
|
||
g_free(data);
|
||
libusb_free_transfer(transfer);
|
||
}
|
||
|
||
|
||
static void finger_det_none_cb(struct fp_img_dev *dev, int result, void *user_data) |