diff --git a/libfprint/drivers/elan.c b/libfprint/drivers/elan.c index dff75a6..97f4c34 100644 --- a/libfprint/drivers/elan.c +++ b/libfprint/drivers/elan.c @@ -2,6 +2,7 @@ * Elan driver for libfprint * * Copyright (C) 2017 Igor Filatov + * Copyright (C) 2018 Sébastien Béchet * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,11 +19,36 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +/* + * The algorithm which libfprint uses to match fingerprints doesn't like small + * images like the ones these drivers produce. There's just not enough minutiae + * (recognizable print-specific points) on them for a reliable match. This means + * that unless another matching algo is found/implemented, these readers will + * not work as good with libfprint as they do with vendor drivers. + * + * To get bigger images the driver expects you to swipe the finger over the + * reader. This works quite well for readers with a rectangular 144x64 sensor. + * Worse than real swipe readers but good enough for day-to-day use. It needs + * a steady and relatively slow swipe. There are also square 96x96 sensors and + * I don't know whether they are in fact usable or not because I don't have one. + * I imagine they'd be less reliable because the resulting image is even + * smaller. If they can't be made usable with libfprint, I might end up dropping + * them because it's better than saying they work when they don't. + */ + #define FP_COMPONENT "elan" #include "drivers_api.h" #include "elan.h" +#define dbg_buf(buf, len) \ + if (len == 1) \ + fp_dbg("%02hx", buf[0]); \ + else if (len == 2) \ + fp_dbg("%04hx", buf[0] << 8 | buf[1]); \ + else if (len > 2) \ + fp_dbg("%04hx... (%d bytes)", buf[0] << 8 | buf[1], len) + unsigned char elan_get_pixel(struct fpi_frame_asmbl_ctx *ctx, struct fpi_frame *frame, unsigned int x, unsigned int y) @@ -38,34 +64,49 @@ static struct fpi_frame_asmbl_ctx assembling_ctx = { }; struct elan_dev { - gboolean deactivating; + /* device config */ + unsigned short dev_type; + unsigned short fw_ver; + void (*process_frame) (unsigned short *raw_frame, GSList ** frames); + /* end device config */ - const struct elan_cmd *cmds; - size_t cmds_len; - int cmd_idx; + /* commands */ + const struct elan_cmd *cmd; int cmd_timeout; struct libusb_transfer *cur_transfer; + /* end commands */ + /* state */ + enum fp_imgdev_state dev_state; + enum fp_imgdev_state dev_state_next; unsigned char *last_read; + unsigned char calib_atts_left; + unsigned char calib_status; + unsigned short *background; unsigned char frame_width; unsigned char frame_height; - unsigned char raw_frame_width; + unsigned char raw_frame_height; int num_frames; GSList *frames; + /* end state */ }; +int cmp_short(const void *a, const void *b) +{ + return (int)(*(short *)a - *(short *)b); +} + static void elan_dev_reset(struct elan_dev *elandev) { G_DEBUG_HERE(); BUG_ON(elandev->cur_transfer); - elandev->deactivating = FALSE; - - elandev->cmds = NULL; - elandev->cmd_idx = 0; + elandev->cmd = NULL; elandev->cmd_timeout = ELAN_CMD_TIMEOUT; + elandev->calib_status = 0; + g_free(elandev->last_read); elandev->last_read = NULL; @@ -74,36 +115,118 @@ static void elan_dev_reset(struct elan_dev *elandev) elandev->num_frames = 0; } -static void elan_save_frame(struct fp_img_dev *dev) +static void elan_save_frame(struct elan_dev *elandev, unsigned short *frame) { - struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); - unsigned char raw_height = elandev->frame_width; - unsigned char raw_width = elandev->raw_frame_width; - unsigned short *frame = - g_malloc(elandev->frame_width * elandev->frame_height * 2); - G_DEBUG_HERE(); - /* Raw images are vertical and perpendicular to swipe direction of a - * normalized image, which means we need to make them horizontal before - * assembling. We also discard stirpes of ELAN_FRAME_MARGIN along raw - * height. */ - for (int y = 0; y < raw_height; y++) - for (int x = ELAN_FRAME_MARGIN; - x < raw_width - ELAN_FRAME_MARGIN; x++) { - int frame_idx = - y + (x - ELAN_FRAME_MARGIN) * raw_height; - int raw_idx = x + y * raw_width; + /* so far 3 types of readers by sensor dimensions and orientation have been + * seen in the wild: + * 1. 144x64. Raw images are in portrait orientation while readers themselves + * are placed (e.g. built into a touchpad) in landscape orientation. These + * need to be rotated before assembling. + * 2. 96x96 rotated. Like the first type but square. Likewise, need to be + * rotated before assembling. + * 3. 96x96 normal. Square and need NOT be rotated. So far there's only been + * 1 report of a 0c03 of this type. Hopefully this type can be identified + * by device id (and manufacturers don't just install the readers as they + * please). + * we also discard stripes of 'frame_margin' from bottom and top because + * assembling works bad for tall frames */ + + unsigned char frame_width = elandev->frame_width; + unsigned char frame_height = elandev->frame_height; + unsigned char raw_height = elandev->raw_frame_height; + unsigned char frame_margin = (raw_height - elandev->frame_height) / 2; + int frame_idx, raw_idx; + + for (int y = 0; y < frame_height; y++) + for (int x = 0; x < frame_width; x++) { + if (elandev->dev_type & ELAN_NOT_ROTATED) + raw_idx = x + (y + frame_margin) * frame_width; + else + raw_idx = frame_margin + y + x * raw_height; + frame_idx = x + y * frame_width; frame[frame_idx] = ((unsigned short *)elandev->last_read)[raw_idx]; } +} + +static void elan_save_background(struct elan_dev *elandev) +{ + G_DEBUG_HERE(); + + g_free(elandev->background); + elandev->background = + g_malloc(elandev->frame_width * elandev->frame_height * + sizeof(short)); + elan_save_frame(elandev, elandev->background); +} + +/* save a frame as part of the fingerprint image + * background needs to have been captured for this routine to work + * Elantech recommends 2-step non-linear normalization in order to reduce + * 2^14 ADC resolution to 2^8 image: + * + * 1. background is subtracted (done here) + * + * 2. pixels are grouped in 3 groups by intensity and each group is mapped + * separately onto the normalized frame (done in elan_process_frame_*) + * ==== 16383 ____> ======== 255 + * / + * ----- lvl3 __/ + * 35% pixels + * + * ----- lvl2 --------> ======== 156 + * + * 30% pixels + * ----- lvl1 --------> ======== 99 + * + * 35% pixels + * ----- lvl0 __ + * \ + * ======== 0 \____> ======== 0 + * + * For some devices we don't do 2. but instead do a simple linear mapping + * because it seems to produce better results (or at least as good): + * ==== 16383 ___> ======== 255 + * / + * ------ max __/ + * + * + * ------ min __ + * \ + * ======== 0 \___> ======== 0 + */ +static int elan_save_img_frame(struct elan_dev *elandev) +{ + G_DEBUG_HERE(); + + unsigned int frame_size = elandev->frame_width * elandev->frame_height; + unsigned short *frame = g_malloc(frame_size * sizeof(short)); + elan_save_frame(elandev, frame); + unsigned int sum = 0; + + for (int i = 0; i < frame_size; i++) { + if (elandev->background[i] > frame[i]) + frame[i] = 0; + else + frame[i] -= elandev->background[i]; + sum += frame[i]; + } + + if (sum == 0) { + fp_dbg + ("frame darker than background; finger present during calibration?"); + return -1; + } elandev->frames = g_slist_prepend(elandev->frames, frame); elandev->num_frames += 1; + return 0; } -/* Transform raw sensor data to normalized 8-bit grayscale image. */ -static void elan_process_frame(unsigned short *raw_frame, GSList ** frames) +static void elan_process_frame_linear(unsigned short *raw_frame, + GSList ** frames) { unsigned int frame_size = assembling_ctx.frame_width * assembling_ctx.frame_height; @@ -123,12 +246,42 @@ static void elan_process_frame(unsigned short *raw_frame, GSList ** frames) unsigned short px; for (int i = 0; i < frame_size; i++) { px = raw_frame[i]; - if (px <= min) - px = 0; - else if (px >= max) - px = 0xff; - else - px = (px - min) * 0xff / (max - min); + px = (px - min) * 0xff / (max - min); + frame->data[i] = (unsigned char)px; + } + + *frames = g_slist_prepend(*frames, frame); +} + +static void elan_process_frame_thirds(unsigned short *raw_frame, + GSList ** frames) +{ + G_DEBUG_HERE(); + + unsigned int frame_size = + assembling_ctx.frame_width * assembling_ctx.frame_height; + struct fpi_frame *frame = + g_malloc(frame_size + sizeof(struct fpi_frame)); + + unsigned short lvl0, lvl1, lvl2, lvl3; + unsigned short *sorted = g_malloc(frame_size * sizeof(short)); + memcpy(sorted, raw_frame, frame_size * sizeof(short)); + qsort(sorted, frame_size, sizeof(short), cmp_short); + lvl0 = sorted[0]; + lvl1 = sorted[frame_size * 3 / 10]; + lvl2 = sorted[frame_size * 65 / 100]; + lvl3 = sorted[frame_size - 1]; + g_free(sorted); + + unsigned short px; + for (int i = 0; i < frame_size; i++) { + px = raw_frame[i]; + if (lvl0 <= px && px < lvl1) + px = (px - lvl0) * 99 / (lvl1 - lvl0); + else if (lvl1 <= px && px < lvl2) + px = 99 + ((px - lvl1) * 56 / (lvl2 - lvl1)); + else // (lvl2 <= px && px <= lvl3) + px = 155 + ((px - lvl2) * 100 / (lvl3 - lvl2)); frame->data[i] = (unsigned char)px; } @@ -145,15 +298,16 @@ static void elan_submit_image(struct fp_img_dev *dev) for (int i = 0; i < ELAN_SKIP_LAST_FRAMES; i++) elandev->frames = g_slist_next(elandev->frames); + elandev->num_frames -= ELAN_SKIP_LAST_FRAMES; assembling_ctx.frame_width = elandev->frame_width; assembling_ctx.frame_height = elandev->frame_height; assembling_ctx.image_width = elandev->frame_width * 3 / 2; - g_slist_foreach(elandev->frames, (GFunc) elan_process_frame, &frames); + g_slist_foreach(elandev->frames, (GFunc) elandev->process_frame, + &frames); fpi_do_movement_estimation(&assembling_ctx, frames, - elandev->num_frames - ELAN_SKIP_LAST_FRAMES); - img = fpi_assemble_frames(&assembling_ctx, frames, - elandev->num_frames - ELAN_SKIP_LAST_FRAMES); + elandev->num_frames); + img = fpi_assemble_frames(&assembling_ctx, frames, elandev->num_frames); img->flags |= FP_IMG_PARTIAL; fpi_imgdev_image_captured(dev, img); @@ -161,16 +315,8 @@ static void elan_submit_image(struct fp_img_dev *dev) static void elan_cmd_done(struct fpi_ssm *ssm) { - struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); - struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); - G_DEBUG_HERE(); - - elandev->cmd_idx += 1; - if (elandev->cmd_idx < elandev->cmds_len) - elan_run_next_cmd(ssm); - else - fpi_ssm_next_state(ssm); + fpi_ssm_next_state(ssm); } static void elan_cmd_cb(struct libusb_transfer *transfer) @@ -186,27 +332,27 @@ static void elan_cmd_cb(struct libusb_transfer *transfer) switch (transfer->status) { case LIBUSB_TRANSFER_COMPLETED: if (transfer->length != transfer->actual_length) { - fp_dbg("unexpected transfer length"); + fp_dbg("transfer length error: expected %d, got %d", + transfer->length, transfer->actual_length); elan_dev_reset(elandev); fpi_ssm_mark_aborted(ssm, -EPROTO); - } else if (transfer->endpoint & LIBUSB_ENDPOINT_IN) + } else if (transfer->endpoint & LIBUSB_ENDPOINT_IN) { /* just finished receiving */ + dbg_buf(elandev->last_read, transfer->actual_length); elan_cmd_done(ssm); - else { + } else { /* just finished sending */ - if (elandev->cmds[elandev->cmd_idx].response_len) - elan_cmd_read(ssm); - else - elan_cmd_done(ssm); + G_DEBUG_HERE(); + elan_cmd_read(ssm); } break; case LIBUSB_TRANSFER_CANCELLED: fp_dbg("transfer cancelled"); fpi_ssm_mark_aborted(ssm, -ECANCELED); + elan_deactivate(dev); break; case LIBUSB_TRANSFER_TIMED_OUT: fp_dbg("transfer timed out"); - // elan_dev_reset(elandev); fpi_ssm_mark_aborted(ssm, -ETIMEDOUT); break; default: @@ -220,14 +366,20 @@ static void elan_cmd_read(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); - int response_len = elandev->cmds[elandev->cmd_idx].response_len; + int response_len = elandev->cmd->response_len; G_DEBUG_HERE(); - if (elandev->cmds[elandev->cmd_idx].cmd == read_cmds[0].cmd) + if (elandev->cmd->response_len == ELAN_CMD_SKIP_READ) { + fp_dbg("skipping read, not expecting anything"); + elan_cmd_done(ssm); + return; + } + + if (elandev->cmd->cmd == get_image_cmd.cmd) /* raw data has 2-byte "pixels" and the frame is vertical */ response_len = - elandev->raw_frame_width * elandev->frame_width * 2; + elandev->raw_frame_height * elandev->frame_width * 2; struct libusb_transfer *transfer = libusb_alloc_transfer(0); if (!transfer) { @@ -240,7 +392,7 @@ static void elan_cmd_read(struct fpi_ssm *ssm) elandev->last_read = g_malloc(response_len); libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev), - elandev->cmds[elandev->cmd_idx].response_in, + elandev->cmd->response_in, elandev->last_read, response_len, elan_cmd_cb, ssm, elandev->cmd_timeout); transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER; @@ -249,12 +401,23 @@ static void elan_cmd_read(struct fpi_ssm *ssm) fpi_ssm_mark_aborted(ssm, r); } -static void elan_run_next_cmd(struct fpi_ssm *ssm) +static void elan_run_cmd(struct fpi_ssm *ssm, const struct elan_cmd *cmd, + int cmd_timeout) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); - G_DEBUG_HERE(); + dbg_buf(cmd->cmd, 2); + + elandev->cmd = cmd; + if (cmd_timeout != -1) + elandev->cmd_timeout = cmd_timeout; + + if (cmd->devices != ELAN_ALL_DEV && !(cmd->devices & elandev->dev_type)) { + fp_dbg("skipping for this device"); + elan_cmd_done(ssm); + return; + } struct libusb_transfer *transfer = libusb_alloc_transfer(0); if (!transfer) { @@ -264,31 +427,12 @@ static void elan_run_next_cmd(struct fpi_ssm *ssm) elandev->cur_transfer = transfer; libusb_fill_bulk_transfer(transfer, fpi_imgdev_get_usb_dev(dev), ELAN_EP_CMD_OUT, - (unsigned char *)elandev->cmds[elandev-> - cmd_idx].cmd, - ELAN_CMD_LEN, elan_cmd_cb, ssm, + (char *) cmd->cmd, ELAN_CMD_LEN, elan_cmd_cb, ssm, elandev->cmd_timeout); transfer->flags = LIBUSB_TRANSFER_FREE_TRANSFER; int r = libusb_submit_transfer(transfer); if (r < 0) fpi_ssm_mark_aborted(ssm, r); - -} - -static void elan_run_cmds(struct fpi_ssm *ssm, const struct elan_cmd *cmds, - size_t cmds_len, int cmd_timeout) -{ - struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); - struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); - - G_DEBUG_HERE(); - - elandev->cmds = cmds; - elandev->cmds_len = cmds_len; - elandev->cmd_idx = 0; - if (cmd_timeout != -1) - elandev->cmd_timeout = cmd_timeout; - elan_run_next_cmd(ssm); } enum deactivate_states { @@ -296,12 +440,13 @@ enum deactivate_states { DEACTIVATE_NUM_STATES, }; -static void elan_deactivate_run_state(struct fpi_ssm *ssm) +static void deactivate_run_state(struct fpi_ssm *ssm) { + G_DEBUG_HERE(); + switch (fpi_ssm_get_cur_state(ssm)) { case DEACTIVATE: - elan_run_cmds(ssm, deactivate_cmds, deactivate_cmds_len, - ELAN_CMD_TIMEOUT); + elan_run_cmd(ssm, &stop_cmd, ELAN_CMD_TIMEOUT); break; } } @@ -310,6 +455,8 @@ static void deactivate_complete(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); + G_DEBUG_HERE(); + fpi_imgdev_deactivate_complete(dev); } @@ -321,60 +468,58 @@ static void elan_deactivate(struct fp_img_dev *dev) elan_dev_reset(elandev); - struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), elan_deactivate_run_state, + struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), deactivate_run_state, DEACTIVATE_NUM_STATES); fpi_ssm_set_user_data(ssm, dev); fpi_ssm_start(ssm, deactivate_complete); } enum capture_states { - CAPTURE_START, + CAPTURE_LED_ON, CAPTURE_WAIT_FINGER, CAPTURE_READ_DATA, - CAPTURE_SAVE_FRAME, + CAPTURE_CHECK_ENOUGH_FRAMES, CAPTURE_NUM_STATES, }; -static void elan_capture_run_state(struct fpi_ssm *ssm) +static void capture_run_state(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); + int r; switch (fpi_ssm_get_cur_state(ssm)) { - case CAPTURE_START: - elan_run_cmds(ssm, capture_start_cmds, capture_start_cmds_len, - ELAN_CMD_TIMEOUT); + case CAPTURE_LED_ON: + elan_run_cmd(ssm, &led_on_cmd, ELAN_CMD_TIMEOUT); break; case CAPTURE_WAIT_FINGER: - elan_run_cmds(ssm, capture_wait_finger_cmds, - capture_wait_finger_cmds_len, -1); + elan_run_cmd(ssm, &pre_scan_cmd, -1); break; case CAPTURE_READ_DATA: /* 0x55 - finger present - * 0xff - device not calibrated */ + * 0xff - device not calibrated (probably) */ if (elandev->last_read && elandev->last_read[0] == 0x55) { - fpi_imgdev_report_finger_status(dev, TRUE); - elan_run_cmds(ssm, read_cmds, read_cmds_len, - ELAN_CMD_TIMEOUT); + if (elandev->dev_state == IMGDEV_STATE_AWAIT_FINGER_ON) + fpi_imgdev_report_finger_status(dev, TRUE); + elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT); } else - fpi_ssm_mark_aborted(ssm, FP_VERIFY_RETRY); + fpi_ssm_mark_aborted(ssm, -EBADMSG); break; - case CAPTURE_SAVE_FRAME: - elan_save_frame(dev); - if (elandev->num_frames < ELAN_MAX_FRAMES) { + case CAPTURE_CHECK_ENOUGH_FRAMES: + r = elan_save_img_frame(elandev); + if (r < 0) + fpi_ssm_mark_aborted(ssm, r); + else if (elandev->num_frames < ELAN_MAX_FRAMES) { /* quickly stop if finger is removed */ elandev->cmd_timeout = ELAN_FINGER_TIMEOUT; fpi_ssm_jump_to_state(ssm, CAPTURE_WAIT_FINGER); + } else { + fpi_ssm_next_state(ssm); } break; } } -static void elan_capture_async(void *data) -{ - elan_capture((struct fp_img_dev *)data); -} - static void capture_complete(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); @@ -382,33 +527,41 @@ static void capture_complete(struct fpi_ssm *ssm) G_DEBUG_HERE(); - if (elandev->deactivating) - elan_deactivate(dev); + if (fpi_ssm_get_error(ssm) == -ECANCELED) { + fpi_ssm_free(ssm); + return; + } /* either max frames captured or timed out waiting for the next frame */ - else if (!fpi_ssm_get_error(ssm) - || (fpi_ssm_get_error(ssm) == -ETIMEDOUT - && fpi_ssm_get_cur_state(ssm) == CAPTURE_WAIT_FINGER)) - if (elandev->num_frames >= ELAN_MIN_FRAMES) { + if (!fpi_ssm_get_error(ssm) + || (fpi_ssm_get_error(ssm) == -ETIMEDOUT + && fpi_ssm_get_cur_state(ssm) == CAPTURE_WAIT_FINGER)) + if (elandev->num_frames >= ELAN_MIN_FRAMES) elan_submit_image(dev); - fpi_imgdev_report_finger_status(dev, FALSE); - } else - fpi_imgdev_session_error(dev, - FP_VERIFY_RETRY_TOO_SHORT); + else { + fp_dbg("swipe too short: want >= %d frames, got %d", + ELAN_MIN_FRAMES, elandev->num_frames); + fpi_imgdev_abort_scan(dev, FP_VERIFY_RETRY_TOO_SHORT); + } /* other error - * It says "...session_error" but repotring 1 during verification - * makes it successful! */ + * It says "...abort_scan" but reporting 1 during verification makes it + * successful! */ else - fpi_imgdev_session_error(dev, FP_VERIFY_NO_MATCH); + fpi_imgdev_abort_scan(dev, fpi_ssm_get_error(ssm)); - /* When enrolling the lib won't restart the capture after a stage has - * completed, so we need to keep feeding it images till it's had enough. - * But after that it can't finalize enrollemnt until this callback exits. - * That's why we schedule elan_capture instead of running it directly. */ - if (fpi_dev_get_dev_state(fpi_imgdev_get_dev(dev)) == DEV_STATE_ENROLLING - && !fpi_timeout_add(10, elan_capture_async, dev)) - fpi_imgdev_session_error(dev, -ETIME); + /* this procedure must be called regardless of outcome because it advances + * dev_state to AWAIT_FINGER_ON under the hood... */ + fpi_imgdev_report_finger_status(dev, FALSE); + + /* ...but only on enroll! If verify or identify fails because of short swipe, + * we need to do it manually. It feels like libfprint or the application + * should know better if they want to retry, but they don't. Unless we've + * been asked to deactivate, try to re-enter the capture loop. Since state + * change is async, there's still a chance to be deactivated by another + * pending event. */ + if (elandev->dev_state_next != IMGDEV_STATE_INACTIVE) + dev_change_state(dev, IMGDEV_STATE_AWAIT_FINGER_ON); fpi_ssm_free(ssm); } @@ -421,55 +574,126 @@ static void elan_capture(struct fp_img_dev *dev) elan_dev_reset(elandev); struct fpi_ssm *ssm = - fpi_ssm_new(fpi_imgdev_get_dev(dev), elan_capture_run_state, CAPTURE_NUM_STATES); + fpi_ssm_new(fpi_imgdev_get_dev(dev), capture_run_state, CAPTURE_NUM_STATES); fpi_ssm_set_user_data(ssm, dev); fpi_ssm_start(ssm, capture_complete); } +static void fpi_ssm_next_state_async(void *data) +{ + fpi_ssm_next_state((struct fpi_ssm *)data); +} + +/* this function needs to have elandev->background and elandev->last_read to be + * the calibration mean */ +static int elan_need_calibration(struct elan_dev *elandev) +{ + G_DEBUG_HERE(); + + unsigned short calib_mean = + elandev->last_read[0] * 0xff + elandev->last_read[1]; + unsigned int bg_mean = 0, delta; + unsigned int frame_size = elandev->frame_width * elandev->frame_height; + + for (int i = 0; i < frame_size; i++) + bg_mean += elandev->background[i]; + bg_mean /= frame_size; + + delta = + bg_mean > calib_mean ? bg_mean - calib_mean : calib_mean - bg_mean; + + fp_dbg("calibration mean: %d, bg mean: %d, delta: %d", calib_mean, + bg_mean, delta); + + return delta > ELAN_CALIBRATION_MAX_DELTA ? 1 : 0; +} + enum calibrate_states { - CALIBRATE_START_1, - CALIBRATE_READ_DATA_1, - CALIBRATE_END_1, - CALIBRATE_START_2, - CALIBRATE_READ_DATA_2, - CALIBRATE_END_2, + CALIBRATE_GET_BACKGROUND, + CALIBRATE_SAVE_BACKGROUND, + CALIBRATE_GET_MEAN, + CALIBRATE_CHECK_NEEDED, + CALIBRATE_GET_STATUS, + CALIBRATE_CHECK_STATUS, + CALIBRATE_REPEAT_STATUS, CALIBRATE_NUM_STATES, }; -static void elan_calibrate_run_state(struct fpi_ssm *ssm) -{ - switch (fpi_ssm_get_cur_state(ssm)) { - case CALIBRATE_START_1: - case CALIBRATE_START_2: - elan_run_cmds(ssm, calibrate_start_cmds, - calibrate_start_cmds_len, ELAN_CMD_TIMEOUT); - break; - case CALIBRATE_READ_DATA_1: - case CALIBRATE_READ_DATA_2: - elan_run_cmds(ssm, read_cmds, read_cmds_len, ELAN_CMD_TIMEOUT); - break; - case CALIBRATE_END_1: - case CALIBRATE_END_2: - elan_run_cmds(ssm, calibrate_end_cmds, calibrate_end_cmds_len, - ELAN_CMD_TIMEOUT); - } -} - -static void calibrate_complete(struct fpi_ssm *ssm) +static void calibrate_run_state(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); - if (elandev->deactivating) - elan_deactivate(dev); - else if (fpi_ssm_get_error(ssm)) - fpi_imgdev_session_error(dev, fpi_ssm_get_error(ssm)); - else { - fpi_imgdev_activate_complete(dev, fpi_ssm_get_error(ssm)); - elan_capture(dev); + switch (fpi_ssm_get_cur_state(ssm)) { + case CALIBRATE_GET_BACKGROUND: + elan_run_cmd(ssm, &get_image_cmd, ELAN_CMD_TIMEOUT); + break; + case CALIBRATE_SAVE_BACKGROUND: + elan_save_background(elandev); + if (elandev->fw_ver < ELAN_MIN_CALIBRATION_FW) { + fp_dbg("FW does not support calibration"); + fpi_ssm_mark_completed(ssm); + } else + fpi_ssm_next_state(ssm); + break; + case CALIBRATE_GET_MEAN: + elan_run_cmd(ssm, &get_calib_mean_cmd, ELAN_CMD_TIMEOUT); + break; + case CALIBRATE_CHECK_NEEDED: + if (elan_need_calibration(elandev)) { + elandev->calib_status = 0; + fpi_ssm_next_state(ssm); + } else + fpi_ssm_mark_completed(ssm); + break; + case CALIBRATE_GET_STATUS: + elandev->calib_atts_left -= 1; + if (elandev->calib_atts_left) + elan_run_cmd(ssm, &get_calib_status_cmd, + ELAN_CMD_TIMEOUT); + else { + fp_dbg("calibration failed"); + fpi_ssm_mark_aborted(ssm, -1); + } + break; + case CALIBRATE_CHECK_STATUS: + /* 0x01 - retry, 0x03 - ok + * It appears that when reading the response soon after 0x4023 the device + * can return 0x03, and only after some time (up to 100 ms) the response + * changes to 0x01. It stays that way for some time and then changes back + * to 0x03. Because of this we don't just expect 0x03, we want to see 0x01 + * first. This is to make sure that a full calibration loop has completed */ + fp_dbg("calibration status: 0x%02x", elandev->last_read[0]); + if (elandev->calib_status == 0x01 + && elandev->last_read[0] == 0x03) { + elandev->calib_status = 0x03; + fpi_ssm_jump_to_state(ssm, CALIBRATE_GET_BACKGROUND); + } else { + if (elandev->calib_status == 0x00 + && elandev->last_read[0] == 0x01) + elandev->calib_status = 0x01; + if (!fpi_timeout_add(50, fpi_ssm_next_state_async, ssm)) + fpi_ssm_mark_aborted(ssm, -ETIME); + } + break; + case CALIBRATE_REPEAT_STATUS: + fpi_ssm_jump_to_state(ssm, CALIBRATE_GET_STATUS); + break; } +} + +static void calibrate_complete(struct fpi_ssm *ssm) +{ + struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); + + G_DEBUG_HERE(); + + + if (fpi_ssm_get_error(ssm) != -ECANCELED) + fpi_imgdev_activate_complete(dev, fpi_ssm_get_error(ssm)); + fpi_ssm_free(ssm); } @@ -480,80 +704,94 @@ static void elan_calibrate(struct fp_img_dev *dev) G_DEBUG_HERE(); elan_dev_reset(elandev); - struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), elan_calibrate_run_state, + elandev->calib_atts_left = ELAN_CALIBRATION_ATTEMPTS; + + struct fpi_ssm *ssm = fpi_ssm_new(fpi_imgdev_get_dev(dev), calibrate_run_state, CALIBRATE_NUM_STATES); fpi_ssm_set_user_data(ssm, dev); fpi_ssm_start(ssm, calibrate_complete); } enum activate_states { + ACTIVATE_GET_FW_VER, + ACTIVATE_SET_FW_VER, ACTIVATE_GET_SENSOR_DIM, ACTIVATE_SET_SENSOR_DIM, - ACTIVATE_START, - ACTIVATE_READ_DATA, - ACTIVATE_END, + ACTIVATE_CMD_1, ACTIVATE_NUM_STATES, }; -static void elan_activate_run_state(struct fpi_ssm *ssm) +static void activate_run_state(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); + G_DEBUG_HERE(); + switch (fpi_ssm_get_cur_state(ssm)) { - case ACTIVATE_GET_SENSOR_DIM: - elan_run_cmds(ssm, get_sensor_dim_cmds, get_sensor_dim_cmds_len, - ELAN_CMD_TIMEOUT); + case ACTIVATE_GET_FW_VER: + elan_run_cmd(ssm, &get_fw_ver_cmd, ELAN_CMD_TIMEOUT); break; - case ACTIVATE_SET_SENSOR_DIM: - elandev->frame_width = elandev->last_read[2]; - elandev->raw_frame_width = elandev->last_read[0]; - elandev->frame_height = - elandev->raw_frame_width - 2 * ELAN_FRAME_MARGIN; + case ACTIVATE_SET_FW_VER: + elandev->fw_ver = + (elandev->last_read[0] << 8 | elandev->last_read[1]); + fp_dbg("FW ver 0x%04hx", elandev->fw_ver); fpi_ssm_next_state(ssm); break; - case ACTIVATE_START: - elan_run_cmds(ssm, init_start_cmds, init_start_cmds_len, - ELAN_CMD_TIMEOUT); + case ACTIVATE_GET_SENSOR_DIM: + elan_run_cmd(ssm, &get_sensor_dim_cmd, ELAN_CMD_TIMEOUT); break; - case ACTIVATE_READ_DATA: - elan_run_cmds(ssm, read_cmds, read_cmds_len, ELAN_CMD_TIMEOUT); + case ACTIVATE_SET_SENSOR_DIM: + /* see elan_save_frame for details */ + if (elandev->dev_type & ELAN_NOT_ROTATED) { + elandev->frame_width = elandev->last_read[0]; + elandev->frame_height = elandev->raw_frame_height = + elandev->last_read[2]; + } else { + elandev->frame_width = elandev->last_read[2]; + elandev->frame_height = elandev->raw_frame_height = + elandev->last_read[0]; + } + if (elandev->frame_height > ELAN_MAX_FRAME_HEIGHT) + elandev->frame_height = ELAN_MAX_FRAME_HEIGHT; + fp_dbg("sensor dimensions, WxH: %dx%d", elandev->frame_width, + elandev->raw_frame_height); + fpi_ssm_next_state(ssm); + break; + case ACTIVATE_CMD_1: + /* TODO: find out what this does, if we need it */ + elan_run_cmd(ssm, &activate_cmd_1, ELAN_CMD_TIMEOUT); break; - case ACTIVATE_END: - elan_run_cmds(ssm, init_end_cmds, init_end_cmds_len, - ELAN_CMD_TIMEOUT); } } static void activate_complete(struct fpi_ssm *ssm) { struct fp_img_dev *dev = fpi_ssm_get_user_data(ssm); - struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); - if (elandev->deactivating) - elan_deactivate(dev); - else if (fpi_ssm_get_error(ssm)) - fpi_imgdev_session_error(dev, fpi_ssm_get_error(ssm)); - else - elan_calibrate(dev); + if (fpi_ssm_get_error(ssm) != -ECANCELED) { + if (fpi_ssm_get_error(ssm)) + fpi_imgdev_activate_complete(dev, fpi_ssm_get_error(ssm)); + else + elan_calibrate(dev); + } + fpi_ssm_free(ssm); } -static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state) +static void elan_activate(struct fp_img_dev *dev) { struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); - elan_dev_reset(elandev); + struct fpi_ssm *ssm = - fpi_ssm_new(fpi_imgdev_get_dev(dev), elan_activate_run_state, ACTIVATE_NUM_STATES); + fpi_ssm_new(fpi_imgdev_get_dev(dev), activate_run_state, ACTIVATE_NUM_STATES); fpi_ssm_set_user_data(ssm, dev); fpi_ssm_start(ssm, activate_complete); - - return 0; } static int dev_init(struct fp_img_dev *dev, unsigned long driver_data) @@ -571,6 +809,18 @@ static int dev_init(struct fp_img_dev *dev, unsigned long driver_data) elandev = g_malloc0(sizeof(struct elan_dev)); fpi_imgdev_set_user_data(dev, elandev); + + /* common params */ + elandev->dev_type = driver_data; + elandev->background = NULL; + elandev->process_frame = elan_process_frame_thirds; + + switch (driver_data) { + case ELAN_0907: + elandev->process_frame = elan_process_frame_linear; + break; + } + fpi_imgdev_open_complete(dev, 0); return 0; } @@ -582,45 +832,110 @@ static void dev_deinit(struct fp_img_dev *dev) G_DEBUG_HERE(); elan_dev_reset(elandev); + g_free(elandev->background); g_free(elandev); libusb_release_interface(fpi_imgdev_get_usb_dev(dev), 0); fpi_imgdev_close_complete(dev); } -static void dev_deactivate(struct fp_img_dev *dev) +static int dev_activate(struct fp_img_dev *dev, enum fp_imgdev_state state) +{ + G_DEBUG_HERE(); + elan_activate(dev); + return 0; +} + +static void elan_change_state(struct fp_img_dev *dev) +{ + struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); + enum fp_imgdev_state next_state = elandev->dev_state_next; + + if (elandev->dev_state == next_state) { + fp_dbg("already in %d", next_state); + return; + } else + fp_dbg("changing to %d", next_state); + + switch (next_state) { + case IMGDEV_STATE_INACTIVE: + if (elandev->cur_transfer) + /* deactivation will complete in transfer callback */ + libusb_cancel_transfer(elandev->cur_transfer); + else + elan_deactivate(dev); + break; + case IMGDEV_STATE_AWAIT_FINGER_ON: + /* activation completed or another enroll stage started */ + elan_capture(dev); + break; + case IMGDEV_STATE_CAPTURE: + case IMGDEV_STATE_AWAIT_FINGER_OFF: + break; + } + + elandev->dev_state = next_state; +} + +static void elan_change_state_async(void *data) +{ + elan_change_state((struct fp_img_dev *)data); +} + +static int dev_change_state(struct fp_img_dev *dev, enum fp_imgdev_state state) { struct elan_dev *elandev = fpi_imgdev_get_user_data(dev); G_DEBUG_HERE(); - elandev->deactivating = TRUE; + switch (state) { + case IMGDEV_STATE_INACTIVE: + case IMGDEV_STATE_AWAIT_FINGER_ON: + /* schedule state change instead of calling it directly to allow all actions + * related to the previous state to complete */ + elandev->dev_state_next = state; + if (!fpi_timeout_add(10, elan_change_state_async, dev)) { + fpi_imgdev_session_error(dev, -ETIME); + return -ETIME; + } + break; + case IMGDEV_STATE_CAPTURE: + case IMGDEV_STATE_AWAIT_FINGER_OFF: + /* TODO MAYBE: split capture ssm into smaller ssms and use these states */ + elandev->dev_state = state; + elandev->dev_state_next = state; + break; + default: + fp_err("unrecognized state %d", state); + fpi_imgdev_session_error(dev, -EINVAL); + return -EINVAL; + } - if (elandev->cur_transfer) - libusb_cancel_transfer(elandev->cur_transfer); - else - elan_deactivate(dev); + /* as of time of writing libfprint never checks the return value */ + return 0; } -static const struct usb_id id_table[] = { - {.vendor = 0x04f3,.product = 0x0907}, - {.vendor = 0x04f3,.product = 0x0c26}, - {0, 0, 0,}, -}; +static void dev_deactivate(struct fp_img_dev *dev) +{ + G_DEBUG_HERE(); + + dev_change_state(dev, IMGDEV_STATE_INACTIVE); +} struct fp_img_driver elan_driver = { .driver = { .id = ELAN_ID, .name = FP_COMPONENT, .full_name = "ElanTech Fingerprint Sensor", - .id_table = id_table, + .id_table = elan_id_table, .scan_type = FP_SCAN_TYPE_SWIPE, }, .flags = 0, - .bz3_threshold = 22, + .bz3_threshold = 24, .open = dev_init, .close = dev_deinit, .activate = dev_activate, .deactivate = dev_deactivate, + .change_state = dev_change_state, }; diff --git a/libfprint/drivers/elan.h b/libfprint/drivers/elan.h index c929287..8a321b4 100644 --- a/libfprint/drivers/elan.h +++ b/libfprint/drivers/elan.h @@ -21,17 +21,38 @@ #ifndef __ELAN_H #define __ELAN_H -#include #include -/* number of pixels to discard on left and right (along raw image height) - * because they have different intensity from the rest of the frame */ -#define ELAN_FRAME_MARGIN 12 +#define ELAN_VEND_ID 0x04f3 + +/* a default device type */ +#define ELAN_ALL_DEV 0 + +/* devices with quirks */ +#define ELAN_0907 (1 << 0) +#define ELAN_0C03 (1 << 1) + +/* devices which don't require frame rotation before assembling */ +#define ELAN_NOT_ROTATED ELAN_0C03 + +/* min FW version that supports calibration */ +#define ELAN_MIN_CALIBRATION_FW 0x0138 + +/* max difference between background image mean and calibration mean + * (the response value of get_calib_mean_cmd)*/ +#define ELAN_CALIBRATION_MAX_DELTA 500 + +/* times to retry reading calibration status during one session + * generally prevents calibration from looping indefinitely */ +#define ELAN_CALIBRATION_ATTEMPTS 10 /* min and max frames in a capture */ #define ELAN_MIN_FRAMES 7 #define ELAN_MAX_FRAMES 30 +/* crop frames to this height to improve stitching */ +#define ELAN_MAX_FRAME_HEIGHT 50 + /* number of frames to drop at the end of capture because frames captured * while the finger is being lifted can be bad */ #define ELAN_SKIP_LAST_FRAMES 1 @@ -41,6 +62,9 @@ #define ELAN_EP_CMD_IN (0x3 | LIBUSB_ENDPOINT_IN) #define ELAN_EP_IMG_IN (0x2 | LIBUSB_ENDPOINT_IN) +/* used as response length to tell the driver to skip reading response */ +#define ELAN_CMD_SKIP_READ 0 + /* usual command timeout and timeout for when we need to check if the finger is * still on the device */ #define ELAN_CMD_TIMEOUT 10000 @@ -50,127 +74,149 @@ struct elan_cmd { unsigned char cmd[ELAN_CMD_LEN]; int response_len; int response_in; + unsigned short devices; }; -static const struct elan_cmd get_sensor_dim_cmds[] = { - { - .cmd = {0x00, 0x0c}, - .response_len = 0x4, - .response_in = ELAN_EP_CMD_IN, - }, +static const struct elan_cmd get_sensor_dim_cmd = { + .cmd = {0x00, 0x0c}, + .response_len = 0x4, + .response_in = ELAN_EP_CMD_IN, + .devices = ELAN_ALL_DEV, }; -static const size_t get_sensor_dim_cmds_len = -G_N_ELEMENTS(get_sensor_dim_cmds); - -static const struct elan_cmd init_start_cmds[] = { - { - .cmd = {0x40, 0x19}, - .response_len = 0x2, - .response_in = ELAN_EP_CMD_IN, - }, - { - .cmd = {0x40, 0x2a}, - .response_len = 0x2, - .response_in = ELAN_EP_CMD_IN, - }, +static const struct elan_cmd get_fw_ver_cmd = { + .cmd = {0x40, 0x19}, + .response_len = 0x2, + .response_in = ELAN_EP_CMD_IN, + .devices = ELAN_ALL_DEV, }; -static const size_t init_start_cmds_len = G_N_ELEMENTS(init_start_cmds); +/* unknown, returns 0x0 0x1 on 0907 */ +static const struct elan_cmd activate_cmd_1 = { + .cmd = {0x40, 0x2a}, + .response_len = 0x2, + .response_in = ELAN_EP_CMD_IN, + .devices = ELAN_0907, +}; -static const struct elan_cmd read_cmds[] = { - /* raw frame sizes are calculated from image dimesions reported by the +static const struct elan_cmd get_image_cmd = { + .cmd = {0x00, 0x09}, + /* raw frame sizes are calculated from image dimensions reported by the * device */ - { - .cmd = {0x00, 0x09}, - .response_len = -1, - .response_in = ELAN_EP_IMG_IN, - }, + .response_len = -1, + .response_in = ELAN_EP_IMG_IN, + .devices = ELAN_ALL_DEV, }; -const size_t read_cmds_len = G_N_ELEMENTS(read_cmds); - -/* issued after data reads during init and calibration */ -static const struct elan_cmd init_end_cmds[] = { - { - .cmd = {0x40, 0x24}, - .response_len = 0x2, - .response_in = ELAN_EP_CMD_IN, - }, +static const struct elan_cmd read_sensor_status_cmd = { + .cmd = {0x40, 0x13}, + .response_len = 0x1, + .response_in = ELAN_EP_CMD_IN, + .devices = ELAN_ALL_DEV, }; -static const size_t init_end_cmds_len = G_N_ELEMENTS(init_end_cmds); - -/* same command 2 times - * original driver may observe return value to determine how many times it - * should be repeated */ -static const struct elan_cmd calibrate_start_cmds[] = { - { - .cmd = {0x40, 0x23}, - .response_len = 0x1, - .response_in = ELAN_EP_CMD_IN, - }, - { - .cmd = {0x40, 0x23}, - .response_len = 0x1, - .response_in = ELAN_EP_CMD_IN, - }, +static const struct elan_cmd get_calib_status_cmd = { + .cmd = {0x40, 0x23}, + .response_len = 0x1, + .response_in = ELAN_EP_CMD_IN, + .devices = ELAN_ALL_DEV, }; -static const size_t calibrate_start_cmds_len = -G_N_ELEMENTS(calibrate_start_cmds); - -/* issued after data reads during init and calibration */ -static const struct elan_cmd calibrate_end_cmds[] = { - { - .cmd = {0x40, 0x24}, - .response_len = 0x2, - .response_in = ELAN_EP_CMD_IN, - }, +static const struct elan_cmd get_calib_mean_cmd = { + .cmd = {0x40, 0x24}, + .response_len = 0x2, + .response_in = ELAN_EP_CMD_IN, + .devices = ELAN_ALL_DEV, }; -static const size_t calibrate_end_cmds_len = -G_N_ELEMENTS(calibrate_end_cmds); - -static const struct elan_cmd capture_start_cmds[] = { - /* led on */ - { - .cmd = {0x40, 0x31}, - .response_len = 0x0, - .response_in = ELAN_EP_CMD_IN, - }, +static const struct elan_cmd led_on_cmd = { + .cmd = {0x40, 0x31}, + .response_len = ELAN_CMD_SKIP_READ, + .response_in = ELAN_EP_CMD_IN, + .devices = ELAN_ALL_DEV, }; -static size_t capture_start_cmds_len = G_N_ELEMENTS(capture_start_cmds); - -static const struct elan_cmd capture_wait_finger_cmds[] = { - /* wait for finger - * subsequent read will not complete until finger is placed on the reader */ - { - .cmd = {0x40, 0x3f}, - .response_len = 0x1, - .response_in = ELAN_EP_CMD_IN, - }, +/* wait for finger + * subsequent read will not complete until finger is placed on the reader */ +static const struct elan_cmd pre_scan_cmd = { + .cmd = {0x40, 0x3f}, + .response_len = 0x1, + .response_in = ELAN_EP_CMD_IN, + .devices = ELAN_ALL_DEV, }; -static size_t capture_wait_finger_cmds_len = -G_N_ELEMENTS(capture_wait_finger_cmds); - -static const struct elan_cmd deactivate_cmds[] = { - /* led off */ - { - .cmd = {0x00, 0x0b}, - .response_len = 0x0, - .response_in = ELAN_EP_CMD_IN, - }, +/* led off, stop waiting for finger */ +static const struct elan_cmd stop_cmd = { + .cmd = {0x00, 0x0b}, + .response_len = ELAN_CMD_SKIP_READ, + .response_in = ELAN_EP_CMD_IN, + .devices = ELAN_ALL_DEV, }; -static const size_t deactivate_cmds_len = G_N_ELEMENTS(deactivate_cmds); +static const struct usb_id elan_id_table[] = { + {.vendor = ELAN_VEND_ID,.product = 0x0903,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0907,.driver_data = ELAN_0907}, + {.vendor = ELAN_VEND_ID,.product = 0x0c01,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c02,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c03,.driver_data = ELAN_0C03}, + {.vendor = ELAN_VEND_ID,.product = 0x0c04,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c05,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c06,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c07,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c08,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c09,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c0a,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c0b,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c0c,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c0d,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c0e,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c0f,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c10,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c11,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c12,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c13,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c14,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c15,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c16,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c17,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c18,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c19,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c1a,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c1b,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c1c,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c1d,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c1e,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c1f,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c20,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c21,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c22,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c23,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c24,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c25,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c26,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c27,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c28,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c29,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c2a,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c2b,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c2c,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c2d,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c2e,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c2f,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c30,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c31,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c32,.driver_data = ELAN_ALL_DEV}, + {.vendor = ELAN_VEND_ID,.product = 0x0c33,.driver_data = ELAN_ALL_DEV}, + {0, 0, 0,}, +}; -static void elan_cmd_cb(struct libusb_transfer *transfer); +static void elan_cmd_done(struct fpi_ssm *ssm); static void elan_cmd_read(struct fpi_ssm *ssm); -static void elan_run_next_cmd(struct fpi_ssm *ssm); +static void elan_calibrate(struct fp_img_dev *dev); static void elan_capture(struct fp_img_dev *dev); +static void elan_deactivate(struct fp_img_dev *dev); + +static int dev_change_state(struct fp_img_dev *dev, enum fp_imgdev_state state); #endif diff --git a/libfprint/fprint-list-udev-rules.c b/libfprint/fprint-list-udev-rules.c index f78ede1..de291f1 100644 --- a/libfprint/fprint-list-udev-rules.c +++ b/libfprint/fprint-list-udev-rules.c @@ -24,10 +24,6 @@ #include "fp_internal.h" static const struct usb_id whitelist_id_table[] = { - /* Unsupported (for now) Elantech finger print readers */ - { .vendor = 0x04f3, .product = 0x0c03 }, - { .vendor = 0x04f3, .product = 0x0c16 }, - { .vendor = 0x04f3, .product = 0x0c26 }, /* Unsupported (for now) Validity Sensors finger print readers */ { .vendor = 0x138a, .product = 0x0090 }, /* Found on e.g. Lenovo T460s */ { .vendor = 0x138a, .product = 0x0091 },