diff --git a/libfprint/Makefile.am b/libfprint/Makefile.am index 9ade9aa0..70aea683 100644 --- a/libfprint/Makefile.am +++ b/libfprint/Makefile.am @@ -8,8 +8,8 @@ AES2501_SRC = drivers/aes2501.c drivers/aes2501.h AES4000_SRC = drivers/aes4000.c FDU2000_SRC = drivers/fdu2000.c -DRIVER_SRC = $(UPEKTS_SRC) $(AES4000_SRC) -#DRIVER_SRC = $(URU4000_SRC) $(AES1610_SRC) $(AES2501_SRC) $(UPEKTC_SRC) $(FDU2000_SRC) +DRIVER_SRC = $(UPEKTS_SRC) $(AES4000_SRC) $(AES2501_SRC) +#DRIVER_SRC = $(URU4000_SRC) $(AES1610_SRC) $(UPEKTC_SRC) $(FDU2000_SRC) NBIS_SRC = \ nbis/include/bozorth.h \ diff --git a/libfprint/core.c b/libfprint/core.c index ed238c84..d6298117 100644 --- a/libfprint/core.c +++ b/libfprint/core.c @@ -330,9 +330,9 @@ static struct fp_driver * const primitive_drivers[] = { static struct fp_img_driver * const img_drivers[] = { &aes4000_driver, + &aes2501_driver, /* &uru4000_driver, &aes1610_driver, - &aes2501_driver, &upektc_driver, &fdu2000_driver, */ }; diff --git a/libfprint/drivers/aes2501.c b/libfprint/drivers/aes2501.c index 26a6e54a..37439b50 100644 --- a/libfprint/drivers/aes2501.c +++ b/libfprint/drivers/aes2501.c @@ -1,6 +1,6 @@ /* * AuthenTec AES2501 driver for libfprint - * Copyright (C) 2007 Daniel Drake + * Copyright (C) 2007-2008 Daniel Drake * Copyright (C) 2007 Cyrille Bagard * Copyright (C) 2007 Vasily Khoruzhick * @@ -32,6 +32,9 @@ #include #include "aes2501.h" +static void start_capture(struct fp_img_dev *dev); +static void complete_deactivation(struct fp_img_dev *dev); + /* FIXME these need checking */ #define EP_IN (1 | LIBUSB_ENDPOINT_IN) #define EP_OUT (2 | LIBUSB_ENDPOINT_OUT) @@ -57,44 +60,612 @@ /* FIXME reduce substantially */ #define MAX_FRAMES 150 -static int read_data(struct fp_img_dev *dev, unsigned char *data, size_t len) -{ - int r; - int transferred; - struct libusb_bulk_transfer msg = { - .endpoint = EP_IN, - .data = data, - .length = len, - }; - fp_dbg("len=%zd", len); +/****** GENERAL FUNCTIONS ******/ - r = libusb_bulk_transfer(dev->udev, &msg, &transferred, BULK_TIMEOUT); - if (r < 0) { - fp_err("bulk read error %d", r); - return r; - } else if (transferred < len) { - fp_err("unexpected short read %d/%zd", r, len); - return -EIO; +struct aes2501_dev { + uint8_t read_regs_retry_count; + GSList *strips; + size_t strips_len; + gboolean deactivating; +}; + +typedef void (*aes2501_read_regs_cb)(struct fp_img_dev *dev, int status, + unsigned char *regs, void *user_data); + +struct aes2501_read_regs { + struct fp_img_dev *dev; + aes2501_read_regs_cb callback; + struct aes_regwrite *regwrite; + void *user_data; +}; + +static void read_regs_data_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh, + enum libusb_urb_cb_status status, unsigned char endpoint, int rqlength, + unsigned char *data, int actual_length, void *user_data) +{ + struct aes2501_read_regs *rdata = user_data; + unsigned char *retdata = NULL; + int r; + + if (status != FP_URB_COMPLETED) { + r = -EIO; + } else if (rqlength != actual_length) { + r = -EPROTO; + } else { + r = 0; + retdata = data; } - return 0; + + rdata->callback(rdata->dev, r, retdata, rdata->user_data); + libusb_urb_handle_free(urbh); + g_free(data); + g_free(rdata); } -static int read_regs(struct fp_img_dev *dev, unsigned char *data) +static void read_regs_rq_cb(struct fp_img_dev *dev, int result, void *user_data) { - int r; - const struct aes_regwrite regwrite = { - AES2501_REG_CTRL2, AES2501_CTRL2_READ_REGS + struct aes2501_read_regs *rdata = user_data; + struct libusb_urb_handle *urbh; + struct libusb_bulk_transfer trf = { + .endpoint = EP_IN, + .length = 126, }; + g_free(rdata->regwrite); + if (result != 0) + goto err; + + trf.data = g_malloc(trf.length); + urbh = libusb_async_bulk_transfer(dev->udev, &trf, read_regs_data_cb, + rdata, BULK_TIMEOUT); + if (!urbh) { + g_free(trf.data); + result = EIO; + goto err; + } + + return; +err: + rdata->callback(dev, result, NULL, rdata->user_data); + g_free(rdata); +} + +static void read_regs(struct fp_img_dev *dev, aes2501_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 aes2501_read_regs *rdata = g_malloc(sizeof(*rdata)); + fp_dbg(""); + regwrite->reg = AES2501_REG_CTRL2; + regwrite->value = AES2501_CTRL2_READ_REGS; + rdata->dev = dev; + rdata->callback = callback; + rdata->user_data = user_data; + rdata->regwrite = regwrite; - r = aes_write_regv(dev, ®write, 1); - if (r < 0) - return r; - - return read_data(dev, data, 126); + 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_AES2501_REG) { + fp_err("not a register dump"); + return -EILSEQ; + } + + if (!(FIRST_AES2501_REG <= target && target <= LAST_AES2501_REG)) { + fp_err("out of range"); + return -EINVAL; + } + + target -= FIRST_AES2501_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); +} + +/* check that read succeeded but ignore all data */ +static void generic_ignore_data_cb(libusb_dev_handle *devh, + libusb_urb_handle *urbh, enum libusb_urb_cb_status status, + unsigned char endpoint, int rqlength, unsigned char *data, + int actual_length, void *user_data) +{ + struct fpi_ssm *ssm = user_data; + + libusb_urb_handle_free(urbh); + g_free(data); + + if (status != FP_URB_COMPLETED) + fpi_ssm_mark_aborted(ssm, -EIO); + else if (rqlength != actual_length) + fpi_ssm_mark_aborted(ssm, -EPROTO); + else + fpi_ssm_next_state(ssm); +} + +/* 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_urb_handle *urbh; + struct libusb_bulk_transfer trf = { + .endpoint = EP_IN, + .data = g_malloc(bytes), + .length = bytes, + }; + + urbh = libusb_async_bulk_transfer(ssm->dev->udev, &trf, + generic_ignore_data_cb, ssm, BULK_TIMEOUT); + if (!urbh) { + g_free(trf.data); + fpi_ssm_mark_aborted(ssm, -EIO); + } +} + +/****** 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 aes2501_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; + + /* Rotating given data by 90 degrees + * Taken from document describing aes2501 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 aes2501_dev *aesdev = dev->priv; + size_t final_size; + struct fp_img *img; + unsigned int errors_sum, r_errors_sum; + + 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); + 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; +} + + +/****** FINGER PRESENCE DETECTION ******/ + +static const struct aes_regwrite finger_det_reqs[] = { + { AES2501_REG_CTRL1, AES2501_CTRL1_MASTER_RESET }, + { AES2501_REG_EXCITCTRL, 0x40 }, + { AES2501_REG_DETCTRL, + AES2501_DETCTRL_DRATE_CONTINUOUS | AES2501_DETCTRL_SDELAY_31_MS }, + { AES2501_REG_COLSCAN, AES2501_COLSCAN_SRATE_128_US }, + { AES2501_REG_MEASDRV, AES2501_MEASDRV_MDRIVE_0_325 | AES2501_MEASDRV_MEASURE_SQUARE }, + { AES2501_REG_MEASFREQ, AES2501_MEASFREQ_2M }, + { AES2501_REG_DEMODPHASE1, DEMODPHASE_NONE }, + { AES2501_REG_DEMODPHASE2, DEMODPHASE_NONE }, + { AES2501_REG_CHANGAIN, + AES2501_CHANGAIN_STAGE2_4X | AES2501_CHANGAIN_STAGE1_16X }, + { AES2501_REG_ADREFHI, 0x44 }, + { AES2501_REG_ADREFLO, 0x34 }, + { AES2501_REG_STRTCOL, 0x16 }, + { AES2501_REG_ENDCOL, 0x16 }, + { AES2501_REG_DATFMT, AES2501_DATFMT_BIN_IMG | 0x08 }, + { AES2501_REG_TREG1, 0x70 }, + { 0xa2, 0x02 }, + { 0xa7, 0x00 }, + { AES2501_REG_TREGC, AES2501_TREGC_ENABLE }, + { AES2501_REG_TREGD, 0x1a }, + { 0, 0 }, + { AES2501_REG_CTRL1, AES2501_CTRL1_REG_UPDATE }, + { AES2501_REG_CTRL2, AES2501_CTRL2_SET_ONE_SHOT }, + { AES2501_REG_LPONT, AES2501_LPONT_MIN_VALUE }, +}; + +static void start_finger_detection(struct fp_img_dev *dev); + +static void finger_det_data_cb(libusb_dev_handle *devh, libusb_urb_handle *urbh, + enum libusb_urb_cb_status status, unsigned char endpoint, + int rqlength, unsigned char *data, int actual_length, void *user_data) +{ + struct fp_img_dev *dev = user_data; + int i; + int sum = 0; + + libusb_urb_handle_free(urbh); + if (status != FP_URB_COMPLETED) { + fpi_imgdev_session_error(dev, -EIO); + goto out; + } else if (rqlength != actual_length) { + fpi_imgdev_session_error(dev, -EPROTO); + goto out; + } + + /* examine histogram to determine finger presence */ + for (i = 1; i < 9; i++) + sum += (data[i] & 0xf) + (data[i] >> 4); + if (sum > 20) { + /* 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); +} + +static void finger_det_reqs_cb(struct fp_img_dev *dev, int result, + void *user_data) +{ + struct libusb_urb_handle *urbh; + struct libusb_bulk_transfer trf = { + .endpoint = EP_IN, + .length = 20, + }; + + if (result) { + fpi_imgdev_session_error(dev, result); + return; + } + + trf.data = g_malloc(trf.length); + urbh = libusb_async_bulk_transfer(dev->udev, &trf, finger_det_data_cb, + dev, BULK_TIMEOUT); + if (!urbh) { + g_free(trf.data); + fpi_imgdev_session_error(dev, -EIO); + } +} + +static void start_finger_detection(struct fp_img_dev *dev) +{ + struct aes2501_dev *aesdev = dev->priv; + fp_dbg(""); + + if (aesdev->deactivating) { + complete_deactivation(dev); + return; + } + + aes_write_regv(dev, finger_det_reqs, G_N_ELEMENTS(finger_det_reqs), + finger_det_reqs_cb, NULL); +} + +/****** CAPTURE ******/ + +static const struct aes_regwrite capture_reqs_1[] = { + { AES2501_REG_CTRL1, AES2501_CTRL1_MASTER_RESET }, + { 0, 0 }, + { AES2501_REG_EXCITCTRL, 0x40 }, + { AES2501_REG_DETCTRL, + AES2501_DETCTRL_SDELAY_31_MS | AES2501_DETCTRL_DRATE_CONTINUOUS }, + { AES2501_REG_COLSCAN, AES2501_COLSCAN_SRATE_128_US }, + { AES2501_REG_DEMODPHASE2, 0x7c }, + { AES2501_REG_MEASDRV, + AES2501_MEASDRV_MEASURE_SQUARE | AES2501_MEASDRV_MDRIVE_0_325 }, + { AES2501_REG_DEMODPHASE1, 0x24 }, + { AES2501_REG_CHWORD1, 0x00 }, + { AES2501_REG_CHWORD2, 0x6c }, + { AES2501_REG_CHWORD3, 0x09 }, + { AES2501_REG_CHWORD4, 0x54 }, + { AES2501_REG_CHWORD5, 0x78 }, + { 0xa2, 0x02 }, + { 0xa7, 0x00 }, + { 0xb6, 0x26 }, + { 0xb7, 0x1a }, + { AES2501_REG_CTRL1, AES2501_CTRL1_REG_UPDATE }, + { AES2501_REG_IMAGCTRL, + AES2501_IMAGCTRL_TST_REG_ENABLE | AES2501_IMAGCTRL_HISTO_DATA_ENABLE | + AES2501_IMAGCTRL_IMG_DATA_DISABLE }, + { AES2501_REG_STRTCOL, 0x10 }, + { AES2501_REG_ENDCOL, 0x1f }, + { AES2501_REG_CHANGAIN, + AES2501_CHANGAIN_STAGE1_2X | AES2501_CHANGAIN_STAGE2_2X }, + { AES2501_REG_ADREFHI, 0x70 }, + { AES2501_REG_ADREFLO, 0x20 }, + { AES2501_REG_CTRL2, AES2501_CTRL2_SET_ONE_SHOT }, + { AES2501_REG_LPONT, AES2501_LPONT_MIN_VALUE }, +}; + +static const struct aes_regwrite capture_reqs_2[] = { + { AES2501_REG_IMAGCTRL, + AES2501_IMAGCTRL_TST_REG_ENABLE | AES2501_IMAGCTRL_HISTO_DATA_ENABLE | + AES2501_IMAGCTRL_IMG_DATA_DISABLE }, + { AES2501_REG_STRTCOL, 0x10 }, + { AES2501_REG_ENDCOL, 0x1f }, + { AES2501_REG_CHANGAIN, AES2501_CHANGAIN_STAGE1_16X }, + { AES2501_REG_ADREFHI, 0x70 }, + { AES2501_REG_ADREFLO, 0x20 }, + { AES2501_REG_CTRL2, AES2501_CTRL2_SET_ONE_SHOT }, +}; + +static const struct aes_regwrite strip_scan_reqs[] = { + { AES2501_REG_IMAGCTRL, + AES2501_IMAGCTRL_TST_REG_ENABLE | AES2501_IMAGCTRL_HISTO_DATA_ENABLE }, + { AES2501_REG_STRTCOL, 0x00 }, + { AES2501_REG_ENDCOL, 0x2f }, + { AES2501_REG_CHANGAIN, AES2501_CHANGAIN_STAGE1_16X }, + { AES2501_REG_ADREFHI, 0x5b }, + { AES2501_REG_ADREFLO, 0x20 }, + { AES2501_REG_CTRL2, AES2501_CTRL2_SET_ONE_SHOT }, +}; + +/* capture SM movement: + * write reqs and read data 1 + 2, + * request and read strip, + * jump back to request UNLESS theres no finger, in which case exit SM, + * report lack of finger presence, and move to finger detection */ + +enum capture_states { + CAPTURE_WRITE_REQS_1, + CAPTURE_READ_DATA_1, + CAPTURE_WRITE_REQS_2, + CAPTURE_READ_DATA_2, + CAPTURE_REQUEST_STRIP, + CAPTURE_READ_STRIP, + CAPTURE_NUM_STATES, +}; + +static void capture_read_strip_cb(libusb_dev_handle *devh, + libusb_urb_handle *urbh, enum libusb_urb_cb_status status, + unsigned char endpoint, int rqlength, unsigned char *data, + int actual_length, void *user_data) +{ + unsigned char *stripdata; + struct fpi_ssm *ssm = user_data; + struct fp_img_dev *dev = ssm->priv; + struct aes2501_dev *aesdev = dev->priv; + int sum; + int threshold; + + libusb_urb_handle_free(urbh); + if (status != FP_URB_COMPLETED) { + fpi_ssm_mark_aborted(ssm, -EIO); + goto out; + } else if (rqlength != actual_length) { + fpi_ssm_mark_aborted(ssm, -EPROTO); + goto out; + } + + /* FIXME: would preallocating strip buffers be a decent optimization? */ + stripdata = g_malloc(192 * 8); + memcpy(stripdata, data + 1, 192*8); + aesdev->strips = g_slist_prepend(aesdev->strips, stripdata); + aesdev->strips_len++; + + threshold = regval_from_dump(data + 1 + 192*8 + 1 + 16*2 + 1 + 8, + AES2501_REG_DATFMT); + if (threshold < 0) { + fpi_ssm_mark_aborted(ssm, threshold); + goto out; + } + + sum = sum_histogram_values(data + 1 + 192*8, threshold & 0x0f); + if (sum < 0) { + fpi_ssm_mark_aborted(ssm, sum); + goto out; + } + fp_dbg("sum=%d", sum); + + /* FIXME: 0 might be too low as a threshold */ + /* FIXME: sometimes we get 0 in the middle of a scan, should we wait for + * a few consecutive zeroes? */ + /* FIXME: we should have an upper limit on the number of strips */ + + /* If sum is 0, finger has been removed */ + if (sum == 0) { + /* assemble image and submit it to library */ + assemble_and_submit_image(dev); + fpi_imgdev_report_finger_status(dev, FALSE); + /* marking machine complete will re-trigger finger detection loop */ + fpi_ssm_mark_completed(ssm); + } else { + /* obtain next strip */ + fpi_ssm_jump_to_state(ssm, CAPTURE_REQUEST_STRIP); + } + +out: + g_free(data); +} + +static void capture_run_state(struct fpi_ssm *ssm) +{ + struct fp_img_dev *dev = ssm->priv; + struct aes2501_dev *aesdev = dev->priv; + + switch (ssm->cur_state) { + case CAPTURE_WRITE_REQS_1: + aes_write_regv(dev, capture_reqs_1, G_N_ELEMENTS(capture_reqs_1), + generic_write_regv_cb, ssm); + break; + case CAPTURE_READ_DATA_1: + generic_read_ignore_data(ssm, 159); + break; + case CAPTURE_WRITE_REQS_2: + aes_write_regv(dev, capture_reqs_2, G_N_ELEMENTS(capture_reqs_2), + generic_write_regv_cb, ssm); + break; + case CAPTURE_READ_DATA_2: + generic_read_ignore_data(ssm, 159); + break; + case CAPTURE_REQUEST_STRIP: + if (aesdev->deactivating) + fpi_ssm_mark_completed(ssm); + else + aes_write_regv(dev, strip_scan_reqs, G_N_ELEMENTS(strip_scan_reqs), + generic_write_regv_cb, ssm); + break; + case CAPTURE_READ_STRIP: ; + struct libusb_urb_handle *urbh; + struct libusb_bulk_transfer trf = { + .endpoint = EP_IN, + .data = g_malloc(1705), + .length = 1705, + }; + urbh = libusb_async_bulk_transfer(dev->udev, &trf, + capture_read_strip_cb, ssm, BULK_TIMEOUT); + if (!urbh) { + g_free(trf.data); + fpi_ssm_mark_aborted(ssm, -EIO); + } + break; + }; +} + +static void capture_sm_complete(struct fpi_ssm *ssm) +{ + struct fp_img_dev *dev = ssm->priv; + struct aes2501_dev *aesdev = dev->priv; + + fp_dbg(""); + if (aesdev->deactivating) + complete_deactivation(dev); + else if (ssm->error) + fpi_imgdev_session_error(dev, ssm->error); + else + start_finger_detection(dev); + fpi_ssm_free(ssm); +} + +static void start_capture(struct fp_img_dev *dev) +{ + struct aes2501_dev *aesdev = dev->priv; + struct fpi_ssm *ssm; + + if (aesdev->deactivating) { + complete_deactivation(dev); + return; + } + + ssm = fpi_ssm_new(dev->dev, capture_run_state, CAPTURE_NUM_STATES); + fp_dbg(""); + ssm->priv = dev; + fpi_ssm_start(ssm, capture_sm_complete); +} + +/****** INITIALIZATION/DEINITIALIZATION ******/ + static const struct aes_regwrite init_1[] = { { AES2501_REG_CTRL1, AES2501_CTRL1_MASTER_RESET }, { 0, 0 }, @@ -187,55 +758,146 @@ static const struct aes_regwrite init_5[] = { { AES2501_REG_CTRL1, AES2501_CTRL1_SCAN_RESET }, }; -static int do_init(struct fp_img_dev *dev) +enum activate_states { + WRITE_INIT_1, + READ_DATA_1, + WRITE_INIT_2, + READ_REGS, + WRITE_INIT_3, + WRITE_INIT_4, + WRITE_INIT_5, + ACTIVATE_NUM_STATES, +}; + +void activate_read_regs_cb(struct fp_img_dev *dev, int status, + unsigned char *regs, void *user_data) { - unsigned char buffer[128]; - int r; - int i; + struct fpi_ssm *ssm = user_data; + struct aes2501_dev *aesdev = dev->priv; - /* part 1, probably not needed */ - r = aes_write_regv(dev, init_1, G_N_ELEMENTS(init_1)); - if (r < 0) - return r; - - r = read_data(dev, buffer, 20); - if (r < 0) - return r; - - /* part 2 */ - r = aes_write_regv(dev, init_2, G_N_ELEMENTS(init_2)); - if (r < 0) - return r; - - r = read_regs(dev, buffer); - if (r < 0) - return r; - - /* part 3 */ - fp_dbg("reg 0xaf = %x", buffer[0x5f]); - i = 0; - while (buffer[0x5f] == 0x6b) { - r = aes_write_regv(dev, init_3, G_N_ELEMENTS(init_3)); - if (r < 0) - return r; - r = read_regs(dev, buffer); - if (r < 0) - return r; - if (++i == 13) - break; + if (status != 0) { + fpi_ssm_mark_aborted(ssm, status); + } else { + fp_dbg("reg 0xaf = %x", regs[0x5f]); + if (regs[0x5f] != 0x6b || ++aesdev->read_regs_retry_count == 13) + fpi_ssm_jump_to_state(ssm, WRITE_INIT_4); + else + fpi_ssm_next_state(ssm); } +} - /* part 4 */ - r = aes_write_regv(dev, init_4, G_N_ELEMENTS(init_4)); - if (r < 0) - return r; +static void activate_init3_cb(struct fp_img_dev *dev, int result, + void *user_data) +{ + struct fpi_ssm *ssm = user_data; + if (result == 0) + fpi_ssm_jump_to_state(ssm, READ_REGS); + else + fpi_ssm_mark_aborted(ssm, result); +} - /* part 5 */ - return aes_write_regv(dev, init_5, G_N_ELEMENTS(init_5)); +static void activate_run_state(struct fpi_ssm *ssm) +{ + struct fp_img_dev *dev = ssm->priv; + + /* This state machine isn't as linear as it may appear. After doing init1 + * and init2 register configuration writes, we have to poll a register + * waiting for a specific value. READ_REGS checks the register value, and + * if we're ready to move on, we jump to init4. Otherwise, we write init3 + * and then jump back to READ_REGS. In a synchronous model: + + [...] + aes_write_regv(init_2); + read_regs(into buffer); + i = 0; + while (buffer[0x5f] == 0x6b) { + aes_write_regv(init_3); + read_regs(into buffer); + if (++i == 13) + break; + } + aes_write_regv(init_4); + */ + + switch (ssm->cur_state) { + case WRITE_INIT_1: + aes_write_regv(dev, init_1, G_N_ELEMENTS(init_1), + generic_write_regv_cb, ssm); + break; + case READ_DATA_1: + fp_dbg("read data 1"); + generic_read_ignore_data(ssm, 20); + break; + case WRITE_INIT_2: + aes_write_regv(dev, init_2, G_N_ELEMENTS(init_2), + generic_write_regv_cb, ssm); + break; + case READ_REGS: + read_regs(dev, activate_read_regs_cb, ssm); + break; + case WRITE_INIT_3: + aes_write_regv(dev, init_4, G_N_ELEMENTS(init_4), + activate_init3_cb, ssm); + break; + case WRITE_INIT_4: + aes_write_regv(dev, init_4, G_N_ELEMENTS(init_4), + generic_write_regv_cb, ssm); + break; + case WRITE_INIT_5: + aes_write_regv(dev, init_5, G_N_ELEMENTS(init_5), + generic_write_regv_cb, ssm); + break; + } +} + +static void activate_sm_complete(struct fpi_ssm *ssm) +{ + struct fp_img_dev *dev = ssm->priv; + fp_dbg("status %d", ssm->error); + fpi_imgdev_activate_complete(dev, ssm->error); + + if (!ssm->error) + start_finger_detection(dev); + fpi_ssm_free(ssm); +} + +static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state) +{ + struct aes2501_dev *aesdev = dev->priv; + struct fpi_ssm *ssm = fpi_ssm_new(dev->dev, activate_run_state, + ACTIVATE_NUM_STATES); + ssm->priv = dev; + aesdev->read_regs_retry_count = 0; + fpi_ssm_start(ssm, activate_sm_complete); + return 0; +} + +static void dev_deactivate(struct fp_img_dev *dev) +{ + struct aes2501_dev *aesdev = dev->priv; + /* FIXME: audit cancellation points, probably need more, specifically + * in error handling paths? */ + aesdev->deactivating = TRUE; +} + +static void complete_deactivation(struct fp_img_dev *dev) +{ + struct aes2501_dev *aesdev = dev->priv; + fp_dbg(""); + + /* FIXME: if we're in the middle of a scan, we should cancel the scan. + * maybe we can do this with a master reset, unconditionally? */ + + aesdev->deactivating = FALSE; + g_slist_free(aesdev->strips); + aesdev->strips = NULL; + aesdev->strips_len = 0; + fpi_imgdev_deactivate_complete(dev); } static int dev_init(struct fp_img_dev *dev, unsigned long driver_data) { + /* FIXME check endpoints */ int r; r = libusb_claim_interface(dev->udev, 0); @@ -244,332 +906,16 @@ static int dev_init(struct fp_img_dev *dev, unsigned long driver_data) return r; } - /* FIXME check endpoints */ - - return do_init(dev); -} - -static void dev_exit(struct fp_img_dev *dev) -{ - libusb_release_interface(dev->udev, 0); -} - -static const struct aes_regwrite finger_det_reqs[] = { - { AES2501_REG_CTRL1, AES2501_CTRL1_MASTER_RESET }, - { AES2501_REG_EXCITCTRL, 0x40 }, - { AES2501_REG_DETCTRL, - AES2501_DETCTRL_DRATE_CONTINUOUS | AES2501_DETCTRL_SDELAY_31_MS }, - { AES2501_REG_COLSCAN, AES2501_COLSCAN_SRATE_128_US }, - { AES2501_REG_MEASDRV, AES2501_MEASDRV_MDRIVE_0_325 | AES2501_MEASDRV_MEASURE_SQUARE }, - { AES2501_REG_MEASFREQ, AES2501_MEASFREQ_2M }, - { AES2501_REG_DEMODPHASE1, DEMODPHASE_NONE }, - { AES2501_REG_DEMODPHASE2, DEMODPHASE_NONE }, - { AES2501_REG_CHANGAIN, - AES2501_CHANGAIN_STAGE2_4X | AES2501_CHANGAIN_STAGE1_16X }, - { AES2501_REG_ADREFHI, 0x44 }, - { AES2501_REG_ADREFLO, 0x34 }, - { AES2501_REG_STRTCOL, 0x16 }, - { AES2501_REG_ENDCOL, 0x16 }, - { AES2501_REG_DATFMT, AES2501_DATFMT_BIN_IMG | 0x08 }, - { AES2501_REG_TREG1, 0x70 }, - { 0xa2, 0x02 }, - { 0xa7, 0x00 }, - { AES2501_REG_TREGC, AES2501_TREGC_ENABLE }, - { AES2501_REG_TREGD, 0x1a }, - { 0, 0 }, - { AES2501_REG_CTRL1, AES2501_CTRL1_REG_UPDATE }, - { AES2501_REG_CTRL2, AES2501_CTRL2_SET_ONE_SHOT }, - { AES2501_REG_LPONT, AES2501_LPONT_MIN_VALUE }, -}; - -static int detect_finger(struct fp_img_dev *dev) -{ - unsigned char buffer[22]; - int r; - int i; - int sum = 0; - - r = aes_write_regv(dev, finger_det_reqs, G_N_ELEMENTS(finger_det_reqs)); - if (r < 0) - return r; - - r = read_data(dev, buffer, 20); - if (r < 0) - return r; - - for (i = 1; i < 9; i++) - sum += (buffer[i] & 0xf) + (buffer[i] >> 4); - - 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_AES2501_REG) { - fp_err("not a register dump"); - return -EILSEQ; - } - - if (!(FIRST_AES2501_REG <= target && target <= LAST_AES2501_REG)) { - fp_err("out of range"); - return -EINVAL; - } - - target -= FIRST_AES2501_REG; - target *= 2; - return data[target + 1]; -} - -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(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 aes2501 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(input, FRAME_WIDTH, FRAME_HEIGHT, output); - input += FRAME_WIDTH * (FRAME_HEIGHT / 2); - - 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 aes_regwrite capture_reqs_1[] = { - { AES2501_REG_CTRL1, AES2501_CTRL1_MASTER_RESET }, - { 0, 0 }, - { AES2501_REG_EXCITCTRL, 0x40 }, - { AES2501_REG_DETCTRL, - AES2501_DETCTRL_SDELAY_31_MS | AES2501_DETCTRL_DRATE_CONTINUOUS }, - { AES2501_REG_COLSCAN, AES2501_COLSCAN_SRATE_128_US }, - { AES2501_REG_DEMODPHASE2, 0x7c }, - { AES2501_REG_MEASDRV, - AES2501_MEASDRV_MEASURE_SQUARE | AES2501_MEASDRV_MDRIVE_0_325 }, - { AES2501_REG_DEMODPHASE1, 0x24 }, - { AES2501_REG_CHWORD1, 0x00 }, - { AES2501_REG_CHWORD2, 0x6c }, - { AES2501_REG_CHWORD3, 0x09 }, - { AES2501_REG_CHWORD4, 0x54 }, - { AES2501_REG_CHWORD5, 0x78 }, - { 0xa2, 0x02 }, - { 0xa7, 0x00 }, - { 0xb6, 0x26 }, - { 0xb7, 0x1a }, - { AES2501_REG_CTRL1, AES2501_CTRL1_REG_UPDATE }, - { AES2501_REG_IMAGCTRL, - AES2501_IMAGCTRL_TST_REG_ENABLE | AES2501_IMAGCTRL_HISTO_DATA_ENABLE | - AES2501_IMAGCTRL_IMG_DATA_DISABLE }, - { AES2501_REG_STRTCOL, 0x10 }, - { AES2501_REG_ENDCOL, 0x1f }, - { AES2501_REG_CHANGAIN, - AES2501_CHANGAIN_STAGE1_2X | AES2501_CHANGAIN_STAGE2_2X }, - { AES2501_REG_ADREFHI, 0x70 }, - { AES2501_REG_ADREFLO, 0x20 }, - { AES2501_REG_CTRL2, AES2501_CTRL2_SET_ONE_SHOT }, - { AES2501_REG_LPONT, AES2501_LPONT_MIN_VALUE }, -}; - -static const struct aes_regwrite capture_reqs_2[] = { - { AES2501_REG_IMAGCTRL, - AES2501_IMAGCTRL_TST_REG_ENABLE | AES2501_IMAGCTRL_HISTO_DATA_ENABLE | - AES2501_IMAGCTRL_IMG_DATA_DISABLE }, - { AES2501_REG_STRTCOL, 0x10 }, - { AES2501_REG_ENDCOL, 0x1f }, - { AES2501_REG_CHANGAIN, AES2501_CHANGAIN_STAGE1_16X }, - { AES2501_REG_ADREFHI, 0x70 }, - { AES2501_REG_ADREFLO, 0x20 }, - { AES2501_REG_CTRL2, AES2501_CTRL2_SET_ONE_SHOT }, -}; - -static const struct aes_regwrite strip_scan_reqs[] = { - { AES2501_REG_IMAGCTRL, - AES2501_IMAGCTRL_TST_REG_ENABLE | AES2501_IMAGCTRL_HISTO_DATA_ENABLE }, - { AES2501_REG_STRTCOL, 0x00 }, - { AES2501_REG_ENDCOL, 0x2f }, - { AES2501_REG_CHANGAIN, AES2501_CHANGAIN_STAGE1_16X }, - { AES2501_REG_ADREFHI, 0x5b }, - { AES2501_REG_ADREFLO, 0x20 }, - { AES2501_REG_CTRL2, AES2501_CTRL2_SET_ONE_SHOT }, -}; - -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[1705]; - int final_size; - int sum; - - /* FIXME can do better here in terms of buffer management? */ - fp_dbg(""); - - r = aes_write_regv(dev, capture_reqs_1, G_N_ELEMENTS(capture_reqs_1)); - if (r < 0) - return r; - - r = read_data(dev, buf, 159); - if (r < 0) - return r; - - r = aes_write_regv(dev, capture_reqs_2, G_N_ELEMENTS(capture_reqs_2)); - if (r < 0) - return r; - - r = read_data(dev, buf, 159); - 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; - - for (nstrips = 0; nstrips < MAX_FRAMES; nstrips++) { - int threshold; - - r = aes_write_regv(dev, strip_scan_reqs, G_N_ELEMENTS(strip_scan_reqs)); - if (r < 0) - goto err; - r = read_data(dev, buf, 1705); - if (r < 0) - goto err; - memcpy(imgptr, buf + 1, 192*8); - imgptr += 192*8; - - threshold = regval_from_dump((buf + 1 + 192*8 + 1 + 16*2 + 1 + 8), - AES2501_REG_DATFMT); - if (threshold < 0) { - r = threshold; - goto err; - } - - sum = sum_histogram_values((buf + 1 + 192*8), threshold & 0x0f); - if (sum < 0) { - r = sum; - goto err; - } - fp_dbg("sum=%d", sum); - if (sum == 0) - break; - } - 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"); - } - - final_size = img->height * FRAME_WIDTH; - memcpy(img->data, cooked, final_size); - img = fpi_img_resize(img, final_size); - *ret = img; + dev->priv = g_malloc0(sizeof(struct aes2501_dev)); + fpi_imgdev_init_complete(dev, 0); return 0; -err: - fp_img_free(img); - return r; +} + +static void dev_deinit(struct fp_img_dev *dev) +{ + g_free(dev->priv); + libusb_release_interface(dev->udev, 0); + fpi_imgdev_deinit_complete(dev); } static const struct usb_id id_table[] = { @@ -589,8 +935,8 @@ struct fp_img_driver aes2501_driver = { .img_width = 192, .init = dev_init, - .exit = dev_exit, - .await_finger_on = await_finger_on, - .capture = capture, + .deinit = dev_deinit, + .activate = dev_activate, + .deactivate = dev_deactivate, };