/* * Validity Sensors, Inc. VFS7552 Fingerprint Reader driver for libfprint * Copyright (C) 2013 Arseniy Lartsev * AceLan Kao * 2018 Mark Harfouche * 2020 Julius Piso * * 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 */ #define FP_COMPONENT "vfs7552" #include "drivers_api.h" #include "vfs7552_proto.h" #define VFS7552_CONTROL_PIXELS (8) #define VFS7552_LINE_SIZE (VFS7552_IMAGE_WIDTH + VFS7552_CONTROL_PIXELS) #define VFS7552_IMAGE_CHUNKS (3) #define CAPTURE_VARIANCE_THRESHOLD 1200 #define FINGER_OFF_VARIANCE_THRESHOLD 100 #define NOISE_VARIANCE_THRESHOLD 4000 /* =================== sync/async USB transfer sequence ==================== */ enum { ACTION_SEND, ACTION_RECEIVE, }; struct usb_action { int type; const char *name; int endpoint; int size; unsigned char *data; int correct_reply_size; }; #define SEND(ENDPOINT, COMMAND) \ { \ .type = ACTION_SEND, \ .endpoint = ENDPOINT, \ .name = #COMMAND, \ .size = sizeof (COMMAND), \ .data = COMMAND \ }, #define RECV(ENDPOINT, SIZE) \ { \ .type = ACTION_RECEIVE, \ .endpoint = ENDPOINT, \ .size = SIZE, \ .data = NULL \ }, #define RECV_CHECK(ENDPOINT, SIZE, EXPECTED) \ { \ .type = ACTION_RECEIVE, \ .endpoint = ENDPOINT, \ .size = SIZE, \ .data = EXPECTED, \ .correct_reply_size = sizeof (EXPECTED) \ }, #define RECV_CHECK_SIZE(ENDPOINT, SIZE, EXPECTED) \ { \ .type = ACTION_RECEIVE, \ .endpoint = ENDPOINT, \ .size = SIZE, \ .data = NULL, \ .correct_reply_size = sizeof (EXPECTED) \ }, struct usbexchange_data { int stepcount; struct usb_action *actions; FpiUsbTransfer *last_recv; int timeout; }; /* ================== Class Definition =================== */ struct _FpDeviceVfs7552 { FpImageDevice parent; FpiImageDeviceState dev_state; FpiImageDeviceState dev_state_next; gboolean background_captured; unsigned char background[VFS7552_IMAGE_SIZE]; unsigned char image[VFS7552_IMAGE_SIZE]; gint lines_captured; gboolean deactivating; gboolean loop_running; struct usbexchange_data init_sequence; FpiUsbTransfer *flying_transfer; }; G_DECLARE_FINAL_TYPE (FpDeviceVfs7552, fpi_device_vfs7552, FPI, DEVICE_VFS7552, FpImageDevice); G_DEFINE_TYPE (FpDeviceVfs7552, fpi_device_vfs7552, FP_TYPE_IMAGE_DEVICE); /* ======================= States ======================== */ enum open_states { DEV_OPEN_START, DEV_OPEN_NUM_STATES }; enum activate_states { ACTIVATE_INIT, ACTIVATE_INTERRUPT_QUERY, ACTIVATE_INTERRUPT_CHECK, ACTIVATE_FINALIZE, ACTIVATE_NUM_STATES }; enum capture_states { CAPTURE_QUERY_DATA_READY, CAPTURE_CHECK_DATA_READY, CAPTURE_REQUEST_CHUNK, CAPTURE_READ_CHUNK, CAPTURE_COMPLETE, CAPTURE_FINALIZE, CAPTURE_NUM_STATES }; enum deactivate_states { DEACTIVATE_ENTER, DEACTIVATE_DISABLE_SENSOR, DEACTIVATE_NUM_STATES }; /* ============== USB Sequence Definitions =============== */ struct usb_action vfs7552_initialization[] = { SEND (VFS7552_OUT_ENDPOINT, vfs7552_cmd_01) RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 64, vfs7552_cmd_01_recv) SEND (VFS7552_OUT_ENDPOINT, vfs7552_cmd_19) RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 128, vfs7552_cmd_19_recv) SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_00) RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY) SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_01) RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY) SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_02) RECV_CHECK (VFS7552_IN_ENDPOINT, 64, vfs7552_init_02_recv) SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_03) RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 64, vfs7552_init_03_recv) SEND (VFS7552_OUT_ENDPOINT, vfs7552_init_04) RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY) /* * Windows driver does this and it works * But in this driver this call never returns... * RECV(VFS7552_IN_ENDPOINT_CTRL2, 8) */ }; struct usb_action vfs7552_stop_capture[] = { SEND (VFS7552_OUT_ENDPOINT, vfs7552_cmd_04) RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY) SEND (VFS7552_OUT_ENDPOINT, vfs7552_cmd_52) RECV_CHECK (VFS7552_IN_ENDPOINT, 64, VFS7552_NORMAL_REPLY) }; struct usb_action vfs7552_initiate_capture[] = { SEND (VFS7552_OUT_ENDPOINT, vfs7552_image_start) RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 2048, vfs7552_image_start_resp) }; struct usb_action vfs7552_wait_finger_init[] = { RECV_CHECK_SIZE (VFS7552_INTERRUPT_ENDPOINT, 8, interrupt_ok) }; struct usb_action vfs7552_data_ready_query[] = { SEND (VFS7552_OUT_ENDPOINT, vfs7552_is_image_ready) RECV_CHECK_SIZE (VFS7552_IN_ENDPOINT, 64, vfs7552_is_image_ready_resp_ready) }; struct usb_action vfs7552_request_chunk[] = { SEND (VFS7552_OUT_ENDPOINT, vfs7552_read_image_chunk) }; /* ================== USB Communication ================== */ static void async_send_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { struct usbexchange_data *data = fpi_ssm_get_data (transfer->ssm); struct usb_action *action; g_assert (!(fpi_ssm_get_cur_state (transfer->ssm) >= data->stepcount)); action = &data->actions[fpi_ssm_get_cur_state (transfer->ssm)]; g_assert (!(action->type != ACTION_SEND)); if (error) { fpi_ssm_mark_failed (transfer->ssm, error); return; } /* success */ fpi_ssm_next_state (transfer->ssm); } static void async_recv_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { struct usbexchange_data *data = fpi_ssm_get_data (transfer->ssm); struct usb_action *action; if (error) { fpi_ssm_mark_failed (transfer->ssm, error); return; } g_assert (!(fpi_ssm_get_cur_state (transfer->ssm) >= data->stepcount)); action = &data->actions[fpi_ssm_get_cur_state (transfer->ssm)]; g_assert (!(action->type != ACTION_RECEIVE)); if (action->data != NULL) { if (transfer->actual_length != action->correct_reply_size) { fpi_ssm_mark_failed (transfer->ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, "Got %d bytes instead of %d", (gint) transfer->actual_length, action->correct_reply_size)); return; } if (memcmp (transfer->buffer, action->data, action->correct_reply_size) != 0) { fpi_ssm_mark_failed (transfer->ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, "Received a wrong reply from the driver.")); return; } } else { fp_dbg ("Got %d bytes out of %d", (gint) transfer->actual_length, (gint) transfer->length); } fpi_ssm_next_state (transfer->ssm); } static void usbexchange_loop (FpiSsm *ssm, FpDevice *_dev) { struct usbexchange_data *data = fpi_ssm_get_data (ssm); struct usb_action *action = &data->actions[fpi_ssm_get_cur_state (ssm)]; FpiUsbTransfer *transfer; g_assert (fpi_ssm_get_cur_state (ssm) < data->stepcount); switch (action->type) { case ACTION_SEND: fp_dbg ("Sending %s", action->name); transfer = fpi_usb_transfer_new (_dev); fpi_usb_transfer_fill_bulk_full (transfer, action->endpoint, action->data, action->size, NULL); transfer->ssm = ssm; transfer->short_is_error = TRUE; fpi_usb_transfer_submit (transfer, data->timeout, NULL, async_send_cb, NULL); break; case ACTION_RECEIVE: fp_dbg ("Receiving %d bytes", action->size); transfer = fpi_usb_transfer_new (_dev); fpi_usb_transfer_fill_bulk (transfer, action->endpoint, action->size); transfer->ssm = ssm; fpi_usb_transfer_submit (transfer, data->timeout, NULL, async_recv_cb, NULL); g_clear_pointer (&data->last_recv, fpi_usb_transfer_unref); data->last_recv = fpi_usb_transfer_ref (transfer); break; default: g_assert_not_reached (); fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); return; } } static void usb_exchange_async (FpiSsm *ssm, struct usbexchange_data *data, const char *exchange_name) { FpiSsm *subsm = fpi_ssm_new_full (fpi_ssm_get_device (ssm), usbexchange_loop, data->stepcount, data->stepcount, exchange_name); fpi_ssm_set_data (subsm, data, NULL); fpi_ssm_start_subsm (ssm, subsm); } /* =========== Image Capturing and Processing ============ */ enum { CHUNK_READ_FINISHED, CHUNK_READ_NEED_MORE, CHUNK_READ_ERROR }; static int clean_image (FpDeviceVfs7552 *self) { fp_dbg ("Cleaning image"); unsigned int sum = 0; for (int i = 0; i < VFS7552_IMAGE_SIZE; i++) { if (self->background[i] < self->image[i]) self->image[i] = 0; else self->image[i] = self->background[i] - self->image[i]; if ((int) (self->image[i]) * 4 > 255) self->image[i] = 255; else self->image[i] *= 4; sum += self->image[i]; } if (sum == 0) { fp_dbg ("frame darker than background; finger present during calibration?"); // Retake an image of the background at the next opportunity. self->background_captured = FALSE; return -1; } return 0; } static int process_chunk (FpDeviceVfs7552 *self, FpiUsbTransfer *transfer) { unsigned char *ptr; int n_bytes_in_chunk; int n_lines; int i; if (transfer->actual_length < 6) return CHUNK_READ_ERROR; ptr = transfer->buffer; n_bytes_in_chunk = ptr[2] + ptr[3] * 256; if (transfer->actual_length < 6 + n_bytes_in_chunk) return CHUNK_READ_ERROR; ptr = ptr + 6; n_lines = n_bytes_in_chunk / VFS7552_LINE_SIZE; if (n_lines + self->lines_captured > VFS7552_IMAGE_HEIGHT) { g_warning ("Device sent more lines that were expected! Aborting."); return CHUNK_READ_ERROR; } for (i = 0; i < n_lines; i++) { ptr = ptr + VFS7552_CONTROL_PIXELS; memcpy (&self->image[self->lines_captured * VFS7552_IMAGE_WIDTH], ptr, VFS7552_IMAGE_WIDTH); ptr = ptr + VFS7552_IMAGE_WIDTH; self->lines_captured += 1; } if (self->lines_captured == VFS7552_IMAGE_HEIGHT) return CHUNK_READ_FINISHED; else return CHUNK_READ_NEED_MORE; } static void chunk_capture_callback (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (device); if (error) { if (!self->deactivating) { fp_err ("Failed to capture data"); fpi_ssm_mark_failed (transfer->ssm, error); } else { // Clear the cancel error, because we are handling deactivation separately g_error_free (error); fpi_ssm_mark_completed (transfer->ssm); } } else { switch (process_chunk (self, transfer)) { case CHUNK_READ_FINISHED: fpi_ssm_next_state (transfer->ssm); break; case CHUNK_READ_NEED_MORE: fpi_ssm_jump_to_state (transfer->ssm, CAPTURE_REQUEST_CHUNK); break; case CHUNK_READ_ERROR: fpi_ssm_mark_failed (transfer->ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, "Failed to decode image packet of length %d", (int) transfer->actual_length)); } } } static void capture_chunk_async (FpiSsm *ssm, FpDevice *_dev, guint timeout) { FpiUsbTransfer *transfer; transfer = fpi_usb_transfer_new (_dev); fpi_usb_transfer_fill_bulk (transfer, VFS7552_IN_ENDPOINT, VFS7552_RECEIVE_BUF_SIZE); transfer->ssm = ssm; fpi_usb_transfer_submit (transfer, timeout, NULL, chunk_capture_callback, NULL); } static void reset_state (FpDeviceVfs7552 *self) { self->lines_captured = 0; } /* ================ Run States ================= */ static void deactivate_run_state (FpiSsm *ssm, FpDevice *_dev) { FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (_dev); switch (fpi_ssm_get_cur_state (ssm)) { case DEACTIVATE_ENTER: fpi_ssm_next_state_delayed (ssm, 10); break; case DEACTIVATE_DISABLE_SENSOR: self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_stop_capture); self->init_sequence.actions = vfs7552_stop_capture; self->init_sequence.timeout = 1000; usb_exchange_async (ssm, &self->init_sequence, "STOP CAPTURE"); break; } } static void capture_run_state (FpiSsm *ssm, FpDevice *_dev) { FpDeviceVfs7552 *self; unsigned char *receive_buf; int variance_before; int variance_after; self = FPI_DEVICE_VFS7552 (_dev); if (self->deactivating) { fp_dbg ("deactivating, marking completed"); fpi_ssm_mark_completed (ssm); return; } switch (fpi_ssm_get_cur_state (ssm)) { case CAPTURE_QUERY_DATA_READY: self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_data_ready_query); self->init_sequence.actions = vfs7552_data_ready_query; self->init_sequence.timeout = 0; // Do not time out usb_exchange_async (ssm, &self->init_sequence, "QUERY DATA READY"); break; case CAPTURE_CHECK_DATA_READY: receive_buf = ((unsigned char *) self->init_sequence.last_recv->buffer); if (receive_buf[0] == vfs7552_is_image_ready_resp_not_ready[0]) { fpi_ssm_jump_to_state (ssm, CAPTURE_QUERY_DATA_READY); } else if (receive_buf[0] == vfs7552_is_image_ready_resp_ready[0]) { reset_state (self); fpi_ssm_next_state (ssm); } else if (receive_buf[0] == vfs7552_is_image_ready_resp_finger_off[0]) { fp_dbg ("finger off response received"); if (self->dev_state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF) { reset_state (self); fpi_ssm_next_state (ssm); } else { fpi_ssm_jump_to_state (ssm, CAPTURE_FINALIZE); } } else { fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, "Unknown response 0x%02x", receive_buf[0])); } break; case CAPTURE_REQUEST_CHUNK: self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_request_chunk); self->init_sequence.actions = vfs7552_request_chunk; self->init_sequence.timeout = 1000; usb_exchange_async (ssm, &self->init_sequence, "REQUEST CHUNK"); break; case CAPTURE_READ_CHUNK: capture_chunk_async (ssm, _dev, 1000); break; case CAPTURE_COMPLETE: // Store the image as a background reference, if the variance is below the finger off threshold. if (!self->background_captured) { // Calculate the variance of the captured image variance_before = fpi_std_sq_dev (self->image, VFS7552_IMAGE_SIZE); fp_dbg ("variance_before = %d\n", variance_before); self->background_captured = TRUE; memcpy (self->background, self->image, VFS7552_IMAGE_SIZE); fp_dbg ("background stored"); fpi_ssm_jump_to_state (ssm, CAPTURE_QUERY_DATA_READY); break; } clean_image (self); variance_after = fpi_std_sq_dev (self->image, VFS7552_IMAGE_SIZE); fp_dbg ("variance_after = %d\n", variance_after); if (self->dev_state == FPI_IMAGE_DEVICE_STATE_CAPTURE || self->dev_state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) { // If the finger is placed on the sensor, the variance should ideally increase above a certain // threshold. Otherwise request a new image and test again. Additionally we want to ensure // that we don't capture prints with a way too high noise level (this sometimes happens). if (variance_after > CAPTURE_VARIANCE_THRESHOLD && variance_after < NOISE_VARIANCE_THRESHOLD) fpi_ssm_mark_completed (ssm); else fpi_ssm_jump_to_state (ssm, CAPTURE_QUERY_DATA_READY); } else if (self->dev_state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF) { // If the finger is removed from the sensor, the variance should ideally drop below a certain // threshold. Otherwise request a new image and test again. if (variance_after < FINGER_OFF_VARIANCE_THRESHOLD) fpi_ssm_mark_completed (ssm); else fpi_ssm_jump_to_state (ssm, CAPTURE_QUERY_DATA_READY); } break; case CAPTURE_FINALIZE: fpi_ssm_mark_completed (ssm); break; } } static void activate_run_state (FpiSsm *ssm, FpDevice *_dev) { FpDeviceVfs7552 *self; unsigned char *receive_buf; self = FPI_DEVICE_VFS7552 (_dev); if (self->deactivating) { fp_dbg ("deactivating, marking completed"); fpi_ssm_mark_completed (ssm); return; } switch (fpi_ssm_get_cur_state (ssm)) { case ACTIVATE_INIT: // This sequence prepares the sensor for capturing the image. self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_initiate_capture); self->init_sequence.actions = vfs7552_initiate_capture; self->init_sequence.timeout = VFS7552_DEFAULT_WAIT_TIMEOUT; usb_exchange_async (ssm, &self->init_sequence, "ACTIVATE INIT"); break; case ACTIVATE_INTERRUPT_QUERY: // This sequence configures the sensor to listen to finger placement events. self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_wait_finger_init); self->init_sequence.actions = vfs7552_wait_finger_init; self->init_sequence.timeout = 0; // Do not time out usb_exchange_async (ssm, &self->init_sequence, "ACTIVATE INTERRUPT QUERY"); break; case ACTIVATE_INTERRUPT_CHECK: receive_buf = ((unsigned char *) self->init_sequence.last_recv->buffer); if (receive_buf[0] == interrupt_ok[0]) { // This seems to mean: "Sensor is all good" fpi_ssm_jump_to_state (ssm, ACTIVATE_INTERRUPT_QUERY); } else if (receive_buf[0] == interrupt_ready[0]) { // This seems to mean: "We detected a finger" fpi_ssm_next_state (ssm); } else if (receive_buf[0] == interrupt_dont_ask[0]) { // This seems to mean: "We already told you we detected a finger, stop asking us" // It will not respond to another request on the interrupt endpoint fpi_ssm_next_state (ssm); } else { fp_dbg ("Unknown response 0x%02x", receive_buf[0]); fpi_ssm_next_state (ssm); } break; case ACTIVATE_FINALIZE: fpi_ssm_mark_completed (ssm); break; } } static void open_run_state (FpiSsm *ssm, FpDevice *_dev) { FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (_dev); switch (fpi_ssm_get_cur_state (ssm)) { case DEV_OPEN_START: self->init_sequence.stepcount = G_N_ELEMENTS (vfs7552_initialization); self->init_sequence.actions = vfs7552_initialization; self->init_sequence.timeout = VFS7552_DEFAULT_WAIT_TIMEOUT; usb_exchange_async (ssm, &self->init_sequence, "DEVICE OPEN"); break; } } /* ============= SSM Finalization Functions ============== */ static void deactivation_complete (FpiSsm *ssm, FpDevice *_dev, GError *error) { FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (_dev); g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref); self->dev_state = FPI_IMAGE_DEVICE_STATE_INACTIVE; self->loop_running = FALSE; if (self->deactivating) { self->deactivating = FALSE; fpi_image_device_deactivate_complete (dev, error); return; } if (!error) fpi_image_device_report_finger_status (dev, FALSE); else fpi_image_device_session_error (dev, error); } static void start_deactivation (FpDevice *_dev) { FpDeviceVfs7552 *self = FPI_DEVICE_VFS7552 (_dev); FpiSsm *ssm; self->loop_running = TRUE; self->dev_state = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF; ssm = fpi_ssm_new (_dev, deactivate_run_state, DEACTIVATE_NUM_STATES); fpi_ssm_start (ssm, deactivation_complete); } static void report_finger_off (FpiSsm *ssm, FpDevice *_dev, GError *error) { FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (_dev); g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref); if (!error) start_deactivation (_dev); else fpi_image_device_session_error (dev, error); } static void start_report_finger_off (FpDevice *_dev) { FpDeviceVfs7552 *self = FPI_DEVICE_VFS7552 (_dev); FpiSsm *ssm; self->loop_running = TRUE; ssm = fpi_ssm_new (_dev, capture_run_state, CAPTURE_NUM_STATES); fpi_ssm_start (ssm, report_finger_off); } static void capture_complete (FpiSsm *ssm, FpDevice *_dev, GError *error) { FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (_dev); g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref); if (!self->deactivating && !error) { FpImage *img; img = fp_image_new (2 * VFS7552_IMAGE_WIDTH, 2 * VFS7552_IMAGE_HEIGHT); // Scale the image for (int j = 0; j < VFS7552_IMAGE_HEIGHT; j++) { for (int i = 0; i < VFS7552_IMAGE_WIDTH; i++) { int ref = j * VFS7552_IMAGE_WIDTH + i; int ref_new = 4 * j * VFS7552_IMAGE_WIDTH + 2 * i; img->data[ref_new] = self->image[ref]; img->data[ref_new + 1] = self->image[ref]; img->data[ref_new + 2 * VFS7552_IMAGE_WIDTH] = self->image[ref]; img->data[ref_new + 2 * VFS7552_IMAGE_WIDTH + 1] = self->image[ref]; } } fp_dbg ("Image captured"); fpi_image_device_image_captured (dev, img); } self->loop_running = FALSE; if (self->deactivating) start_deactivation (_dev); else if (error) fpi_image_device_session_error (dev, error); } static void start_capture (FpDevice *_dev) { FpDeviceVfs7552 *self = FPI_DEVICE_VFS7552 (_dev); FpiSsm *ssm; self->loop_running = TRUE; ssm = fpi_ssm_new (_dev, capture_run_state, CAPTURE_NUM_STATES); fpi_ssm_start (ssm, capture_complete); } static void report_finger_on_complete (FpiSsm *ssm, FpDevice *_dev, GError *error) { FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (_dev); g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref); if (!self->deactivating && !error) fpi_image_device_report_finger_status (dev, TRUE); self->loop_running = FALSE; if (self->deactivating) start_deactivation (_dev); else if (error) fpi_image_device_session_error (dev, error); } static void activate_complete (FpiSsm *ssm, FpDevice *_dev, GError *error) { FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (_dev); g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref); if (!self->deactivating && !error) { ssm = fpi_ssm_new (_dev, capture_run_state, CAPTURE_NUM_STATES); fpi_ssm_start (ssm, report_finger_on_complete); return; } if (self->deactivating) { start_deactivation (_dev); } else if (error) { self->loop_running = FALSE; fpi_image_device_session_error (dev, error); } } static void start_report_finger_on (FpDevice *_dev) { FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (dev); FpiSsm *ssm; self->deactivating = FALSE; self->loop_running = TRUE; ssm = fpi_ssm_new (_dev, activate_run_state, ACTIVATE_NUM_STATES); fpi_ssm_start (ssm, activate_complete); } static void validity_change_state (FpDevice *_dev, void *data) { FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (dev); FpiImageDeviceState 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 FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON: self->dev_state = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON; start_report_finger_on (_dev); break; case FPI_IMAGE_DEVICE_STATE_CAPTURE: self->dev_state = FPI_IMAGE_DEVICE_STATE_CAPTURE; start_capture (_dev); break; case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF: self->dev_state = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF; start_report_finger_off (_dev); break; /* Ignored States */ case FPI_IMAGE_DEVICE_STATE_INACTIVE: case FPI_IMAGE_DEVICE_STATE_ACTIVATING: case FPI_IMAGE_DEVICE_STATE_DEACTIVATING: case FPI_IMAGE_DEVICE_STATE_IDLE: break; } } static void open_complete (FpiSsm *ssm, FpDevice *_dev, GError *error) { FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (_dev); g_clear_pointer (&self->init_sequence.last_recv, fpi_usb_transfer_unref); fpi_image_device_open_complete (dev, error); } /* ================== Driver Entrypoints =================== */ static void dev_close (FpImageDevice *dev) { GError *error = NULL; 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_deactivate (FpImageDevice *dev) { FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (dev); if (self->loop_running) self->deactivating = TRUE; else fpi_image_device_deactivate_complete (dev, NULL); } static void delayed_change_state (FpDevice *_dev, FpiImageDeviceState state) { GSource *timeout; char *name; // schedule state change instead of calling it directly to allow all actions // related to the previous state to complete timeout = fpi_device_add_timeout (_dev, 50, validity_change_state, NULL, NULL); name = g_strdup_printf ("dev_change_state to %d", state); g_source_set_name (timeout, name); g_free (name); } static void dev_change_state (FpImageDevice *dev, FpiImageDeviceState state) { FpDeviceVfs7552 *self; self = FPI_DEVICE_VFS7552 (dev); switch (state) { case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON: self->dev_state_next = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON; delayed_change_state (FP_DEVICE (dev), state); break; case FPI_IMAGE_DEVICE_STATE_CAPTURE: self->dev_state_next = FPI_IMAGE_DEVICE_STATE_CAPTURE; delayed_change_state (FP_DEVICE (dev), state); break; case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF: self->dev_state_next = FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF; delayed_change_state (FP_DEVICE (dev), state); break; /* Ignored States */ case FPI_IMAGE_DEVICE_STATE_INACTIVE: case FPI_IMAGE_DEVICE_STATE_ACTIVATING: case FPI_IMAGE_DEVICE_STATE_DEACTIVATING: case FPI_IMAGE_DEVICE_STATE_IDLE: break; default: g_assert_not_reached (); } } static void dev_activate (FpImageDevice *dev) { fpi_image_device_activate_complete (dev, NULL); } static void dev_open (FpImageDevice *dev) { FpiSsm *ssm; GError *error = NULL; // First we need to reset the device, otherwise opening will fail at state 13 if (!g_usb_device_reset (fpi_device_get_usb_device (FP_DEVICE (dev)), &error)) { fpi_image_device_open_complete (dev, error); return; } 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; } ssm = fpi_ssm_new (FP_DEVICE (dev), open_run_state, DEV_OPEN_NUM_STATES); fpi_ssm_start (ssm, open_complete); } static const FpIdEntry id_table[] = { /* Validity device from some Dell XPS laptops (9560, 9360 at least) */ { .vid = 0x138a, .pid = 0x0091 }, { .vid = 0x0, .pid = 0x0 }, }; static void fpi_device_vfs7552_init (FpDeviceVfs7552 *self) { } static void fpi_device_vfs7552_class_init (FpDeviceVfs7552Class *klass) { FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass); dev_class->id = "vfs7552"; dev_class->full_name = "Validity VFS7552"; dev_class->type = FP_DEVICE_TYPE_USB; dev_class->id_table = id_table; dev_class->scan_type = FP_SCAN_TYPE_PRESS; img_class->img_close = dev_close; img_class->deactivate = dev_deactivate; img_class->change_state = dev_change_state; img_class->activate = dev_activate; img_class->img_open = dev_open; img_class->bz3_threshold = 20; img_class->img_width = VFS7552_IMAGE_WIDTH; img_class->img_height = VFS7552_IMAGE_HEIGHT; }