libfprint/libfprint/drivers/elan.c
Benjamin Berg 788fd9ca7a elan: Do not leak converted frames
The elan driver converts frames into a different format. These frames
are only needed to assemable the image and should be free'ed afterwards.

Fixes: #213
2019-12-16 11:41:22 +01:00

1086 lines
29 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"
static 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 _FpiDeviceElan
{
FpImageDevice parent;
/* 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;
/* end commands */
/* state */
gboolean deactivating;
FpImageDeviceState dev_state;
FpImageDeviceState 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 */
};
G_DECLARE_FINAL_TYPE (FpiDeviceElan, fpi_device_elan, FPI, DEVICE_ELAN,
FpImageDevice);
G_DEFINE_TYPE (FpiDeviceElan, fpi_device_elan, FP_TYPE_IMAGE_DEVICE);
static int
cmp_short (const void *a, const void *b)
{
return (int) (*(short *) a - *(short *) b);
}
static void
elan_dev_reset_state (FpiDeviceElan *elandev)
{
G_DEBUG_HERE ();
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 (FpiDeviceElan *self, 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 = self->frame_width;
unsigned char frame_height = self->frame_height;
unsigned char raw_height = self->raw_frame_height;
unsigned char frame_margin = (raw_height - self->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 (self->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 *) self->last_read)[raw_idx];
}
}
static void
elan_save_background (FpiDeviceElan *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 (FpiDeviceElan *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?");
g_free (frame);
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 (FpImageDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
GSList *raw_frames;
GSList *frames = NULL;
FpImage *img;
G_DEBUG_HERE ();
raw_frames = g_slist_nth (self->frames, ELAN_SKIP_LAST_FRAMES);
assembling_ctx.frame_width = self->frame_width;
assembling_ctx.frame_height = self->frame_height;
assembling_ctx.image_width = self->frame_width * 3 / 2;
g_slist_foreach (raw_frames, (GFunc) self->process_frame, &frames);
fpi_do_movement_estimation (&assembling_ctx, frames);
img = fpi_assemble_frames (&assembling_ctx, frames);
g_slist_free_full (frames, g_free);
fpi_image_device_image_captured (dev, img);
}
static void
elan_cmd_done (FpiSsm *ssm)
{
G_DEBUG_HERE ();
fpi_ssm_next_state (ssm);
}
static void
elan_cmd_cb (FpiUsbTransfer *transfer, FpDevice *dev,
gpointer user_data, GError *error)
{
FpiSsm *ssm = transfer->ssm;
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
if (error)
{
/* XXX: In the cancellation case we used to not
* mark the SSM as failed?! */
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
/* XXX: We used to reset the device in error cases! */
if (transfer->endpoint & FPI_USB_ENDPOINT_IN)
{
/* just finished receiving */
self->last_read = g_memdup (transfer->buffer, transfer->actual_length);
elan_cmd_done (ssm);
}
else
{
/* just finished sending */
G_DEBUG_HERE ();
elan_cmd_read (ssm, dev);
}
}
static void
elan_cmd_read (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
FpiUsbTransfer *transfer;
GCancellable *cancellable = NULL;
int response_len = self->cmd->response_len;
G_DEBUG_HERE ();
if (self->cmd->response_len == ELAN_CMD_SKIP_READ)
{
fp_dbg ("skipping read, not expecting anything");
elan_cmd_done (ssm);
return;
}
if (self->dev_type == ELAN_0C42)
{
/* ELAN_0C42 sends an extra byte in one byte responses */
if (self->cmd->response_len == 1)
response_len = 2;
}
if (self->cmd->cmd == get_image_cmd.cmd)
/* raw data has 2-byte "pixels" and the frame is vertical */
response_len =
self->raw_frame_height * self->frame_width * 2;
g_clear_pointer (&self->last_read, g_free);
transfer = fpi_usb_transfer_new (dev);
transfer->ssm = ssm;
transfer->short_is_error = TRUE;
fpi_usb_transfer_fill_bulk (transfer,
self->cmd->response_in,
response_len);
if (!self->cmd->never_cancel)
cancellable = fpi_device_get_cancellable (dev);
fpi_usb_transfer_submit (transfer, self->cmd_timeout, cancellable, elan_cmd_cb, NULL);
}
static void
elan_run_cmd (FpiSsm *ssm,
FpDevice *dev,
const struct elan_cmd *cmd,
int cmd_timeout)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
FpiUsbTransfer *transfer;
GCancellable *cancellable = NULL;
self->cmd = cmd;
if (cmd_timeout != -1)
self->cmd_timeout = cmd_timeout;
if (cmd->devices != ELAN_ALL_DEV && !(cmd->devices & self->dev_type))
{
fp_dbg ("skipping command 0x%x 0x%x for this device (for devices 0x%x but device is 0x%x)",
cmd->cmd[0], cmd->cmd[1], cmd->devices, self->dev_type);
elan_cmd_done (ssm);
return;
}
transfer = fpi_usb_transfer_new (dev);
transfer->ssm = ssm;
transfer->short_is_error = TRUE;
fpi_usb_transfer_fill_bulk_full (transfer,
ELAN_EP_CMD_OUT,
(guint8 *) cmd->cmd,
ELAN_CMD_LEN,
NULL);
if (!self->cmd->never_cancel)
cancellable = fpi_device_get_cancellable (dev);
fpi_usb_transfer_submit (transfer,
self->cmd_timeout,
cancellable,
elan_cmd_cb,
NULL);
}
enum stop_capture_states {
STOP_CAPTURE,
STOP_CAPTURE_NUM_STATES,
};
static void
stop_capture_run_state (FpiSsm *ssm, FpDevice *dev)
{
G_DEBUG_HERE ();
switch (fpi_ssm_get_cur_state (ssm))
{
case STOP_CAPTURE:
elan_run_cmd (ssm, dev, &stop_cmd,
ELAN_CMD_TIMEOUT);
break;
}
}
static void
stop_capture_complete (FpiSsm *ssm, FpDevice *_dev, GError *error)
{
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
/* The device is inactive at this point. */
self->dev_state = FP_IMAGE_DEVICE_STATE_INACTIVE;
if (self->deactivating)
{
/* Simply complete the pending deactivation. */
self->deactivating = FALSE;
fpi_image_device_deactivate_complete (dev, error);
return;
}
if (!error)
fpi_image_device_report_finger_status (dev, FALSE);
else
/* NOTE: We cannot get a cancellation error here. */
fpi_image_device_session_error (dev, error);
}
static void
elan_stop_capture (FpDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
elan_dev_reset_state (self);
FpiSsm *ssm =
fpi_ssm_new (dev, stop_capture_run_state, STOP_CAPTURE_NUM_STATES);
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 (FpiSsm *ssm, FpDevice *dev)
{
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
FpiDeviceElan *self = FPI_DEVICE_ELAN (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:
self->dev_state = FP_IMAGE_DEVICE_STATE_CAPTURE;
/* 0x55 - finger present
* 0xff - device not calibrated (probably) */
if (self->last_read && self->last_read[0] == 0x55)
{
fpi_image_device_report_finger_status (idev, TRUE);
elan_run_cmd (ssm, dev, &get_image_cmd, ELAN_CMD_TIMEOUT);
}
else
{
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
}
break;
case CAPTURE_CHECK_ENOUGH_FRAMES:
r = elan_save_img_frame (self);
if (r < 0)
{
fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
}
else if (self->num_frames < ELAN_MAX_FRAMES)
{
/* quickly stop if finger is removed */
self->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 (FpiSsm *ssm, FpDevice *_dev, GError *error)
{
FpImageDevice *dev = FP_IMAGE_DEVICE (_dev);
FpiDeviceElan *self = FPI_DEVICE_ELAN (_dev);
G_DEBUG_HERE ();
/* XXX: cancellation was specially handled by doing nothing! */
/* either max frames captured or timed out waiting for the next frame */
if (!error ||
(g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT) &&
fpi_ssm_get_cur_state (ssm) == CAPTURE_WAIT_FINGER))
{
if (self->num_frames >= ELAN_MIN_FRAMES)
{
elan_submit_image (dev);
}
else
{
fp_dbg ("swipe too short: want >= %d frames, got %d",
ELAN_MIN_FRAMES, self->num_frames);
fpi_image_device_retry_scan (dev, FP_DEVICE_RETRY_TOO_SHORT);
}
g_clear_error (&error);
}
else
{
fpi_image_device_session_error (dev, error);
}
}
static void
elan_capture (FpDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
elan_dev_reset_state (self);
FpiSsm *ssm =
fpi_ssm_new (dev, capture_run_state, CAPTURE_NUM_STATES);
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 (FpiDeviceElan *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 (FpiDeviceElan *elandev)
{
if (elandev->dev_type == ELAN_0C42)
return TRUE;
return elandev->fw_ver >= ELAN_MIN_CALIBRATION_FW;
}
static void
calibrate_run_state (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (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 (self);
if (!elan_supports_calibration (self))
{
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 (self))
{
self->calib_status = 0;
fpi_ssm_next_state (ssm);
}
else
{
fpi_ssm_mark_completed (ssm);
}
break;
case CALIBRATE_GET_STATUS:
self->calib_atts_left -= 1;
if (self->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,
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Callibration failed!"));
}
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", self->last_read[0]);
if (self->calib_status == 0x01 &&
self->last_read[0] == 0x03)
{
self->calib_status = 0x03;
fpi_ssm_jump_to_state (ssm, CALIBRATE_GET_BACKGROUND);
}
else
{
if (self->calib_status == 0x00 &&
self->last_read[0] == 0x01)
self->calib_status = 0x01;
fpi_ssm_next_state_delayed (ssm, 50, NULL);
}
break;
case CALIBRATE_REPEAT_STATUS:
fpi_ssm_jump_to_state (ssm, CALIBRATE_GET_STATUS);
break;
}
}
static void
calibrate_complete (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
if (error)
{
self->dev_state = FP_IMAGE_DEVICE_STATE_INACTIVE;
fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);
}
else
{
elan_capture (dev);
}
}
static void
elan_calibrate (FpDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
elan_dev_reset_state (self);
self->calib_atts_left = ELAN_CALIBRATION_ATTEMPTS;
FpiSsm *ssm = fpi_ssm_new (FP_DEVICE (dev), calibrate_run_state,
CALIBRATE_NUM_STATES);
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 (FpiSsm *ssm, FpDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (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:
self->fw_ver =
(self->last_read[0] << 8 | self->last_read[1]);
fp_dbg ("FW ver 0x%04hx", self->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 (self->dev_type & ELAN_NOT_ROTATED)
{
self->frame_width = self->last_read[0];
self->frame_height = self->raw_frame_height =
self->last_read[2];
}
else
{
self->frame_width = self->last_read[2];
self->frame_height = self->raw_frame_height =
self->last_read[0];
}
/* Work-around sensors returning the sizes as zero-based index
* rather than the number of pixels. */
if ((self->frame_width % 2 == 1) &&
(self->frame_height % 2 == 1))
{
self->frame_width++;
self->frame_height++;
self->raw_frame_height = self->frame_height;
}
if (self->frame_height > ELAN_MAX_FRAME_HEIGHT)
self->frame_height = ELAN_MAX_FRAME_HEIGHT;
fp_dbg ("sensor dimensions, WxH: %dx%d", self->frame_width,
self->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 (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpImageDevice *idev = FP_IMAGE_DEVICE (dev);
G_DEBUG_HERE ();
fpi_image_device_activate_complete (idev, error);
}
static void
elan_activate (FpImageDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
elan_dev_reset_state (self);
FpiSsm *ssm =
fpi_ssm_new (FP_DEVICE (dev), activate_run_state,
ACTIVATE_NUM_STATES);
fpi_ssm_start (ssm, activate_complete);
}
static void
dev_init (FpImageDevice *dev)
{
GError *error = NULL;
FpiDeviceElan *self;
G_DEBUG_HERE ();
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (FP_DEVICE (dev)), 0, 0, &error))
{
fpi_image_device_open_complete (dev, error);
return;
}
self = FPI_DEVICE_ELAN (dev);
/* common params */
self->dev_type = fpi_device_get_driver_data (FP_DEVICE (dev));
self->background = NULL;
self->process_frame = elan_process_frame_thirds;
switch (self->dev_type)
{
case ELAN_0907:
self->process_frame = elan_process_frame_linear;
break;
}
fpi_image_device_open_complete (dev, NULL);
}
static void
dev_deinit (FpImageDevice *dev)
{
GError *error = NULL;
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
elan_dev_reset_state (self);
g_free (self->background);
g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (dev)),
0, 0, &error);
fpi_image_device_close_complete (dev, error);
}
static void
dev_activate (FpImageDevice *dev)
{
G_DEBUG_HERE ();
elan_activate (dev);
}
static void
elan_change_state (FpImageDevice *idev)
{
FpDevice *dev = FP_DEVICE (idev);
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
FpImageDeviceState next_state = self->dev_state_next;
if (self->dev_state == next_state)
{
fp_dbg ("already in %d", next_state);
return;
}
else
{
fp_dbg ("changing to %d", next_state);
}
switch (next_state)
{
case FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON:
/* activation completed or another enroll stage started */
self->dev_state = FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON;
elan_calibrate (dev);
break;
case FP_IMAGE_DEVICE_STATE_CAPTURE:
/* not used */
break;
case FP_IMAGE_DEVICE_STATE_INACTIVE:
case FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF:
elan_stop_capture (dev);
break;
}
}
static void
elan_change_state_async (FpDevice *dev,
void *data)
{
g_message ("state change dev: %p", dev);
elan_change_state (FP_IMAGE_DEVICE (dev));
}
static void
dev_change_state (FpImageDevice *dev, FpImageDeviceState state)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
GSource *timeout;
G_DEBUG_HERE ();
/* Inactive and await finger off are equivalent for the elan driver. */
if (state == FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF)
state = FP_IMAGE_DEVICE_STATE_INACTIVE;
if (self->dev_state_next == state)
fp_dbg ("change to state %d already queued", state);
switch (state)
{
case FP_IMAGE_DEVICE_STATE_INACTIVE:
case FP_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON:
case FP_IMAGE_DEVICE_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 */
self->dev_state_next = state;
timeout = fpi_device_add_timeout (FP_DEVICE (dev), 10,
elan_change_state_async,
NULL, NULL);
name = g_strdup_printf ("dev_change_state to %d", state);
g_source_set_name (timeout, name);
g_free (name);
break;
}
case FP_IMAGE_DEVICE_STATE_CAPTURE:
/* TODO MAYBE: split capture ssm into smaller ssms and use this state */
self->dev_state = state;
self->dev_state_next = state;
break;
default:
g_assert_not_reached ();
}
}
static void
dev_deactivate (FpImageDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
if (self->dev_state == FP_IMAGE_DEVICE_STATE_INACTIVE)
{
/* The device is inactive already, complete the operation immediately. */
fpi_image_device_deactivate_complete (dev, NULL);
}
else
{
/* The device is not yet inactive, flag that we are deactivating (and
* need to signal back deactivation) and then ensure we will change
* to the inactive state eventually. */
self->deactivating = TRUE;
dev_change_state (dev, FP_IMAGE_DEVICE_STATE_INACTIVE);
}
}
static void
fpi_device_elan_init (FpiDeviceElan *self)
{
}
static void
fpi_device_elan_class_init (FpiDeviceElanClass *klass)
{
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass);
dev_class->id = "elan";
dev_class->full_name = "ElanTech Fingerprint Sensor";
dev_class->type = FP_DEVICE_TYPE_USB;
dev_class->id_table = elan_id_table;
dev_class->scan_type = FP_SCAN_TYPE_SWIPE;
img_class->img_open = dev_init;
img_class->img_close = dev_deinit;
img_class->activate = dev_activate;
img_class->deactivate = dev_deactivate;
img_class->change_state = dev_change_state;
img_class->bz3_threshold = 24;
}