libfprint/libfprint/drivers/elan.c
Benjamin Berg d683b271d4 ssm: Remove delayed action GCancellable integration
Unfortunately, the implementation was not thread safe and was not
sticking to the thread local main context.

In addition to this, it is not entirely clear to me how this API should
behave. The current approach is to simply cancel the transition with the
state machine halting in its current state. Instead, it could also make
sense for cancellation to cause the state machine to return a
G_IO_ERROR_CANCELLED.

As such, simply remove the feature for now. If anyone actually has a
good use-case then we can add it again.
2021-04-28 22:16:37 +02:00

1011 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"
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 active;
gboolean deactivating;
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_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);
img->flags |= FPI_IMAGE_PARTIAL;
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->active = FALSE;
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 (FpiDeviceElan *self)
{
G_DEBUG_HERE ();
elan_dev_reset_state (self);
FpiSsm *ssm =
fpi_ssm_new (FP_DEVICE (self), 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:
/* 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
{
/* XXX: The timeout is emulated incorrectly, resulting in a zero byte read. */
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
fpi_ssm_mark_completed (ssm);
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 ();
/* 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);
}
/* Note: We always stop capturing even if that may not be needed always.
* Doing this between captures appears to make it at least less likely for
* devices to end up in a bad state.
*/
elan_stop_capture (self);
}
static void
elan_capture (FpiDeviceElan *self)
{
G_DEBUG_HERE ();
elan_dev_reset_state (self);
FpiSsm *ssm =
fpi_ssm_new (FP_DEVICE (self), 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,
"Calibration 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);
}
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)
{
G_DEBUG_HERE ();
if (error)
{
fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), error);
elan_stop_capture (FPI_DEVICE_ELAN (dev));
}
else
{
elan_capture (FPI_DEVICE_ELAN (dev));
}
}
static void
elan_calibrate (FpiDeviceElan *self)
{
G_DEBUG_HERE ();
elan_dev_reset_state (self);
g_return_if_fail (!self->active);
self->active = TRUE;
self->calib_atts_left = ELAN_CALIBRATION_ATTEMPTS;
FpiSsm *ssm = fpi_ssm_new (FP_DEVICE (self), 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
dev_change_state (FpImageDevice *dev, FpiImageDeviceState state)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
/* Note: We always calibrate even if that may not be needed always.
* Doing this for each capture appears to make it at least less likely for
* devices to end up in a bad state.
*/
if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON)
elan_calibrate (self);
}
static void
dev_deactivate (FpImageDevice *dev)
{
FpiDeviceElan *self = FPI_DEVICE_ELAN (dev);
G_DEBUG_HERE ();
if (!self->active)
/* 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).
* Note that any running capture will be cancelled already if needed. */
self->deactivating = TRUE;
}
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;
}