From ec204fb5403c2f5f3142caa74c334e44d2d8ba56 Mon Sep 17 00:00:00 2001 From: Nikolay Metchev Date: Thu, 23 Apr 2026 09:36:10 +0100 Subject: [PATCH 1/8] upeksonly: experiment with 147e:1002 support --- libfprint/drivers/upeksonly.c | 63 +++++++++++++++++++++++++++++++++++ libfprint/drivers/upeksonly.h | 9 +++++ 2 files changed, 72 insertions(+) diff --git a/libfprint/drivers/upeksonly.c b/libfprint/drivers/upeksonly.c index e7ea3ce2..3a20ce92 100644 --- a/libfprint/drivers/upeksonly.c +++ b/libfprint/drivers/upeksonly.c @@ -41,6 +41,7 @@ enum { UPEKSONLY_2016, UPEKSONLY_1000, + UPEKSONLY_1002, UPEKSONLY_1001, }; @@ -507,6 +508,12 @@ img_data_cb (FpiUsbTransfer *transfer, FpDevice *device, self->num_flying--; + fp_dbg ("bulk cb: error=%s actual_length=%zu num_flying=%d capturing=%d", + error ? error->message : "none", + transfer->actual_length, + self->num_flying, + self->capturing); + if (self->killing_transfers) { if (self->num_flying == 0) @@ -554,6 +561,10 @@ img_data_cb (FpiUsbTransfer *transfer, FpDevice *device, if (is_capturing (self)) { + fp_dbg ("re-submitting bulk transfer actual_length=%zu rows=%u finger_state=%d", + transfer->actual_length, + self->num_rows, + self->finger_state); fpi_usb_transfer_submit (fpi_usb_transfer_ref (transfer), 0, self->img_cancellable, @@ -876,6 +887,13 @@ enum capsm_1000_states { CAPSM_1000_NUM_STATES, }; +enum capsm_1002_states { + CAPSM_1002_INIT, + CAPSM_1002_FIRE_BULK, + CAPSM_1002_WRITEV, + CAPSM_1002_NUM_STATES, +}; + enum capsm_1001_states { CAPSM_1001_INIT, CAPSM_1001_FIRE_BULK, @@ -898,6 +916,7 @@ capsm_fire_bulk (FpiSsm *ssm, g_clear_object (&self->img_cancellable); self->img_cancellable = g_cancellable_new (); + fp_dbg ("submitting %u bulk transfers on ep 0x81", self->img_transfers->len); for (i = 0; i < self->img_transfers->len; i++) { fpi_usb_transfer_submit (fpi_usb_transfer_ref (g_ptr_array_index (self->img_transfers, i)), @@ -1023,6 +1042,35 @@ capsm_1001_run_state (FpiSsm *ssm, FpDevice *_dev) } } +static void +capsm_1002_run_state (FpiSsm *ssm, FpDevice *_dev) +{ + FpiDeviceUpeksonly *self = FPI_DEVICE_UPEKSONLY (_dev); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case CAPSM_1002_INIT: + self->rowbuf_offset = -1; + self->num_rows = 0; + self->wraparounds = -1; + self->num_blank = 0; + self->num_nonblank = 0; + self->finger_state = FINGER_DETECTED; + self->last_seqnum = 16383; + self->killing_transfers = 0; + fpi_ssm_next_state (ssm); + break; + + case CAPSM_1002_FIRE_BULK: + capsm_fire_bulk (ssm, _dev); + break; + + case CAPSM_1002_WRITEV: + sm_write_regs (ssm, _dev, capsm_1002_writev, G_N_ELEMENTS (capsm_1002_writev)); + break; + } +} + /***** DEINITIALIZATION *****/ enum deinitsm_2016_states { @@ -1223,6 +1271,7 @@ loopsm_run_state (FpiSsm *ssm, FpDevice *_dev) break; case UPEKSONLY_1000: + case UPEKSONLY_1002: awfsm = fpi_ssm_new (FP_DEVICE (dev), awfsm_1000_run_state, AWFSM_1000_NUM_STATES); @@ -1266,6 +1315,12 @@ loopsm_run_state (FpiSsm *ssm, FpDevice *_dev) CAPSM_1000_NUM_STATES); break; + case UPEKSONLY_1002: + capsm = fpi_ssm_new (FP_DEVICE (dev), + capsm_1002_run_state, + CAPSM_1002_NUM_STATES); + break; + case UPEKSONLY_1001: capsm = fpi_ssm_new (FP_DEVICE (dev), capsm_1001_run_state, @@ -1292,6 +1347,7 @@ loopsm_run_state (FpiSsm *ssm, FpDevice *_dev) break; case UPEKSONLY_1000: + case UPEKSONLY_1002: deinitsm = fpi_ssm_new (FP_DEVICE (dev), deinitsm_1000_run_state, DEINITSM_1000_NUM_STATES); @@ -1421,6 +1477,7 @@ dev_activate (FpImageDevice *dev) break; case UPEKSONLY_1000: + case UPEKSONLY_1002: ssm = fpi_ssm_new (FP_DEVICE (dev), initsm_1000_run_state, INITSM_1000_NUM_STATES); break; @@ -1461,6 +1518,10 @@ dev_discover (GUsbDevice *usb_device) if (bcd == 0x0033) /* Looking for revision 0.33 */ return 1; + if (pid == 0x1002) + if (bcd == 0x0048) /* TouchStrip Fingerprint Sensor */ + return 1; + if (pid == 0x1001) return 1; @@ -1470,6 +1531,7 @@ dev_discover (GUsbDevice *usb_device) static const FpIdEntry id_table[] = { { .vid = 0x147e, .pid = 0x2016, .driver_data = UPEKSONLY_2016 }, { .vid = 0x147e, .pid = 0x1000, .driver_data = UPEKSONLY_1000 }, + { .vid = 0x147e, .pid = 0x1002, .driver_data = UPEKSONLY_1002 }, { .vid = 0x147e, .pid = 0x1001, .driver_data = UPEKSONLY_1001 }, { .vid = 0, .pid = 0, .driver_data = 0 }, }; @@ -1531,6 +1593,7 @@ dev_init (FpImageDevice *dev) switch (self->dev_model) { case UPEKSONLY_1000: + case UPEKSONLY_1002: self->img_width = IMG_WIDTH_1000; self->assembling_ctx.line_width = IMG_WIDTH_1000; break; diff --git a/libfprint/drivers/upeksonly.h b/libfprint/drivers/upeksonly.h index 611afda5..d637fe71 100644 --- a/libfprint/drivers/upeksonly.h +++ b/libfprint/drivers/upeksonly.h @@ -92,6 +92,15 @@ static const struct sonly_regwrite capsm_1000_writev[] = { { 0x08, 0x80 }, { 0x13, 0x55 }, { 0x0b, 0x80 }, /* Enter capture mode */ }; +static const struct sonly_regwrite capsm_1002_writev[] = { + /* 147e:1002 appears close to the 1000 family for init/interrupts, but it + * does not start streaming image data with the plain 1000 capture writes. + * Try the broader capture-mode enable sequence used by the 2016 variant. + */ + { 0x09, 0x28 }, { 0x13, 0x55 }, { 0x0b, 0x80 }, { 0x04, 0x00 }, + { 0x05, 0x00 }, +}; + static const struct sonly_regwrite capsm_1001_writev_1[] = { { 0x1a, 0x02 }, { 0x4a, 0x9d }, From 17a13fec10129faf5cf88d2b8efcb2d8c49bc826 Mon Sep 17 00:00:00 2001 From: Nikolay Metchev Date: Thu, 23 Apr 2026 11:03:14 +0100 Subject: [PATCH 2/8] upeksonly: use Windows trace sequence for 147e:1002 --- libfprint/drivers/upeksonly.c | 12 +++++++++--- libfprint/drivers/upeksonly.h | 22 ++++++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/libfprint/drivers/upeksonly.c b/libfprint/drivers/upeksonly.c index 3a20ce92..fe3c110d 100644 --- a/libfprint/drivers/upeksonly.c +++ b/libfprint/drivers/upeksonly.c @@ -864,7 +864,10 @@ awfsm_1000_run_state (FpiSsm *ssm, FpDevice *_dev) break; case AWFSM_1000_WRITEV_2: - sm_write_regs (ssm, _dev, awfsm_1000_writev_2, G_N_ELEMENTS (awfsm_1000_writev_2)); + if (FPI_DEVICE_UPEKSONLY (_dev)->dev_model == UPEKSONLY_1002) + sm_write_regs (ssm, _dev, awfsm_1002_writev_2, G_N_ELEMENTS (awfsm_1002_writev_2)); + else + sm_write_regs (ssm, _dev, awfsm_1000_writev_2, G_N_ELEMENTS (awfsm_1000_writev_2)); break; } } @@ -889,8 +892,8 @@ enum capsm_1000_states { enum capsm_1002_states { CAPSM_1002_INIT, - CAPSM_1002_FIRE_BULK, CAPSM_1002_WRITEV, + CAPSM_1002_FIRE_BULK, CAPSM_1002_NUM_STATES, }; @@ -1192,7 +1195,10 @@ initsm_1000_run_state (FpiSsm *ssm, FpDevice *_dev) switch (fpi_ssm_get_cur_state (ssm)) { case INITSM_1000_WRITEV_1: - sm_write_regs (ssm, _dev, initsm_1000_writev_1, G_N_ELEMENTS (initsm_1000_writev_1)); + if (FPI_DEVICE_UPEKSONLY (_dev)->dev_model == UPEKSONLY_1002) + sm_write_regs (ssm, _dev, initsm_1002_writev_1, G_N_ELEMENTS (initsm_1002_writev_1)); + else + sm_write_regs (ssm, _dev, initsm_1000_writev_1, G_N_ELEMENTS (initsm_1000_writev_1)); break; } } diff --git a/libfprint/drivers/upeksonly.h b/libfprint/drivers/upeksonly.h index d637fe71..596249ff 100644 --- a/libfprint/drivers/upeksonly.h +++ b/libfprint/drivers/upeksonly.h @@ -65,6 +65,11 @@ static const struct sonly_regwrite awfsm_1000_writev_2[] = { { 0x30, 0xe1 }, { 0x15, 0x06 }, { 0x15, 0x86 }, }; +static const struct sonly_regwrite awfsm_1002_writev_2[] = { + /* Windows enables 147e:1002 finger detection with bit 0x01 clear here. */ + { 0x30, 0xe1 }, { 0x15, 0x05 }, { 0x15, 0x85 }, +}; + static const struct sonly_regwrite awfsm_2016_writev_3[] = { { 0x13, 0x45 }, { 0x30, 0xe0 }, { 0x12, 0x01 }, { 0x20, 0x01 }, { 0x09, 0x20 }, { 0x0a, 0x00 }, { 0x30, 0xe0 }, { 0x20, 0x01 }, @@ -94,11 +99,12 @@ static const struct sonly_regwrite capsm_1000_writev[] = { static const struct sonly_regwrite capsm_1002_writev[] = { /* 147e:1002 appears close to the 1000 family for init/interrupts, but it - * does not start streaming image data with the plain 1000 capture writes. - * Try the broader capture-mode enable sequence used by the 2016 variant. + * does not start streaming image data with the plain 1000/2016 capture + * writes. This sequence matches the Windows driver immediately before bulk + * image packets start arriving on endpoint 0x81. */ - { 0x09, 0x28 }, { 0x13, 0x55 }, { 0x0b, 0x80 }, { 0x04, 0x00 }, - { 0x05, 0x00 }, + { 0x15, 0x25 }, { 0x30, 0xe0 }, { 0x09, 0x23 }, { 0x13, 0x75 }, + { 0x0b, 0x80 }, }; static const struct sonly_regwrite capsm_1001_writev_1[] = { @@ -200,6 +206,14 @@ static const struct sonly_regwrite initsm_1000_writev_1[] = { { 0x0b, 0x00 }, { 0x08, 0x00 }, /* Initialize capture control registers */ }; +static const struct sonly_regwrite initsm_1002_writev_1[] = { + /* Captured from the Windows 147e:1002 driver before finger detection. */ + { 0x49, 0x08 }, + { 0x3e, 0x3d }, { 0x3e, 0x18 }, { 0x3e, 0x38 }, { 0x3e, 0x63 }, + { 0x3e, 0x9d }, { 0x3e, 0x5c }, { 0x3e, 0xb1 }, { 0x3e, 0x1f }, + { 0x1a, 0x01 }, { 0x1a, 0x03 }, { 0x0b, 0x00 }, { 0x49, 0x0c }, +}; + static const struct sonly_regwrite initsm_1001_writev_1[] = { { 0x4a, 0x9d }, { 0x4f, 0x06 }, From fafa3718dace6a6a53f3bbe047f2233a8edb263b Mon Sep 17 00:00:00 2001 From: Nikolay Metchev Date: Fri, 1 May 2026 08:50:24 +0000 Subject: [PATCH 3/8] upeksonly: make 147e:1002 enrollment work --- libfprint/drivers/upeksonly.c | 683 ++++++++++++++++++++++++++++++++-- libfprint/drivers/upeksonly.h | 160 +++++++- 2 files changed, 794 insertions(+), 49 deletions(-) diff --git a/libfprint/drivers/upeksonly.c b/libfprint/drivers/upeksonly.c index fe3c110d..c84c25c6 100644 --- a/libfprint/drivers/upeksonly.c +++ b/libfprint/drivers/upeksonly.c @@ -31,7 +31,15 @@ #define CTRL_TIMEOUT 1000 #define NUM_BULK_TRANSFERS 24 #define MAX_ROWS 2048 +#define MAX_ROWS_1002 24000 #define MIN_ROWS 64 +#define DETAIL_THRESHOLD_SQ_1002 800.0 +#define MOTION_THRESHOLD_1002 18.0 +#define DETAIL_TRIGGER_ROWS_1002 3 +#define MIN_DETECTION_ROW_1002 1000 +#define INITIAL_FINGER_IRQ_IGNORE_MS_1002 250 +#define MAX_EARLY_FINGER_IRQS_1002 3 +#define AWAIT_FINGER_SETTLE_DELAY_MS_1002 100 #define BLANK_THRESHOLD 250 #define FINGER_PRESENT_THRESHOLD 32 @@ -61,6 +69,18 @@ enum sonly_fs { FINGER_REMOVED, }; +enum loopsm_states { + LOOPSM_RUN_AWFSM, + LOOPSM_DELAY_AWAIT_FINGER, + LOOPSM_STATUS_READ, + LOOPSM_AWAIT_FINGER, + LOOPSM_RUN_CAPSM, + LOOPSM_CAPTURE, + LOOPSM_RUN_DEINITSM, + LOOPSM_FINAL, + LOOPSM_NUM_STATES, +}; + struct _FpiDeviceUpeksonly { FpImageDevice parent; @@ -76,6 +96,7 @@ struct _FpiDeviceUpeksonly /* Do we really need multiple concurrent transfers? */ GCancellable *img_cancellable; + GCancellable *await_cancellable; GPtrArray *img_transfers; int num_flying; @@ -89,6 +110,14 @@ struct _FpiDeviceUpeksonly int num_nonblank; enum sonly_fs finger_state; int last_seqnum; + guint32 marker_window; + int marker_skip; + int finger_start_row_1002; + unsigned detail_rows_1002; + gboolean have_avg_row_1002; + guchar avg_row_1002[IMG_CROP_WIDTH_1002]; + gint64 await_intr_start_us; + unsigned early_finger_irq_count_1002; enum sonly_kill_transfers_action killing_transfers; GError *kill_error; @@ -150,6 +179,43 @@ upeksonly_get_pixel (struct fpi_line_asmbl_ctx *ctx, return buf[offset]; } +static int +upeksonly_get_deviation_simple (struct fpi_line_asmbl_ctx *ctx, + GSList *line1, + GSList *line2) +{ + unsigned char *buf1 = line1->data, *buf2 = line2->data; + int res = 0, mean = 0; + + g_assert (ctx->line_width > 0); + + for (int i = 0; i < ctx->line_width; i++) + mean += (int) buf1[i] + (int) buf2[i]; + + mean /= ctx->line_width; + + for (int i = 0; i < ctx->line_width; i++) + { + int dev = (int) buf1[i] + (int) buf2[i] - mean; + res += dev * dev; + } + + return res / ctx->line_width; +} + +static unsigned char +upeksonly_get_pixel_simple (struct fpi_line_asmbl_ctx *ctx, + GSList *row, + unsigned x) +{ + unsigned char *buf = row->data; + + if (x >= ctx->line_width) + return 0; + + return buf[x]; +} + /***** IMAGE PROCESSING *****/ static void @@ -197,7 +263,9 @@ cancel_img_transfers (FpImageDevice *dev) static gboolean is_capturing (FpiDeviceUpeksonly *sdev) { - return sdev->num_rows < MAX_ROWS && (sdev->finger_state != FINGER_REMOVED); + guint max_rows = sdev->dev_model == UPEKSONLY_1002 ? MAX_ROWS_1002 : MAX_ROWS; + + return sdev->num_rows < max_rows && (sdev->finger_state != FINGER_REMOVED); } static void @@ -217,7 +285,64 @@ handoff_img (FpImageDevice *dev) self->rows = g_slist_reverse (self->rows); fp_dbg ("%u rows", self->num_rows); - img = fpi_assemble_lines (&self->assembling_ctx, self->rows, self->num_rows); + + if (self->dev_model == UPEKSONLY_1002) + { + guint crop_start = 0; + guint cropped_rows; + guint out_height; + guint row_count = g_slist_length (self->rows); + g_autofree guchar **rows = g_new (guchar *, row_count); + guint row_idx = 0; + + if (row_count != self->num_rows) + { + fp_warn ("1002 row list length %u differs from row counter %u", + row_count, + self->num_rows); + self->num_rows = row_count; + } + + if (self->finger_start_row_1002 >= 0) + crop_start = self->finger_start_row_1002 + IMG_CROP_OFFSET_1002; + else if (row_count > IMG_CROP_ROWS_1002) + crop_start = row_count - IMG_CROP_ROWS_1002; + + if (crop_start >= row_count) + crop_start = 0; + + cropped_rows = MIN (IMG_CROP_ROWS_1002, row_count - crop_start); + out_height = cropped_rows / IMG_HEIGHT_SCALE_1002; + + for (GSList *row = self->rows; row; row = g_slist_next (row)) + rows[row_idx++] = row->data; + + img = fp_image_new (IMG_CROP_WIDTH_1002, out_height); + img->flags = FPI_IMAGE_COLORS_INVERTED; + + for (guint y = 0; y < out_height; y++) + { + for (guint x = 0; x < IMG_CROP_WIDTH_1002; x++) + { + guint sum = 0; + + for (guint i = 0; i < IMG_HEIGHT_SCALE_1002; i++) + { + guint src_y = crop_start + y * IMG_HEIGHT_SCALE_1002 + i; + guint src_x = IMG_CROP_X_1002 + x; + + sum += rows[src_y][src_x]; + } + + img->data[y * IMG_CROP_WIDTH_1002 + x] = sum / IMG_HEIGHT_SCALE_1002; + } + } + + } + else + { + img = fpi_assemble_lines (&self->assembling_ctx, self->rows, self->num_rows); + } g_slist_free_full (self->rows, g_free); self->rows = NULL; @@ -230,21 +355,116 @@ handoff_img (FpImageDevice *dev) cancel_img_transfers (dev); } +static gboolean +update_1002_finger_detection (FpiDeviceUpeksonly *self) +{ + GSList *row; + double sum = 0.0; + double sq_sum = 0.0; + double motion = 0.0; + guchar avg_row[IMG_CROP_WIDTH_1002]; + + if (self->num_rows % IMG_HEIGHT_SCALE_1002 != 0) + return FALSE; + + for (guint x = 0; x < IMG_CROP_WIDTH_1002; x++) + { + guint col_sum = 0; + + row = self->rows; + for (guint i = 0; i < IMG_HEIGHT_SCALE_1002 && row; i++, row = g_slist_next (row)) + col_sum += ((guchar *) row->data)[IMG_CROP_X_1002 + x]; + + avg_row[x] = col_sum / IMG_HEIGHT_SCALE_1002; + sum += avg_row[x]; + sq_sum += avg_row[x] * avg_row[x]; + + if (self->have_avg_row_1002) + motion += ABS ((int) avg_row[x] - (int) self->avg_row_1002[x]); + } + + if (self->have_avg_row_1002) + motion /= IMG_CROP_WIDTH_1002; + + if (self->num_rows >= MIN_DETECTION_ROW_1002 && + (sq_sum / IMG_CROP_WIDTH_1002) - + (sum / IMG_CROP_WIDTH_1002) * (sum / IMG_CROP_WIDTH_1002) > DETAIL_THRESHOLD_SQ_1002 && + self->have_avg_row_1002 && + motion > MOTION_THRESHOLD_1002) + { + self->detail_rows_1002++; + + if (self->finger_start_row_1002 < 0 && + self->detail_rows_1002 >= DETAIL_TRIGGER_ROWS_1002) + { + self->finger_start_row_1002 = + MAX (0, (int) self->num_rows - + (int) self->detail_rows_1002 * IMG_HEIGHT_SCALE_1002); + fp_dbg ("detected 1002 finger data at row %d", self->finger_start_row_1002); + } + } + else if (self->finger_start_row_1002 < 0) + { + self->detail_rows_1002 = 0; + } + + memcpy (self->avg_row_1002, avg_row, sizeof (avg_row)); + self->have_avg_row_1002 = TRUE; + + return self->finger_start_row_1002 >= 0 && + self->num_rows >= (guint) self->finger_start_row_1002 + + IMG_CROP_OFFSET_1002 + + IMG_CROP_ROWS_1002; +} + static void row_complete (FpImageDevice *dev) { FpiDeviceUpeksonly *self = FPI_DEVICE_UPEKSONLY (dev); + int line_width = self->assembling_ctx.line_width; self->rowbuf_offset = -1; + if (self->dev_model == UPEKSONLY_1002) + { + if (!is_capturing (self) || self->killing_transfers) + { + g_clear_pointer (&self->rowbuf, g_free); + return; + } + + self->rows = g_slist_prepend (self->rows, self->rowbuf); + self->num_rows++; + self->rowbuf = NULL; + + if (update_1002_finger_detection (self)) + { + fp_dbg ("row limit met"); + self->finger_state = FINGER_REMOVED; + handoff_img (dev); + } + else if (self->num_rows >= MAX_ROWS_1002) + { + fp_dbg ("1002 capture timed out without finger data"); + self->finger_state = FINGER_REMOVED; + fpi_image_device_retry_scan (dev, FP_DEVICE_RETRY_TOO_SHORT); + fpi_image_device_report_finger_status (dev, FALSE); + self->killing_transfers = ITERATE_SSM; + self->kill_ssm = self->loopsm; + cancel_img_transfers (dev); + } + + return; + } + if (self->num_rows > 0) { unsigned char *lastrow = self->rows->data; int std_sq_dev, mean_sq_diff; - std_sq_dev = fpi_std_sq_dev (self->rowbuf, self->img_width); + std_sq_dev = fpi_std_sq_dev (self->rowbuf, line_width); mean_sq_diff = fpi_mean_sq_diff_norm (lastrow, self->rowbuf, - self->img_width); + line_width); switch (self->finger_state) { @@ -498,6 +718,56 @@ handle_packet (FpImageDevice *dev, unsigned char *data) start_new_row (self, data + diff, 62 - diff); } +static void +handle_packet_1002 (FpImageDevice *dev, unsigned char *data) +{ + FpiDeviceUpeksonly *self = FPI_DEVICE_UPEKSONLY (dev); + + data += 2; /* skip sequence number */ + + for (int i = 0; i < 62; i++) + { + guint8 byte = data[i]; + + if (self->marker_skip > 0) + { + self->marker_skip--; + continue; + } + + if (self->rowbuf) + { + if (self->rowbuf_offset < self->img_width) + self->rowbuf[self->rowbuf_offset++] = byte; + } + + self->marker_window = ((self->marker_window << 8) | byte) & 0xffffff; + + if (self->marker_window == 0xffa5a5) + { + if (self->rowbuf) + { + if (self->rowbuf_offset >= 3) + self->rowbuf_offset -= 3; + + if (self->rowbuf_offset > IMG_WIDTH_1002 / 2) + { + row_complete (dev); + if (!is_capturing (self)) + return; + } + else + g_clear_pointer (&self->rowbuf, g_free); + } + + self->rowbuf = g_malloc0 (self->img_width); + self->rowbuf_offset = 0; + self->marker_skip = 5; + self->marker_window = 0; + } + } +} + static void img_data_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) @@ -508,11 +778,12 @@ img_data_cb (FpiUsbTransfer *transfer, FpDevice *device, self->num_flying--; - fp_dbg ("bulk cb: error=%s actual_length=%zu num_flying=%d capturing=%d", - error ? error->message : "none", - transfer->actual_length, - self->num_flying, - self->capturing); + if (self->dev_model != UPEKSONLY_1002) + fp_dbg ("bulk cb: error=%s actual_length=%zu num_flying=%d capturing=%d", + error ? error->message : "none", + transfer->actual_length, + self->num_flying, + self->capturing); if (self->killing_transfers) { @@ -556,15 +827,20 @@ img_data_cb (FpiUsbTransfer *transfer, FpDevice *device, { if (!is_capturing (self)) return; - handle_packet (dev, transfer->buffer + i); + + if (self->dev_model == UPEKSONLY_1002) + handle_packet_1002 (dev, transfer->buffer + i); + else + handle_packet (dev, transfer->buffer + i); } if (is_capturing (self)) { - fp_dbg ("re-submitting bulk transfer actual_length=%zu rows=%u finger_state=%d", - transfer->actual_length, - self->num_rows, - self->finger_state); + if (self->dev_model != UPEKSONLY_1002) + fp_dbg ("re-submitting bulk transfer actual_length=%zu rows=%u finger_state=%d", + transfer->actual_length, + self->num_rows, + self->finger_state); fpi_usb_transfer_submit (fpi_usb_transfer_ref (transfer), 0, self->img_cancellable, @@ -586,6 +862,17 @@ struct write_regs_data size_t regs_written; }; +struct write_regs_data_data +{ + FpDevice *dev; + FpiSsm *ssm; + const struct sonly_regwrite_data *regs; + size_t num_regs; + size_t regs_written; +}; + +static void sm_await_intr (FpiSsm *ssm, FpImageDevice *dev); + static void write_regs_finished (struct write_regs_data *wrdata, GError *error) { @@ -663,6 +950,161 @@ sm_write_regs (FpiSsm *ssm, write_regs_iterate (wrdata); } +static void write_regs_data_iterate (struct write_regs_data_data *wrdata); +static void write_regs_data_finished (struct write_regs_data_data *wrdata, GError *error); + +static void +write_regs_data_readback_cb (FpiUsbTransfer *transfer, FpDevice *device, + gpointer user_data, GError *error) +{ + struct write_regs_data_data *wrdata = user_data; + + if (error) + { + write_regs_data_finished (wrdata, error); + return; + } + + fp_dbg ("read %02x result = %02x %02x %02x %02x %02x %02x %02x %02x", + transfer->endpoint & 0xff, + transfer->buffer[0], transfer->buffer[1], transfer->buffer[2], transfer->buffer[3], + transfer->buffer[4], transfer->buffer[5], transfer->buffer[6], transfer->buffer[7]); + + wrdata->regs_written++; + write_regs_data_iterate (wrdata); +} + +static void +write_regs_data_finished (struct write_regs_data_data *wrdata, GError *error) +{ + if (!error) + fpi_ssm_next_state (wrdata->ssm); + else + fpi_ssm_mark_failed (wrdata->ssm, error); + + g_free (wrdata); +} + +static void +write_regs_data_cb (FpiUsbTransfer *transfer, FpDevice *device, + gpointer user_data, GError *error) +{ + struct write_regs_data_data *wrdata = user_data; + const struct sonly_regwrite_data *regwrite; + + if (error) + { + write_regs_data_finished (wrdata, error); + return; + } + + regwrite = &wrdata->regs[wrdata->regs_written]; + + if (regwrite->reg == 0x47 && + regwrite->len == 1 && + (regwrite->value[0] == 0x00 || regwrite->value[0] == 0x04)) + { + FpiUsbTransfer *read_transfer = fpi_usb_transfer_new (wrdata->dev); + + fp_dbg ("read 58 len=8"); + fpi_usb_transfer_fill_control (read_transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0x0c, + 0, + 0x58, + 8); + read_transfer->short_is_error = TRUE; + read_transfer->ssm = wrdata->ssm; + read_transfer->endpoint = 0x58; + + fpi_usb_transfer_submit (read_transfer, + CTRL_TIMEOUT, + NULL, + write_regs_data_readback_cb, + wrdata); + return; + } + + wrdata->regs_written++; + write_regs_data_iterate (wrdata); +} + +static void +write_regs_data_iterate (struct write_regs_data_data *wrdata) +{ + FpiUsbTransfer *transfer; + const struct sonly_regwrite_data *regwrite; + + if (wrdata->regs_written >= wrdata->num_regs) + { + write_regs_data_finished (wrdata, NULL); + return; + } + + regwrite = &wrdata->regs[wrdata->regs_written]; + fp_dbg ("set %02x len=%u", regwrite->reg, regwrite->len); + + if (regwrite->len == 0) + { + guint8 read_len = regwrite->value[0] ? regwrite->value[0] : 8; + + fp_dbg ("read %02x len=%u", regwrite->reg, read_len); + transfer = fpi_usb_transfer_new (wrdata->dev); + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0x0c, + 0, + regwrite->reg, + read_len); + transfer->short_is_error = TRUE; + transfer->ssm = wrdata->ssm; + transfer->endpoint = regwrite->reg; + + fpi_usb_transfer_submit (transfer, + CTRL_TIMEOUT, + NULL, + write_regs_data_readback_cb, + wrdata); + return; + } + + transfer = fpi_usb_transfer_new (wrdata->dev); + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0x0c, + 0, + regwrite->reg, + regwrite->len); + transfer->short_is_error = TRUE; + transfer->ssm = wrdata->ssm; + memcpy (transfer->buffer, regwrite->value, regwrite->len); + + fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, write_regs_data_cb, wrdata); +} + +static void +sm_write_regs_data (FpiSsm *ssm, + FpDevice *dev, + const struct sonly_regwrite_data *regs, + size_t num_regs) +{ + struct write_regs_data_data *wrdata = g_malloc (sizeof (*wrdata)); + + wrdata->ssm = ssm; + wrdata->regs = regs; + wrdata->num_regs = num_regs; + wrdata->regs_written = 0; + wrdata->dev = dev; + + write_regs_data_iterate (wrdata); +} + static void sm_write_reg (FpiSsm *ssm, FpImageDevice *dev, @@ -732,15 +1174,65 @@ sm_read_reg (FpiSsm *ssm, NULL); } +static void +sm_read_status_cb (FpiUsbTransfer *transfer, FpDevice *device, + gpointer user_data, GError *error) +{ + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + + fp_dbg ("read status result = %02x %02x %02x %02x %02x %02x %02x %02x", + transfer->buffer[0], transfer->buffer[1], transfer->buffer[2], transfer->buffer[3], + transfer->buffer[4], transfer->buffer[5], transfer->buffer[6], transfer->buffer[7]); + fpi_ssm_next_state (transfer->ssm); +} + +static void +sm_read_status (FpiSsm *ssm, + FpImageDevice *dev) +{ + FpiUsbTransfer *transfer = fpi_usb_transfer_new (FP_DEVICE (dev)); + + fp_dbg ("read status"); + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + 0x04, + 0, + 0, + 8); + transfer->ssm = ssm; + transfer->short_is_error = TRUE; + fpi_usb_transfer_submit (transfer, + CTRL_TIMEOUT, + NULL, + sm_read_status_cb, + NULL); +} + static void sm_await_intr_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { FpImageDevice *dev = FP_IMAGE_DEVICE (device); FpiDeviceUpeksonly *self = FPI_DEVICE_UPEKSONLY (dev); + gboolean finger_detected = TRUE; + + g_clear_object (&self->await_cancellable); if (error) { + if (self->deactivating && + g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + fpi_ssm_mark_completed (transfer->ssm); + return; + } + fpi_ssm_mark_failed (transfer->ssm, error); return; } @@ -749,6 +1241,42 @@ sm_await_intr_cb (FpiUsbTransfer *transfer, FpDevice *device, transfer->buffer[0], transfer->buffer[1], transfer->buffer[2], transfer->buffer[3]); + if (self->dev_model == UPEKSONLY_1002) + { + gint64 elapsed_ms = (g_get_monotonic_time () - self->await_intr_start_us) / 1000; + + /* Windows keeps polling 0x83 with 12 12 12 12 while idle and only + * switches to 01 00 00 00 when a finger is actually detected. */ + finger_detected = transfer->buffer[0] == 0x01 && + transfer->buffer[1] == 0x00 && + transfer->buffer[2] == 0x00 && + transfer->buffer[3] == 0x00; + + if (finger_detected && elapsed_ms < INITIAL_FINGER_IRQ_IGNORE_MS_1002) + { + self->early_finger_irq_count_1002++; + + if (self->early_finger_irq_count_1002 < MAX_EARLY_FINGER_IRQS_1002) + { + fp_dbg ("1002 early finger interrupt after %" G_GINT64_FORMAT " ms, restart wait setup", + elapsed_ms); + fpi_ssm_jump_to_state_delayed (transfer->ssm, LOOPSM_RUN_AWFSM, 200); + return; + } + + fp_dbg ("1002 accepting repeated early finger interrupt after %u attempts", + self->early_finger_irq_count_1002); + } + + if (!finger_detected) + { + fp_dbg ("1002 idle interrupt, continue waiting"); + sm_await_intr (transfer->ssm, dev); + return; + } + } + + self->early_finger_irq_count_1002 = 0; self->finger_state = FINGER_DETECTED; fpi_image_device_report_finger_status (dev, TRUE); fpi_ssm_next_state (transfer->ssm); @@ -759,9 +1287,14 @@ sm_await_intr (FpiSsm *ssm, FpImageDevice *dev) { FpiUsbTransfer *transfer = fpi_usb_transfer_new (FP_DEVICE (dev)); + FpiDeviceUpeksonly *self = FPI_DEVICE_UPEKSONLY (dev); G_DEBUG_HERE (); + self->await_intr_start_us = g_get_monotonic_time (); + g_clear_object (&self->await_cancellable); + self->await_cancellable = g_cancellable_new (); + fpi_usb_transfer_fill_interrupt (transfer, 0x83, 4); transfer->short_is_error = TRUE; transfer->ssm = ssm; @@ -769,7 +1302,7 @@ sm_await_intr (FpiSsm *ssm, /* NOTE: This was changed to be cancellable with the version 2 port! */ fpi_usb_transfer_submit (transfer, 0, - fpi_device_get_cancellable (FP_DEVICE (dev)), + self->await_cancellable, sm_await_intr_cb, NULL); } @@ -860,7 +1393,10 @@ awfsm_1000_run_state (FpiSsm *ssm, FpDevice *_dev) switch (fpi_ssm_get_cur_state (ssm)) { case AWFSM_1000_WRITEV_1: - sm_write_regs (ssm, _dev, awfsm_1000_writev_1, G_N_ELEMENTS (awfsm_1000_writev_1)); + if (FPI_DEVICE_UPEKSONLY (_dev)->dev_model == UPEKSONLY_1002) + fpi_ssm_next_state (ssm); + else + sm_write_regs (ssm, _dev, awfsm_1000_writev_1, G_N_ELEMENTS (awfsm_1000_writev_1)); break; case AWFSM_1000_WRITEV_2: @@ -892,8 +1428,9 @@ enum capsm_1000_states { enum capsm_1002_states { CAPSM_1002_INIT, - CAPSM_1002_WRITEV, + CAPSM_1002_STREAM_SETUP, CAPSM_1002_FIRE_BULK, + CAPSM_1002_WRITEV, CAPSM_1002_NUM_STATES, }; @@ -933,6 +1470,14 @@ capsm_fire_bulk (FpiSsm *ssm, fpi_ssm_next_state (ssm); } +static void +clear_capture_rows (FpiDeviceUpeksonly *self) +{ + g_clear_pointer (&self->rowbuf, g_free); + g_slist_free_full (self->rows, g_free); + self->rows = NULL; +} + static void capsm_2016_run_state (FpiSsm *ssm, FpDevice *_dev) { @@ -942,6 +1487,7 @@ capsm_2016_run_state (FpiSsm *ssm, FpDevice *_dev) switch (fpi_ssm_get_cur_state (ssm)) { case CAPSM_2016_INIT: + clear_capture_rows (self); self->rowbuf_offset = -1; self->num_rows = 0; self->wraparounds = -1; @@ -949,6 +1495,8 @@ capsm_2016_run_state (FpiSsm *ssm, FpDevice *_dev) self->num_nonblank = 0; self->finger_state = FINGER_DETECTED; self->last_seqnum = 16383; + self->marker_window = 0; + self->marker_skip = 0; self->killing_transfers = 0; fpi_ssm_next_state (ssm); break; @@ -979,6 +1527,7 @@ capsm_1000_run_state (FpiSsm *ssm, FpDevice *_dev) switch (fpi_ssm_get_cur_state (ssm)) { case CAPSM_1000_INIT: + clear_capture_rows (self); self->rowbuf_offset = -1; self->num_rows = 0; self->wraparounds = -1; @@ -986,6 +1535,8 @@ capsm_1000_run_state (FpiSsm *ssm, FpDevice *_dev) self->num_nonblank = 0; self->finger_state = FINGER_DETECTED; self->last_seqnum = 16383; + self->marker_window = 0; + self->marker_skip = 0; self->killing_transfers = 0; fpi_ssm_next_state (ssm); break; @@ -1008,6 +1559,7 @@ capsm_1001_run_state (FpiSsm *ssm, FpDevice *_dev) switch (fpi_ssm_get_cur_state (ssm)) { case CAPSM_1001_INIT: + clear_capture_rows (self); self->rowbuf_offset = -1; self->num_rows = 0; self->wraparounds = -1; @@ -1015,6 +1567,8 @@ capsm_1001_run_state (FpiSsm *ssm, FpDevice *_dev) self->num_nonblank = 0; self->finger_state = AWAIT_FINGER; self->last_seqnum = 16383; + self->marker_window = 0; + self->marker_skip = 0; self->killing_transfers = 0; fpi_ssm_next_state (ssm); break; @@ -1053,6 +1607,7 @@ capsm_1002_run_state (FpiSsm *ssm, FpDevice *_dev) switch (fpi_ssm_get_cur_state (ssm)) { case CAPSM_1002_INIT: + clear_capture_rows (self); self->rowbuf_offset = -1; self->num_rows = 0; self->wraparounds = -1; @@ -1060,17 +1615,27 @@ capsm_1002_run_state (FpiSsm *ssm, FpDevice *_dev) self->num_nonblank = 0; self->finger_state = FINGER_DETECTED; self->last_seqnum = 16383; + self->marker_window = 0; + self->marker_skip = 0; + self->finger_start_row_1002 = -1; + self->detail_rows_1002 = 0; + self->have_avg_row_1002 = FALSE; + self->early_finger_irq_count_1002 = 0; self->killing_transfers = 0; fpi_ssm_next_state (ssm); break; - case CAPSM_1002_FIRE_BULK: - capsm_fire_bulk (ssm, _dev); + case CAPSM_1002_STREAM_SETUP: + sm_write_regs_data (ssm, _dev, capsm_1002_writev_data, G_N_ELEMENTS (capsm_1002_writev_data)); break; case CAPSM_1002_WRITEV: sm_write_regs (ssm, _dev, capsm_1002_writev, G_N_ELEMENTS (capsm_1002_writev)); break; + + case CAPSM_1002_FIRE_BULK: + capsm_fire_bulk (ssm, _dev); + break; } } @@ -1196,7 +1761,7 @@ initsm_1000_run_state (FpiSsm *ssm, FpDevice *_dev) { case INITSM_1000_WRITEV_1: if (FPI_DEVICE_UPEKSONLY (_dev)->dev_model == UPEKSONLY_1002) - sm_write_regs (ssm, _dev, initsm_1002_writev_1, G_N_ELEMENTS (initsm_1002_writev_1)); + sm_write_regs_data (ssm, _dev, initsm_1002_writev_1, G_N_ELEMENTS (initsm_1002_writev_1)); else sm_write_regs (ssm, _dev, initsm_1000_writev_1, G_N_ELEMENTS (initsm_1000_writev_1)); break; @@ -1232,16 +1797,6 @@ initsm_1001_run_state (FpiSsm *ssm, FpDevice *_dev) /***** CAPTURE LOOP *****/ -enum loopsm_states { - LOOPSM_RUN_AWFSM, - LOOPSM_AWAIT_FINGER, - LOOPSM_RUN_CAPSM, - LOOPSM_CAPTURE, - LOOPSM_RUN_DEINITSM, - LOOPSM_FINAL, - LOOPSM_NUM_STATES, -}; - static void loopsm_run_state (FpiSsm *ssm, FpDevice *_dev) { @@ -1292,7 +1847,39 @@ loopsm_run_state (FpiSsm *ssm, FpDevice *_dev) } break; + case LOOPSM_DELAY_AWAIT_FINGER: + if (self->deactivating) + { + fpi_ssm_mark_completed (ssm); + break; + } + + if (self->dev_model == UPEKSONLY_1002) + fpi_ssm_next_state_delayed (ssm, AWAIT_FINGER_SETTLE_DELAY_MS_1002); + else + fpi_ssm_next_state (ssm); + break; + + case LOOPSM_STATUS_READ: + if (self->deactivating) + { + fpi_ssm_mark_completed (ssm); + break; + } + + if (self->dev_model == UPEKSONLY_1002) + sm_read_status (ssm, dev); + else + fpi_ssm_next_state (ssm); + break; + case LOOPSM_AWAIT_FINGER: + if (self->deactivating) + { + fpi_ssm_mark_completed (ssm); + break; + } + switch (self->dev_model) { case UPEKSONLY_1001: @@ -1373,7 +1960,10 @@ loopsm_run_state (FpiSsm *ssm, FpDevice *_dev) break; case LOOPSM_FINAL: - fpi_ssm_jump_to_state (ssm, LOOPSM_RUN_AWFSM); + if (self->deactivating) + fpi_ssm_mark_completed (ssm); + else + fpi_ssm_jump_to_state (ssm, LOOPSM_RUN_AWFSM); break; } @@ -1388,6 +1978,7 @@ deactivate_done (FpImageDevice *dev, GError *error) G_DEBUG_HERE (); free_img_transfers (self); + g_clear_object (&self->await_cancellable); g_free (self->rowbuf); self->rowbuf = NULL; @@ -1404,7 +1995,16 @@ dev_deactivate (FpImageDevice *dev) if (!self->capturing) { - deactivate_done (dev, NULL); + if (self->loopsm) + { + self->deactivating = TRUE; + if (self->await_cancellable) + g_cancellable_cancel (self->await_cancellable); + } + else + { + deactivate_done (dev, NULL); + } return; } @@ -1421,6 +2021,8 @@ loopsm_complete (FpiSsm *ssm, FpDevice *_dev, GError *error) FpiDeviceUpeksonly *self = FPI_DEVICE_UPEKSONLY (_dev); + self->loopsm = NULL; + if (self->deactivating) { deactivate_done (dev, error); @@ -1591,20 +2193,29 @@ dev_init (FpImageDevice *dev) self->assembling_ctx.resolution = 8; self->assembling_ctx.median_filter_size = 25; self->assembling_ctx.max_search_offset = 30; - self->assembling_ctx.get_deviation = upeksonly_get_deviation2; - self->assembling_ctx.get_pixel = upeksonly_get_pixel; self = FPI_DEVICE_UPEKSONLY (dev); self->dev_model = (int) fpi_device_get_driver_data (FP_DEVICE (dev)); switch (self->dev_model) { case UPEKSONLY_1000: - case UPEKSONLY_1002: + self->assembling_ctx.get_deviation = upeksonly_get_deviation2; + self->assembling_ctx.get_pixel = upeksonly_get_pixel; self->img_width = IMG_WIDTH_1000; self->assembling_ctx.line_width = IMG_WIDTH_1000; break; + case UPEKSONLY_1002: + self->assembling_ctx.get_deviation = upeksonly_get_deviation_simple; + self->assembling_ctx.get_pixel = upeksonly_get_pixel_simple; + self->img_width = IMG_WIDTH_1002; + self->assembling_ctx.line_width = IMG_WIDTH_1002; + fpi_image_device_set_bz3_threshold (dev, 7); + break; + case UPEKSONLY_1001: + self->assembling_ctx.get_deviation = upeksonly_get_deviation2; + self->assembling_ctx.get_pixel = upeksonly_get_pixel; self->img_width = IMG_WIDTH_1001; self->assembling_ctx.line_width = IMG_WIDTH_1001; @@ -1613,6 +2224,8 @@ dev_init (FpImageDevice *dev) break; case UPEKSONLY_2016: + self->assembling_ctx.get_deviation = upeksonly_get_deviation2; + self->assembling_ctx.get_pixel = upeksonly_get_pixel; self->img_width = IMG_WIDTH_2016; self->assembling_ctx.line_width = IMG_WIDTH_2016; break; diff --git a/libfprint/drivers/upeksonly.h b/libfprint/drivers/upeksonly.h index 596249ff..bd7c814b 100644 --- a/libfprint/drivers/upeksonly.h +++ b/libfprint/drivers/upeksonly.h @@ -25,6 +25,12 @@ #define IMG_WIDTH_2016 288 #define IMG_WIDTH_1000 288 +#define IMG_WIDTH_1002 162 +#define IMG_CROP_X_1002 18 +#define IMG_CROP_WIDTH_1002 108 +#define IMG_CROP_ROWS_1002 992 +#define IMG_CROP_OFFSET_1002 0 +#define IMG_HEIGHT_SCALE_1002 4 #define IMG_WIDTH_1001 216 struct sonly_regwrite @@ -33,6 +39,15 @@ struct sonly_regwrite guint8 value; }; +struct sonly_regwrite_data +{ + guint8 reg; + guint8 len; + guint8 value[8]; +}; + +#define UPEKSONLY_READ(reg, len) { reg, 0, { len } } + /***** AWAIT FINGER *****/ static const struct sonly_regwrite awfsm_2016_writev_1[] = { @@ -55,6 +70,15 @@ static const struct sonly_regwrite awfsm_1000_writev_1[] = { { 0x10, 0x00 }, { 0x11, 0xbf }, }; +static const struct sonly_regwrite awfsm_1002_writev_1[] = { + /* Windows sets up the 1002 wait path with this 3e/1a prelude before + * enabling the finger detector. */ + { 0x49, 0x08 }, + { 0x3e, 0x45 }, { 0x3e, 0x92 }, { 0x3e, 0xd9 }, { 0x3e, 0x3a }, + { 0x3e, 0x99 }, { 0x3e, 0x8c }, { 0x3e, 0xc6 }, { 0x3e, 0xb9 }, + { 0x1a, 0x01 }, +}; + static const struct sonly_regwrite awfsm_2016_writev_2[] = { { 0x01, 0xc6 }, { 0x0c, 0x13 }, { 0x0d, 0x0d }, { 0x0e, 0x0e }, { 0x0f, 0x0d }, { 0x0b, 0x00 }, @@ -66,7 +90,9 @@ static const struct sonly_regwrite awfsm_1000_writev_2[] = { }; static const struct sonly_regwrite awfsm_1002_writev_2[] = { - /* Windows enables 147e:1002 finger detection with bit 0x01 clear here. */ + /* Windows arms 147e:1002 wait mode with the 1a/0b/49 prelude followed by + * the actual detector-enable writes on 0x30/0x15. */ + { 0x1a, 0x03 }, { 0x0b, 0x00 }, { 0x49, 0x0c }, { 0x30, 0xe1 }, { 0x15, 0x05 }, { 0x15, 0x85 }, }; @@ -98,13 +124,49 @@ static const struct sonly_regwrite capsm_1000_writev[] = { }; static const struct sonly_regwrite capsm_1002_writev[] = { - /* 147e:1002 appears close to the 1000 family for init/interrupts, but it - * does not start streaming image data with the plain 1000/2016 capture - * writes. This sequence matches the Windows driver immediately before bulk - * image packets start arriving on endpoint 0x81. - */ - { 0x15, 0x25 }, { 0x30, 0xe0 }, { 0x09, 0x23 }, { 0x13, 0x75 }, - { 0x0b, 0x80 }, + /* BSAPI submits bulk URBs before these capture-start writes. */ + { 0x09, 0x22 }, { 0x13, 0x75 }, { 0x0b, 0x80 }, +}; + +static const struct sonly_regwrite_data capsm_1002_writev_data[] = { + /* Finger-detect mode changes security state; replay the final BSAPI + * key/tail immediately before the low-security streaming setup. */ + { 0x49, 1, { 0x08 } }, + { 0x3e, 1, { 0x15 } }, { 0x3e, 1, { 0x5c } }, + { 0x3e, 1, { 0x27 } }, { 0x3e, 1, { 0xf5 } }, + { 0x3e, 1, { 0xfb } }, { 0x3e, 1, { 0x82 } }, + { 0x3e, 1, { 0x25 } }, { 0x3e, 1, { 0x61 } }, + { 0x09, 1, { 0x2a } }, { 0x1a, 1, { 0x01 } }, + UPEKSONLY_READ (0x0b, 8), { 0x0b, 1, { 0x00 } }, + UPEKSONLY_READ (0x13, 8), { 0x13, 1, { 0x45 } }, + { 0x04, 1, { 0x00 } }, { 0x05, 1, { 0x00 } }, + + /* Replay the BSAPI low-security streaming setup after finger-detect mode. */ + { 0x7e, 1, { 0x0f } }, + { 0x6e, 1, { 0x20 } }, { 0x6e, 1, { 0x00 } }, + { 0x6f, 8, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x77, 7, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { 0x74, 8, { 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff } }, + { 0x7c, 1, { 0x07 } }, { 0x6e, 1, { 0x10 } }, + { 0x7e, 1, { 0x05 } }, { 0x6e, 1, { 0x1f } }, + { 0x7e, 1, { 0x0f } }, { 0x6e, 1, { 0x10 } }, + { 0x7e, 1, { 0x05 } }, { 0x1a, 1, { 0x03 } }, + UPEKSONLY_READ (0x17, 8), UPEKSONLY_READ (0x0a, 8), + UPEKSONLY_READ (0x03, 8), UPEKSONLY_READ (0x00, 8), + UPEKSONLY_READ (0x01, 8), UPEKSONLY_READ (0x30, 8), + UPEKSONLY_READ (0x15, 8), UPEKSONLY_READ (0x12, 8), + UPEKSONLY_READ (0x25, 8), UPEKSONLY_READ (0x31, 8), + UPEKSONLY_READ (0x08, 8), UPEKSONLY_READ (0x07, 8), + UPEKSONLY_READ (0x24, 8), UPEKSONLY_READ (0x26, 8), + UPEKSONLY_READ (0x1d, 8), + { 0x00, 2, { 0x53, 0xee } }, + { 0x03, 1, { 0x27 } }, + { 0x07, 8, { 0x00, 0x00, 0x2a, 0x20, 0x00, 0x0f, 0x09, 0x0a } }, + { 0x0f, 3, { 0x09, 0x00, 0x8f } }, + { 0x1d, 1, { 0x02 } }, + { 0x25, 4, { 0x10, 0x00, 0x8f, 0x23 } }, + { 0x31, 1, { 0x44 } }, + { 0x07, 1, { 0x10 } }, }; static const struct sonly_regwrite capsm_1001_writev_1[] = { @@ -206,14 +268,84 @@ static const struct sonly_regwrite initsm_1000_writev_1[] = { { 0x0b, 0x00 }, { 0x08, 0x00 }, /* Initialize capture control registers */ }; -static const struct sonly_regwrite initsm_1002_writev_1[] = { - /* Captured from the Windows 147e:1002 driver before finger detection. */ - { 0x49, 0x08 }, - { 0x3e, 0x3d }, { 0x3e, 0x18 }, { 0x3e, 0x38 }, { 0x3e, 0x63 }, - { 0x3e, 0x9d }, { 0x3e, 0x5c }, { 0x3e, 0xb1 }, { 0x3e, 0x1f }, - { 0x1a, 0x01 }, { 0x1a, 0x03 }, { 0x0b, 0x00 }, { 0x49, 0x0c }, +#define UPEKSONLY_1002_CAL_PASS \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x0a } }, \ + { 0x47, 1, { 0x00 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x0a } }, \ + { 0x47, 1, { 0x00 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x0a } }, \ + { 0x47, 1, { 0x00 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x02 } }, \ + { 0x47, 1, { 0x02 } }, { 0x47, 1, { 0x0a } }, \ + { 0x47, 1, { 0x00 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } }, { 0x47, 1, { 0x04 } }, \ + { 0x47, 1, { 0x04 } } + +static const struct sonly_regwrite_data initsm_1002_writev_1[] = { + /* Captured from Linux BSAPI 4.0 in low sensor-security mode. */ + UPEKSONLY_READ (0x4b, 8), + { 0x4b, 1, { 0x01 } }, { 0x4f, 1, { 0x06 } }, + { 0x4f, 1, { 0x05 } }, { 0x4f, 1, { 0x04 } }, + { 0x4b, 1, { 0x00 } }, + UPEKSONLY_READ (0x49, 8), + { 0x3e, 1, { 0x45 } }, { 0x3e, 1, { 0x92 } }, + { 0x3e, 1, { 0xd9 } }, { 0x3e, 1, { 0x3a } }, + { 0x3e, 1, { 0x99 } }, { 0x3e, 1, { 0x8c } }, + { 0x3e, 1, { 0xc6 } }, { 0x3e, 1, { 0xb9 } }, + UPEKSONLY_READ (0x1a, 8), UPEKSONLY_READ (0x09, 8), + { 0x09, 1, { 0x2a } }, { 0x1a, 1, { 0x01 } }, + { 0x09, 1, { 0x22 } }, { 0x1a, 1, { 0x03 } }, + { 0x49, 1, { 0x09 } }, + UPEKSONLY_1002_CAL_PASS, + { 0x49, 1, { 0x08 } }, + { 0x3e, 1, { 0x2e } }, { 0x3e, 1, { 0xfe } }, + { 0x3e, 1, { 0x14 } }, { 0x3e, 1, { 0xd5 } }, + { 0x3e, 1, { 0x33 } }, { 0x3e, 1, { 0x34 } }, + { 0x3e, 1, { 0xc5 } }, { 0x3e, 1, { 0xbf } }, + { 0x09, 1, { 0x2a } }, { 0x1a, 1, { 0x01 } }, + { 0x09, 1, { 0x22 } }, { 0x1a, 1, { 0x03 } }, + { 0x49, 1, { 0x09 } }, + UPEKSONLY_1002_CAL_PASS, + { 0x49, 1, { 0x08 } }, + { 0x3e, 1, { 0x15 } }, { 0x3e, 1, { 0x5c } }, + { 0x3e, 1, { 0x27 } }, { 0x3e, 1, { 0xf5 } }, + { 0x3e, 1, { 0xfb } }, { 0x3e, 1, { 0x82 } }, + { 0x3e, 1, { 0x25 } }, { 0x3e, 1, { 0x61 } }, + { 0x09, 1, { 0x2a } }, { 0x1a, 1, { 0x01 } }, + UPEKSONLY_READ (0x0b, 8), { 0x0b, 1, { 0x00 } }, + UPEKSONLY_READ (0x13, 8), { 0x13, 1, { 0x45 } }, + /* Do not preload the capture stream here. The capture-specific low-security + * programming is replayed in CAPSM once a real finger interrupt arrives. */ + { 0x04, 1, { 0x00 } }, { 0x05, 1, { 0x00 } }, }; +#undef UPEKSONLY_1002_CAL_PASS + static const struct sonly_regwrite initsm_1001_writev_1[] = { { 0x4a, 0x9d }, { 0x4f, 0x06 }, From 03696b70d1e6280f7447469064de5248fa76efb1 Mon Sep 17 00:00:00 2001 From: Nikolay Metchev Date: Fri, 1 May 2026 08:55:38 +0000 Subject: [PATCH 4/8] upeksonly: apply formatting --- libfprint/drivers/upeksonly.c | 46 ++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/libfprint/drivers/upeksonly.c b/libfprint/drivers/upeksonly.c index c84c25c6..3256b7d5 100644 --- a/libfprint/drivers/upeksonly.c +++ b/libfprint/drivers/upeksonly.c @@ -108,7 +108,7 @@ struct _FpiDeviceUpeksonly int wraparounds; int num_blank; int num_nonblank; - enum sonly_fs finger_state; + enum sonly_fs finger_state; int last_seqnum; guint32 marker_window; int marker_skip; @@ -399,7 +399,7 @@ update_1002_finger_detection (FpiDeviceUpeksonly *self) { self->finger_start_row_1002 = MAX (0, (int) self->num_rows - - (int) self->detail_rows_1002 * IMG_HEIGHT_SCALE_1002); + (int) self->detail_rows_1002 * IMG_HEIGHT_SCALE_1002); fp_dbg ("detected 1002 finger data at row %d", self->finger_start_row_1002); } } @@ -413,8 +413,8 @@ update_1002_finger_detection (FpiDeviceUpeksonly *self) return self->finger_start_row_1002 >= 0 && self->num_rows >= (guint) self->finger_start_row_1002 + - IMG_CROP_OFFSET_1002 + - IMG_CROP_ROWS_1002; + IMG_CROP_OFFSET_1002 + + IMG_CROP_ROWS_1002; } static void @@ -736,10 +736,8 @@ handle_packet_1002 (FpImageDevice *dev, unsigned char *data) } if (self->rowbuf) - { - if (self->rowbuf_offset < self->img_width) - self->rowbuf[self->rowbuf_offset++] = byte; - } + if (self->rowbuf_offset < self->img_width) + self->rowbuf[self->rowbuf_offset++] = byte; self->marker_window = ((self->marker_window << 8) | byte) & 0xffffff; @@ -757,7 +755,9 @@ handle_packet_1002 (FpImageDevice *dev, unsigned char *data) return; } else - g_clear_pointer (&self->rowbuf, g_free); + { + g_clear_pointer (&self->rowbuf, g_free); + } } self->rowbuf = g_malloc0 (self->img_width); @@ -779,11 +779,13 @@ img_data_cb (FpiUsbTransfer *transfer, FpDevice *device, self->num_flying--; if (self->dev_model != UPEKSONLY_1002) - fp_dbg ("bulk cb: error=%s actual_length=%zu num_flying=%d capturing=%d", - error ? error->message : "none", - transfer->actual_length, - self->num_flying, - self->capturing); + { + fp_dbg ("bulk cb: error=%s actual_length=%zu num_flying=%d capturing=%d", + error ? error->message : "none", + transfer->actual_length, + self->num_flying, + self->capturing); + } if (self->killing_transfers) { @@ -837,10 +839,12 @@ img_data_cb (FpiUsbTransfer *transfer, FpDevice *device, if (is_capturing (self)) { if (self->dev_model != UPEKSONLY_1002) - fp_dbg ("re-submitting bulk transfer actual_length=%zu rows=%u finger_state=%d", - transfer->actual_length, - self->num_rows, - self->finger_state); + { + fp_dbg ("re-submitting bulk transfer actual_length=%zu rows=%u finger_state=%d", + transfer->actual_length, + self->num_rows, + self->finger_state); + } fpi_usb_transfer_submit (fpi_usb_transfer_ref (transfer), 0, self->img_cancellable, @@ -871,7 +875,8 @@ struct write_regs_data_data size_t regs_written; }; -static void sm_await_intr (FpiSsm *ssm, FpImageDevice *dev); +static void sm_await_intr (FpiSsm *ssm, + FpImageDevice *dev); static void write_regs_finished (struct write_regs_data *wrdata, GError *error) @@ -951,7 +956,8 @@ sm_write_regs (FpiSsm *ssm, } static void write_regs_data_iterate (struct write_regs_data_data *wrdata); -static void write_regs_data_finished (struct write_regs_data_data *wrdata, GError *error); +static void write_regs_data_finished (struct write_regs_data_data *wrdata, + GError *error); static void write_regs_data_readback_cb (FpiUsbTransfer *transfer, FpDevice *device, From c5896e0f24f0182f126b963a91a9db981bb889ab Mon Sep 17 00:00:00 2001 From: Nikolay Metchev Date: Fri, 1 May 2026 09:09:17 +0000 Subject: [PATCH 5/8] upeksonly: fix scan-build and analyzer issues --- .gitlab-ci.yml | 2 +- libfprint/drivers/fpcmoc/fpc.c | 5 ++--- libfprint/drivers/upeksonly.c | 12 ++++++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3b2fc93..dad6523a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -110,7 +110,7 @@ test_valgrind: extends: - .standard_job script: - - meson setup _build -Ddrivers=all + - meson setup _build -Ddrivers=all -Dintrospection=false - meson compile -C _build - meson test -C _build --print-errorlogs --no-stdsplit --setup=valgrind artifacts: diff --git a/libfprint/drivers/fpcmoc/fpc.c b/libfprint/drivers/fpcmoc/fpc.c index 466a8096..eb2729db 100644 --- a/libfprint/drivers/fpcmoc/fpc.c +++ b/libfprint/drivers/fpcmoc/fpc.c @@ -272,15 +272,14 @@ static void fpc_cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) { FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (dev); - CommandData *data = fpi_ssm_get_data (ssm); self->cmd_ssm = NULL; /* Notify about the SSM failure from here instead. */ if (error) { fp_err ("%s error: %s ", G_STRFUNC, error->message); - if (data->callback) - data->callback (self, NULL, error); + if (fpi_ssm_get_data (ssm)->callback) + fpi_ssm_get_data (ssm)->callback (self, NULL, error); } } diff --git a/libfprint/drivers/upeksonly.c b/libfprint/drivers/upeksonly.c index 3256b7d5..e8044d6b 100644 --- a/libfprint/drivers/upeksonly.c +++ b/libfprint/drivers/upeksonly.c @@ -292,7 +292,7 @@ handoff_img (FpImageDevice *dev) guint cropped_rows; guint out_height; guint row_count = g_slist_length (self->rows); - g_autofree guchar **rows = g_new (guchar *, row_count); + g_autofree guchar **rows = g_new0 (guchar *, row_count); guint row_idx = 0; if (row_count != self->num_rows) @@ -331,7 +331,15 @@ handoff_img (FpImageDevice *dev) guint src_y = crop_start + y * IMG_HEIGHT_SCALE_1002 + i; guint src_x = IMG_CROP_X_1002 + x; - sum += rows[src_y][src_x]; + if (src_y >= row_count) + continue; + + guchar *src_row = rows[src_y]; + + if (!src_row) + continue; + + sum += src_row[src_x]; } img->data[y * IMG_CROP_WIDTH_1002 + x] = sum / IMG_HEIGHT_SCALE_1002; From 3e102e5ca0dab7bdebefec89ef69f1a09b8deb8e Mon Sep 17 00:00:00 2001 From: Nikolay Metchev Date: Wed, 6 May 2026 09:07:31 +0000 Subject: [PATCH 6/8] fpcmoc: fix analyzer cast warning --- libfprint/drivers/fpcmoc/fpc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libfprint/drivers/fpcmoc/fpc.c b/libfprint/drivers/fpcmoc/fpc.c index eb2729db..1b6af881 100644 --- a/libfprint/drivers/fpcmoc/fpc.c +++ b/libfprint/drivers/fpcmoc/fpc.c @@ -277,9 +277,11 @@ fpc_cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) /* Notify about the SSM failure from here instead. */ if (error) { + CommandData *data = fpi_ssm_get_data (ssm); + fp_err ("%s error: %s ", G_STRFUNC, error->message); - if (fpi_ssm_get_data (ssm)->callback) - fpi_ssm_get_data (ssm)->callback (self, NULL, error); + if (data->callback) + data->callback (self, NULL, error); } } From 60ef19c8fdf48c26addaed6760d4fee15745329d Mon Sep 17 00:00:00 2001 From: Nikolay Metchev Date: Wed, 6 May 2026 09:14:05 +0000 Subject: [PATCH 7/8] fpcmoc: silence scan-build dead store --- data/autosuspend.hwdb | 2 +- libfprint/drivers/fpcmoc/fpc.c | 6 ++---- libfprint/fprint-list-udev-hwdb.c | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index fe682714..6ab0a23b 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -293,6 +293,7 @@ usb:v06CBp01A0* # Supported by libfprint driver upeksonly usb:v147Ep2016* usb:v147Ep1000* +usb:v147Ep1002* usb:v147Ep1001* ID_AUTOSUSPEND=1 ID_PERSIST=0 @@ -443,7 +444,6 @@ usb:v138Ap0097* usb:v138Ap009D* usb:v138Ap00AB* usb:v138Ap00A6* -usb:v147Ep1002* usb:v1491p0088* usb:v16D1p1027* usb:v1C7Ap0300* diff --git a/libfprint/drivers/fpcmoc/fpc.c b/libfprint/drivers/fpcmoc/fpc.c index 1b6af881..4412ecb5 100644 --- a/libfprint/drivers/fpcmoc/fpc.c +++ b/libfprint/drivers/fpcmoc/fpc.c @@ -277,11 +277,9 @@ fpc_cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) /* Notify about the SSM failure from here instead. */ if (error) { - CommandData *data = fpi_ssm_get_data (ssm); - fp_err ("%s error: %s ", G_STRFUNC, error->message); - if (data->callback) - data->callback (self, NULL, error); + if (((CommandData *) fpi_ssm_get_data (ssm))->callback) + ((CommandData *) fpi_ssm_get_data (ssm))->callback (self, NULL, error); } } diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index 6e2adb04..21b41943 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -117,7 +117,6 @@ static const FpIdEntry allowlist_id_table[] = { { .vid = 0x138a, .pid = 0x009d }, { .vid = 0x138a, .pid = 0x00ab }, { .vid = 0x138a, .pid = 0x00a6 }, - { .vid = 0x147e, .pid = 0x1002 }, { .vid = 0x1491, .pid = 0x0088 }, { .vid = 0x16d1, .pid = 0x1027 }, { .vid = 0x1c7a, .pid = 0x0300 }, From b004d110e44fc9674ec7f395745868e615edd180 Mon Sep 17 00:00:00 2001 From: Nikolay Metchev Date: Wed, 6 May 2026 09:19:29 +0000 Subject: [PATCH 8/8] fpcmoc: silence scan-build dead store