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

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

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

988 lines
27 KiB
C

/*
* Elan driver for libfprint
*
* Copyright (C) 2017 Igor Filatov <ia.filatov@gmail.com>
* Copyright (C) 2018 Sébastien Béchet <sebastien.bechet@osinix.com >
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* 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("%02x", buf[0]); \
else if (len == 2) \
fp_dbg("%04x", buf[0] << 8 | buf[1]); \
else if (len > 2) \
fp_dbg("%04x... (%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)
{
return frame->data[x + y * ctx->frame_width];
}
static struct fpi_frame_asmbl_ctx assembling_ctx = {
.frame_width = 0,
.frame_height = 0,
.image_width = 0,
.get_pixel = elan_get_pixel,
};
struct elan_dev {
/* device config */
unsigned short dev_type;
unsigned short fw_ver;
void (*process_frame) (unsigned short *raw_frame, GSList ** frames);
/* end device config */
/* commands */
const struct elan_cmd *cmd;
int cmd_timeout;
fpi_usb_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_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->cmd = NULL;
elandev->cmd_timeout = ELAN_CMD_TIMEOUT;
elandev->calib_status = 0;
g_free(elandev->last_read);
elandev->last_read = NULL;
g_slist_free_full(elandev->frames, g_free);
elandev->frames = NULL;
elandev->num_frames = 0;
}
static void elan_save_frame(struct elan_dev *elandev, unsigned short *frame)
{
G_DEBUG_HERE();
/* 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;
}
static void elan_process_frame_linear(unsigned short *raw_frame,
GSList ** frames)
{
unsigned int frame_size =
assembling_ctx.frame_width * assembling_ctx.frame_height;
struct fpi_frame *frame =
g_malloc(frame_size + sizeof(struct fpi_frame));
G_DEBUG_HERE();
unsigned short min = 0xffff, max = 0;
for (int i = 0; i < frame_size; i++) {
if (raw_frame[i] < min)
min = raw_frame[i];
if (raw_frame[i] > max)
max = raw_frame[i];
}
g_assert(max != min);
unsigned short px;
for (int i = 0; i < frame_size; i++) {
px = raw_frame[i];
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;
}
*frames = g_slist_prepend(*frames, frame);
}
static void elan_submit_image(struct fp_img_dev *dev)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
GSList *frames = NULL;
struct fp_img *img;
G_DEBUG_HERE();
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) elandev->process_frame,
&frames);
fpi_do_movement_estimation(&assembling_ctx, 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);
}
static void elan_cmd_done(fpi_ssm *ssm)
{
G_DEBUG_HERE();
fpi_ssm_next_state(ssm);
}
static void elan_cmd_cb(struct libusb_transfer *transfer,
struct fp_dev *_dev,
fpi_ssm *ssm,
void *user_data)
{
struct fp_img_dev *dev;
struct elan_dev *elandev;
G_DEBUG_HERE();
if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
fp_dbg("transfer cancelled");
return;
}
dev = FP_IMG_DEV(_dev);
elandev = FP_INSTANCE_DATA(_dev);
elandev->cur_transfer = NULL;
switch (transfer->status) {
case LIBUSB_TRANSFER_COMPLETED:
if (transfer->length != transfer->actual_length) {
fp_dbg("transfer length error: expected %d, got %d",
transfer->length, transfer->actual_length);
elan_dev_reset(elandev);
fpi_ssm_mark_failed(ssm, -EPROTO);
} else if (transfer->endpoint & LIBUSB_ENDPOINT_IN) {
/* just finished receiving */
elandev->last_read = g_memdup(transfer->buffer, transfer->actual_length);
dbg_buf(transfer->buffer, transfer->actual_length);
elan_cmd_done(ssm);
} else {
/* just finished sending */
G_DEBUG_HERE();
elan_cmd_read(ssm, dev);
}
break;
case LIBUSB_TRANSFER_TIMED_OUT:
fp_dbg("transfer timed out");
fpi_ssm_mark_failed(ssm, -ETIMEDOUT);
break;
default:
fp_dbg("transfer failed: %d", transfer->status);
elan_dev_reset(elandev);
fpi_ssm_mark_failed(ssm, -EIO);
}
}
static void elan_cmd_read(fpi_ssm *ssm, struct fp_img_dev *dev)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
int response_len = elandev->cmd->response_len;
unsigned char *buffer;
G_DEBUG_HERE();
if (elandev->cmd->response_len == ELAN_CMD_SKIP_READ) {
fp_dbg("skipping read, not expecting anything");
elan_cmd_done(ssm);
return;
}
if (elandev->dev_type == ELAN_0C42) {
/* ELAN_0C42 sends an extra byte in one byte responses */
if (elandev->cmd->response_len == 1)
response_len = 2;
}
if (elandev->cmd->cmd == get_image_cmd.cmd)
/* raw data has 2-byte "pixels" and the frame is vertical */
response_len =
elandev->raw_frame_height * elandev->frame_width * 2;
g_clear_pointer(&elandev->last_read, g_free);
buffer = g_malloc(response_len);
elandev->cur_transfer = fpi_usb_fill_bulk_transfer(FP_DEV(dev),
ssm,
elandev->cmd->response_in,
buffer,
response_len,
elan_cmd_cb,
NULL,
elandev->cmd_timeout);
int r = fpi_usb_submit_transfer(elandev->cur_transfer);
if (r < 0)
fpi_ssm_mark_failed(ssm, r);
}
static void
elan_run_cmd(fpi_ssm *ssm,
struct fp_img_dev *dev,
const struct elan_cmd *cmd,
int cmd_timeout)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
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;
}
elandev->cur_transfer = fpi_usb_fill_bulk_transfer(FP_DEV(dev),
ssm,
ELAN_EP_CMD_OUT,
g_memdup((char *) cmd->cmd, ELAN_CMD_LEN),
ELAN_CMD_LEN,
elan_cmd_cb,
NULL,
elandev->cmd_timeout);
int r = fpi_usb_submit_transfer(elandev->cur_transfer);
if (r < 0)
fpi_ssm_mark_failed(ssm, r);
}
enum stop_capture_states {
STOP_CAPTURE,
STOP_CAPTURE_NUM_STATES,
};
static void stop_capture_run_state(fpi_ssm *ssm, struct fp_dev *dev, void *user_data)
{
G_DEBUG_HERE();
switch (fpi_ssm_get_cur_state(ssm)) {
case STOP_CAPTURE:
elan_run_cmd(ssm, FP_IMG_DEV(dev), &stop_cmd, ELAN_CMD_TIMEOUT);
break;
}
}
static void stop_capture_complete(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
struct elan_dev *elandev = FP_INSTANCE_DATA(_dev);
int error = fpi_ssm_get_error(ssm);
G_DEBUG_HERE();
fpi_ssm_free(ssm);
if (!error) {
fpi_imgdev_report_finger_status(dev, FALSE);
/* If verify or identify fails because of short swipe, we need to restart
* capture 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);
} else if (error != -ECANCELED)
fpi_imgdev_abort_scan(dev, error);
}
static void elan_stop_capture(struct fp_img_dev *dev)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
G_DEBUG_HERE();
elan_dev_reset(elandev);
fpi_ssm *ssm =
fpi_ssm_new(FP_DEV(dev), stop_capture_run_state,
STOP_CAPTURE_NUM_STATES, dev);
fpi_ssm_start(ssm, stop_capture_complete);
}
enum capture_states {
CAPTURE_LED_ON,
CAPTURE_WAIT_FINGER,
CAPTURE_READ_DATA,
CAPTURE_CHECK_ENOUGH_FRAMES,
CAPTURE_NUM_STATES,
};
static void capture_run_state(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
struct elan_dev *elandev = FP_INSTANCE_DATA(_dev);
int r;
switch (fpi_ssm_get_cur_state(ssm)) {
case CAPTURE_LED_ON:
elan_run_cmd(ssm, dev, &led_on_cmd, ELAN_CMD_TIMEOUT);
break;
case CAPTURE_WAIT_FINGER:
elan_run_cmd(ssm, dev, &pre_scan_cmd, -1);
break;
case CAPTURE_READ_DATA:
/* 0x55 - finger present
* 0xff - device not calibrated (probably) */
if (elandev->last_read && elandev->last_read[0] == 0x55) {
if (elandev->dev_state == IMGDEV_STATE_AWAIT_FINGER_ON)
fpi_imgdev_report_finger_status(dev, TRUE);
elan_run_cmd(ssm, dev, &get_image_cmd, ELAN_CMD_TIMEOUT);
} else
fpi_ssm_mark_failed(ssm, -EBADMSG);
break;
case CAPTURE_CHECK_ENOUGH_FRAMES:
r = elan_save_img_frame(elandev);
if (r < 0)
fpi_ssm_mark_failed(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 capture_complete(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
struct elan_dev *elandev = FP_INSTANCE_DATA(_dev);
G_DEBUG_HERE();
if (fpi_ssm_get_error(ssm) == -ECANCELED) {
fpi_ssm_free(ssm);
return;
}
/* either max frames captured or timed out waiting for the next frame */
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);
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 "...abort_scan" but reporting 1 during verification makes it
* successful! */
else
fpi_imgdev_abort_scan(dev, fpi_ssm_get_error(ssm));
fpi_ssm_free(ssm);
}
static void elan_capture(struct fp_img_dev *dev)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
G_DEBUG_HERE();
elan_dev_reset(elandev);
fpi_ssm *ssm =
fpi_ssm_new(FP_DEV(dev), capture_run_state, CAPTURE_NUM_STATES, dev);
fpi_ssm_start(ssm, capture_complete);
}
/* 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;
g_assert(frame_size != 0);
if (elandev->dev_type == ELAN_0C42) {
if (calib_mean > 5500 ||
calib_mean < 2500) {
fp_dbg("Forcing needed recalibration");
return 1;
}
}
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_GET_BACKGROUND,
CALIBRATE_SAVE_BACKGROUND,
CALIBRATE_GET_MEAN,
CALIBRATE_CHECK_NEEDED,
CALIBRATE_GET_STATUS,
CALIBRATE_CHECK_STATUS,
CALIBRATE_REPEAT_STATUS,
CALIBRATE_NUM_STATES,
};
static gboolean elan_supports_calibration(struct elan_dev *elandev)
{
if (elandev->dev_type == ELAN_0C42)
return TRUE;
return elandev->fw_ver >= ELAN_MIN_CALIBRATION_FW;
}
static void calibrate_run_state(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
struct elan_dev *elandev = FP_INSTANCE_DATA(_dev);
G_DEBUG_HERE();
switch (fpi_ssm_get_cur_state(ssm)) {
case CALIBRATE_GET_BACKGROUND:
elan_run_cmd(ssm, dev, &get_image_cmd, ELAN_CMD_TIMEOUT);
break;
case CALIBRATE_SAVE_BACKGROUND:
elan_save_background(elandev);
if (!elan_supports_calibration(elandev)) {
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, dev, &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, dev, &get_calib_status_cmd,
ELAN_CMD_TIMEOUT);
else {
fp_dbg("calibration failed");
fpi_ssm_mark_failed(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 {
fpi_timeout *timeout;
if (elandev->calib_status == 0x00
&& elandev->last_read[0] == 0x01)
elandev->calib_status = 0x01;
timeout = fpi_timeout_add(50, fpi_ssm_next_state_timeout_cb, _dev, ssm);
fpi_timeout_set_name(timeout, "calibrate_run_state");
}
break;
case CALIBRATE_REPEAT_STATUS:
fpi_ssm_jump_to_state(ssm, CALIBRATE_GET_STATUS);
break;
}
}
static void calibrate_complete(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
G_DEBUG_HERE();
if (fpi_ssm_get_error(ssm) != -ECANCELED)
elan_capture(dev);
fpi_ssm_free(ssm);
}
static void elan_calibrate(struct fp_img_dev *dev)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
G_DEBUG_HERE();
elan_dev_reset(elandev);
elandev->calib_atts_left = ELAN_CALIBRATION_ATTEMPTS;
fpi_ssm *ssm = fpi_ssm_new(FP_DEV(dev), calibrate_run_state,
CALIBRATE_NUM_STATES, 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_CMD_1,
ACTIVATE_NUM_STATES,
};
static void activate_run_state(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
struct elan_dev *elandev = FP_INSTANCE_DATA(_dev);
G_DEBUG_HERE();
switch (fpi_ssm_get_cur_state(ssm)) {
case ACTIVATE_GET_FW_VER:
elan_run_cmd(ssm, dev, &get_fw_ver_cmd, ELAN_CMD_TIMEOUT);
break;
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_GET_SENSOR_DIM:
elan_run_cmd(ssm, dev, &get_sensor_dim_cmd, ELAN_CMD_TIMEOUT);
break;
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];
}
/* Work-around sensors returning the sizes as zero-based index
* rather than the number of pixels. */
if ((elandev->frame_width % 2 == 1) &&
(elandev->frame_height % 2 == 1)) {
elandev->frame_width++;
elandev->frame_height++;
elandev->raw_frame_height = elandev->frame_height;
}
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, dev, &activate_cmd_1, ELAN_CMD_TIMEOUT);
break;
}
}
static void activate_complete(fpi_ssm *ssm, struct fp_dev *_dev, void *user_data)
{
struct fp_img_dev *dev = user_data;
G_DEBUG_HERE();
if (fpi_ssm_get_error(ssm) != -ECANCELED) {
fpi_imgdev_activate_complete(dev, fpi_ssm_get_error(ssm));
}
fpi_ssm_free(ssm);
}
static void elan_activate(struct fp_img_dev *dev)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
G_DEBUG_HERE();
elan_dev_reset(elandev);
fpi_ssm *ssm =
fpi_ssm_new(FP_DEV(dev), activate_run_state, ACTIVATE_NUM_STATES, dev);
fpi_ssm_start(ssm, activate_complete);
}
static int dev_init(struct fp_img_dev *dev, unsigned long driver_data)
{
struct elan_dev *elandev;
int r;
G_DEBUG_HERE();
r = libusb_claim_interface(fpi_dev_get_usb_dev(FP_DEV(dev)), 0);
if (r < 0) {
fp_err("could not claim interface 0: %s", libusb_error_name(r));
return r;
}
elandev = g_malloc0(sizeof(struct elan_dev));
fp_dev_set_instance_data(FP_DEV(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;
}
static void elan_deactivate(struct fp_img_dev *dev)
{
G_DEBUG_HERE();
fpi_imgdev_deactivate_complete(dev);
}
static void dev_deinit(struct fp_img_dev *dev)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
G_DEBUG_HERE();
elan_dev_reset(elandev);
g_free(elandev->background);
g_free(elandev);
libusb_release_interface(fpi_dev_get_usb_dev(FP_DEV(dev)), 0);
fpi_imgdev_close_complete(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 = FP_INSTANCE_DATA(FP_DEV(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 */
fpi_usb_cancel_transfer(elandev->cur_transfer);
else
elan_deactivate(dev);
break;
case IMGDEV_STATE_AWAIT_FINGER_ON:
/* activation completed or another enroll stage started */
elan_calibrate(dev);
break;
case IMGDEV_STATE_CAPTURE:
/* not used */
break;
case IMGDEV_STATE_AWAIT_FINGER_OFF:
elan_stop_capture(dev);
}
elandev->dev_state = next_state;
}
static void
elan_change_state_async(struct fp_dev *dev,
void *data)
{
g_message ("state change dev: %p", dev);
elan_change_state(FP_IMG_DEV (dev));
}
static int dev_change_state(struct fp_img_dev *dev, enum fp_imgdev_state state)
{
struct elan_dev *elandev = FP_INSTANCE_DATA(FP_DEV(dev));
fpi_timeout *timeout;
G_DEBUG_HERE();
switch (state) {
case IMGDEV_STATE_INACTIVE:
case IMGDEV_STATE_AWAIT_FINGER_ON:
case IMGDEV_STATE_AWAIT_FINGER_OFF: {
char *name;
/* schedule state change instead of calling it directly to allow all actions
* related to the previous state to complete */
elandev->dev_state_next = state;
timeout = fpi_timeout_add(10, elan_change_state_async, FP_DEV(dev), NULL);
name = g_strdup_printf ("dev_change_state to %d", state);
fpi_timeout_set_name(timeout, name);
g_free (name);
break;
}
case IMGDEV_STATE_CAPTURE:
/* TODO MAYBE: split capture ssm into smaller ssms and use this state */
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;
}
/* as of time of writing libfprint never checks the return value */
return 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 = {
.name = FP_COMPONENT,
.full_name = "ElanTech Fingerprint Sensor",
.id_table = elan_id_table,
.scan_type = FP_SCAN_TYPE_SWIPE,
},
.flags = 0,
.bz3_threshold = 24,
.open = dev_init,
.close = dev_deinit,
.activate = dev_activate,
.deactivate = dev_deactivate,
.change_state = dev_change_state,
};