diff --git a/AUTHORS b/AUTHORS index db6fd07f..cc829548 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,3 +6,4 @@ Copyright (C) 2004,2006 Thomas Vander Stichele Copyright (C) 2007 Cyrille Bagard Copyright (C) 2007 Vasily Khoruzhick Copyright (C) 2007 Jan-Michael Brummer +Copyright (C) 2007 Anthony Bretaudeau diff --git a/TODO b/TODO index bbb4cf6f..07f00195 100644 --- a/TODO +++ b/TODO @@ -10,7 +10,6 @@ race-free way of saying "save this print but don't overwrite" NEW DRIVERS =========== Sunplus 895 driver -AES1610 driver AES3400/3500 driver ID Mouse driver Support for 2nd generation MS devices diff --git a/libfprint/Makefile.am b/libfprint/Makefile.am index 194f423e..549deff7 100644 --- a/libfprint/Makefile.am +++ b/libfprint/Makefile.am @@ -3,10 +3,11 @@ lib_LTLIBRARIES = libfprint.la UPEKTS_SRC = drivers/upekts.c UPEKTC_SRC = drivers/upektc.c URU4000_SRC = drivers/uru4000.c +AES1610_SRC = drivers/aes1610.c AES2501_SRC = drivers/aes2501.c drivers/aes2501.h AES4000_SRC = drivers/aes4000.c -DRIVER_SRC = $(UPEKTS_SRC) $(URU4000_SRC) $(AES2501_SRC) $(AES4000_SRC) $(UPEKTC_SRC) +DRIVER_SRC = $(UPEKTS_SRC) $(URU4000_SRC) $(AES1610_SRC) $(AES2501_SRC) $(AES4000_SRC) $(UPEKTC_SRC) NBIS_SRC = \ nbis/include/bozorth.h \ diff --git a/libfprint/drivers/aes1610.c b/libfprint/drivers/aes1610.c new file mode 100644 index 00000000..f1b1a509 --- /dev/null +++ b/libfprint/drivers/aes1610.c @@ -0,0 +1,626 @@ +/* + * AuthenTec AES1610 driver for libfprint + * Copyright (C) 2007 Anthony Bretaudeau + * Copyright (C) 2007 Daniel Drake + * Copyright (C) 2007 Cyrille Bagard + * Copyright (C) 2007 Vasily Khoruzhick + * + * Based on code from http://home.gna.org/aes2501, relicensed with permission + * + * 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 +#include + +#include + +#include + +/* FIXME these need checking */ +#define EP_IN (1 | USB_ENDPOINT_IN) +#define EP_OUT (2 | USB_ENDPOINT_OUT) + +#define BULK_TIMEOUT 4000 +#define MAX_REGWRITES_PER_REQUEST 16 + +#define FIRST_AES1610_REG 0x1B +#define LAST_AES1610_REG 0xFF + +/* + * 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 150 + +struct aes1610_regwrite { + unsigned char reg; + unsigned char value; +}; + +static int do_write_regv(struct fp_img_dev *dev, const struct aes1610_regwrite *regs, + unsigned int num) +{ + size_t alloc_size = num * 2; + unsigned char *data = g_malloc(alloc_size); + unsigned int i; + size_t offset = 0; + int r; + + for (i = 0; i < num; i++) { + data[offset++] = regs[i].reg; + data[offset++] = regs[i].value; + } + + r = usb_bulk_write(dev->udev, EP_OUT, data, alloc_size, BULK_TIMEOUT); + g_free(data); + if (r < 0) { + fp_err("bulk write error %d", r); + return r; + } else if (r < (int) alloc_size) { + fp_err("unexpected short write %d/%d", r, alloc_size); + return -EIO; + } + + return 0; +} + +static int write_regv(struct fp_img_dev *dev, const struct aes1610_regwrite *regs, + unsigned int num) +{ + unsigned int i; + int skip = 0; + int add_offset = 0; + fp_dbg("write %d regs", num); + + for (i = 0; i < num; i += add_offset + skip) { + int r, j; + int limit = MIN(num, i + MAX_REGWRITES_PER_REQUEST); + skip = 0; + + for (j = i; j < limit; j++) + if (!regs[j].reg) { + skip = 1; + break; + } + + add_offset = j - i; + r = do_write_regv(dev, ®s[i], add_offset); + if (r < 0) + return r; + } + + return 0; +} + +static int read_data(struct fp_img_dev *dev, unsigned char *data, size_t len) +{ + int r; + fp_dbg("len=%zd", len); + + r = usb_bulk_read(dev->udev, EP_IN, data, len, BULK_TIMEOUT); + if (r < 0) { + fp_err("bulk read error %d", r); + return r; + } else if (r < (int) len) { + fp_err("unexpected short read %d/%zd", r, len); + return -EIO; + } + return 0; +} + +static const struct aes1610_regwrite init[] = { + { 0x82, 0x00 } +}; + +static const struct aes1610_regwrite stop_reader[] = { + { 0xFF, 0x00 } +}; + +static int do_init(struct fp_img_dev *dev) +{ + return write_regv(dev, init, G_N_ELEMENTS(init)); +} + +static int dev_init(struct fp_img_dev *dev, unsigned long driver_data) +{ + int r; + + r = usb_claim_interface(dev->udev, 0); + if (r < 0) { + fp_err("could not claim interface 0"); + return r; + } + + /* FIXME check endpoints */ + + return do_init(dev); +} + +static int do_exit(struct fp_img_dev *dev) +{ + return write_regv(dev, stop_reader, G_N_ELEMENTS(stop_reader)); +} + +static void dev_exit(struct fp_img_dev *dev) +{ + do_exit(dev); + usb_release_interface(dev->udev, 0); +} + +static const struct aes1610_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 aes1610_regwrite finger_det_none[] = { + { 0x80, 0x01 }, + { 0x82, 0x00 }, + { 0x86, 0x00 }, + { 0xB1, 0x28 }, + { 0x1D, 0x00 } +}; + +static int detect_finger(struct fp_img_dev *dev) +{ + unsigned char buffer[19]; + int r; + int i; + int sum = 0; + + r = write_regv(dev, finger_det_reqs, G_N_ELEMENTS(finger_det_reqs)); + if (r < 0) + return r; + + r = read_data(dev, buffer, 19); + if (r < 0) + return r; + + for (i = 3; i < 17; i++) + sum += (buffer[i] & 0xf) + (buffer[i] >> 4); + + /* We need to answer something if no finger has been detected */ + if (sum <= 20) { + r = write_regv(dev, finger_det_none, G_N_ELEMENTS(finger_det_none)); + if (r < 0) + return r; + } + + return sum > 20; +} + +static int await_finger_on(struct fp_img_dev *dev) +{ + int r; + do { + r = detect_finger(dev); + } while (r == 0); + return (r < 0) ? r : 0; +} + +/* 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]; +} + +/* 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(unsigned char *input, unsigned char *output, + int num_strips, gboolean reverse, unsigned int *errors_sum) +{ + uint8_t *assembled = output; + int frame; + uint32_t image_height = FRAME_HEIGHT; + unsigned int min_error; + *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++) { + int column; + for (column = 0; column < FRAME_WIDTH; column++) { + int row; + for (row = 0; row < (FRAME_HEIGHT / 2); row++) { + output[FRAME_WIDTH * ( 2 * row) + column] = *input & 0x0F; + output[FRAME_WIDTH * ( 2 * row + 1) + column] = *input >> 4; + input++; + } + } + + if (reverse) + output -= FRAME_SIZE; + else + output += FRAME_SIZE; + } + + /* 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 const struct aes1610_regwrite capture_reqs[] = { + { 0x80, 0x01 }, + { 0x80, 0x12 }, + { 0x84, 0x01 }, + { 0x85, 0x00 }, + { 0x89, 0x64 }, + { 0x8A, 0x00 }, + { 0x8B, 0x0E }, + { 0x8C, 0x90 }, + { 0xBE, 0x23 }, + { 0x29, 0x06 }, + { 0x2A, 0x35 }, + { 0x96, 0x00 }, + { 0x98, 0x03 }, + { 0x99, 0x00 }, + { 0x9C, 0xA5 }, + { 0x9D, 0x40 }, + { 0x9E, 0xC6 }, + { 0x9F, 0x8E }, + { 0xA2, 0x50 }, + { 0xA3, 0xF0 }, + { 0xAD, 0x08 }, + { 0xBD, 0x4F }, + { 0xAF, 0x54 }, + { 0xB1, 0x08 }, + { 0xB5, 0xAB }, + { 0x1B, 0x2D }, + { 0xB6, 0x4E }, + { 0xB8, 0x70 }, + { 0x2B, 0xB3 }, + { 0x2C, 0x5D }, + { 0x2D, 0x98 }, + { 0x2E, 0xB0 }, + { 0x2F, 0x20 }, + { 0xA2, 0xD0 }, + { 0x1D, 0x21 }, + { 0x1E, 0xBE }, + { 0x1C, 0x00 }, + { 0x1D, 0x30 }, + { 0x1E, 0x29 }, + { 0x1C, 0x01 }, + { 0x1D, 0x00 }, + { 0x1E, 0x9E }, + { 0x1C, 0x02 }, + { 0x1D, 0x30 }, + { 0x1E, 0xBB }, + { 0x1C, 0x03 }, + { 0x1D, 0x00 }, + { 0x1E, 0x9D }, + { 0x1C, 0x04 }, + { 0x1D, 0x22 }, + { 0x1E, 0xFF }, + { 0x1C, 0x05 }, + { 0x1D, 0x1B }, + { 0x1E, 0x4E }, + { 0x1C, 0x06 }, + { 0x1D, 0x16 }, + { 0x1E, 0x28 }, + { 0x1C, 0x07 }, + { 0x1D, 0x22 }, + { 0x1E, 0xFF }, + { 0x1C, 0x08 }, + { 0x1D, 0x15 }, + { 0x1E, 0xF1 }, + { 0x1C, 0x09 }, + { 0x1D, 0x30 }, + { 0x1E, 0xD5 }, + { 0x1C, 0x0A }, + { 0x1D, 0x00 }, + { 0x1E, 0x9E }, + { 0x1C, 0x0B }, + { 0x1D, 0x17 }, + { 0x1E, 0x9D }, + { 0x1C, 0x0C }, + { 0x1D, 0x28 }, + { 0x1E, 0xD7 }, + { 0x1C, 0x0D }, + { 0x1D, 0x17 }, + { 0x1E, 0xD7 }, + { 0x1C, 0x0E }, + { 0x1D, 0x0A }, + { 0x1E, 0xCB }, + { 0x1C, 0x0F }, + { 0x1D, 0x24 }, + { 0x1E, 0x14 }, + { 0x1C, 0x10 }, + { 0x1D, 0x17 }, + { 0x1E, 0x85 }, + { 0x1C, 0x11 }, + { 0x1D, 0x15 }, + { 0x1E, 0x71 }, + { 0x1C, 0x12 }, + { 0x1D, 0x2B }, + { 0x1E, 0x36 }, + { 0x1C, 0x13 }, + { 0x1D, 0x12 }, + { 0x1E, 0x06 }, + { 0x1C, 0x14 }, + { 0x1D, 0x30 }, + { 0x1E, 0x97 }, + { 0x1C, 0x15 }, + { 0x1D, 0x21 }, + { 0x1E, 0x32 }, + { 0x1C, 0x16 }, + { 0x1D, 0x06 }, + { 0x1E, 0xE6 }, + { 0x1C, 0x17 }, + { 0x1D, 0x16 }, + { 0x1E, 0x06 }, + { 0x1C, 0x18 }, + { 0x1D, 0x30 }, + { 0x1E, 0x01 }, + { 0x1C, 0x19 }, + { 0x1D, 0x21 }, + { 0x1E, 0x37 }, + { 0x1C, 0x1A }, + { 0x1D, 0x00 }, + { 0x1E, 0x08 }, + { 0x1C, 0x1B }, + { 0x1D, 0x80 }, + { 0x1E, 0xD5 }, + { 0xA2, 0x50 }, + { 0xA2, 0x50 }, + { 0x81, 0x01 } +}; + +static const struct aes1610_regwrite strip_scan_reqs[] = { + { 0xBE, 0x23 }, + { 0x29, 0x06 }, + { 0x2A, 0x35 }, + { 0xBD, 0x4F }, + { 0xFF, 0x00 } +}; + +static const struct aes1610_regwrite capture_stop[] = { + { 0x81,0x00 } +}; + +static int capture(struct fp_img_dev *dev, gboolean unconditional, + struct fp_img **ret) +{ + int r; + struct fp_img *img; + unsigned int nstrips; + unsigned int errors_sum, r_errors_sum; + unsigned char *cooked; + unsigned char *imgptr; + unsigned char buf[665]; + int sum; + int i; + + /* FIXME can do better here in terms of buffer management? */ + fp_dbg(""); + + r = write_regv(dev, capture_reqs, G_N_ELEMENTS(capture_reqs)); + if (r < 0) + return r; + + /* FIXME: use histogram data above for gain calibration (0x8e xx) */ + + img = fpi_img_new((3 * MAX_FRAMES * FRAME_SIZE) / 2); + imgptr = img->data; + cooked = imgptr + (MAX_FRAMES * FRAME_SIZE) / 2; + + r = read_data(dev, buf, 665); + if (r < 0) + goto err; + memcpy(imgptr, buf + 1, 128*4); + imgptr += 128*4; + + r = read_data(dev, buf, 665); + if (r < 0) + goto err; + memcpy(imgptr, buf + 1, 128*4); + imgptr += 128*4; + + for (nstrips = 0; nstrips < MAX_FRAMES; nstrips++) { + int threshold; + + r = write_regv(dev, strip_scan_reqs, G_N_ELEMENTS(strip_scan_reqs)); + if (r < 0) + goto err; + r = read_data(dev, buf, 665); + if (r < 0) + goto err; + memcpy(imgptr, buf + 1, 128*4); + imgptr += 128*4; + + r = read_data(dev, buf, 665); + if (r < 0) + goto err; + memcpy(imgptr, buf + 1, 128*4); + imgptr += 128*4; + + sum = 0; + for (i = 515; i != 530; i++) + { + /* histogram[i] = number of pixels of value i + Only the pixel values from 10 to 15 are used to detect finger. */ + sum += buf[i]; + } + if (sum < 0) { + r = sum; + goto err; + } + fp_dbg("sum=%d", sum); + if (sum == 0) + break; + } + + r = write_regv(dev, capture_stop, G_N_ELEMENTS(capture_stop)); + if (r < 0) + goto err; + r = read_data(dev, buf, 665); + if (r < 0) + goto err; + memcpy(imgptr, buf + 1, 128*4); + imgptr += 128*4; + + r = read_data(dev, buf, 665); + if (r < 0) + goto err; + memcpy(imgptr, buf + 1, 128*4); + imgptr += 128*4; + + if (nstrips == MAX_FRAMES) + fp_warn("swiping finger too slow?"); + + img->flags = FP_IMG_COLORS_INVERTED; + img->height = assemble(img->data, cooked, nstrips, FALSE, &errors_sum); + img->height = assemble(img->data, cooked, nstrips, TRUE, &r_errors_sum); + + if (r_errors_sum > errors_sum) { + img->height = assemble(img->data, cooked, nstrips, FALSE, &errors_sum); + img->flags |= FP_IMG_V_FLIPPED | FP_IMG_H_FLIPPED; + fp_dbg("normal scan direction"); + } else { + fp_dbg("reversed scan direction"); + } + + for (i = 0; i < img->height * FRAME_WIDTH; i++) + img->data[i] = (cooked[i] << 4) | 0xf; + + img = fpi_img_resize(img, img->height * FRAME_WIDTH); + *ret = img; + return 0; +err: + fp_img_free(img); + return r; +} + +static const struct usb_id id_table[] = { + { .vendor = 0x08ff, .product = 0x1600 }, + { 0, 0, 0, }, +}; + +struct fp_img_driver aes1610_driver = { + .driver = { + .id = 6, + .name = FP_COMPONENT, + .full_name = "AuthenTec AES1610", + .id_table = id_table, + }, + .flags = 0, + .img_height = -1, + .img_width = 128, + + /* temporarily lowered until we sort out image processing code + * binarized scan quality is good, minutiae detection is accurate, + * it's just that we get fewer minutiae than other scanners (less scanning + * area) */ + .bz3_threshold = 15, + + .init = dev_init, + .exit = dev_exit, + .await_finger_on = await_finger_on, + .capture = capture, +}; +