libfprint/drivers/secugen.c
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * SecuGen Hamster Pro 20 (FDU05) driver for libfprint | ||
| 3 | * Copyright (C) 2026 | ||
| 4 | * | ||
| 5 | * Protocol reverse-engineered from USB packet captures. | ||
| 6 | * | ||
| 7 | * SIDO020A sensor configured via I2C-over-USB control transfers. | ||
| 8 | * Raw sensor output: 956x688 grayscale via USB bulk (657KB). | ||
| 9 | * Downsampled to 300x400 at 500 DPI for libfprint. | ||
| 10 | * | ||
| 11 | * This library is free software; you can redistribute it and/or | ||
| 12 | * modify it under the terms of the GNU Lesser General Public | ||
| 13 | * License as published by the Free Software Foundation; either | ||
| 14 | * version 2.1 of the License, or (at your option) any later version. | ||
| 15 | * | ||
| 16 | * This library is distributed in the hope that it will be useful, | ||
| 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 19 | * Lesser General Public License for more details. | ||
| 20 | * | ||
| 21 | * You should have received a copy of the GNU Lesser General Public | ||
| 22 | * License along with this library; if not, write to the Free Software | ||
| 23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 24 | */ | ||
| 25 | |||
| 26 | #define FP_COMPONENT "secugen" | ||
| 27 | |||
| 28 | #include <string.h> | ||
| 29 | |||
| 30 | #include "drivers_api.h" | ||
| 31 | |||
| 32 | /* ---- Device constants ---- */ | ||
| 33 | |||
| 34 | /* Final output image (what libfprint sees) */ | ||
| 35 | #define SECUGEN_IMG_WIDTH 300 | ||
| 36 | #define SECUGEN_IMG_HEIGHT 400 | ||
| 37 | #define SECUGEN_IMG_SIZE (SECUGEN_IMG_WIDTH * SECUGEN_IMG_HEIGHT) /* 120000 */ | ||
| 38 | |||
| 39 | /* Raw sensor array (full SIDO020A readout) */ | ||
| 40 | #define SECUGEN_RAW_WIDTH 956 | ||
| 41 | #define SECUGEN_RAW_HEIGHT 688 | ||
| 42 | #define SECUGEN_RAW_SIZE (SECUGEN_RAW_WIDTH * SECUGEN_RAW_HEIGHT) /* 657728 */ | ||
| 43 | #define SECUGEN_BULK_BUF_SIZE 657920 /* Actual USB stream: 688*956 + 192 trailing */ | ||
| 44 | /* Per-URB read size for the frame stream. Kept below the usbmon capture cap | ||
| 45 | * (ring_size/5 ~= 245KB) and a multiple of the 512-byte bulk max-packet size, | ||
| 46 | * so the full frame is recorded by the standard pcap-based test capture as a | ||
| 47 | * sequence of complete URBs rather than one truncated transfer. 128 * 512. */ | ||
| 48 | #define SECUGEN_BULK_CHUNK 65536 | ||
| 49 | #define SECUGEN_DPI 500 | ||
| 50 | #define SECUGEN_PPMM 19.685 /* 500 DPI / 25.4 mm/in */ | ||
| 51 | #define SECUGEN_EP_DATA 0x82 /* Bulk IN endpoint */ | ||
| 52 | |||
| 53 | /* ---- Vendor control transfer bRequest codes ---- */ | ||
| 54 | |||
| 55 | #define SECUGEN_REQ_START_STREAM 1 | ||
| 56 | #define SECUGEN_REQ_STOP_STREAM 2 | ||
| 57 | #define SECUGEN_REQ_START_CAPTURE 5 | ||
| 58 | #define SECUGEN_REQ_READ_FW_DATA 8 | ||
| 59 | #define SECUGEN_REQ_LED_CONTROL 17 | ||
| 60 | #define SECUGEN_REQ_GET_STATUS 22 | ||
| 61 | #define SECUGEN_REQ_I2C_REG 34 | ||
| 62 | #define SECUGEN_REQ_GET_DEVICE_ID 37 | ||
| 63 | #define SECUGEN_REQ_SET_EXPOSURE 64 | ||
| 64 | |||
| 65 | /* ---- I2C / SIDO020A sensor ---- */ | ||
| 66 | |||
| 67 | #define SECUGEN_I2C_ADDR 0x0037 /* wValue for I2C transfers */ | ||
| 68 | |||
| 69 | /* ---- Exposure values ---- */ | ||
| 70 | |||
| 71 | #define SECUGEN_EXPOSURE_INIT 1000 /* Initial calibration exposure */ | ||
| 72 | #define SECUGEN_EXPOSURE_NORMAL 1116 /* Normal capture exposure */ | ||
| 73 | |||
| 74 | /* ---- Calibration / FW data ---- */ | ||
| 75 | |||
| 76 | #define SECUGEN_FW_DATA_START 0x2000 | ||
| 77 | #define SECUGEN_FW_DATA_CHUNK 4096 | ||
| 78 | #define SECUGEN_FW_DATA_CHUNKS 6 | ||
| 79 | #define SECUGEN_FW_DATA_LAST_LEN 2462 /* Last chunk is partial */ | ||
| 80 | #define SECUGEN_FW_DATA_SIZE 22942 /* Total FW data: 4*4096 + 2*3072 ... */ | ||
| 81 | |||
| 82 | /* Flat-field reference image stored in FW data at struct offset 0x1df8 */ | ||
| 83 | #define SECUGEN_REF_WIDTH 150 | ||
| 84 | #define SECUGEN_REF_HEIGHT 100 | ||
| 85 | #define SECUGEN_REF_SIZE (SECUGEN_REF_WIDTH * SECUGEN_REF_HEIGHT) /* 15000 */ | ||
| 86 | #define SECUGEN_REF_OFFSET 0x1df8 /* Offset of ref image in FW data */ | ||
| 87 | #define SECUGEN_BLEND_CAL_VAL 240 /* Target uniform brightness */ | ||
| 88 | |||
| 89 | /* FW data offsets for image processing parameters */ | ||
| 90 | #define SECUGEN_BLC_OFFSETS_FW 0x0e /* 16 × int16 BLC region offsets */ | ||
| 91 | #define SECUGEN_CAL_VALUE_FW 0x5892 /* uint16 blend target (usually 240) */ | ||
| 92 | #define SECUGEN_SHARPEN_THRESH_FW 0x599C /* uint8 sharpening threshold */ | ||
| 93 | #define SECUGEN_SHARPEN_AMOUNT_FW 0x599D /* uint8 sharpening amount */ | ||
| 94 | |||
| 95 | /* ---- Timeouts (ms) ---- */ | ||
| 96 | |||
| 97 | #define SECUGEN_CTRL_TIMEOUT 2000 | ||
| 98 | #define SECUGEN_BULK_TIMEOUT 10000 /* Longer timeout for 657KB bulk read */ | ||
| 99 | #define SECUGEN_FW_READ_TIMEOUT 5000 | ||
| 100 | #define SECUGEN_FINGER_POLL_MS 200 /* Finger detection polling interval */ | ||
| 101 | #define SECUGEN_FINGER_THRESHOLD 25 /* Mean brightness above this = finger present */ | ||
| 102 | |||
| 103 | /* ---- I2C register init table entry ---- */ | ||
| 104 | |||
| 105 | struct secugen_reg_entry | ||
| 106 | { | ||
| 107 | guint8 reg; | ||
| 108 | guint8 val; | ||
| 109 | }; | ||
| 110 | |||
| 111 | /* ---- Driver instance ---- */ | ||
| 112 | |||
| 113 | struct _FpiDeviceSecugen | ||
| 114 | { | ||
| 115 | FpImageDevice parent; | ||
| 116 | |||
| 117 | /* Initialization tracking */ | ||
| 118 | int init_reg_idx; /* Current I2C register being written */ | ||
| 119 | int fw_read_idx; /* Current calibration data chunk */ | ||
| 120 | guint8 iface_num; /* Claimed USB interface number */ | ||
| 121 | |||
| 122 | /* Finger detection */ | ||
| 123 | GSource *finger_poll_source; /* Detection/finger-off timeout source */ | ||
| 124 | |||
| 125 | /* Calibration / flat-field correction */ | ||
| 126 | guint8 *cal_raw; /* Background frame at raw sensor res (956x688) */ | ||
| 127 | guint8 *fw_data; /* Accumulated FW data from device (22942 bytes) */ | ||
| 128 | gsize fw_data_len; /* Bytes accumulated so far */ | ||
| 129 | guint8 *ref_image; /* Resized 300x400 flat-field reference image */ | ||
| 130 | gboolean has_ref_image; /* Whether ref_image was successfully extracted */ | ||
| 131 | |||
| 132 | /* BLC band compensation */ | ||
| 133 | gint16 blc_offsets[16]; /* Factory-calibrated region offsets from FW */ | ||
| 134 | gboolean has_blc_offsets; /* Whether BLC offsets were extracted */ | ||
| 135 | |||
| 136 | /* Image processing params from FW */ | ||
| 137 | guint16 cal_value; /* Blend target brightness (from FW, default 240) */ | ||
| 138 | guint8 sharpen_threshold; /* Min gradient for sharpening */ | ||
| 139 | guint8 sharpen_amount; /* Max gradient contribution */ | ||
| 140 | guint8 sharpen_limit; /* Overall scaling factor (/10) */ | ||
| 141 | gboolean sharpen_enabled; /* Whether post-resize sharpening is active */ | ||
| 142 | |||
| 143 | /* Bulk read buffer for capture/detect frames */ | ||
| 144 | guint8 *bulk_buffer; /* Reusable buffer for 657KB bulk reads */ | ||
| 145 | |||
| 146 | /* Chunked frame-read state (see secugen_read_frame) */ | ||
| 147 | guint8 *bulk_dest; /* Destination of the in-progress frame read */ | ||
| 148 | gsize bulk_offset; /* Bytes accumulated into bulk_dest so far */ | ||
| 149 | |||
| 150 | /* Activation / teardown state */ | ||
| 151 | gboolean deactivating; /* Deactivate requested while an SSM runs */ | ||
| 152 | int ssm_count; /* Number of init/detect/capture SSMs in flight. | ||
| 153 | * A counter (not a flag): the detect->capture | ||
| 154 | * handoff is synchronous, so a capture SSM is | ||
| 155 | * started before the detect SSM's completion | ||
| 156 | * handler runs - both must be tracked. */ | ||
| 157 | |||
| 158 | /* Capture state */ | ||
| 159 | guint16 exposure; /* Current exposure value */ | ||
| 160 | |||
| 161 | /* Device info */ | ||
| 162 | guint8 device_id[30]; | ||
| 163 | |||
| 164 | /* Last register readback */ | ||
| 165 | guint8 last_reg_rd; | ||
| 166 | |||
| 167 | /* Last status */ | ||
| 168 | guint8 last_status[4]; | ||
| 169 | }; | ||
| 170 | |||
| 171 | G_DECLARE_FINAL_TYPE (FpiDeviceSecugen, | ||
| 172 | fpi_device_secugen, | ||
| 173 | FPI, | ||
| 174 | DEVICE_SECUGEN, | ||
| 175 | FpImageDevice); | ||
| 176 | |||
| 177 |
4/6fpi_device_secugen_class_intern_init:
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 125 times.
fpi_device_secugen_get_type:
✓ Branch 2 → 3 taken 125 times.
✓ Branch 2 → 7 taken 24 times.
✓ Branch 4 → 5 taken 125 times.
✗ Branch 4 → 7 not taken.
|
399 | G_DEFINE_TYPE (FpiDeviceSecugen, |
| 178 | fpi_device_secugen, | ||
| 179 | FP_TYPE_IMAGE_DEVICE); | ||
| 180 | |||
| 181 | /* ---- Blend curve LUT (256 bytes) ---- | ||
| 182 | * | ||
| 183 | * Brightness-dependent correction weight for flat-field correction. | ||
| 184 | * Dark pixels (<16) get zero correction, bright pixels (>191) get full | ||
| 185 | * correction, middle range scales linearly. Identical to the table | ||
| 186 | * stored in the device's firmware data block. | ||
| 187 | */ | ||
| 188 | static const guint8 SECUGEN_BLEND_CURVE[256] = { | ||
| 189 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 190 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, | ||
| 191 | 16, 17, 19, 21, 23, 25, 27, 29, 31, 32, 34, 36, 38, 40, 42, 44, | ||
| 192 | 46, 47, 49, 51, 53, 55, 57, 59, 61, 62, 64, 66, 68, 70, 72, 74, | ||
| 193 | 76, 77, 79, 81, 83, 85, 87, 89, 91, 92, 94, 96, 98, 100, 102, 104, | ||
| 194 | 106, 107, 109, 111, 113, 115, 117, 119, 121, 122, 124, 126, 128, 130, 132, 134, | ||
| 195 | 136, 137, 138, 139, 141, 142, 143, 144, 146, 147, 148, 149, 151, 152, 153, 154, | ||
| 196 | 156, 157, 158, 159, 161, 162, 163, 164, 166, 167, 168, 169, 171, 172, 173, 174, | ||
| 197 | 176, 177, 178, 179, 181, 182, 183, 184, 186, 187, 188, 189, 191, 192, 193, 194, | ||
| 198 | 196, 197, 198, 199, 201, 202, 203, 204, 206, 207, 208, 209, 211, 212, 213, 214, | ||
| 199 | 216, 217, 218, 219, 221, 222, 223, 224, 226, 227, 228, 229, 231, 232, 233, 234, | ||
| 200 | 236, 237, 238, 239, 240, 241, 243, 244, 245, 246, 247, 249, 250, 251, 252, 253, | ||
| 201 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, | ||
| 202 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, | ||
| 203 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, | ||
| 204 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, | ||
| 205 | }; | ||
| 206 | |||
| 207 | static void secugen_resize_bilinear (const guint8 *src, | ||
| 208 | int src_w, | ||
| 209 | int src_h, | ||
| 210 | guint8 *dst, | ||
| 211 | int dst_w, | ||
| 212 | int dst_h); | ||
| 213 | |||
| 214 | /* ---- Bilinear resize 150x100 → 300x400 ---- | ||
| 215 | * | ||
| 216 | * The FW stores a low-res (150x100) flat-field reference image captured | ||
| 217 | * during factory calibration. It is resized to 300x400 before being | ||
| 218 | * used for the blend correction. | ||
| 219 | */ | ||
| 220 | static void | ||
| 221 | 1 | secugen_resize_ref_image (const guint8 *src, guint8 *dst) | |
| 222 | { | ||
| 223 | 1 | secugen_resize_bilinear (src, SECUGEN_REF_WIDTH, SECUGEN_REF_HEIGHT, | |
| 224 | dst, SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT); | ||
| 225 | 1 | } | |
| 226 | |||
| 227 | /* ---- SIDO020A I2C register init table ---- */ | ||
| 228 | |||
| 229 | /* | ||
| 230 | * Phase 1: Main init registers (54 regs). | ||
| 231 | * Written first during init. 0xa5-0xa8 are NOT included here -- they are | ||
| 232 | * written later (after a 0x03 rewrite to enable the sensor). | ||
| 233 | * 0xb7-0xb9 come BEFORE 0xa5-0xa8, matching the captured init sequence. | ||
| 234 | */ | ||
| 235 | static const struct secugen_reg_entry sido020a_init_regs[] = { | ||
| 236 | { 0x03, 0x02 }, /* Power/reset control - hold in reset */ | ||
| 237 | { 0x04, 0x81 }, | ||
| 238 | { 0x05, 0x0a }, | ||
| 239 | { 0x08, 0x00 }, | ||
| 240 | { 0x09, 0x11 }, | ||
| 241 | { 0x0a, 0x11 }, | ||
| 242 | { 0x10, 0x11 }, | ||
| 243 | { 0x11, 0x23 }, | ||
| 244 | { 0x12, 0x85 }, | ||
| 245 | { 0x13, 0x00 }, | ||
| 246 | { 0x14, 0x27 }, | ||
| 247 | { 0x16, 0xb6 }, | ||
| 248 | { 0x30, 0x01 }, | ||
| 249 | { 0x31, 0xc0 }, | ||
| 250 | { 0x32, 0x08 }, | ||
| 251 | { 0x41, 0x00 }, | ||
| 252 | { 0x42, 0x00 }, | ||
| 253 | { 0x43, 0x06 }, | ||
| 254 | { 0x44, 0x43 }, | ||
| 255 | { 0x45, 0x00 }, | ||
| 256 | { 0x46, 0x00 }, | ||
| 257 | { 0x47, 0x04 }, | ||
| 258 | { 0x48, 0xb3 }, | ||
| 259 | { 0x49, 0x00 }, | ||
| 260 | { 0x4a, 0x20 }, | ||
| 261 | { 0x4b, 0x00 }, | ||
| 262 | { 0x4c, 0x00 }, | ||
| 263 | { 0x4d, 0x00 }, | ||
| 264 | { 0x4e, 0x00 }, | ||
| 265 | { 0x60, 0x0b }, | ||
| 266 | { 0x61, 0x16 }, | ||
| 267 | { 0x62, 0x32 }, | ||
| 268 | { 0x63, 0x80 }, | ||
| 269 | { 0x71, 0x08 }, | ||
| 270 | { 0x80, 0xf8 }, | ||
| 271 | { 0x81, 0x06 }, | ||
| 272 | { 0x90, 0xaa }, | ||
| 273 | { 0x91, 0x08 }, | ||
| 274 | { 0x92, 0x10 }, | ||
| 275 | { 0x93, 0x40 }, | ||
| 276 | { 0x94, 0x04 }, | ||
| 277 | { 0x95, 0x01 }, | ||
| 278 | { 0x96, 0x02 }, | ||
| 279 | { 0x97, 0x08 }, | ||
| 280 | { 0x98, 0x10 }, | ||
| 281 | { 0x99, 0x08 }, | ||
| 282 | { 0x9a, 0x03 }, | ||
| 283 | { 0x9b, 0xb0 }, | ||
| 284 | { 0x9c, 0x08 }, | ||
| 285 | { 0x9d, 0x24 }, | ||
| 286 | { 0x9e, 0x30 }, | ||
| 287 | { 0xb7, 0x15 }, | ||
| 288 | { 0xb8, 0x28 }, | ||
| 289 | { 0xb9, 0x04 }, | ||
| 290 | }; | ||
| 291 | |||
| 292 | #define N_INIT_REGS G_N_ELEMENTS (sido020a_init_regs) | ||
| 293 | |||
| 294 | /* | ||
| 295 | * Phase 2 late init: written AFTER 0x03 is rewritten to 0x05 (sensor enable), | ||
| 296 | * following the power-on transition. | ||
| 297 | */ | ||
| 298 | static const struct secugen_reg_entry late_init_regs[] = { | ||
| 299 | { 0xa5, 0x00 }, | ||
| 300 | { 0xa6, 0x00 }, | ||
| 301 | { 0xa7, 0x00 }, | ||
| 302 | { 0xa8, 0x00 }, | ||
| 303 | }; | ||
| 304 | |||
| 305 | #define N_LATE_INIT_REGS G_N_ELEMENTS (late_init_regs) | ||
| 306 | |||
| 307 | /* Capture window config (written after late init, before FW reads) */ | ||
| 308 | static const struct secugen_reg_entry capture_window_regs[] = { | ||
| 309 | { 0x41, 0x00 }, { 0x42, 0xfa }, { 0x43, 0x04 }, { 0x44, 0x4f }, | ||
| 310 | { 0x45, 0x00 }, { 0x46, 0x28 }, { 0x47, 0x03 }, { 0x48, 0x23 }, | ||
| 311 | }; | ||
| 312 | |||
| 313 | /* Frame window config (written after calibration capture) */ | ||
| 314 | static const struct secugen_reg_entry frame_window_regs[] = { | ||
| 315 | { 0x41, 0x01 }, { 0x42, 0x48 }, { 0x43, 0x03 }, { 0x44, 0xbf }, | ||
| 316 | { 0x45, 0x00 }, { 0x46, 0xcc }, { 0x47, 0x02 }, { 0x48, 0xb3 }, | ||
| 317 | }; | ||
| 318 | |||
| 319 | #define N_CAPTURE_WINDOW_REGS G_N_ELEMENTS (capture_window_regs) | ||
| 320 | #define N_FRAME_WINDOW_REGS G_N_ELEMENTS (frame_window_regs) | ||
| 321 | |||
| 322 | /* ================================================================ | ||
| 323 | * USB Transfer Helpers | ||
| 324 | * ================================================================ */ | ||
| 325 | |||
| 326 | /* Send a vendor control OUT transfer (no data) and advance SSM */ | ||
| 327 | static void | ||
| 328 | 11 | secugen_ctrl_out (FpiSsm *ssm, | |
| 329 | FpImageDevice *dev, | ||
| 330 | guint8 request, | ||
| 331 | guint16 value, | ||
| 332 | guint16 idx) | ||
| 333 | { | ||
| 334 | 11 | g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (FP_DEVICE (dev)); | |
| 335 | |||
| 336 | 11 | fpi_usb_transfer_fill_control (transfer, | |
| 337 | G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, | ||
| 338 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, | ||
| 339 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
| 340 | request, value, idx, 0); | ||
| 341 | 11 | transfer->ssm = ssm; | |
| 342 | 11 | fpi_usb_transfer_submit (g_steal_pointer (&transfer), SECUGEN_CTRL_TIMEOUT, | |
| 343 | fpi_device_get_cancellable (FP_DEVICE (dev)), | ||
| 344 | fpi_ssm_usb_transfer_cb, NULL); | ||
| 345 | 11 | } | |
| 346 | |||
| 347 | /* Send a vendor control OUT transfer with data payload and advance SSM */ | ||
| 348 | static void | ||
| 349 | 88 | secugen_ctrl_out_data (FpiSsm *ssm, | |
| 350 | FpImageDevice *dev, | ||
| 351 | guint8 request, | ||
| 352 | guint16 value, | ||
| 353 | guint16 idx, | ||
| 354 | const guint8 *data, | ||
| 355 | gsize len) | ||
| 356 | { | ||
| 357 | 88 | g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (FP_DEVICE (dev)); | |
| 358 | |||
| 359 | 88 | fpi_usb_transfer_fill_control (transfer, | |
| 360 | G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, | ||
| 361 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, | ||
| 362 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
| 363 | request, value, idx, len); | ||
| 364 | 88 | memcpy (transfer->buffer, data, len); | |
| 365 | 88 | transfer->ssm = ssm; | |
| 366 | 88 | fpi_usb_transfer_submit (g_steal_pointer (&transfer), SECUGEN_CTRL_TIMEOUT, | |
| 367 | fpi_device_get_cancellable (FP_DEVICE (dev)), | ||
| 368 | fpi_ssm_usb_transfer_cb, NULL); | ||
| 369 | 88 | } | |
| 370 | |||
| 371 | /* Read via vendor control IN transfer, callback receives data */ | ||
| 372 | static void | ||
| 373 | 76 | secugen_ctrl_in (FpiSsm *ssm, | |
| 374 | FpImageDevice *dev, | ||
| 375 | guint8 request, | ||
| 376 | guint16 value, | ||
| 377 | guint16 idx, | ||
| 378 | gsize len, | ||
| 379 | FpiUsbTransferCallback callback) | ||
| 380 | { | ||
| 381 | 76 | g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (FP_DEVICE (dev)); | |
| 382 | |||
| 383 | 76 | fpi_usb_transfer_fill_control (transfer, | |
| 384 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, | ||
| 385 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, | ||
| 386 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
| 387 | request, value, idx, len); | ||
| 388 | 76 | transfer->ssm = ssm; | |
| 389 | 76 | fpi_usb_transfer_submit (g_steal_pointer (&transfer), SECUGEN_CTRL_TIMEOUT, | |
| 390 | fpi_device_get_cancellable (FP_DEVICE (dev)), | ||
| 391 | callback, NULL); | ||
| 392 | 76 | } | |
| 393 | |||
| 394 | /* Write a single I2C register on the SIDO020A sensor */ | ||
| 395 | static void | ||
| 396 | 84 | secugen_i2c_write (FpiSsm *ssm, | |
| 397 | FpImageDevice *dev, | ||
| 398 | guint8 reg, | ||
| 399 | guint8 val) | ||
| 400 | { | ||
| 401 | 84 | secugen_ctrl_out_data (ssm, dev, SECUGEN_REQ_I2C_REG, | |
| 402 | SECUGEN_I2C_ADDR, reg, &val, 1); | ||
| 403 | } | ||
| 404 | |||
| 405 | /* Read back a single I2C register (1 byte) */ | ||
| 406 | static void | ||
| 407 | 74 | secugen_i2c_read (FpiSsm *ssm, | |
| 408 | FpImageDevice *dev, | ||
| 409 | guint8 reg, | ||
| 410 | FpiUsbTransferCallback callback) | ||
| 411 | { | ||
| 412 | 74 | secugen_ctrl_in (ssm, dev, SECUGEN_REQ_I2C_REG, | |
| 413 | SECUGEN_I2C_ADDR, reg, 1, callback); | ||
| 414 | } | ||
| 415 | |||
| 416 | /* Set exposure value (big-endian 16-bit + 2 zero bytes) */ | ||
| 417 | static void | ||
| 418 | 4 | secugen_set_exposure (FpiSsm *ssm, | |
| 419 | FpImageDevice *dev, | ||
| 420 | guint16 exposure) | ||
| 421 | { | ||
| 422 | 4 | guint8 data[4] = { | |
| 423 | (exposure >> 8) & 0xff, | ||
| 424 | exposure & 0xff, | ||
| 425 | 0x00, | ||
| 426 | 0x00 | ||
| 427 | }; | ||
| 428 | |||
| 429 | 4 | secugen_ctrl_out_data (ssm, dev, SECUGEN_REQ_SET_EXPOSURE, | |
| 430 | 0x0000, 0, data, 4); | ||
| 431 | 4 | } | |
| 432 | |||
| 433 | /* ================================================================ | ||
| 434 | * Init State Machine | ||
| 435 | * | ||
| 436 | * Matches the init sequence observed in USB captures: | ||
| 437 | * 1. GET_DEVICE_ID | ||
| 438 | * 2. 54 main I2C regs (with readback) | ||
| 439 | * 3. Rewrite 0x03 = 0x05 (enable sensor) | ||
| 440 | * 4. 4 late I2C regs: 0xa5-0xa8 (with readback) | ||
| 441 | * 5. 8 capture window regs: 0x41-0x48 (with readback) | ||
| 442 | * 6. START_CAPTURE (activates sensor subsystem) | ||
| 443 | * 7. 6 FW/calibration data reads | ||
| 444 | * 8. 0x32=0x00, SET_EXPOSURE(1000), START_CAPTURE, START_STREAM, | ||
| 445 | * bulk read 120KB calibration image, STOP_STREAM | ||
| 446 | * 9. 8 frame window regs: 0x41-0x48 (with readback) | ||
| 447 | * 10. Post-calibration tuning: 0x32/0x30/0x31 writes + SET_EXPOSURE(1116) | ||
| 448 | * ================================================================ */ | ||
| 449 | |||
| 450 | enum init_states { | ||
| 451 | INIT_GET_DEVICE_ID = 0, | ||
| 452 | /* Phase 1: Main I2C register init (54 regs) */ | ||
| 453 | INIT_I2C_WRITE, | ||
| 454 | INIT_I2C_READBACK, | ||
| 455 | /* Phase 2: Power enable + late regs */ | ||
| 456 | INIT_POWER_ENABLE, | ||
| 457 | INIT_LATE_I2C_WRITE, | ||
| 458 | INIT_LATE_I2C_READBACK, | ||
| 459 | /* Phase 3: Capture window config */ | ||
| 460 | INIT_WINDOW_I2C_WRITE, | ||
| 461 | INIT_WINDOW_I2C_READBACK, | ||
| 462 | /* Phase 4: START_CAPTURE + FW data reads */ | ||
| 463 | INIT_PRE_FW_CAPTURE, | ||
| 464 | INIT_FW_READ, | ||
| 465 | /* Phase 5: Calibration capture */ | ||
| 466 | INIT_CAL_REG32, | ||
| 467 | INIT_CAL_EXPOSURE, | ||
| 468 | INIT_CAL_START_CAPTURE, | ||
| 469 | INIT_CAL_START_STREAM, | ||
| 470 | INIT_CAL_BULK_READ, | ||
| 471 | INIT_CAL_STOP_STREAM, | ||
| 472 | /* Phase 6: Frame window config */ | ||
| 473 | INIT_FRAME_I2C_WRITE, | ||
| 474 | INIT_FRAME_I2C_READBACK, | ||
| 475 | /* Phase 7: Post-calibration tuning */ | ||
| 476 | INIT_POST_REG32_CLEAR, | ||
| 477 | INIT_POST_EXPOSURE, | ||
| 478 | INIT_POST_REG32_CLEAR2, | ||
| 479 | INIT_POST_REG30, | ||
| 480 | INIT_POST_REG31, | ||
| 481 | INIT_POST_REG32_CLEAR3, | ||
| 482 | INIT_POST_REG32_SET, | ||
| 483 | INIT_POST_EXPOSURE2, | ||
| 484 | /* Phase 8: Final calibration capture (operational settings) */ | ||
| 485 | INIT_FINAL_REG32, | ||
| 486 | INIT_FINAL_START_CAPTURE, | ||
| 487 | INIT_FINAL_START_STREAM, | ||
| 488 | INIT_FINAL_BULK_READ, | ||
| 489 | INIT_FINAL_STOP_STREAM, | ||
| 490 | INIT_NUM_STATES, | ||
| 491 | }; | ||
| 492 | |||
| 493 | static void | ||
| 494 | 1 | init_device_id_cb (FpiUsbTransfer *transfer, | |
| 495 | FpDevice *dev, | ||
| 496 | gpointer user_data, | ||
| 497 | GError *error) | ||
| 498 | { | ||
| 499 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 500 | |||
| 501 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 1 time.
|
1 | if (error) |
| 502 | { | ||
| 503 | ✗ | fpi_ssm_mark_failed (transfer->ssm, error); | |
| 504 | ✗ | return; | |
| 505 | } | ||
| 506 | |||
| 507 | 1 | memcpy (self->device_id, transfer->buffer, MIN (transfer->actual_length, 30)); | |
| 508 | 1 | fp_dbg ("Device ID: %02x %02x %02x %02x ...", | |
| 509 | self->device_id[0], self->device_id[1], | ||
| 510 | self->device_id[2], self->device_id[3]); | ||
| 511 | 1 | fpi_ssm_next_state (transfer->ssm); | |
| 512 | } | ||
| 513 | |||
| 514 | /* Readback callback for main init regs (54 entries) */ | ||
| 515 | static void | ||
| 516 | 54 | init_i2c_readback_cb (FpiUsbTransfer *transfer, | |
| 517 | FpDevice *dev, | ||
| 518 | gpointer user_data, | ||
| 519 | GError *error) | ||
| 520 | { | ||
| 521 | 54 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 522 | |||
| 523 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 54 times.
|
54 | if (error) |
| 524 | { | ||
| 525 | ✗ | fpi_ssm_mark_failed (transfer->ssm, error); | |
| 526 | ✗ | return; | |
| 527 | } | ||
| 528 | |||
| 529 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 9 taken 54 times.
|
54 | if (transfer->actual_length < 1) |
| 530 | { | ||
| 531 | ✗ | fpi_ssm_mark_failed (transfer->ssm, | |
| 532 | fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); | ||
| 533 | ✗ | return; | |
| 534 | } | ||
| 535 | |||
| 536 | 54 | self->last_reg_rd = transfer->buffer[0]; | |
| 537 | 54 | self->init_reg_idx++; | |
| 538 | |||
| 539 |
2/2✓ Branch 9 → 10 taken 53 times.
✓ Branch 9 → 11 taken 1 time.
|
54 | if (self->init_reg_idx < (int) N_INIT_REGS) |
| 540 | { | ||
| 541 | 53 | fpi_ssm_jump_to_state (transfer->ssm, INIT_I2C_WRITE); | |
| 542 | } | ||
| 543 | else | ||
| 544 | { | ||
| 545 | 1 | self->init_reg_idx = 0; | |
| 546 | 1 | fpi_ssm_next_state (transfer->ssm); | |
| 547 | } | ||
| 548 | } | ||
| 549 | |||
| 550 | /* Readback callback for late init regs (4 entries: 0xa5-0xa8) */ | ||
| 551 | static void | ||
| 552 | 4 | init_late_readback_cb (FpiUsbTransfer *transfer, | |
| 553 | FpDevice *dev, | ||
| 554 | gpointer user_data, | ||
| 555 | GError *error) | ||
| 556 | { | ||
| 557 | 4 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 558 | |||
| 559 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 4 times.
|
4 | if (error) |
| 560 | { | ||
| 561 | ✗ | fpi_ssm_mark_failed (transfer->ssm, error); | |
| 562 | ✗ | return; | |
| 563 | } | ||
| 564 | |||
| 565 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 9 taken 4 times.
|
4 | if (transfer->actual_length < 1) |
| 566 | { | ||
| 567 | ✗ | fpi_ssm_mark_failed (transfer->ssm, | |
| 568 | fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); | ||
| 569 | ✗ | return; | |
| 570 | } | ||
| 571 | |||
| 572 | 4 | self->last_reg_rd = transfer->buffer[0]; | |
| 573 | 4 | self->init_reg_idx++; | |
| 574 | |||
| 575 |
2/2✓ Branch 9 → 10 taken 3 times.
✓ Branch 9 → 11 taken 1 time.
|
4 | if (self->init_reg_idx < (int) N_LATE_INIT_REGS) |
| 576 | { | ||
| 577 | 3 | fpi_ssm_jump_to_state (transfer->ssm, INIT_LATE_I2C_WRITE); | |
| 578 | } | ||
| 579 | else | ||
| 580 | { | ||
| 581 | 1 | self->init_reg_idx = 0; | |
| 582 | 1 | fpi_ssm_next_state (transfer->ssm); | |
| 583 | } | ||
| 584 | } | ||
| 585 | |||
| 586 | /* Readback callback for capture window regs (8 entries) */ | ||
| 587 | static void | ||
| 588 | 8 | init_window_readback_cb (FpiUsbTransfer *transfer, | |
| 589 | FpDevice *dev, | ||
| 590 | gpointer user_data, | ||
| 591 | GError *error) | ||
| 592 | { | ||
| 593 | 8 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 594 | |||
| 595 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 8 times.
|
8 | if (error) |
| 596 | { | ||
| 597 | ✗ | fpi_ssm_mark_failed (transfer->ssm, error); | |
| 598 | ✗ | return; | |
| 599 | } | ||
| 600 | |||
| 601 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 9 taken 8 times.
|
8 | if (transfer->actual_length < 1) |
| 602 | { | ||
| 603 | ✗ | fpi_ssm_mark_failed (transfer->ssm, | |
| 604 | fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); | ||
| 605 | ✗ | return; | |
| 606 | } | ||
| 607 | |||
| 608 | 8 | self->last_reg_rd = transfer->buffer[0]; | |
| 609 | 8 | self->init_reg_idx++; | |
| 610 | |||
| 611 |
2/2✓ Branch 9 → 10 taken 7 times.
✓ Branch 9 → 11 taken 1 time.
|
8 | if (self->init_reg_idx < (int) N_CAPTURE_WINDOW_REGS) |
| 612 | { | ||
| 613 | 7 | fpi_ssm_jump_to_state (transfer->ssm, INIT_WINDOW_I2C_WRITE); | |
| 614 | } | ||
| 615 | else | ||
| 616 | { | ||
| 617 | 1 | self->init_reg_idx = 0; | |
| 618 | 1 | fpi_ssm_next_state (transfer->ssm); | |
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | /* FW data read callback - accumulate data and extract reference image */ | ||
| 623 | static void | ||
| 624 | 6 | init_fw_read_cb (FpiUsbTransfer *transfer, | |
| 625 | FpDevice *dev, | ||
| 626 | gpointer user_data, | ||
| 627 | GError *error) | ||
| 628 | { | ||
| 629 | 12 | g_autoptr(GError) local_error = error; | |
| 630 | 6 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 631 | |||
| 632 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 6 times.
|
6 | if (local_error) |
| 633 | { | ||
| 634 | ✗ | fp_warn ("FW data read failed (non-fatal): %s", local_error->message); | |
| 635 | ✗ | fpi_ssm_next_state (transfer->ssm); | |
| 636 | ✗ | return; | |
| 637 | } | ||
| 638 | |||
| 639 | /* Accumulate FW data into contiguous buffer */ | ||
| 640 | { | ||
| 641 | 6 | gsize to_copy = transfer->actual_length; | |
| 642 | |||
| 643 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 6 times.
|
6 | if (self->fw_data_len + to_copy > SECUGEN_FW_DATA_SIZE) |
| 644 | ✗ | to_copy = SECUGEN_FW_DATA_SIZE - self->fw_data_len; | |
| 645 |
2/4✓ Branch 8 → 9 taken 6 times.
✗ Branch 8 → 11 not taken.
✓ Branch 9 → 10 taken 6 times.
✗ Branch 9 → 11 not taken.
|
6 | if (to_copy > 0 && self->fw_data) |
| 646 | { | ||
| 647 | 6 | memcpy (self->fw_data + self->fw_data_len, transfer->buffer, to_copy); | |
| 648 | 6 | self->fw_data_len += to_copy; | |
| 649 | } | ||
| 650 | } | ||
| 651 | |||
| 652 | 6 | self->fw_read_idx++; | |
| 653 |
2/2✓ Branch 11 → 12 taken 5 times.
✓ Branch 11 → 13 taken 1 time.
|
6 | if (self->fw_read_idx < SECUGEN_FW_DATA_CHUNKS) |
| 654 | { | ||
| 655 | 5 | fpi_ssm_jump_to_state (transfer->ssm, INIT_FW_READ); | |
| 656 | } | ||
| 657 | else | ||
| 658 | { | ||
| 659 | /* All FW data received - extract image processing parameters */ | ||
| 660 |
1/2✗ Branch 13 → 14 not taken.
✓ Branch 13 → 16 taken 1 time.
|
1 | if (!self->fw_data) |
| 661 | { | ||
| 662 | ✗ | fp_warn ("FW data buffer missing; skipping calibration extraction"); | |
| 663 | ✗ | fpi_ssm_next_state (transfer->ssm); | |
| 664 | ✗ | return; | |
| 665 | } | ||
| 666 | |||
| 667 | /* BLC region offsets: 16 × int16 at fw_data[0x0e] */ | ||
| 668 |
1/2✓ Branch 16 → 18 taken 1 time.
✗ Branch 16 → 20 not taken.
|
1 | if (self->fw_data_len >= SECUGEN_BLC_OFFSETS_FW + 32) |
| 669 | { | ||
| 670 | int i; | ||
| 671 | |||
| 672 |
2/2✓ Branch 18 → 17 taken 16 times.
✓ Branch 18 → 19 taken 1 time.
|
17 | for (i = 0; i < 16; i++) |
| 673 | { | ||
| 674 | 16 | guint16 raw_val; | |
| 675 | |||
| 676 | 16 | memcpy (&raw_val, | |
| 677 | 16 | self->fw_data + SECUGEN_BLC_OFFSETS_FW + i * 2, 2); | |
| 678 | 16 | self->blc_offsets[i] = (gint16) GUINT16_FROM_LE (raw_val); | |
| 679 | } | ||
| 680 | 1 | self->has_blc_offsets = TRUE; | |
| 681 | 1 | fp_dbg ("BLC offsets: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", | |
| 682 | self->blc_offsets[0], self->blc_offsets[1], | ||
| 683 | self->blc_offsets[2], self->blc_offsets[3], | ||
| 684 | self->blc_offsets[4], self->blc_offsets[5], | ||
| 685 | self->blc_offsets[6], self->blc_offsets[7], | ||
| 686 | self->blc_offsets[8], self->blc_offsets[9], | ||
| 687 | self->blc_offsets[10], self->blc_offsets[11], | ||
| 688 | self->blc_offsets[12], self->blc_offsets[13], | ||
| 689 | self->blc_offsets[14], self->blc_offsets[15]); | ||
| 690 | } | ||
| 691 | |||
| 692 | /* Reference image: 150×100 at fw_data[0x1df8] */ | ||
| 693 |
1/2✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 26 not taken.
|
1 | if (self->fw_data_len >= SECUGEN_REF_OFFSET + SECUGEN_REF_SIZE) |
| 694 | { | ||
| 695 | 1 | const guint8 *ref_src = self->fw_data + SECUGEN_REF_OFFSET; | |
| 696 | |||
| 697 |
1/2✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 24 not taken.
|
1 | if (!self->ref_image) |
| 698 | 1 | self->ref_image = g_malloc (SECUGEN_IMG_SIZE); | |
| 699 | |||
| 700 | 1 | secugen_resize_ref_image (ref_src, self->ref_image); | |
| 701 | 1 | self->has_ref_image = TRUE; | |
| 702 | 1 | fp_dbg ("Flat-field reference image extracted and resized to %dx%d", | |
| 703 | SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT); | ||
| 704 | } | ||
| 705 | else | ||
| 706 | { | ||
| 707 | ✗ | fp_warn ("FW data too short for reference image: %zu < %d", | |
| 708 | self->fw_data_len, | ||
| 709 | SECUGEN_REF_OFFSET + SECUGEN_REF_SIZE); | ||
| 710 | } | ||
| 711 | |||
| 712 | /* Blend cal_value: uint16 at fw_data[0x5892] */ | ||
| 713 |
1/2✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 29 not taken.
|
1 | if (self->fw_data_len >= SECUGEN_CAL_VALUE_FW + 2) |
| 714 | { | ||
| 715 | 1 | guint16 raw_val; | |
| 716 | |||
| 717 | 1 | memcpy (&raw_val, self->fw_data + SECUGEN_CAL_VALUE_FW, 2); | |
| 718 | 1 | self->cal_value = GUINT16_FROM_LE (raw_val); | |
| 719 | 1 | fp_dbg ("Blend cal_value from FW: %u", self->cal_value); | |
| 720 | } | ||
| 721 | |||
| 722 | /* Sharpen parameters */ | ||
| 723 |
1/2✓ Branch 29 → 30 taken 1 time.
✗ Branch 29 → 34 not taken.
|
1 | if (self->fw_data_len >= SECUGEN_SHARPEN_AMOUNT_FW + 1) |
| 724 | { | ||
| 725 | 1 | self->sharpen_threshold = self->fw_data[SECUGEN_SHARPEN_THRESH_FW]; | |
| 726 | 1 | self->sharpen_amount = self->fw_data[SECUGEN_SHARPEN_AMOUNT_FW]; | |
| 727 | 1 | self->sharpen_limit = 10; /* Default; exact FW offset is past our read */ | |
| 728 |
2/4✓ Branch 30 → 31 taken 1 time.
✗ Branch 30 → 32 not taken.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 1 time.
|
1 | self->sharpen_enabled = (self->sharpen_threshold > 0 && |
| 729 | self->sharpen_amount > 0); | ||
| 730 | 1 | fp_dbg ("Sharpen: threshold=%u amount=%u limit=%u enabled=%d", | |
| 731 | self->sharpen_threshold, self->sharpen_amount, | ||
| 732 | self->sharpen_limit, self->sharpen_enabled); | ||
| 733 | } | ||
| 734 | |||
| 735 | /* The pipeline degrades gracefully when calibration data could not | ||
| 736 | * be extracted, but image quality suffers - say so once. */ | ||
| 737 |
2/4✓ Branch 34 → 35 taken 1 time.
✗ Branch 34 → 36 not taken.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 41 taken 1 time.
|
1 | if (!self->has_blc_offsets || !self->has_ref_image) |
| 738 | { | ||
| 739 | ✗ | fp_warn ("Missing FW calibration data (BLC offsets: %s, reference " | |
| 740 | "image: %s); captured images will have reduced quality", | ||
| 741 | self->has_blc_offsets ? "ok" : "missing", | ||
| 742 | self->has_ref_image ? "ok" : "missing"); | ||
| 743 | } | ||
| 744 | |||
| 745 | 1 | fpi_ssm_next_state (transfer->ssm); | |
| 746 | } | ||
| 747 | } | ||
| 748 | |||
| 749 | /* Readback callback for frame window regs (8 entries) */ | ||
| 750 | static void | ||
| 751 | 8 | init_frame_readback_cb (FpiUsbTransfer *transfer, | |
| 752 | FpDevice *dev, | ||
| 753 | gpointer user_data, | ||
| 754 | GError *error) | ||
| 755 | { | ||
| 756 | 8 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 757 | |||
| 758 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 8 times.
|
8 | if (error) |
| 759 | { | ||
| 760 | ✗ | fpi_ssm_mark_failed (transfer->ssm, error); | |
| 761 | ✗ | return; | |
| 762 | } | ||
| 763 | |||
| 764 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 9 taken 8 times.
|
8 | if (transfer->actual_length < 1) |
| 765 | { | ||
| 766 | ✗ | fpi_ssm_mark_failed (transfer->ssm, | |
| 767 | fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); | ||
| 768 | ✗ | return; | |
| 769 | } | ||
| 770 | |||
| 771 | 8 | self->last_reg_rd = transfer->buffer[0]; | |
| 772 | 8 | self->init_reg_idx++; | |
| 773 | |||
| 774 |
2/2✓ Branch 9 → 10 taken 7 times.
✓ Branch 9 → 11 taken 1 time.
|
8 | if (self->init_reg_idx < (int) N_FRAME_WINDOW_REGS) |
| 775 | { | ||
| 776 | 7 | fpi_ssm_jump_to_state (transfer->ssm, INIT_FRAME_I2C_WRITE); | |
| 777 | } | ||
| 778 | else | ||
| 779 | { | ||
| 780 | 1 | self->init_reg_idx = 0; | |
| 781 | 1 | fpi_ssm_next_state (transfer->ssm); | |
| 782 | } | ||
| 783 | } | ||
| 784 | |||
| 785 | /* | ||
| 786 | * Chunked frame read. | ||
| 787 | * | ||
| 788 | * The SIDO020A streams a full ~657KB sensor frame over the bulk-IN endpoint. | ||
| 789 | * Reading it as a single USB transfer makes the frame impossible to record | ||
| 790 | * with the standard usbmon/pcap-based test capture: the kernel usbmon ring | ||
| 791 | * buffer caps per-URB payload at ring_size/5 (~245KB), so one 657KB URB is | ||
| 792 | * truncated on capture and the replay cannot reconstruct the frame. | ||
| 793 | * | ||
| 794 | * Instead the frame is read in SECUGEN_BULK_CHUNK-sized pieces (a multiple of | ||
| 795 | * the 512-byte bulk max-packet size, below the usbmon cap), accumulating into | ||
| 796 | * a caller-supplied destination buffer. Every URB is then small enough to be | ||
| 797 | * captured intact and the test capture works with the standard tooling. Once | ||
| 798 | * the whole frame is read the owning SSM advances to its next state. | ||
| 799 | */ | ||
| 800 | static void | ||
| 801 | 33 | secugen_frame_chunk_cb (FpiUsbTransfer *transfer, | |
| 802 | FpDevice *dev, | ||
| 803 | gpointer user_data, | ||
| 804 | GError *error) | ||
| 805 | { | ||
| 806 | 66 | g_autoptr(GError) local_error = error; | |
| 807 | 33 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 808 | 33 | gsize remaining; | |
| 809 | 33 | gsize copied; | |
| 810 | |||
| 811 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 33 times.
|
33 | if (local_error) |
| 812 | { | ||
| 813 | /* A deactivate may have cancelled the transfer; unwind quietly. */ | ||
| 814 | ✗ | if (self->deactivating) | |
| 815 | ✗ | fpi_ssm_mark_completed (transfer->ssm); | |
| 816 | else | ||
| 817 | ✗ | fpi_ssm_mark_failed (transfer->ssm, g_steal_pointer (&local_error)); | |
| 818 | ✗ | return; | |
| 819 | } | ||
| 820 | |||
| 821 | /* Abandon the read if a deactivate arrived mid-frame. */ | ||
| 822 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 33 times.
|
33 | if (self->deactivating) |
| 823 | { | ||
| 824 | ✗ | fpi_ssm_mark_completed (transfer->ssm); | |
| 825 | ✗ | return; | |
| 826 | } | ||
| 827 | |||
| 828 | /* Append this chunk to the frame buffer, guarding against overrun. */ | ||
| 829 | 33 | copied = MIN ((gsize) transfer->actual_length, | |
| 830 | SECUGEN_BULK_BUF_SIZE - self->bulk_offset); | ||
| 831 | 33 | memcpy (self->bulk_dest + self->bulk_offset, transfer->buffer, copied); | |
| 832 | 33 | self->bulk_offset += copied; | |
| 833 | |||
| 834 | 33 | remaining = SECUGEN_BULK_BUF_SIZE - self->bulk_offset; | |
| 835 | |||
| 836 | /* A full-length chunk means the device may still have more frame data; | ||
| 837 | * a short read means the stream has ended. */ | ||
| 838 |
3/4✓ Branch 9 → 10 taken 30 times.
✓ Branch 9 → 16 taken 3 times.
✓ Branch 10 → 11 taken 30 times.
✗ Branch 10 → 15 not taken.
|
33 | if (remaining > 0 && transfer->actual_length == transfer->length) |
| 839 | { | ||
| 840 | 60 | g_autoptr(FpiUsbTransfer) next = fpi_usb_transfer_new (dev); | |
| 841 | |||
| 842 | 30 | next->ssm = transfer->ssm; | |
| 843 | 30 | fpi_usb_transfer_fill_bulk (next, SECUGEN_EP_DATA, | |
| 844 | MIN ((gsize) SECUGEN_BULK_CHUNK, remaining)); | ||
| 845 | 30 | fpi_usb_transfer_submit (g_steal_pointer (&next), SECUGEN_BULK_TIMEOUT, | |
| 846 | fpi_device_get_cancellable (dev), | ||
| 847 | secugen_frame_chunk_cb, NULL); | ||
| 848 | 30 | return; | |
| 849 | } | ||
| 850 | |||
| 851 | /* Zero any unread tail so a short frame never leaves stale data from a | ||
| 852 | * previous capture in the destination buffer. */ | ||
| 853 | ✗ | if (remaining > 0) | |
| 854 | ✗ | memset (self->bulk_dest + self->bulk_offset, 0, remaining); | |
| 855 | |||
| 856 |
1/2✗ Branch 16 → 17 not taken.
✓ Branch 16 → 21 taken 3 times.
|
3 | if (self->bulk_offset < SECUGEN_RAW_SIZE) |
| 857 | { | ||
| 858 | ✗ | fp_warn ("Short image data: got %" G_GSIZE_FORMAT ", expected %d", | |
| 859 | self->bulk_offset, SECUGEN_RAW_SIZE); | ||
| 860 | ✗ | fpi_ssm_mark_failed (transfer->ssm, | |
| 861 | fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); | ||
| 862 | ✗ | return; | |
| 863 | } | ||
| 864 | |||
| 865 | 3 | fp_dbg ("Read %" G_GSIZE_FORMAT " byte frame in chunks", self->bulk_offset); | |
| 866 | 3 | fpi_ssm_next_state (transfer->ssm); | |
| 867 | } | ||
| 868 | |||
| 869 | /* Begin a chunked read of a full sensor frame into dest. */ | ||
| 870 | static void | ||
| 871 | 3 | secugen_read_frame (FpiSsm *ssm, | |
| 872 | FpDevice *dev, | ||
| 873 | guint8 *dest) | ||
| 874 | { | ||
| 875 | 6 | g_autoptr(FpiUsbTransfer) transfer = NULL; | |
| 876 | 3 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 877 | |||
| 878 | 3 | self->bulk_dest = dest; | |
| 879 | 3 | self->bulk_offset = 0; | |
| 880 | |||
| 881 | 3 | transfer = fpi_usb_transfer_new (dev); | |
| 882 | 3 | transfer->ssm = ssm; | |
| 883 | 3 | fpi_usb_transfer_fill_bulk (transfer, SECUGEN_EP_DATA, | |
| 884 | MIN ((gsize) SECUGEN_BULK_CHUNK, | ||
| 885 | (gsize) SECUGEN_BULK_BUF_SIZE)); | ||
| 886 | 3 | fpi_usb_transfer_submit (g_steal_pointer (&transfer), SECUGEN_BULK_TIMEOUT, | |
| 887 | fpi_device_get_cancellable (dev), | ||
| 888 | secugen_frame_chunk_cb, NULL); | ||
| 889 | 3 | } | |
| 890 | |||
| 891 | static void | ||
| 892 | 176 | init_run_state (FpiSsm *ssm, FpDevice *_dev) | |
| 893 | { | ||
| 894 | 176 | FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); | |
| 895 | 176 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (_dev); | |
| 896 | |||
| 897 |
31/32✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 54 times.
✓ Branch 3 → 8 taken 54 times.
✓ Branch 3 → 10 taken 1 time.
✓ Branch 3 → 12 taken 4 times.
✓ Branch 3 → 14 taken 4 times.
✓ Branch 3 → 16 taken 8 times.
✓ Branch 3 → 18 taken 8 times.
✓ Branch 3 → 20 taken 1 time.
✓ Branch 3 → 22 taken 6 times.
✓ Branch 3 → 29 taken 1 time.
✓ Branch 3 → 31 taken 1 time.
✓ Branch 3 → 33 taken 1 time.
✓ Branch 3 → 35 taken 1 time.
✓ Branch 3 → 37 taken 1 time.
✓ Branch 3 → 39 taken 1 time.
✓ Branch 3 → 41 taken 8 times.
✓ Branch 3 → 43 taken 8 times.
✓ Branch 3 → 45 taken 1 time.
✓ Branch 3 → 47 taken 1 time.
✓ Branch 3 → 49 taken 1 time.
✓ Branch 3 → 51 taken 1 time.
✓ Branch 3 → 53 taken 1 time.
✓ Branch 3 → 55 taken 1 time.
✓ Branch 3 → 57 taken 1 time.
✓ Branch 3 → 59 taken 1 time.
✓ Branch 3 → 61 taken 1 time.
✓ Branch 3 → 63 taken 1 time.
✓ Branch 3 → 65 taken 1 time.
✓ Branch 3 → 67 taken 1 time.
✓ Branch 3 → 69 taken 1 time.
✗ Branch 3 → 71 not taken.
|
176 | switch (fpi_ssm_get_cur_state (ssm)) |
| 898 | { | ||
| 899 | 1 | case INIT_GET_DEVICE_ID: | |
| 900 | 1 | secugen_ctrl_in (ssm, dev, SECUGEN_REQ_GET_DEVICE_ID, | |
| 901 | 0x0000, 0, 30, init_device_id_cb); | ||
| 902 | 1 | break; | |
| 903 | |||
| 904 | /* ---- Phase 1: Main I2C init (54 regs) ---- */ | ||
| 905 | |||
| 906 | 54 | case INIT_I2C_WRITE: | |
| 907 | { | ||
| 908 | 54 | const struct secugen_reg_entry *entry = | |
| 909 | 54 | &sido020a_init_regs[self->init_reg_idx]; | |
| 910 | 54 | secugen_i2c_write (ssm, dev, entry->reg, entry->val); | |
| 911 | } | ||
| 912 | 54 | break; | |
| 913 | |||
| 914 | 54 | case INIT_I2C_READBACK: | |
| 915 | { | ||
| 916 | 54 | const struct secugen_reg_entry *entry = | |
| 917 | 54 | &sido020a_init_regs[self->init_reg_idx]; | |
| 918 | 54 | secugen_i2c_read (ssm, dev, entry->reg, init_i2c_readback_cb); | |
| 919 | } | ||
| 920 | 54 | break; | |
| 921 | |||
| 922 | /* ---- Phase 2: Power enable + late regs ---- */ | ||
| 923 | |||
| 924 | 1 | case INIT_POWER_ENABLE: | |
| 925 | /* Rewrite reg 0x03 from 0x02 (reset) to 0x05 (enable) */ | ||
| 926 | 1 | secugen_i2c_write (ssm, dev, 0x03, 0x05); | |
| 927 | 1 | break; | |
| 928 | |||
| 929 | 4 | case INIT_LATE_I2C_WRITE: | |
| 930 | { | ||
| 931 | 4 | const struct secugen_reg_entry *entry; | |
| 932 | |||
| 933 | 4 | entry = &late_init_regs[self->init_reg_idx]; | |
| 934 | 4 | secugen_i2c_write (ssm, dev, entry->reg, entry->val); | |
| 935 | } | ||
| 936 | 4 | break; | |
| 937 | |||
| 938 | 4 | case INIT_LATE_I2C_READBACK: | |
| 939 | { | ||
| 940 | 4 | const struct secugen_reg_entry *entry = | |
| 941 | 4 | &late_init_regs[self->init_reg_idx]; | |
| 942 | 4 | secugen_i2c_read (ssm, dev, entry->reg, init_late_readback_cb); | |
| 943 | } | ||
| 944 | 4 | break; | |
| 945 | |||
| 946 | /* ---- Phase 3: Capture window config ---- */ | ||
| 947 | |||
| 948 | 8 | case INIT_WINDOW_I2C_WRITE: | |
| 949 | { | ||
| 950 | 8 | const struct secugen_reg_entry *entry; | |
| 951 | |||
| 952 | 8 | entry = &capture_window_regs[self->init_reg_idx]; | |
| 953 | 8 | secugen_i2c_write (ssm, dev, entry->reg, entry->val); | |
| 954 | } | ||
| 955 | 8 | break; | |
| 956 | |||
| 957 | 8 | case INIT_WINDOW_I2C_READBACK: | |
| 958 | { | ||
| 959 | 8 | const struct secugen_reg_entry *entry = | |
| 960 | 8 | &capture_window_regs[self->init_reg_idx]; | |
| 961 | 8 | secugen_i2c_read (ssm, dev, entry->reg, init_window_readback_cb); | |
| 962 | } | ||
| 963 | 8 | break; | |
| 964 | |||
| 965 | /* ---- Phase 4: START_CAPTURE + FW data reads ---- */ | ||
| 966 | |||
| 967 | 1 | case INIT_PRE_FW_CAPTURE: | |
| 968 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_START_CAPTURE, 0x0001, 0); | |
| 969 | 1 | break; | |
| 970 | |||
| 971 | 6 | case INIT_FW_READ: | |
| 972 | { | ||
| 973 | 6 | guint16 offset = SECUGEN_FW_DATA_START + | |
| 974 | 6 | (self->fw_read_idx * SECUGEN_FW_DATA_CHUNK); | |
| 975 | 5 | gsize len = (self->fw_read_idx == SECUGEN_FW_DATA_CHUNKS - 1) ? | |
| 976 |
2/2✓ Branch 22 → 23 taken 5 times.
✓ Branch 22 → 24 taken 1 time.
|
6 | SECUGEN_FW_DATA_LAST_LEN : |
| 977 | SECUGEN_FW_DATA_CHUNK; | ||
| 978 | |||
| 979 | { | ||
| 980 | 6 | g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (FP_DEVICE (dev)); | |
| 981 | |||
| 982 | 6 | fpi_usb_transfer_fill_control (transfer, | |
| 983 | G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, | ||
| 984 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, | ||
| 985 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
| 986 | SECUGEN_REQ_READ_FW_DATA, | ||
| 987 | 0x0000, offset, len); | ||
| 988 | 6 | transfer->ssm = ssm; | |
| 989 | 6 | fpi_usb_transfer_submit (g_steal_pointer (&transfer), SECUGEN_FW_READ_TIMEOUT, | |
| 990 | fpi_device_get_cancellable (FP_DEVICE (dev)), | ||
| 991 | init_fw_read_cb, NULL); | ||
| 992 | } | ||
| 993 | } | ||
| 994 | 6 | break; | |
| 995 | |||
| 996 | /* ---- Phase 5: Calibration capture ---- */ | ||
| 997 | |||
| 998 | 1 | case INIT_CAL_REG32: | |
| 999 | /* Must clear reg 0x32 before each image capture */ | ||
| 1000 | 1 | secugen_i2c_write (ssm, dev, 0x32, 0x00); | |
| 1001 | 1 | break; | |
| 1002 | |||
| 1003 | 1 | case INIT_CAL_EXPOSURE: | |
| 1004 | 1 | self->exposure = SECUGEN_EXPOSURE_INIT; | |
| 1005 | 1 | secugen_set_exposure (ssm, dev, SECUGEN_EXPOSURE_INIT); | |
| 1006 | 1 | break; | |
| 1007 | |||
| 1008 | 1 | case INIT_CAL_START_CAPTURE: | |
| 1009 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_START_CAPTURE, 0x0001, 0); | |
| 1010 | 1 | break; | |
| 1011 | |||
| 1012 | 1 | case INIT_CAL_START_STREAM: | |
| 1013 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_START_STREAM, 0x0000, 0); | |
| 1014 | 1 | break; | |
| 1015 | |||
| 1016 | 1 | case INIT_CAL_BULK_READ: | |
| 1017 | 1 | secugen_read_frame (ssm, _dev, self->cal_raw); | |
| 1018 | 1 | break; | |
| 1019 | |||
| 1020 | 1 | case INIT_CAL_STOP_STREAM: | |
| 1021 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_STOP_STREAM, 0x0000, 0); | |
| 1022 | 1 | break; | |
| 1023 | |||
| 1024 | /* ---- Phase 6: Frame window config ---- */ | ||
| 1025 | |||
| 1026 | 8 | case INIT_FRAME_I2C_WRITE: | |
| 1027 | { | ||
| 1028 | 8 | const struct secugen_reg_entry *entry; | |
| 1029 | |||
| 1030 | 8 | entry = &frame_window_regs[self->init_reg_idx]; | |
| 1031 | 8 | secugen_i2c_write (ssm, dev, entry->reg, entry->val); | |
| 1032 | } | ||
| 1033 | 8 | break; | |
| 1034 | |||
| 1035 | 8 | case INIT_FRAME_I2C_READBACK: | |
| 1036 | { | ||
| 1037 | 8 | const struct secugen_reg_entry *entry = | |
| 1038 | 8 | &frame_window_regs[self->init_reg_idx]; | |
| 1039 | 8 | secugen_i2c_read (ssm, dev, entry->reg, init_frame_readback_cb); | |
| 1040 | } | ||
| 1041 | 8 | break; | |
| 1042 | |||
| 1043 | /* ---- Phase 7: Post-calibration tuning ---- */ | ||
| 1044 | |||
| 1045 | 1 | case INIT_POST_REG32_CLEAR: | |
| 1046 | 1 | secugen_i2c_write (ssm, dev, 0x32, 0x00); | |
| 1047 | 1 | break; | |
| 1048 | |||
| 1049 | 1 | case INIT_POST_EXPOSURE: | |
| 1050 | 1 | self->exposure = SECUGEN_EXPOSURE_NORMAL; | |
| 1051 | 1 | secugen_set_exposure (ssm, dev, SECUGEN_EXPOSURE_NORMAL); | |
| 1052 | 1 | break; | |
| 1053 | |||
| 1054 | 1 | case INIT_POST_REG32_CLEAR2: | |
| 1055 | 1 | secugen_i2c_write (ssm, dev, 0x32, 0x00); | |
| 1056 | 1 | break; | |
| 1057 | |||
| 1058 | 1 | case INIT_POST_REG30: | |
| 1059 | /* Frame control transitions from init value 0x01 to 0x04 */ | ||
| 1060 | 1 | secugen_i2c_write (ssm, dev, 0x30, 0x04); | |
| 1061 | 1 | break; | |
| 1062 | |||
| 1063 | 1 | case INIT_POST_REG31: | |
| 1064 | /* Frame size transitions from init value 0xc0 to 0x5c */ | ||
| 1065 | 1 | secugen_i2c_write (ssm, dev, 0x31, 0x5c); | |
| 1066 | 1 | break; | |
| 1067 | |||
| 1068 | 1 | case INIT_POST_REG32_CLEAR3: | |
| 1069 | 1 | secugen_i2c_write (ssm, dev, 0x32, 0x00); | |
| 1070 | 1 | break; | |
| 1071 | |||
| 1072 | 1 | case INIT_POST_REG32_SET: | |
| 1073 | /* Final 0x32 value for operational mode */ | ||
| 1074 | 1 | secugen_i2c_write (ssm, dev, 0x32, 0x24); | |
| 1075 | 1 | break; | |
| 1076 | |||
| 1077 | 1 | case INIT_POST_EXPOSURE2: | |
| 1078 | 1 | secugen_set_exposure (ssm, dev, SECUGEN_EXPOSURE_NORMAL); | |
| 1079 | 1 | break; | |
| 1080 | |||
| 1081 | /* ---- Phase 8: Final calibration capture (operational settings) ---- */ | ||
| 1082 | |||
| 1083 | 1 | case INIT_FINAL_REG32: | |
| 1084 | /* Clear reg 0x32 before capture (same as every capture) */ | ||
| 1085 | 1 | secugen_i2c_write (ssm, dev, 0x32, 0x00); | |
| 1086 | 1 | break; | |
| 1087 | |||
| 1088 | 1 | case INIT_FINAL_START_CAPTURE: | |
| 1089 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_START_CAPTURE, 0x0001, 0); | |
| 1090 | 1 | break; | |
| 1091 | |||
| 1092 | 1 | case INIT_FINAL_START_STREAM: | |
| 1093 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_START_STREAM, 0x0000, 0); | |
| 1094 | 1 | break; | |
| 1095 | |||
| 1096 | 1 | case INIT_FINAL_BULK_READ: | |
| 1097 | 1 | secugen_read_frame (ssm, _dev, self->cal_raw); | |
| 1098 | 1 | break; | |
| 1099 | |||
| 1100 | 1 | case INIT_FINAL_STOP_STREAM: | |
| 1101 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_STOP_STREAM, 0x0000, 0); | |
| 1102 | 1 | break; | |
| 1103 | } | ||
| 1104 | 176 | } | |
| 1105 | |||
| 1106 | /* ================================================================ | ||
| 1107 | * Image Processing Pipeline | ||
| 1108 | * | ||
| 1109 | * 1. Band compensation at 956x688 | ||
| 1110 | * 2. Edge-aware unsharp mask at 956x688 | ||
| 1111 | * 3. Bilinear downsample to 300x400 | ||
| 1112 | * 4. Flat-field (blend) correction at 300x400 | ||
| 1113 | * 5. 6-directional sharpening at 300x400 | ||
| 1114 | * 6. Invert - bitwise NOT (~pixel) | ||
| 1115 | * | ||
| 1116 | * The raw SIDO020A readout has strong fixed-pattern noise (per-region | ||
| 1117 | * brightness bands, optical vignetting) that NBIS minutiae detection | ||
| 1118 | * does not compensate for; stages 1-5 replicate the capture processing | ||
| 1119 | * the sensor needs to produce a usable ridge image. Stages 1, 4 and 5 | ||
| 1120 | * are driven by per-device factory calibration data read from the FW | ||
| 1121 | * (see init_fw_read_cb) and degrade gracefully when it is unavailable. | ||
| 1122 | * ================================================================ */ | ||
| 1123 | |||
| 1124 | /* Brightness-dependent offset scaling for band compensation. | ||
| 1125 | * Correction strength steps down at pixel values 200, 160, 100, 70, 26. */ | ||
| 1126 | static inline int | ||
| 1127 | 328864 | secugen_blc_scale_offset (int pixel, int offset) | |
| 1128 | { | ||
| 1129 |
2/2✓ Branch 2 → 3 taken 327926 times.
✓ Branch 2 → 11 taken 938 times.
|
328864 | if (pixel > 200) |
| 1130 | return offset; | ||
| 1131 |
2/2✓ Branch 3 → 4 taken 3643 times.
✓ Branch 3 → 5 taken 324283 times.
|
327926 | else if (pixel > 160) |
| 1132 | 3643 | return (offset * 3) >> 2; | |
| 1133 |
2/2✓ Branch 5 → 6 taken 21776 times.
✓ Branch 5 → 7 taken 302507 times.
|
324283 | else if (pixel > 100) |
| 1134 | 21776 | return offset >> 1; | |
| 1135 |
2/2✓ Branch 7 → 8 taken 15731 times.
✓ Branch 7 → 9 taken 286776 times.
|
302507 | else if (pixel > 70) |
| 1136 | 15731 | return offset >> 2; | |
| 1137 |
2/2✓ Branch 9 → 10 taken 73752 times.
✓ Branch 9 → 11 taken 213024 times.
|
286776 | else if (pixel >= 26) |
| 1138 | 73752 | return offset >> 3; | |
| 1139 | else | ||
| 1140 | return 0; | ||
| 1141 | } | ||
| 1142 | |||
| 1143 | /* Per-region band compensation. | ||
| 1144 | * Region layout: 2 rows × 8 columns (16 regions). | ||
| 1145 | * Processes every other row with 3-phase dithering pattern. */ | ||
| 1146 | static void | ||
| 1147 | 1 | secugen_blc_compensate (guint8 *image, | |
| 1148 | int width, | ||
| 1149 | int height, | ||
| 1150 | const gint16 *offsets) | ||
| 1151 | { | ||
| 1152 | 1 | static const int phase_table[12] = { | |
| 1153 | 7, 11, 13, 14, 5, 3, 6, 9, 4, 8, 2, 1 | ||
| 1154 | }; | ||
| 1155 | 1 | int half_h = height / 2; | |
| 1156 | 1 | int eighth_w = width / 8; | |
| 1157 | 1 | int region; | |
| 1158 | |||
| 1159 |
2/2✓ Branch 27 → 3 taken 16 times.
✓ Branch 27 → 28 taken 1 time.
|
17 | for (region = 0; region < 16; region++) |
| 1160 | { | ||
| 1161 | 16 | int rc = region % 8; | |
| 1162 | 16 | int rr = region / 8; | |
| 1163 | 16 | int x0 = rc * eighth_w; | |
| 1164 |
2/2✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 14 times.
|
16 | int x1 = (rc == 7) ? width - 1 : (rc + 1) * eighth_w - 1; |
| 1165 | 16 | int y0 = rr * half_h; | |
| 1166 |
2/2✓ Branch 6 → 7 taken 8 times.
✓ Branch 6 → 8 taken 8 times.
|
16 | int y1 = (rr == 1) ? height - 1 : half_h - 1; |
| 1167 | 16 | int offset = offsets[region]; | |
| 1168 | 16 | int phase_counter = -1; | |
| 1169 | 16 | int row, col; | |
| 1170 | |||
| 1171 |
2/2✓ Branch 25 → 10 taken 2752 times.
✓ Branch 25 → 26 taken 16 times.
|
2768 | for (row = y0 + 1; row <= y1; row += 2) |
| 1172 | { | ||
| 1173 | 2752 | phase_counter++; | |
| 1174 |
2/2✓ Branch 10 → 11 taken 912 times.
✓ Branch 10 → 12 taken 1840 times.
|
2752 | if (phase_counter > 2) |
| 1175 | 912 | phase_counter = 0; | |
| 1176 | |||
| 1177 |
2/2✓ Branch 23 → 13 taken 328864 times.
✓ Branch 23 → 24 taken 2752 times.
|
331616 | for (col = x0; col <= x1; col++) |
| 1178 | { | ||
| 1179 | 328864 | int idx = row * width + col; | |
| 1180 | 328864 | int pixel = image[idx]; | |
| 1181 | 328864 | int scaled = secugen_blc_scale_offset (pixel, offset); | |
| 1182 | 328864 | int quot = scaled / 100; | |
| 1183 | 328864 | int remainder = scaled - quot * 100; | |
| 1184 | 328864 | int abs_rem = remainder < 0 ? -remainder : remainder; | |
| 1185 | 328864 | int extra = 0; | |
| 1186 | 328864 | int bit_idx = (col - x0) & 3; | |
| 1187 | 328864 | int result; | |
| 1188 | |||
| 1189 |
2/2✓ Branch 13 → 14 taken 19277 times.
✓ Branch 13 → 15 taken 309587 times.
|
328864 | if (abs_rem > 74) |
| 1190 | 19277 | extra = (phase_table[phase_counter] >> bit_idx) & 1; | |
| 1191 |
2/2✓ Branch 15 → 16 taken 76113 times.
✓ Branch 15 → 17 taken 233474 times.
|
309587 | else if (abs_rem > 49) |
| 1192 | 76113 | extra = (phase_table[phase_counter + 4] >> bit_idx) & 1; | |
| 1193 |
2/2✓ Branch 17 → 18 taken 16029 times.
✓ Branch 17 → 19 taken 217445 times.
|
233474 | else if (abs_rem > 24) |
| 1194 | 16029 | extra = (phase_table[phase_counter + 8] >> bit_idx) & 1; | |
| 1195 | |||
| 1196 |
1/2✓ Branch 19 → 20 taken 328864 times.
✗ Branch 19 → 21 not taken.
|
328864 | if (scaled >= 0) |
| 1197 | 328864 | result = pixel - (quot + extra); | |
| 1198 | else | ||
| 1199 | ✗ | result = pixel - (quot - extra); | |
| 1200 | |||
| 1201 | 328864 | if (result < 0) | |
| 1202 | result = 0; | ||
| 1203 | 328864 | else if (result > 255) | |
| 1204 | result = 255; | ||
| 1205 | 328864 | image[idx] = (guint8) result; | |
| 1206 | } | ||
| 1207 | } | ||
| 1208 | } | ||
| 1209 | 1 | } | |
| 1210 | |||
| 1211 | /* Edge-aware unsharp mask. | ||
| 1212 | * 3×3 box filter, threshold = pixel/8 + 2. | ||
| 1213 | * Only sharpens pixels where any neighbor differs by more than threshold. | ||
| 1214 | * Boost = (center - averaged) / 2, only for bright-side edges. */ | ||
| 1215 | static void | ||
| 1216 | 1 | secugen_unsharp_mask (guint8 *image, int width, int height) | |
| 1217 | { | ||
| 1218 | 1 | int total = width * height; | |
| 1219 | 2 | g_autofree guint8 *original = NULL; | |
| 1220 | 1 | g_autofree guint8 *averaged = NULL; | |
| 1221 | 1 | int r, c; | |
| 1222 | |||
| 1223 | 1 | original = g_malloc (total); | |
| 1224 | 1 | memcpy (original, image, total); | |
| 1225 | 1 | averaged = g_malloc (total); | |
| 1226 | 1 | memcpy (averaged, original, total); | |
| 1227 | |||
| 1228 | /* 3×3 box filter (AverageFilter with radius=1) */ | ||
| 1229 |
2/2✓ Branch 8 → 9 taken 686 times.
✓ Branch 8 → 10 taken 1 time.
|
687 | for (r = 1; r < height - 1; r++) |
| 1230 |
2/2✓ Branch 6 → 5 taken 654444 times.
✓ Branch 6 → 7 taken 686 times.
|
655130 | for (c = 1; c < width - 1; c++) |
| 1231 | { | ||
| 1232 | 654444 | int sum = original[(r - 1) * width + c - 1] | |
| 1233 | 654444 | + original[(r - 1) * width + c] | |
| 1234 | 654444 | + original[(r - 1) * width + c + 1] | |
| 1235 | 654444 | + original[r * width + c - 1] | |
| 1236 | 654444 | + original[r * width + c] | |
| 1237 | 654444 | + original[r * width + c + 1] | |
| 1238 | 654444 | + original[(r + 1) * width + c - 1] | |
| 1239 | 654444 | + original[(r + 1) * width + c] | |
| 1240 | 654444 | + original[(r + 1) * width + c + 1]; | |
| 1241 | |||
| 1242 | 654444 | averaged[r * width + c] = (guint8) (sum / 9); | |
| 1243 | } | ||
| 1244 | |||
| 1245 | /* Edge-aware sharpening (skips top 2 rows, bottom 1 row, | ||
| 1246 | * left/right 1 column) */ | ||
| 1247 |
2/2✓ Branch 20 → 21 taken 685 times.
✓ Branch 20 → 22 taken 1 time.
|
686 | for (r = 2; r < height - 1; r++) |
| 1248 |
2/2✓ Branch 18 → 11 taken 653490 times.
✓ Branch 18 → 19 taken 685 times.
|
654175 | for (c = 1; c < width - 1; c++) |
| 1249 | { | ||
| 1250 | 653490 | int idx = r * width + c; | |
| 1251 | 653490 | int center = original[idx]; | |
| 1252 | 653490 | int threshold = (center >> 3) + 2; | |
| 1253 | |||
| 1254 |
2/2✓ Branch 11 → 12 taken 597797 times.
✓ Branch 11 → 15 taken 55693 times.
|
653490 | if (abs (center - original[idx - 1]) > threshold || |
| 1255 |
2/2✓ Branch 12 → 13 taken 568348 times.
✓ Branch 12 → 15 taken 29449 times.
|
597797 | abs (center - original[idx + 1]) > threshold || |
| 1256 |
2/2✓ Branch 13 → 14 taken 527483 times.
✓ Branch 13 → 15 taken 40865 times.
|
568348 | abs (center - original[idx - width]) > threshold || |
| 1257 |
2/2✓ Branch 14 → 15 taken 18804 times.
✓ Branch 14 → 17 taken 508679 times.
|
527483 | abs (center - original[idx + width]) > threshold) |
| 1258 | { | ||
| 1259 | 144811 | int avg = averaged[idx]; | |
| 1260 | |||
| 1261 |
2/2✓ Branch 15 → 16 taken 49027 times.
✓ Branch 15 → 17 taken 95784 times.
|
144811 | if (center > avg + 1) |
| 1262 | { | ||
| 1263 | 49027 | int boost = (center - avg) / 2; | |
| 1264 | 49027 | int result = center + boost; | |
| 1265 | |||
| 1266 | 49027 | if (result > 255) | |
| 1267 | result = 255; | ||
| 1268 | 49027 | image[idx] = (guint8) result; | |
| 1269 | } | ||
| 1270 | /* else: keep original (already in image) */ | ||
| 1271 | } | ||
| 1272 | /* else: keep original */ | ||
| 1273 | } | ||
| 1274 | |||
| 1275 | 1 | } | |
| 1276 | |||
| 1277 | /* | ||
| 1278 | * Bilinear downsample from raw sensor to output dimensions. | ||
| 1279 | * | ||
| 1280 | * Uses 8-bit fixed-point weights rather than floating point so the decoded | ||
| 1281 | * image is bit-identical across compilers and architectures: the umockdev | ||
| 1282 | * test compares the output against a committed reference image pixel-for-pixel | ||
| 1283 | * (with no tolerance), and float rounding would diverge between build | ||
| 1284 | * environments. Interpolation weights sum to FRAC*FRAC, so the weighted sum is | ||
| 1285 | * rounded back down by that factor with round-to-nearest. | ||
| 1286 | */ | ||
| 1287 | #define SECUGEN_RESIZE_FRAC_BITS 8 | ||
| 1288 | #define SECUGEN_RESIZE_FRAC (1 << SECUGEN_RESIZE_FRAC_BITS) | ||
| 1289 | |||
| 1290 | static void | ||
| 1291 | 2 | secugen_resize_bilinear (const guint8 *src, int src_w, int src_h, | |
| 1292 | guint8 *dst, int dst_w, int dst_h) | ||
| 1293 | { | ||
| 1294 | 2 | int r, c; | |
| 1295 | |||
| 1296 |
2/2✓ Branch 13 → 3 taken 800 times.
✓ Branch 13 → 14 taken 2 times.
|
802 | for (r = 0; r < dst_h; r++) |
| 1297 | { | ||
| 1298 | /* Source row position in fixed point (scaled by SECUGEN_RESIZE_FRAC). */ | ||
| 1299 | 800 | int src_r = (int) ((gint64) r * (src_h - 1) * SECUGEN_RESIZE_FRAC / | |
| 1300 | 800 | (dst_h - 1)); | |
| 1301 | 800 | int r0 = src_r >> SECUGEN_RESIZE_FRAC_BITS; | |
| 1302 | 800 | int r1 = r0 + 1; | |
| 1303 | 800 | int wr1 = src_r & (SECUGEN_RESIZE_FRAC - 1); | |
| 1304 | 800 | int wr0 = SECUGEN_RESIZE_FRAC - wr1; | |
| 1305 | |||
| 1306 |
2/2✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 798 times.
|
800 | if (r1 >= src_h) |
| 1307 | 2 | r1 = src_h - 1; | |
| 1308 | |||
| 1309 |
2/2✓ Branch 11 → 6 taken 240000 times.
✓ Branch 11 → 12 taken 800 times.
|
240800 | for (c = 0; c < dst_w; c++) |
| 1310 | { | ||
| 1311 | 240000 | int src_c = (int) ((gint64) c * (src_w - 1) * SECUGEN_RESIZE_FRAC / | |
| 1312 | 240000 | (dst_w - 1)); | |
| 1313 | 240000 | int c0 = src_c >> SECUGEN_RESIZE_FRAC_BITS; | |
| 1314 | 240000 | int c1 = c0 + 1; | |
| 1315 | 240000 | int wc1 = src_c & (SECUGEN_RESIZE_FRAC - 1); | |
| 1316 | 240000 | int wc0 = SECUGEN_RESIZE_FRAC - wc1; | |
| 1317 | 240000 | guint32 acc; | |
| 1318 | 240000 | int pixel; | |
| 1319 | |||
| 1320 |
2/2✓ Branch 6 → 7 taken 800 times.
✓ Branch 6 → 8 taken 239200 times.
|
240000 | if (c1 >= src_w) |
| 1321 | 800 | c1 = src_w - 1; | |
| 1322 | |||
| 1323 | 240000 | acc = (guint32) wr0 * wc0 * src[r0 * src_w + c0] | |
| 1324 | 240000 | + (guint32) wr0 * wc1 * src[r0 * src_w + c1] | |
| 1325 | 240000 | + (guint32) wr1 * wc0 * src[r1 * src_w + c0] | |
| 1326 | 240000 | + (guint32) wr1 * wc1 * src[r1 * src_w + c1]; | |
| 1327 | |||
| 1328 | 240000 | pixel = (int) ((acc + (SECUGEN_RESIZE_FRAC * SECUGEN_RESIZE_FRAC) / 2) | |
| 1329 | 240000 | >> (2 * SECUGEN_RESIZE_FRAC_BITS)); | |
| 1330 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 240000 times.
|
240000 | if (pixel > 255) |
| 1331 | ✗ | pixel = 255; | |
| 1332 | 240000 | dst[r * dst_w + c] = (guint8) pixel; | |
| 1333 | } | ||
| 1334 | } | ||
| 1335 | 2 | } | |
| 1336 | |||
| 1337 | /* Flat-field (blend) correction. | ||
| 1338 | * Formula: result = pixel + ((cal_val - ref[i]) * curve[pixel]) >> 8 | ||
| 1339 | * The curve is an S-curve that protects dark pixels and applies | ||
| 1340 | * full correction to bright pixels. */ | ||
| 1341 | static void | ||
| 1342 | 1 | secugen_blend (guint8 *image, | |
| 1343 | int width, | ||
| 1344 | int height, | ||
| 1345 | const guint8 *ref_image, | ||
| 1346 | int cal_val, | ||
| 1347 | const guint8 *curve) | ||
| 1348 | { | ||
| 1349 | 1 | int r, c; | |
| 1350 | |||
| 1351 |
2/2✓ Branch 6 → 7 taken 400 times.
✓ Branch 6 → 8 taken 1 time.
|
401 | for (r = 0; r < height; r++) |
| 1352 |
2/2✓ Branch 4 → 3 taken 120000 times.
✓ Branch 4 → 5 taken 400 times.
|
120400 | for (c = 0; c < width; c++) |
| 1353 | { | ||
| 1354 | 120000 | int idx = r * width + c; | |
| 1355 | 120000 | int pixel = image[idx]; | |
| 1356 | 120000 | int ref_val = ref_image[idx]; | |
| 1357 | 120000 | int curve_val = curve[pixel]; | |
| 1358 | 120000 | int correction = ((cal_val - ref_val) * curve_val) >> 8; | |
| 1359 | 120000 | int result = pixel + correction; | |
| 1360 | |||
| 1361 | 120000 | if (result > 255) | |
| 1362 | result = 255; | ||
| 1363 | 120000 | else if (result < 0) | |
| 1364 | result = 0; | ||
| 1365 | 120000 | image[idx] = (guint8) result; | |
| 1366 | } | ||
| 1367 | 1 | } | |
| 1368 | |||
| 1369 | /* 6-directional gradient sharpening. | ||
| 1370 | * Only enhances pixels where ALL 6 gradients agree on edge direction. | ||
| 1371 | * Case 1: all gradients > threshold (peak) → positive boost. | ||
| 1372 | * Case 2: all gradients < -threshold (valley) → negative boost. */ | ||
| 1373 | static void | ||
| 1374 | 1 | secugen_sharpen (const guint8 *src, | |
| 1375 | guint8 *dst, | ||
| 1376 | int width, | ||
| 1377 | int height, | ||
| 1378 | int threshold, | ||
| 1379 | int amount, | ||
| 1380 | int limit) | ||
| 1381 | { | ||
| 1382 | 1 | int neg_threshold = -threshold; | |
| 1383 | 1 | int neg_amount = -amount; | |
| 1384 | 1 | int r, c; | |
| 1385 | |||
| 1386 |
2/2✓ Branch 20 → 21 taken 400 times.
✓ Branch 20 → 22 taken 1 time.
|
401 | for (r = 0; r < height; r++) |
| 1387 |
2/2✓ Branch 18 → 3 taken 120000 times.
✓ Branch 18 → 19 taken 400 times.
|
120400 | for (c = 0; c < width; c++) |
| 1388 | { | ||
| 1389 | 120000 | int idx = r * width + c; | |
| 1390 | |||
| 1391 |
8/8✓ Branch 3 → 4 taken 119700 times.
✓ Branch 3 → 7 taken 300 times.
✓ Branch 4 → 5 taken 119400 times.
✓ Branch 4 → 7 taken 300 times.
✓ Branch 5 → 6 taken 119002 times.
✓ Branch 5 → 7 taken 398 times.
✓ Branch 6 → 7 taken 398 times.
✓ Branch 6 → 8 taken 118604 times.
|
120000 | if (r == 0 || r == height - 1 || c == 0 || c == width - 1) |
| 1392 | { | ||
| 1393 | 1396 | dst[idx] = src[idx]; | |
| 1394 | 1396 | continue; | |
| 1395 | } | ||
| 1396 | |||
| 1397 | { | ||
| 1398 | 118604 | int center = src[idx]; | |
| 1399 | 118604 | int d_up = center - src[(r - 1) * width + c]; | |
| 1400 | 118604 | int d_down = center - src[(r + 1) * width + c]; | |
| 1401 | 118604 | int d_left = center - src[r * width + c - 1]; | |
| 1402 | 118604 | int d_right = center - src[r * width + c + 1]; | |
| 1403 | 118604 | int d_ul = src[r * width + c - 1] - | |
| 1404 | 118604 | src[(r - 1) * width + c - 1]; | |
| 1405 | 118604 | int d_dr = src[r * width + c + 1] - | |
| 1406 | 118604 | src[(r + 1) * width + c + 1]; | |
| 1407 | |||
| 1408 |
2/2✓ Branch 8 → 9 taken 68 times.
✓ Branch 8 → 12 taken 118536 times.
|
118604 | if (d_up > threshold && d_down > threshold && |
| 1409 |
2/2✓ Branch 9 → 10 taken 6 times.
✓ Branch 9 → 12 taken 62 times.
|
68 | d_left > threshold && d_right > threshold && |
| 1410 |
1/2✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 6 times.
|
6 | d_ul > threshold && d_dr > threshold) |
| 1411 | ✗ | { | |
| 1412 | ✗ | int g1 = MIN (MIN (d_up, d_left), amount); | |
| 1413 | ✗ | int g2 = MIN (MIN (d_down, d_right), amount); | |
| 1414 | ✗ | int g3 = MIN (MIN (d_ul, d_dr), amount); | |
| 1415 | ✗ | int avg = (g1 + g2 + g3) / 3; | |
| 1416 | ✗ | int boost = (avg - threshold) * limit / 10; | |
| 1417 | ✗ | int result = center + boost; | |
| 1418 | |||
| 1419 | ✗ | if (result > 255) | |
| 1420 | result = 255; | ||
| 1421 | ✗ | else if (result < 0) | |
| 1422 | result = 0; | ||
| 1423 | ✗ | dst[idx] = (guint8) result; | |
| 1424 | } | ||
| 1425 |
2/2✓ Branch 12 → 13 taken 14 times.
✓ Branch 12 → 16 taken 118590 times.
|
118604 | else if (d_up < neg_threshold && d_down < neg_threshold && |
| 1426 |
1/2✗ Branch 13 → 14 not taken.
✓ Branch 13 → 16 taken 14 times.
|
14 | d_left < neg_threshold && d_right < neg_threshold && |
| 1427 | ✗ | d_ul < neg_threshold && d_dr < neg_threshold) | |
| 1428 | ✗ | { | |
| 1429 | ✗ | int g1 = MAX (MAX (d_up, d_left), neg_amount); | |
| 1430 | ✗ | int g2 = MAX (MAX (d_down, d_right), neg_amount); | |
| 1431 | ✗ | int g3 = MAX (MAX (d_ul, d_dr), neg_amount); | |
| 1432 | ✗ | int avg = (g1 + g2 + g3) / 3; | |
| 1433 | ✗ | int boost = (avg + threshold) * limit / 10; | |
| 1434 | ✗ | int result = center + boost; | |
| 1435 | |||
| 1436 | ✗ | if (result > 255) | |
| 1437 | result = 255; | ||
| 1438 | ✗ | else if (result < 0) | |
| 1439 | result = 0; | ||
| 1440 | ✗ | dst[idx] = (guint8) result; | |
| 1441 | } | ||
| 1442 | else | ||
| 1443 | { | ||
| 1444 | 118604 | dst[idx] = src[idx]; | |
| 1445 | } | ||
| 1446 | } | ||
| 1447 | } | ||
| 1448 | 1 | } | |
| 1449 | |||
| 1450 | typedef struct | ||
| 1451 | { | ||
| 1452 | guint8 *raw_frame; | ||
| 1453 | gboolean has_blc_offsets; | ||
| 1454 | gint16 blc_offsets[16]; | ||
| 1455 | gboolean has_ref_image; | ||
| 1456 | guint8 *ref_image; | ||
| 1457 | guint16 cal_value; | ||
| 1458 | gboolean sharpen_enabled; | ||
| 1459 | guint8 sharpen_threshold; | ||
| 1460 | guint8 sharpen_amount; | ||
| 1461 | guint8 sharpen_limit; | ||
| 1462 | } SecugenCaptureTaskData; | ||
| 1463 | |||
| 1464 | typedef struct | ||
| 1465 | { | ||
| 1466 | guint8 *frame; | ||
| 1467 | guint8 *cal_raw; | ||
| 1468 | } SecugenDetectTaskData; | ||
| 1469 | |||
| 1470 | typedef struct | ||
| 1471 | { | ||
| 1472 | guint mean; | ||
| 1473 | gboolean finger_present; | ||
| 1474 | gboolean update_calibration; | ||
| 1475 | guint8 *new_cal_raw; | ||
| 1476 | } SecugenDetectTaskResult; | ||
| 1477 | |||
| 1478 | static void | ||
| 1479 | 1 | secugen_capture_task_data_free (SecugenCaptureTaskData *data) | |
| 1480 | { | ||
| 1481 |
1/2✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
|
1 | g_clear_pointer (&data->raw_frame, g_free); |
| 1482 |
1/2✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
|
1 | g_clear_pointer (&data->ref_image, g_free); |
| 1483 | 1 | g_free (data); | |
| 1484 | 1 | } | |
| 1485 | |||
| 1486 | static void | ||
| 1487 | ✗ | secugen_detect_task_data_free (SecugenDetectTaskData *data) | |
| 1488 | { | ||
| 1489 | ✗ | g_clear_pointer (&data->frame, g_free); | |
| 1490 | ✗ | g_clear_pointer (&data->cal_raw, g_free); | |
| 1491 | ✗ | g_free (data); | |
| 1492 | ✗ | } | |
| 1493 | |||
| 1494 | static void | ||
| 1495 | ✗ | secugen_detect_task_result_free (SecugenDetectTaskResult *result) | |
| 1496 | { | ||
| 1497 | ✗ | g_clear_pointer (&result->new_cal_raw, g_free); | |
| 1498 | ✗ | g_free (result); | |
| 1499 | ✗ | } | |
| 1500 | |||
| 1501 | 1 | G_DEFINE_AUTOPTR_CLEANUP_FUNC (SecugenCaptureTaskData, secugen_capture_task_data_free) | |
| 1502 | ✗ | G_DEFINE_AUTOPTR_CLEANUP_FUNC (SecugenDetectTaskData, secugen_detect_task_data_free) | |
| 1503 | ✗ | G_DEFINE_AUTOPTR_CLEANUP_FUNC (SecugenDetectTaskResult, secugen_detect_task_result_free) | |
| 1504 | |||
| 1505 | static void | ||
| 1506 | 1 | secugen_capture_process_thread (GTask *task, | |
| 1507 | gpointer source_object G_GNUC_UNUSED, | ||
| 1508 | gpointer task_data, | ||
| 1509 | GCancellable *cancellable G_GNUC_UNUSED) | ||
| 1510 | { | ||
| 1511 | 1 | SecugenCaptureTaskData *data = task_data; | |
| 1512 | 2 | g_autofree guint8 *image = g_malloc (SECUGEN_IMG_SIZE); | |
| 1513 | |||
| 1514 |
1/2✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 26 not taken.
|
1 | if (g_task_return_error_if_cancelled (task)) |
| 1515 | return; | ||
| 1516 | |||
| 1517 | /* | ||
| 1518 | * Image processing pipeline: | ||
| 1519 | * 1. Band compensation at 956x688 | ||
| 1520 | * 2. Edge-aware unsharp mask at 956x688 | ||
| 1521 | * 3. Bilinear downsample 956x688 → 300x400 | ||
| 1522 | * 4. Flat-field blend at 300x400 | ||
| 1523 | * 5. Directional sharpening at 300x400 (conditional) | ||
| 1524 | * 6. Bitwise NOT invert | ||
| 1525 | */ | ||
| 1526 | |||
| 1527 | /* Step 1: BLC band compensation */ | ||
| 1528 |
1/2✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 7 not taken.
|
1 | if (data->has_blc_offsets) |
| 1529 | 1 | secugen_blc_compensate (data->raw_frame, SECUGEN_RAW_WIDTH, | |
| 1530 | 1 | SECUGEN_RAW_HEIGHT, data->blc_offsets); | |
| 1531 | |||
| 1532 | /* Step 2: Edge-aware unsharp mask */ | ||
| 1533 | 1 | secugen_unsharp_mask (data->raw_frame, SECUGEN_RAW_WIDTH, SECUGEN_RAW_HEIGHT); | |
| 1534 | |||
| 1535 | /* Step 3: Bilinear downsample to 300x400 */ | ||
| 1536 | 1 | secugen_resize_bilinear (data->raw_frame, | |
| 1537 | SECUGEN_RAW_WIDTH, SECUGEN_RAW_HEIGHT, | ||
| 1538 | image, | ||
| 1539 | SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT); | ||
| 1540 | |||
| 1541 |
1/2✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 26 not taken.
|
1 | if (g_task_return_error_if_cancelled (task)) |
| 1542 | return; | ||
| 1543 | |||
| 1544 | /* Step 4: Blend flat-field correction */ | ||
| 1545 |
1/2✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 13 not taken.
|
1 | if (data->has_ref_image) |
| 1546 | { | ||
| 1547 | 1 | secugen_blend (image, | |
| 1548 | SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT, | ||
| 1549 | 1 | data->ref_image, | |
| 1550 | 1 | data->cal_value, | |
| 1551 | SECUGEN_BLEND_CURVE); | ||
| 1552 | } | ||
| 1553 | |||
| 1554 |
1/2✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 26 not taken.
|
1 | if (g_task_return_error_if_cancelled (task)) |
| 1555 | return; | ||
| 1556 | |||
| 1557 | /* Step 5: Directional sharpening (conditional) */ | ||
| 1558 |
1/2✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 19 not taken.
|
1 | if (data->sharpen_enabled) |
| 1559 | { | ||
| 1560 | 2 | g_autofree guint8 *sharpened = g_malloc (SECUGEN_IMG_SIZE); | |
| 1561 | |||
| 1562 | 1 | secugen_sharpen (image, sharpened, | |
| 1563 | SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT, | ||
| 1564 | 1 | data->sharpen_threshold, | |
| 1565 | 1 | data->sharpen_amount, | |
| 1566 | 1 | data->sharpen_limit); | |
| 1567 | 1 | memcpy (image, sharpened, SECUGEN_IMG_SIZE); | |
| 1568 | } | ||
| 1569 | |||
| 1570 | /* Step 6: Invert (bitwise NOT) */ | ||
| 1571 |
2/2✓ Branch 21 → 20 taken 120000 times.
✓ Branch 21 → 22 taken 1 time.
|
120001 | for (int i = 0; i < SECUGEN_IMG_SIZE; i++) |
| 1572 | 120000 | image[i] = ~image[i]; | |
| 1573 | |||
| 1574 |
1/2✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 26 not taken.
|
1 | if (g_task_return_error_if_cancelled (task)) |
| 1575 | return; | ||
| 1576 | |||
| 1577 | 1 | g_task_return_pointer (task, g_steal_pointer (&image), g_free); | |
| 1578 | } | ||
| 1579 | |||
| 1580 | static void | ||
| 1581 | ✗ | secugen_detect_analyze_thread (GTask *task, | |
| 1582 | gpointer source_object G_GNUC_UNUSED, | ||
| 1583 | gpointer task_data, | ||
| 1584 | GCancellable *cancellable) | ||
| 1585 | { | ||
| 1586 | ✗ | SecugenDetectTaskData *data = task_data; | |
| 1587 | |||
| 1588 | ✗ | g_autoptr(SecugenDetectTaskResult) result = g_new0 (SecugenDetectTaskResult, 1); | |
| 1589 | ✗ | guint64 sum = 0; | |
| 1590 | ✗ | int count = 0; | |
| 1591 | ✗ | int start_row = SECUGEN_RAW_HEIGHT / 4; | |
| 1592 | ✗ | int end_row = 3 * SECUGEN_RAW_HEIGHT / 4; | |
| 1593 | ✗ | int start_col = SECUGEN_RAW_WIDTH / 4; | |
| 1594 | ✗ | int end_col = 3 * SECUGEN_RAW_WIDTH / 4; | |
| 1595 | |||
| 1596 | ✗ | if (g_task_return_error_if_cancelled (task)) | |
| 1597 | return; | ||
| 1598 | |||
| 1599 | /* Compute mean brightness of central 50% region */ | ||
| 1600 | ✗ | for (int y = start_row; y < end_row; y++) | |
| 1601 | { | ||
| 1602 | ✗ | for (int x = start_col; x < end_col; x++) | |
| 1603 | { | ||
| 1604 | ✗ | int idx = y * SECUGEN_RAW_WIDTH + x; | |
| 1605 | ✗ | int val = (int) data->frame[idx] - (int) data->cal_raw[idx]; | |
| 1606 | |||
| 1607 | ✗ | if (val < 0) | |
| 1608 | val = 0; | ||
| 1609 | ✗ | sum += val; | |
| 1610 | ✗ | count++; | |
| 1611 | } | ||
| 1612 | |||
| 1613 | ✗ | if (g_task_return_error_if_cancelled (task)) | |
| 1614 | return; | ||
| 1615 | } | ||
| 1616 | |||
| 1617 | ✗ | result->mean = count > 0 ? (guint) (sum / count) : 0; | |
| 1618 | ✗ | result->finger_present = (result->mean >= SECUGEN_FINGER_THRESHOLD); | |
| 1619 | ✗ | result->update_calibration = (!result->finger_present && result->mean <= 5); | |
| 1620 | ✗ | if (result->update_calibration) | |
| 1621 | ✗ | result->new_cal_raw = g_memdup2 (data->frame, SECUGEN_BULK_BUF_SIZE); | |
| 1622 | |||
| 1623 | ✗ | g_task_return_pointer (task, | |
| 1624 | g_steal_pointer (&result), | ||
| 1625 | (GDestroyNotify) secugen_detect_task_result_free); | ||
| 1626 | } | ||
| 1627 | |||
| 1628 | /* ================================================================ | ||
| 1629 | * Capture State Machine | ||
| 1630 | * | ||
| 1631 | * Capture sequence observed in USB captures: | ||
| 1632 | * I2C 0x32 = 0x00 -> SET_EXPOSURE -> START_CAPTURE -> | ||
| 1633 | * START_STREAM -> bulk read 657KB -> STOP_STREAM -> GET_STATUS | ||
| 1634 | * ================================================================ */ | ||
| 1635 | |||
| 1636 | enum capture_states { | ||
| 1637 | CAPTURE_REG32_CLEAR = 0, | ||
| 1638 | CAPTURE_LED_ON, | ||
| 1639 | CAPTURE_SET_EXPOSURE, | ||
| 1640 | CAPTURE_START, | ||
| 1641 | CAPTURE_STREAM_ON, | ||
| 1642 | CAPTURE_BULK_READ, | ||
| 1643 | CAPTURE_STREAM_OFF, | ||
| 1644 | CAPTURE_GET_STATUS, | ||
| 1645 | CAPTURE_DONE, | ||
| 1646 | CAPTURE_NUM_STATES, | ||
| 1647 | }; | ||
| 1648 | |||
| 1649 | static void | ||
| 1650 | 1 | capture_process_done (GObject *source_object, | |
| 1651 | GAsyncResult *result, | ||
| 1652 | gpointer user_data) | ||
| 1653 | { | ||
| 1654 | 1 | g_autoptr(FpImage) img = NULL; | |
| 1655 | 1 | g_autoptr(GError) error = NULL; | |
| 1656 |
1/4✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 1 time.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
|
1 | g_autofree guint8 *processed = NULL; |
| 1657 | 1 | FpDevice *dev = FP_DEVICE (source_object); | |
| 1658 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 1659 | 1 | FpiSsm *ssm = user_data; | |
| 1660 | |||
| 1661 | 1 | processed = g_task_propagate_pointer (G_TASK (result), &error); | |
| 1662 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 11 taken 1 time.
|
1 | if (error) |
| 1663 | { | ||
| 1664 | ✗ | if (self->deactivating && | |
| 1665 | ✗ | g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) | |
| 1666 | { | ||
| 1667 | ✗ | fpi_ssm_mark_completed (ssm); | |
| 1668 | ✗ | return; | |
| 1669 | } | ||
| 1670 | |||
| 1671 | ✗ | fpi_ssm_mark_failed (ssm, g_steal_pointer (&error)); | |
| 1672 | ✗ | return; | |
| 1673 | } | ||
| 1674 | |||
| 1675 |
1/2✗ Branch 11 → 12 not taken.
✓ Branch 11 → 14 taken 1 time.
|
1 | if (self->deactivating) |
| 1676 | { | ||
| 1677 | ✗ | fpi_ssm_mark_completed (ssm); | |
| 1678 | ✗ | return; | |
| 1679 | } | ||
| 1680 | |||
| 1681 | 1 | img = fp_image_new (SECUGEN_IMG_WIDTH, SECUGEN_IMG_HEIGHT); | |
| 1682 | 1 | img->ppmm = SECUGEN_PPMM; | |
| 1683 | 1 | img->flags = FPI_IMAGE_V_FLIPPED | FPI_IMAGE_H_FLIPPED; | |
| 1684 | 1 | memcpy (img->data, processed, SECUGEN_IMG_SIZE); | |
| 1685 | |||
| 1686 | 1 | fpi_image_device_image_captured (FP_IMAGE_DEVICE (dev), g_steal_pointer (&img)); | |
| 1687 | 1 | fpi_ssm_mark_completed (ssm); | |
| 1688 | } | ||
| 1689 | |||
| 1690 | static void | ||
| 1691 | 1 | capture_status_cb (FpiUsbTransfer *transfer, | |
| 1692 | FpDevice *dev, | ||
| 1693 | gpointer user_data, | ||
| 1694 | GError *error) | ||
| 1695 | { | ||
| 1696 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 1697 | |||
| 1698 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 1 time.
|
1 | if (error) |
| 1699 | { | ||
| 1700 | ✗ | fpi_ssm_mark_failed (transfer->ssm, error); | |
| 1701 | ✗ | return; | |
| 1702 | } | ||
| 1703 | |||
| 1704 | 1 | memcpy (self->last_status, transfer->buffer, | |
| 1705 | 1 | MIN (transfer->actual_length, 4)); | |
| 1706 | 1 | fpi_ssm_next_state (transfer->ssm); | |
| 1707 | } | ||
| 1708 | |||
| 1709 | static void | ||
| 1710 | 9 | capture_run_state (FpiSsm *ssm, FpDevice *_dev) | |
| 1711 | { | ||
| 1712 | 9 | FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); | |
| 1713 | 9 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (_dev); | |
| 1714 | |||
| 1715 |
9/10✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 1 time.
✓ Branch 3 → 8 taken 1 time.
✓ Branch 3 → 10 taken 1 time.
✓ Branch 3 → 12 taken 1 time.
✓ Branch 3 → 14 taken 1 time.
✓ Branch 3 → 16 taken 1 time.
✓ Branch 3 → 18 taken 1 time.
✓ Branch 3 → 20 taken 1 time.
✗ Branch 3 → 33 not taken.
|
9 | switch (fpi_ssm_get_cur_state (ssm)) |
| 1716 | { | ||
| 1717 | 1 | case CAPTURE_REG32_CLEAR: | |
| 1718 | /* Must clear reg 0x32 before each capture */ | ||
| 1719 | 1 | secugen_i2c_write (ssm, dev, 0x32, 0x00); | |
| 1720 | 1 | break; | |
| 1721 | |||
| 1722 | 1 | case CAPTURE_LED_ON: | |
| 1723 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_LED_CONTROL, 0x0001, 0); | |
| 1724 | 1 | break; | |
| 1725 | |||
| 1726 | 1 | case CAPTURE_SET_EXPOSURE: | |
| 1727 | 1 | secugen_set_exposure (ssm, dev, self->exposure); | |
| 1728 | 1 | break; | |
| 1729 | |||
| 1730 | 1 | case CAPTURE_START: | |
| 1731 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_START_CAPTURE, 0x0001, 0); | |
| 1732 | 1 | break; | |
| 1733 | |||
| 1734 | 1 | case CAPTURE_STREAM_ON: | |
| 1735 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_START_STREAM, 0x0000, 0); | |
| 1736 | 1 | break; | |
| 1737 | |||
| 1738 | 1 | case CAPTURE_BULK_READ: | |
| 1739 | 1 | secugen_read_frame (ssm, _dev, self->bulk_buffer); | |
| 1740 | 1 | break; | |
| 1741 | |||
| 1742 | 1 | case CAPTURE_STREAM_OFF: | |
| 1743 | 1 | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_STOP_STREAM, 0x0000, 0); | |
| 1744 | 1 | break; | |
| 1745 | |||
| 1746 | 1 | case CAPTURE_GET_STATUS: | |
| 1747 | 1 | secugen_ctrl_in (ssm, dev, SECUGEN_REQ_GET_STATUS, | |
| 1748 | 0x0000, 0, 4, capture_status_cb); | ||
| 1749 | 1 | break; | |
| 1750 | |||
| 1751 | case CAPTURE_DONE: | ||
| 1752 | { | ||
| 1753 | 10 | g_autoptr(GTask) task = NULL; | |
| 1754 | 2 | g_autoptr(SecugenCaptureTaskData) task_data = NULL; | |
| 1755 | |||
| 1756 | /* Snapshot frame + calibration parameters for worker-thread processing. | ||
| 1757 | * The shared driver buffers remain owned by the main thread. */ | ||
| 1758 | 1 | task_data = g_new0 (SecugenCaptureTaskData, 1); | |
| 1759 | 1 | task_data->raw_frame = g_memdup2 (self->bulk_buffer, SECUGEN_RAW_SIZE); | |
| 1760 | 1 | task_data->has_blc_offsets = self->has_blc_offsets; | |
| 1761 | 1 | memcpy (task_data->blc_offsets, self->blc_offsets, sizeof (task_data->blc_offsets)); | |
| 1762 | 1 | task_data->has_ref_image = self->has_ref_image; | |
| 1763 | |||
| 1764 |
1/2✓ Branch 22 → 23 taken 1 time.
✗ Branch 22 → 25 not taken.
|
1 | if (self->has_ref_image) |
| 1765 | 1 | task_data->ref_image = g_memdup2 (self->ref_image, SECUGEN_IMG_SIZE); | |
| 1766 | |||
| 1767 | 1 | task_data->cal_value = self->cal_value; | |
| 1768 | 1 | task_data->sharpen_enabled = self->sharpen_enabled; | |
| 1769 | 1 | task_data->sharpen_threshold = self->sharpen_threshold; | |
| 1770 | 1 | task_data->sharpen_amount = self->sharpen_amount; | |
| 1771 | 1 | task_data->sharpen_limit = self->sharpen_limit; | |
| 1772 | |||
| 1773 | 1 | task = g_task_new (FP_DEVICE (dev), | |
| 1774 | fpi_device_get_cancellable (FP_DEVICE (dev)), | ||
| 1775 | capture_process_done, | ||
| 1776 | ssm); | ||
| 1777 | 1 | g_task_set_source_tag (task, secugen_capture_process_thread); | |
| 1778 | 1 | g_task_set_check_cancellable (task, TRUE); | |
| 1779 | 1 | g_task_set_task_data (task, g_steal_pointer (&task_data), | |
| 1780 | (GDestroyNotify) secugen_capture_task_data_free); | ||
| 1781 | 1 | g_task_run_in_thread (task, secugen_capture_process_thread); | |
| 1782 |
1/2✓ Branch 31 → 32 taken 1 time.
✗ Branch 31 → 33 not taken.
|
1 | break; |
| 1783 | } | ||
| 1784 | } | ||
| 1785 | 9 | } | |
| 1786 | |||
| 1787 | static void secugen_finish_deactivate (FpImageDevice *dev); | ||
| 1788 | |||
| 1789 | static void | ||
| 1790 | 1 | capture_ssm_complete (FpiSsm *ssm, FpDevice *dev, GError *error) | |
| 1791 | { | ||
| 1792 | 2 | g_autoptr(GError) local_error = error; | |
| 1793 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 1794 | 1 | FpImageDevice *imgdev = FP_IMAGE_DEVICE (dev); | |
| 1795 | |||
| 1796 | 1 | self->ssm_count--; | |
| 1797 | |||
| 1798 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 7 taken 1 time.
|
1 | if (self->deactivating) |
| 1799 | { | ||
| 1800 | ✗ | if (self->ssm_count == 0) | |
| 1801 | ✗ | secugen_finish_deactivate (imgdev); | |
| 1802 | ✗ | return; | |
| 1803 | } | ||
| 1804 | |||
| 1805 |
1/2✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 1 time.
|
1 | if (local_error) |
| 1806 | { | ||
| 1807 | ✗ | fp_warn ("Capture failed: %s", local_error->message); | |
| 1808 | ✗ | fpi_image_device_session_error (imgdev, g_steal_pointer (&local_error)); | |
| 1809 | } | ||
| 1810 | } | ||
| 1811 | |||
| 1812 | /* No-op callback for fire-and-forget USB transfers (e.g. LED control) */ | ||
| 1813 | static void | ||
| 1814 | 1 | secugen_noop_cb (FpiUsbTransfer *transfer, | |
| 1815 | FpDevice *dev, | ||
| 1816 | gpointer user_data, | ||
| 1817 | GError *error) | ||
| 1818 | { | ||
| 1819 | 2 | g_autoptr(GError) local_error = error; | |
| 1820 | |||
| 1821 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 5 taken 1 time.
|
1 | if (local_error) |
| 1822 | ✗ | fp_warn ("Fire-and-forget transfer failed: %s", local_error->message); | |
| 1823 | 1 | } | |
| 1824 | |||
| 1825 | /* ================================================================ | ||
| 1826 | * Finger Detection (Image-Based) | ||
| 1827 | * | ||
| 1828 | * The SIDO020A has no touch/proximity chip. GET_STATUS always returns | ||
| 1829 | * zeros. Finger presence is instead detected by capturing preview | ||
| 1830 | * frames and checking mean brightness: | ||
| 1831 | * - No finger: dark image (mean ~20) | ||
| 1832 | * - Finger present: reflected LED light (mean ~70+) | ||
| 1833 | * | ||
| 1834 | * We implement this as a detect SSM that captures a frame, computes | ||
| 1835 | * mean brightness of the central region, and reports finger status. | ||
| 1836 | * If no finger, we schedule another poll after SECUGEN_FINGER_POLL_MS. | ||
| 1837 | * ================================================================ */ | ||
| 1838 | |||
| 1839 | enum detect_states { | ||
| 1840 | DETECT_REG32_CLEAR = 0, | ||
| 1841 | DETECT_SET_EXPOSURE, | ||
| 1842 | DETECT_START_CAPTURE, | ||
| 1843 | DETECT_START_STREAM, | ||
| 1844 | DETECT_BULK_READ, | ||
| 1845 | DETECT_STOP_STREAM, | ||
| 1846 | DETECT_ANALYZE, | ||
| 1847 | DETECT_NUM_STATES, | ||
| 1848 | }; | ||
| 1849 | |||
| 1850 | static void detect_start (FpImageDevice *dev); | ||
| 1851 | static void detect_retry_timeout (FpDevice *dev, | ||
| 1852 | gpointer user_data G_GNUC_UNUSED); | ||
| 1853 | static void detect_analyze_done (GObject *source_object, | ||
| 1854 | GAsyncResult *result, | ||
| 1855 | gpointer user_data); | ||
| 1856 | |||
| 1857 | static void | ||
| 1858 | ✗ | detect_analyze_done (GObject *source_object, | |
| 1859 | GAsyncResult *result, | ||
| 1860 | gpointer user_data) | ||
| 1861 | { | ||
| 1862 | ✗ | FpDevice *dev = FP_DEVICE (source_object); | |
| 1863 | ✗ | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 1864 | ✗ | FpiSsm *ssm = user_data; | |
| 1865 | |||
| 1866 | ✗ | g_autoptr(GError) error = NULL; | |
| 1867 | ✗ | g_autoptr(SecugenDetectTaskResult) analysis = NULL; | |
| 1868 | |||
| 1869 | ✗ | analysis = g_task_propagate_pointer (G_TASK (result), &error); | |
| 1870 | ✗ | if (error) | |
| 1871 | { | ||
| 1872 | ✗ | if (self->deactivating && | |
| 1873 | ✗ | g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) | |
| 1874 | { | ||
| 1875 | ✗ | fpi_ssm_mark_completed (ssm); | |
| 1876 | ✗ | return; | |
| 1877 | } | ||
| 1878 | ✗ | fpi_ssm_mark_failed (ssm, g_steal_pointer (&error)); | |
| 1879 | ✗ | return; | |
| 1880 | } | ||
| 1881 | |||
| 1882 | ✗ | if (self->deactivating) | |
| 1883 | { | ||
| 1884 | ✗ | fpi_ssm_mark_completed (ssm); | |
| 1885 | ✗ | return; | |
| 1886 | } | ||
| 1887 | |||
| 1888 | ✗ | fp_dbg ("Finger detect: mean brightness = %u (threshold %d)", | |
| 1889 | analysis->mean, SECUGEN_FINGER_THRESHOLD); | ||
| 1890 | |||
| 1891 | ✗ | if (analysis->finger_present) | |
| 1892 | { | ||
| 1893 | ✗ | fp_dbg ("Finger detected!"); | |
| 1894 | ✗ | fpi_image_device_report_finger_status (FP_IMAGE_DEVICE (dev), TRUE); | |
| 1895 | } | ||
| 1896 | else | ||
| 1897 | { | ||
| 1898 | ✗ | if (analysis->update_calibration && analysis->new_cal_raw) | |
| 1899 | ✗ | memcpy (self->cal_raw, analysis->new_cal_raw, SECUGEN_BULK_BUF_SIZE); | |
| 1900 | |||
| 1901 | ✗ | self->finger_poll_source = | |
| 1902 | ✗ | fpi_device_add_timeout (FP_DEVICE (dev), | |
| 1903 | SECUGEN_FINGER_POLL_MS, | ||
| 1904 | detect_retry_timeout, | ||
| 1905 | NULL, | ||
| 1906 | NULL); | ||
| 1907 | } | ||
| 1908 | |||
| 1909 | ✗ | fpi_ssm_mark_completed (ssm); | |
| 1910 | } | ||
| 1911 | |||
| 1912 | static void | ||
| 1913 | ✗ | detect_run_state (FpiSsm *ssm, FpDevice *_dev) | |
| 1914 | { | ||
| 1915 | ✗ | FpImageDevice *dev = FP_IMAGE_DEVICE (_dev); | |
| 1916 | ✗ | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (_dev); | |
| 1917 | |||
| 1918 | ✗ | switch (fpi_ssm_get_cur_state (ssm)) | |
| 1919 | { | ||
| 1920 | ✗ | case DETECT_REG32_CLEAR: | |
| 1921 | ✗ | secugen_i2c_write (ssm, dev, 0x32, 0x00); | |
| 1922 | ✗ | break; | |
| 1923 | |||
| 1924 | ✗ | case DETECT_SET_EXPOSURE: | |
| 1925 | ✗ | secugen_set_exposure (ssm, dev, self->exposure); | |
| 1926 | ✗ | break; | |
| 1927 | |||
| 1928 | ✗ | case DETECT_START_CAPTURE: | |
| 1929 | ✗ | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_START_CAPTURE, 0x0001, 0); | |
| 1930 | ✗ | break; | |
| 1931 | |||
| 1932 | ✗ | case DETECT_START_STREAM: | |
| 1933 | ✗ | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_START_STREAM, 0x0000, 0); | |
| 1934 | ✗ | break; | |
| 1935 | |||
| 1936 | ✗ | case DETECT_BULK_READ: | |
| 1937 | ✗ | secugen_read_frame (ssm, _dev, self->bulk_buffer); | |
| 1938 | ✗ | break; | |
| 1939 | |||
| 1940 | ✗ | case DETECT_STOP_STREAM: | |
| 1941 | ✗ | secugen_ctrl_out (ssm, dev, SECUGEN_REQ_STOP_STREAM, 0x0000, 0); | |
| 1942 | ✗ | break; | |
| 1943 | |||
| 1944 | ✗ | case DETECT_ANALYZE: | |
| 1945 | { | ||
| 1946 | ✗ | g_autoptr(GTask) task = NULL; | |
| 1947 | ✗ | g_autoptr(SecugenDetectTaskData) task_data = NULL; | |
| 1948 | |||
| 1949 | /* If a deactivate arrived while this detect cycle ran, do not report | ||
| 1950 | * finger state or arm another poll - just unwind the SSM so the | ||
| 1951 | * deferred deactivation can complete. */ | ||
| 1952 | ✗ | if (self->deactivating) | |
| 1953 | { | ||
| 1954 | ✗ | fpi_ssm_mark_completed (ssm); | |
| 1955 | ✗ | break; | |
| 1956 | } | ||
| 1957 | |||
| 1958 | /* Snapshot current frame/calibration for worker-thread analysis. */ | ||
| 1959 | ✗ | task_data = g_new0 (SecugenDetectTaskData, 1); | |
| 1960 | ✗ | task_data->frame = g_memdup2 (self->bulk_buffer, SECUGEN_BULK_BUF_SIZE); | |
| 1961 | ✗ | task_data->cal_raw = g_memdup2 (self->cal_raw, SECUGEN_BULK_BUF_SIZE); | |
| 1962 | |||
| 1963 | ✗ | task = g_task_new (FP_DEVICE (dev), | |
| 1964 | fpi_device_get_cancellable (FP_DEVICE (dev)), | ||
| 1965 | detect_analyze_done, | ||
| 1966 | ssm); | ||
| 1967 | ✗ | g_task_set_source_tag (task, secugen_detect_analyze_thread); | |
| 1968 | ✗ | g_task_set_check_cancellable (task, TRUE); | |
| 1969 | ✗ | g_task_set_task_data (task, g_steal_pointer (&task_data), | |
| 1970 | (GDestroyNotify) secugen_detect_task_data_free); | ||
| 1971 | ✗ | g_task_run_in_thread (task, secugen_detect_analyze_thread); | |
| 1972 | } | ||
| 1973 | break; | ||
| 1974 | } | ||
| 1975 | ✗ | } | |
| 1976 | |||
| 1977 | static void | ||
| 1978 | ✗ | detect_retry_timeout (FpDevice *dev, gpointer user_data G_GNUC_UNUSED) | |
| 1979 | { | ||
| 1980 | ✗ | FpImageDevice *img_dev = FP_IMAGE_DEVICE (dev); | |
| 1981 | ✗ | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (img_dev); | |
| 1982 | |||
| 1983 | ✗ | self->finger_poll_source = NULL; | |
| 1984 | ✗ | detect_start (img_dev); | |
| 1985 | ✗ | } | |
| 1986 | |||
| 1987 | static void | ||
| 1988 | ✗ | detect_ssm_complete (FpiSsm *ssm, FpDevice *dev, GError *error) | |
| 1989 | { | ||
| 1990 | ✗ | g_autoptr(GError) local_error = error; | |
| 1991 | ✗ | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 1992 | |||
| 1993 | ✗ | self->ssm_count--; | |
| 1994 | |||
| 1995 | ✗ | if (self->deactivating) | |
| 1996 | { | ||
| 1997 | ✗ | if (self->ssm_count == 0) | |
| 1998 | ✗ | secugen_finish_deactivate (FP_IMAGE_DEVICE (dev)); | |
| 1999 | ✗ | return; | |
| 2000 | } | ||
| 2001 | |||
| 2002 | ✗ | if (local_error) | |
| 2003 | { | ||
| 2004 | ✗ | fp_warn ("Finger detect failed: %s", local_error->message); | |
| 2005 | ✗ | fpi_image_device_session_error (FP_IMAGE_DEVICE (dev), | |
| 2006 | ✗ | g_steal_pointer (&local_error)); | |
| 2007 | } | ||
| 2008 | } | ||
| 2009 | |||
| 2010 | static void | ||
| 2011 | ✗ | detect_start (FpImageDevice *dev) | |
| 2012 | { | ||
| 2013 | ✗ | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 2014 | ✗ | FpiSsm *ssm; | |
| 2015 | |||
| 2016 | ✗ | self->ssm_count++; | |
| 2017 | ✗ | ssm = fpi_ssm_new (FP_DEVICE (dev), detect_run_state, DETECT_NUM_STATES); | |
| 2018 | ✗ | fpi_ssm_start (ssm, detect_ssm_complete); | |
| 2019 | ✗ | } | |
| 2020 | |||
| 2021 | static void | ||
| 2022 | 1 | finger_off_timeout (FpDevice *dev, gpointer user_data G_GNUC_UNUSED) | |
| 2023 | { | ||
| 2024 | 1 | FpImageDevice *img_dev = FP_IMAGE_DEVICE (dev); | |
| 2025 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (img_dev); | |
| 2026 | |||
| 2027 | 1 | self->finger_poll_source = NULL; | |
| 2028 | 1 | fpi_image_device_report_finger_status (img_dev, FALSE); | |
| 2029 | 1 | } | |
| 2030 | |||
| 2031 | /* ================================================================ | ||
| 2032 | * Device Lifecycle | ||
| 2033 | * ================================================================ */ | ||
| 2034 | |||
| 2035 | static void | ||
| 2036 | 1 | dev_init (FpImageDevice *dev) | |
| 2037 | { | ||
| 2038 | 1 | g_autoptr(GError) error = NULL; | |
| 2039 |
1/4✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 1 time.
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 37 not taken.
|
1 | g_autoptr(GPtrArray) interfaces = NULL; |
| 2040 | 1 | GUsbInterface *iface = NULL; | |
| 2041 | 1 | int i; | |
| 2042 | |||
| 2043 | 1 | interfaces = g_usb_device_get_interfaces ( | |
| 2044 | fpi_device_get_usb_device (FP_DEVICE (dev)), &error); | ||
| 2045 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 13 taken 1 time.
|
1 | if (error) |
| 2046 | { | ||
| 2047 | ✗ | fpi_image_device_open_complete (dev, g_steal_pointer (&error)); | |
| 2048 | ✗ | return; | |
| 2049 | } | ||
| 2050 | |||
| 2051 | /* Find our vendor-specific interface (0xFF/0xFF/0xFF) */ | ||
| 2052 |
1/2✓ Branch 13 → 6 taken 1 time.
✗ Branch 13 → 14 not taken.
|
1 | for (i = 0; i < (int) interfaces->len; i++) |
| 2053 | { | ||
| 2054 | 1 | GUsbInterface *cur = g_ptr_array_index (interfaces, i); | |
| 2055 | |||
| 2056 |
2/4✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 12 not taken.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 9 → 12 not taken.
|
2 | if (g_usb_interface_get_class (cur) == 0xFF && |
| 2057 |
1/2✗ Branch 11 → 12 not taken.
✓ Branch 11 → 14 taken 1 time.
|
2 | g_usb_interface_get_subclass (cur) == 0xFF && |
| 2058 | 1 | g_usb_interface_get_protocol (cur) == 0xFF) | |
| 2059 | { | ||
| 2060 | iface = cur; | ||
| 2061 | break; | ||
| 2062 | } | ||
| 2063 | } | ||
| 2064 | |||
| 2065 |
1/2✗ Branch 14 → 15 not taken.
✓ Branch 14 → 18 taken 1 time.
|
1 | if (!iface) |
| 2066 | { | ||
| 2067 | ✗ | fpi_image_device_open_complete (dev, | |
| 2068 | fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, | ||
| 2069 | "Could not find fingerprint interface")); | ||
| 2070 | ✗ | return; | |
| 2071 | } | ||
| 2072 | |||
| 2073 | { | ||
| 2074 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 2075 | |||
| 2076 | 1 | self->iface_num = g_usb_interface_get_number (iface); | |
| 2077 | |||
| 2078 |
1/2✗ Branch 21 → 22 not taken.
✓ Branch 21 → 24 taken 1 time.
|
1 | if (!g_usb_device_claim_interface ( |
| 2079 | fpi_device_get_usb_device (FP_DEVICE (dev)), | ||
| 2080 | self->iface_num, 0, &error)) | ||
| 2081 | { | ||
| 2082 | ✗ | fpi_image_device_open_complete (dev, g_steal_pointer (&error)); | |
| 2083 | ✗ | return; | |
| 2084 | } | ||
| 2085 | |||
| 2086 | /* Allocate buffers */ | ||
| 2087 | 1 | self->cal_raw = g_malloc0 (SECUGEN_BULK_BUF_SIZE); | |
| 2088 | 1 | self->bulk_buffer = g_malloc0 (SECUGEN_BULK_BUF_SIZE); | |
| 2089 | 1 | self->fw_data = g_malloc0 (SECUGEN_FW_DATA_SIZE); | |
| 2090 | 1 | self->fw_data_len = 0; | |
| 2091 | 1 | self->ref_image = NULL; | |
| 2092 | 1 | self->has_ref_image = FALSE; | |
| 2093 | 1 | self->has_blc_offsets = FALSE; | |
| 2094 | 1 | self->cal_value = SECUGEN_BLEND_CAL_VAL; /* Default 240, overridden by FW */ | |
| 2095 | 1 | self->sharpen_enabled = FALSE; | |
| 2096 | } | ||
| 2097 | |||
| 2098 | 1 | fpi_image_device_open_complete (dev, NULL); | |
| 2099 | } | ||
| 2100 | |||
| 2101 | static void | ||
| 2102 | 1 | dev_deinit (FpImageDevice *dev) | |
| 2103 | { | ||
| 2104 | 2 | g_autoptr(GError) error = NULL; | |
| 2105 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 2106 | |||
| 2107 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
|
1 | g_clear_pointer (&self->finger_poll_source, g_source_destroy); |
| 2108 | |||
| 2109 |
1/2✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
|
1 | g_clear_pointer (&self->cal_raw, g_free); |
| 2110 |
1/2✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
|
1 | g_clear_pointer (&self->bulk_buffer, g_free); |
| 2111 |
1/2✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
|
1 | g_clear_pointer (&self->fw_data, g_free); |
| 2112 |
1/2✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 12 not taken.
|
1 | g_clear_pointer (&self->ref_image, g_free); |
| 2113 | 1 | self->has_ref_image = FALSE; | |
| 2114 | |||
| 2115 | 1 | g_usb_device_release_interface ( | |
| 2116 | fpi_device_get_usb_device (FP_DEVICE (dev)), | ||
| 2117 | 1 | self->iface_num, 0, &error); | |
| 2118 | |||
| 2119 |
1/2✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 1 time.
|
1 | fpi_image_device_close_complete (dev, g_steal_pointer (&error)); |
| 2120 | 1 | } | |
| 2121 | |||
| 2122 | static void | ||
| 2123 | 1 | activate_complete (FpiSsm *ssm, FpDevice *dev, GError *error) | |
| 2124 | { | ||
| 2125 | 1 | g_autoptr(GError) local_error = error; | |
| 2126 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 2127 | 1 | FpImageDevice *imgdev = FP_IMAGE_DEVICE (dev); | |
| 2128 | |||
| 2129 | 1 | self->ssm_count--; | |
| 2130 | |||
| 2131 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 7 taken 1 time.
|
1 | if (self->deactivating) |
| 2132 | { | ||
| 2133 | ✗ | if (self->ssm_count == 0) | |
| 2134 | ✗ | secugen_finish_deactivate (imgdev); | |
| 2135 | ✗ | return; | |
| 2136 | } | ||
| 2137 | |||
| 2138 |
1/2✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
|
1 | if (local_error) |
| 2139 | ✗ | fp_warn ("Activation failed: %s", local_error->message); | |
| 2140 | |||
| 2141 | 1 | fpi_image_device_activate_complete (imgdev, g_steal_pointer (&local_error)); | |
| 2142 | } | ||
| 2143 | |||
| 2144 | static void | ||
| 2145 | 1 | dev_activate (FpImageDevice *dev) | |
| 2146 | { | ||
| 2147 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 2148 | 1 | FpiSsm *ssm; | |
| 2149 | |||
| 2150 | /* Reset init counters */ | ||
| 2151 | 1 | self->init_reg_idx = 0; | |
| 2152 | 1 | self->fw_read_idx = 0; | |
| 2153 | 1 | self->exposure = SECUGEN_EXPOSURE_NORMAL; | |
| 2154 | 1 | self->deactivating = FALSE; | |
| 2155 | 1 | self->ssm_count++; | |
| 2156 | |||
| 2157 | 1 | ssm = fpi_ssm_new (FP_DEVICE (dev), init_run_state, INIT_NUM_STATES); | |
| 2158 | 1 | fpi_ssm_start (ssm, activate_complete); | |
| 2159 | 1 | } | |
| 2160 | |||
| 2161 | /* Turn the LED off and report deactivation complete. Called either directly | ||
| 2162 | * from dev_deactivate (nothing in flight) or, when a deactivate arrives while | ||
| 2163 | * an SSM is still running, from that SSM's completion handler once it unwinds. */ | ||
| 2164 | static void | ||
| 2165 | 1 | secugen_finish_deactivate (FpImageDevice *dev) | |
| 2166 | { | ||
| 2167 | 2 | g_autoptr(FpiUsbTransfer) transfer = NULL; | |
| 2168 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 2169 | |||
| 2170 | 1 | self->deactivating = FALSE; | |
| 2171 | |||
| 2172 | /* Turn off LED (fire and forget) */ | ||
| 2173 | 1 | transfer = fpi_usb_transfer_new (FP_DEVICE (dev)); | |
| 2174 | 1 | fpi_usb_transfer_fill_control (transfer, | |
| 2175 | G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, | ||
| 2176 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, | ||
| 2177 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
| 2178 | SECUGEN_REQ_LED_CONTROL, | ||
| 2179 | 0x0000, 0, 0); | ||
| 2180 | 1 | fpi_usb_transfer_submit (g_steal_pointer (&transfer), SECUGEN_CTRL_TIMEOUT, NULL, | |
| 2181 | secugen_noop_cb, NULL); | ||
| 2182 | |||
| 2183 | 1 | fpi_image_device_deactivate_complete (dev, NULL); | |
| 2184 | 1 | } | |
| 2185 | |||
| 2186 | static void | ||
| 2187 | 1 | dev_deactivate (FpImageDevice *dev) | |
| 2188 | { | ||
| 2189 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 2190 | |||
| 2191 | /* Cancel any pending timeout */ | ||
| 2192 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
|
1 | g_clear_pointer (&self->finger_poll_source, g_source_destroy); |
| 2193 | |||
| 2194 | /* If an init/detect/capture SSM is still in flight, defer completion until | ||
| 2195 | * it unwinds - its completion handler calls secugen_finish_deactivate(). This | ||
| 2196 | * guarantees no bulk transfer is in flight (and no chunk callback can run | ||
| 2197 | * against a reset buffer) by the time we report deactivation complete. */ | ||
| 2198 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
|
1 | if (self->ssm_count > 0) |
| 2199 | { | ||
| 2200 | ✗ | self->deactivating = TRUE; | |
| 2201 | ✗ | return; | |
| 2202 | } | ||
| 2203 | |||
| 2204 | 1 | secugen_finish_deactivate (dev); | |
| 2205 | } | ||
| 2206 | |||
| 2207 | static void | ||
| 2208 | 8 | dev_change_state (FpImageDevice *dev, | |
| 2209 | FpiImageDeviceState state) | ||
| 2210 | { | ||
| 2211 | 8 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (dev); | |
| 2212 | |||
| 2213 |
4/4✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 13 taken 1 time.
✓ Branch 2 → 19 taken 1 time.
✓ Branch 2 → 22 taken 5 times.
|
8 | switch (state) |
| 2214 | { | ||
| 2215 | 1 | case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON: | |
| 2216 | 1 | fp_dbg ("Awaiting finger (image-based detection)"); | |
| 2217 | |||
| 2218 | /* Under fpi_device_emulation (set by the umockdev test harness so | ||
| 2219 | * drivers can adapt to replay; see tests/meson.build) skip the | ||
| 2220 | * timer-driven detect poll: each poll consumes a full ~658KB frame, | ||
| 2221 | * so exercising it in replay would require recording an unbounded | ||
| 2222 | * number of extra frames in the test fixture. The recorded capture | ||
| 2223 | * starts with the finger already present, so report it directly - | ||
| 2224 | * the same approach other drivers use for replay-incompatible | ||
| 2225 | * hardware behavior (e.g. elanspi's HID reset skip). */ | ||
| 2226 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 11 taken 1 time.
|
1 | if (fpi_device_emulation_mode_enabled (FP_DEVICE (self))) |
| 2227 | { | ||
| 2228 | 1 | fpi_image_device_report_finger_status (dev, TRUE); | |
| 2229 | 1 | break; | |
| 2230 | } | ||
| 2231 | |||
| 2232 | /* Turn on LED */ | ||
| 2233 | { | ||
| 2234 | ✗ | g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (FP_DEVICE (dev)); | |
| 2235 | |||
| 2236 | ✗ | fpi_usb_transfer_fill_control (transfer, | |
| 2237 | G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, | ||
| 2238 | G_USB_DEVICE_REQUEST_TYPE_VENDOR, | ||
| 2239 | G_USB_DEVICE_RECIPIENT_DEVICE, | ||
| 2240 | SECUGEN_REQ_LED_CONTROL, | ||
| 2241 | 0x0001, 0, 0); | ||
| 2242 | ✗ | fpi_usb_transfer_submit (g_steal_pointer (&transfer), SECUGEN_CTRL_TIMEOUT, NULL, | |
| 2243 | secugen_noop_cb, NULL); | ||
| 2244 | } | ||
| 2245 | |||
| 2246 | /* Start image-based finger detection polling */ | ||
| 2247 | ✗ | detect_start (dev); | |
| 2248 | ✗ | break; | |
| 2249 | |||
| 2250 | 1 | case FPI_IMAGE_DEVICE_STATE_CAPTURE: | |
| 2251 | { | ||
| 2252 | 1 | FpiSsm *ssm; | |
| 2253 | |||
| 2254 | 1 | fp_dbg ("Starting image capture"); | |
| 2255 | |||
| 2256 | /* Cancel timeout if still running */ | ||
| 2257 |
1/2✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 1 time.
|
1 | g_clear_pointer (&self->finger_poll_source, g_source_destroy); |
| 2258 | |||
| 2259 | 1 | self->ssm_count++; | |
| 2260 | 1 | ssm = fpi_ssm_new (FP_DEVICE (dev), capture_run_state, | |
| 2261 | CAPTURE_NUM_STATES); | ||
| 2262 | 1 | fpi_ssm_start (ssm, capture_ssm_complete); | |
| 2263 | } | ||
| 2264 | 1 | break; | |
| 2265 | |||
| 2266 | 1 | case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF: | |
| 2267 | 1 | fp_dbg ("Waiting for finger off"); | |
| 2268 | 1 | self->finger_poll_source = fpi_device_add_timeout (FP_DEVICE (dev), | |
| 2269 | 500, | ||
| 2270 | finger_off_timeout, | ||
| 2271 | NULL, | ||
| 2272 | NULL); | ||
| 2273 | 1 | break; | |
| 2274 | |||
| 2275 | case FPI_IMAGE_DEVICE_STATE_IDLE: | ||
| 2276 | case FPI_IMAGE_DEVICE_STATE_INACTIVE: | ||
| 2277 | case FPI_IMAGE_DEVICE_STATE_ACTIVATING: | ||
| 2278 | case FPI_IMAGE_DEVICE_STATE_DEACTIVATING: | ||
| 2279 | break; | ||
| 2280 | } | ||
| 2281 | 8 | } | |
| 2282 | |||
| 2283 | /* ================================================================ | ||
| 2284 | * Driver Registration | ||
| 2285 | * ================================================================ */ | ||
| 2286 | |||
| 2287 | static const FpIdEntry id_table[] = { | ||
| 2288 | { .vid = 0x1162, .pid = 0x2200, }, /* SecuGen Hamster Pro 20 (FDU05) */ | ||
| 2289 | { .vid = 0, .pid = 0, }, /* terminator */ | ||
| 2290 | }; | ||
| 2291 | |||
| 2292 | static void | ||
| 2293 | 1 | fpi_device_secugen_init (FpiDeviceSecugen *self) | |
| 2294 | { | ||
| 2295 | 1 | self->init_reg_idx = 0; | |
| 2296 | 1 | self->fw_read_idx = 0; | |
| 2297 | 1 | self->finger_poll_source = NULL; | |
| 2298 | 1 | self->exposure = SECUGEN_EXPOSURE_NORMAL; | |
| 2299 | 1 | self->deactivating = FALSE; | |
| 2300 | 1 | self->ssm_count = 0; | |
| 2301 | 1 | } | |
| 2302 | |||
| 2303 | static void | ||
| 2304 | 1 | fpi_device_secugen_finalize (GObject *object) | |
| 2305 | { | ||
| 2306 | 1 | FpiDeviceSecugen *self = FPI_DEVICE_SECUGEN (object); | |
| 2307 | |||
| 2308 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
|
1 | g_clear_pointer (&self->finger_poll_source, g_source_destroy); |
| 2309 | |||
| 2310 | /* Safety net: free heap buffers even if img_close never ran (e.g. an | ||
| 2311 | * activation failure tore the device down before dev_deinit). */ | ||
| 2312 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
|
1 | g_clear_pointer (&self->cal_raw, g_free); |
| 2313 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1 time.
|
1 | g_clear_pointer (&self->bulk_buffer, g_free); |
| 2314 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
|
1 | g_clear_pointer (&self->fw_data, g_free); |
| 2315 |
1/2✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
|
1 | g_clear_pointer (&self->ref_image, g_free); |
| 2316 | |||
| 2317 | 1 | G_OBJECT_CLASS (fpi_device_secugen_parent_class)->finalize (object); | |
| 2318 | 1 | } | |
| 2319 | |||
| 2320 | static void | ||
| 2321 | 125 | fpi_device_secugen_class_init (FpiDeviceSecugenClass *klass) | |
| 2322 | { | ||
| 2323 | 125 | GObjectClass *obj_class = G_OBJECT_CLASS (klass); | |
| 2324 | 125 | FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); | |
| 2325 | 125 | FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass); | |
| 2326 | |||
| 2327 | 125 | obj_class->finalize = fpi_device_secugen_finalize; | |
| 2328 | |||
| 2329 | 125 | dev_class->id = "secugen"; | |
| 2330 | 125 | dev_class->full_name = "SecuGen Hamster Pro 20"; | |
| 2331 | 125 | dev_class->type = FP_DEVICE_TYPE_USB; | |
| 2332 | 125 | dev_class->id_table = id_table; | |
| 2333 | 125 | dev_class->scan_type = FP_SCAN_TYPE_PRESS; | |
| 2334 | |||
| 2335 | 125 | img_class->img_open = dev_init; | |
| 2336 | 125 | img_class->img_close = dev_deinit; | |
| 2337 | 125 | img_class->activate = dev_activate; | |
| 2338 | 125 | img_class->deactivate = dev_deactivate; | |
| 2339 | 125 | img_class->change_state = dev_change_state; | |
| 2340 | |||
| 2341 | 125 | img_class->bz3_threshold = 24; | |
| 2342 | |||
| 2343 | 125 | img_class->img_width = SECUGEN_IMG_WIDTH; | |
| 2344 | 125 | img_class->img_height = SECUGEN_IMG_HEIGHT; | |
| 2345 | } | ||
| 2346 |