mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-03-26 16:00:44 +01:00
Added support for 1c7a:0576
This commit is contained in:
parent
2c7842c905
commit
034e7e9bee
6 changed files with 808 additions and 2 deletions
|
|
@ -350,6 +350,11 @@ usb:v138Ap0091*
|
|||
ID_AUTOSUSPEND=1
|
||||
ID_PERSIST=0
|
||||
|
||||
# Supported by libfprint driver egis0576
|
||||
usb:v1C7Ap0576*
|
||||
ID_AUTOSUSPEND=1
|
||||
ID_PERSIST=0
|
||||
|
||||
# Known unsupported devices
|
||||
usb:v0A5Cp5802*
|
||||
usb:v047Dp00F2*
|
||||
|
|
@ -444,7 +449,6 @@ usb:v1491p0088*
|
|||
usb:v16D1p1027*
|
||||
usb:v1C7Ap0300*
|
||||
usb:v1C7Ap0575*
|
||||
usb:v1C7Ap0576*
|
||||
usb:v1C7Ap0577*
|
||||
usb:v1C7Ap057E*
|
||||
usb:v2541p0236*
|
||||
|
|
|
|||
595
libfprint/drivers/egis0576.c
Normal file
595
libfprint/drivers/egis0576.c
Normal file
|
|
@ -0,0 +1,595 @@
|
|||
/*
|
||||
* Egis Technology Inc. (aka. LighTuning) 0576 driver for libfprint
|
||||
* Copyright (C) 2026 Marcel (Sprayxe) <sprayxe.marcel@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define FP_COMPONENT "egis0576"
|
||||
|
||||
#include "egis0576.h"
|
||||
|
||||
#include "drivers_api.h"
|
||||
|
||||
/* Sequence types */
|
||||
typedef enum
|
||||
{
|
||||
SEQ_INIT,
|
||||
SEQ_REPEAT,
|
||||
SEQ_POLL,
|
||||
SEQ_IMAGE
|
||||
} seq_types;
|
||||
|
||||
/* SSM States */
|
||||
enum sm_states
|
||||
{
|
||||
DEV_OPEN,
|
||||
DEV_START,
|
||||
DEV_REQ,
|
||||
DEV_RESP,
|
||||
DEV_FULFILLED,
|
||||
NUM_STATES
|
||||
};
|
||||
|
||||
/* Struct */
|
||||
struct _FpDeviceEgis0576
|
||||
{
|
||||
FpImageDevice parent;
|
||||
|
||||
gboolean running;
|
||||
gboolean stop;
|
||||
|
||||
gboolean has_background;
|
||||
guchar background[EGIS0576_IMG_SIZE];
|
||||
|
||||
seq_types seq_type;
|
||||
int seq_pkt_index;
|
||||
Egis0576Pkt last_sent_pkt;
|
||||
};
|
||||
G_DECLARE_FINAL_TYPE(FpDeviceEgis0576, fpi_device_egis0576, FPI, DEVICE_EGIS0576, FpImageDevice);
|
||||
G_DEFINE_TYPE(FpDeviceEgis0576, fpi_device_egis0576, FP_TYPE_IMAGE_DEVICE);
|
||||
|
||||
/*
|
||||
* ========================
|
||||
* Processing
|
||||
* ========================
|
||||
*/
|
||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
static void normalize_img(guchar *bg, guchar *img, double *dark_portion)
|
||||
{
|
||||
// Find diffs, min and max
|
||||
int diff[EGIS0576_IMG_SIZE];
|
||||
int min = 255;
|
||||
int max = 0;
|
||||
for (int i = 0; i < EGIS0576_IMG_SIZE; i++)
|
||||
{
|
||||
diff[i] = (int)bg[i] - (int)img[i];
|
||||
if (diff[i] < min)
|
||||
min = diff[i];
|
||||
if (diff[i] > max)
|
||||
max = diff[i];
|
||||
}
|
||||
|
||||
max -= EGIS0576_CONTRAST;
|
||||
min += EGIS0576_CONTRAST;
|
||||
int range = max - min;
|
||||
if (range == 0)
|
||||
range = 1; // Prevent division by zero
|
||||
|
||||
// Adjust contrast / normalize
|
||||
int count_ridges = 0;
|
||||
for (int i = 0; i < EGIS0576_IMG_SIZE; i++)
|
||||
{
|
||||
int normalized = ((diff[i] - min) * 255) / range;
|
||||
|
||||
if (normalized < 0)
|
||||
normalized = 0;
|
||||
else if (normalized > 255)
|
||||
normalized = 255;
|
||||
|
||||
img[i] = (unsigned char)normalized;
|
||||
if (img[i] < 170)
|
||||
count_ridges++;
|
||||
}
|
||||
|
||||
*dark_portion = (double)count_ridges / EGIS0576_IMG_SIZE;
|
||||
}
|
||||
|
||||
/* Uses bilinear interpolation */
|
||||
static void upscale_img(guchar *src_img, guchar *dst_img)
|
||||
{
|
||||
const int scale = EGIS0576_IMG_UPSCALE;
|
||||
const int src_w = EGIS0576_IMG_WIDTH;
|
||||
const int src_h = EGIS0576_IMG_HEIGHT;
|
||||
const int dst_w = EGIS0576_IMG_WIDTH_UPSCALE;
|
||||
const int dst_h = EGIS0576_IMG_HEIGHT_UPSCALE;
|
||||
|
||||
for (int y = 0; y < dst_h; y++)
|
||||
{
|
||||
float gy = ((float)y) / scale;
|
||||
int y1 = (int)gy;
|
||||
int y2 = (y1 >= src_h - 1) ? src_h - 1 : y1 + 1;
|
||||
float ty = gy - y1;
|
||||
|
||||
for (int x = 0; x < dst_w; x++)
|
||||
{
|
||||
float gx = ((float)x) / scale;
|
||||
int x1 = (int)gx;
|
||||
int x2 = (x1 >= src_w - 1) ? src_w - 1 : x1 + 1;
|
||||
float tx = gx - x1;
|
||||
|
||||
float p00 = src_img[y1 * src_w + x1]; // Top left
|
||||
float p10 = src_img[y1 * src_w + x2]; // Top right
|
||||
float p01 = src_img[y2 * src_w + x1]; // Bottom left
|
||||
float p11 = src_img[y2 * src_w + x2]; // Bottom right
|
||||
|
||||
// Interp. horizontally across the top and bottom
|
||||
float top_p = p00 * (1.0f - tx) + p10 * tx;
|
||||
float bottom_p = p01 * (1.0f - tx) + p11 * tx;
|
||||
|
||||
// Interp. vertically between the two horizontal results
|
||||
float pixel = top_p * (1.0f - ty) + bottom_p * ty;
|
||||
|
||||
dst_img[y * dst_w + x] = (guchar)pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* As it is already known, libfprint absolutely sucks at processing small images.
|
||||
* Therefore the 'trick' is to create a canvas that is big enough to be liked by libfprint,
|
||||
* fill it with 255 (white background) and put an upscaled version of the sensor image into the
|
||||
* center of that canvas.
|
||||
* With this technique I have managed to get scores as high as 32!
|
||||
*/
|
||||
static void upscale_and_pad_img(guchar *img, guchar *canvas)
|
||||
{
|
||||
const int img_width = EGIS0576_IMG_WIDTH_UPSCALE;
|
||||
const int img_height = EGIS0576_IMG_HEIGHT_UPSCALE;
|
||||
|
||||
// Upscale sensor image
|
||||
guchar upscaled_img[EGIS0576_IMG_SIZE_UPSCALE];
|
||||
upscale_img(img, upscaled_img);
|
||||
|
||||
// Prepare canvas
|
||||
memset(canvas, 255, EGIS0576_CANVAS_SIZE);
|
||||
int offset_x = (EGIS0576_CANVAS_WIDTH - img_width) / 2;
|
||||
int offset_y = (EGIS0576_CANVAS_HEIGHT - img_height) / 2;
|
||||
|
||||
for (int y = 0; y < img_height; y++)
|
||||
{
|
||||
for (int x = 0; x < img_width; x++)
|
||||
{
|
||||
int dest_y = y + offset_y;
|
||||
int dest_x = x + offset_x;
|
||||
|
||||
canvas[dest_y * EGIS0576_CANVAS_WIDTH + dest_x] = upscaled_img[y * img_width + x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void process_finger(FpDevice *dev, FpiUsbTransfer *transfer)
|
||||
{
|
||||
FpImageDevice *img_self = FP_IMAGE_DEVICE(dev);
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
|
||||
guchar *img = transfer->buffer;
|
||||
|
||||
gint variance = fpi_std_sq_dev(img, EGIS0576_IMG_SIZE);
|
||||
if (!self->has_background)
|
||||
{
|
||||
/* Background has been gathered, user can put finger on sensor. */
|
||||
if (variance < EGIS0576_BG_VARIANCE)
|
||||
{
|
||||
memcpy(self->background, img, EGIS0576_IMG_SIZE);
|
||||
self->has_background = TRUE;
|
||||
|
||||
fpi_device_report_finger_status(dev, FP_FINGER_STATUS_NEEDED);
|
||||
|
||||
self->seq_type = SEQ_REPEAT;
|
||||
fpi_ssm_next_state_delayed(transfer->ssm, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
/* User should remove finger so the driver can grab a clear image. */
|
||||
fpi_image_device_retry_scan(img_self, FP_DEVICE_RETRY_REMOVE_FINGER);
|
||||
|
||||
self->seq_type = SEQ_REPEAT;
|
||||
fpi_ssm_next_state_delayed(transfer->ssm, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
gboolean finger_present = FALSE;
|
||||
double dark_portion = -1;
|
||||
if (variance > EGIS0576_VARIANCE)
|
||||
{
|
||||
normalize_img(self->background, img, &dark_portion);
|
||||
finger_present = dark_portion > EGIS0576_DARK_PORTION;
|
||||
}
|
||||
|
||||
fp_dbg("Finger status (present, variance, dark port) : "
|
||||
"%d , %d, %.2f",
|
||||
finger_present, variance, dark_portion);
|
||||
|
||||
if (!finger_present)
|
||||
{
|
||||
self->seq_type = SEQ_REPEAT;
|
||||
fpi_image_device_report_finger_status(img_self, FALSE);
|
||||
fpi_ssm_next_state_delayed(transfer->ssm, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
FpImage *fp_img = fp_image_new(EGIS0576_CANVAS_WIDTH, EGIS0576_CANVAS_HEIGHT);
|
||||
/* Sensor returns full image */
|
||||
upscale_and_pad_img(img, fp_img->data);
|
||||
|
||||
fpi_image_device_report_finger_status(img_self, TRUE);
|
||||
fpi_image_device_image_captured(img_self, fp_img);
|
||||
|
||||
fpi_ssm_next_state_delayed(transfer->ssm, 50);
|
||||
}
|
||||
|
||||
static void process_poll_transfer(FpDevice *dev, FpiUsbTransfer *transfer)
|
||||
{
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
|
||||
if (transfer->actual_length < 6)
|
||||
{
|
||||
GError *error
|
||||
= fpi_device_error_new_msg(FP_DEVICE_ERROR_DATA_INVALID, "Device reported invalid poll.");
|
||||
fpi_ssm_mark_failed(transfer->ssm, error);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((transfer->buffer[6] & 0x01) == 0x01)
|
||||
{
|
||||
self->seq_type = SEQ_IMAGE;
|
||||
fpi_ssm_jump_to_state(transfer->ssm, DEV_REQ);
|
||||
return;
|
||||
}
|
||||
|
||||
self->seq_pkt_index += 1;
|
||||
if (self->seq_pkt_index < EGIS0576_POLL_COUNT)
|
||||
{
|
||||
fpi_ssm_jump_to_state(transfer->ssm, DEV_REQ);
|
||||
return;
|
||||
}
|
||||
|
||||
GError *error
|
||||
= fpi_device_error_new_msg(FP_DEVICE_ERROR_GENERAL, "Device exceeded maximum poll count.");
|
||||
fpi_ssm_mark_failed(transfer->ssm, error);
|
||||
g_error_free(error);
|
||||
}
|
||||
|
||||
/* Verifies that received data is processable. */
|
||||
static void process_image_transfer(FpDevice *dev, FpiUsbTransfer *transfer)
|
||||
{
|
||||
guchar *buffer = transfer->buffer;
|
||||
gssize buffer_len = transfer->actual_length;
|
||||
|
||||
if (buffer_len != EGIS0576_IMG_SIZE)
|
||||
{
|
||||
GError *error = fpi_device_error_new_msg(
|
||||
FP_DEVICE_ERROR_DATA_INVALID, "Device image data size does not match expected size.");
|
||||
fpi_ssm_mark_failed(transfer->ssm, error);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
uint sum = 0;
|
||||
/* Roughly check whether the buffer is empty aka invalid. */
|
||||
for (int i = 0; i < MIN(buffer_len, 255); i++)
|
||||
{
|
||||
sum += buffer[i];
|
||||
}
|
||||
|
||||
/* No/invalid data was present. */
|
||||
if (sum == 0)
|
||||
{
|
||||
GError *error
|
||||
= fpi_device_error_new_msg(FP_DEVICE_ERROR_DATA_INVALID, "Device reported invalid data.");
|
||||
fpi_ssm_mark_failed(transfer->ssm, error);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
process_finger(dev, transfer);
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================
|
||||
* I / O
|
||||
* ========================
|
||||
*/
|
||||
static void cmd_resp_cb(FpiUsbTransfer *transfer, FpDevice *dev, gpointer user_data, GError *error)
|
||||
{
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
|
||||
if (error)
|
||||
{
|
||||
fp_dbg("During the %d sequence an error occurred at pkt index %d", self->seq_type,
|
||||
self->seq_pkt_index);
|
||||
fpi_ssm_mark_failed(transfer->ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (self->seq_type)
|
||||
{
|
||||
/* not processed */
|
||||
case SEQ_INIT:
|
||||
case SEQ_REPEAT:
|
||||
fpi_ssm_jump_to_state(transfer->ssm, DEV_REQ);
|
||||
break;
|
||||
|
||||
case SEQ_POLL:
|
||||
process_poll_transfer(dev, transfer);
|
||||
break;
|
||||
|
||||
case SEQ_IMAGE:
|
||||
process_image_transfer(dev, transfer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void recv_cmd_resp(FpiSsm *ssm, FpDevice *dev, Egis0576Pkt last_pkt)
|
||||
{
|
||||
FpiUsbTransfer *transfer = fpi_usb_transfer_new(dev);
|
||||
|
||||
fpi_usb_transfer_fill_bulk(transfer, EGIS0576_EPIN, last_pkt.res_len);
|
||||
|
||||
transfer->ssm = ssm;
|
||||
|
||||
fpi_usb_transfer_submit(transfer, EGIS0576_TIMEOUT, NULL, cmd_resp_cb, NULL);
|
||||
}
|
||||
|
||||
static void send_cmd_req(FpiSsm *ssm, FpDevice *dev, Egis0576Pkt pkt)
|
||||
{
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
FpiUsbTransfer *transfer = fpi_usb_transfer_new(dev);
|
||||
|
||||
self->last_sent_pkt = pkt;
|
||||
fpi_usb_transfer_fill_bulk_full(transfer, EGIS0576_EPOUT, pkt.cmd, pkt.len, NULL);
|
||||
|
||||
transfer->ssm = ssm;
|
||||
transfer->short_is_error = TRUE;
|
||||
|
||||
fpi_usb_transfer_submit(transfer, EGIS0576_TIMEOUT, NULL, fpi_ssm_usb_transfer_cb, NULL);
|
||||
}
|
||||
|
||||
static gboolean init_repeat_last_pkt(FpDevice *dev)
|
||||
{
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
|
||||
int type = self->seq_type;
|
||||
int index = self->seq_pkt_index;
|
||||
|
||||
return (type == SEQ_INIT && index == EGIS0576_INIT_PACKETS_LENGTH - 1)
|
||||
|| (type == SEQ_REPEAT && index == EGIS0576_REPEAT_PACKETS_LENGTH - 1);
|
||||
}
|
||||
|
||||
static void recv_cmd(FpiSsm *ssm, FpDevice *dev)
|
||||
{
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
Egis0576Pkt last_pkt = self->last_sent_pkt;
|
||||
|
||||
switch (self->seq_type)
|
||||
{
|
||||
case SEQ_INIT:
|
||||
case SEQ_REPEAT:
|
||||
if (!init_repeat_last_pkt(dev))
|
||||
{
|
||||
recv_cmd_resp(ssm, dev, last_pkt);
|
||||
self->seq_pkt_index += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->seq_pkt_index = 0;
|
||||
self->seq_type = SEQ_POLL;
|
||||
fpi_ssm_jump_to_state(ssm, DEV_REQ);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SEQ_POLL:
|
||||
case SEQ_IMAGE:
|
||||
recv_cmd_resp(ssm, dev, last_pkt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void send_cmd(FpiSsm *ssm, FpDevice *dev)
|
||||
{
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
|
||||
switch (self->seq_type)
|
||||
{
|
||||
case SEQ_INIT:
|
||||
send_cmd_req(ssm, dev, EGIS0576_INIT_PACKETS[self->seq_pkt_index]);
|
||||
break;
|
||||
|
||||
case SEQ_REPEAT:
|
||||
send_cmd_req(ssm, dev, EGIS0576_REPEAT_PACKETS[self->seq_pkt_index]);
|
||||
break;
|
||||
|
||||
case SEQ_POLL:
|
||||
send_cmd_req(ssm, dev, EGIS0576_POLL_PACKET);
|
||||
break;
|
||||
|
||||
case SEQ_IMAGE:
|
||||
send_cmd_req(ssm, dev, EGIS0576_IMAGE_PACKET);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void ssm_run_state(FpiSsm *ssm, FpDevice *dev)
|
||||
{
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
|
||||
switch (fpi_ssm_get_cur_state(ssm))
|
||||
{
|
||||
case DEV_OPEN:
|
||||
self->seq_type = SEQ_INIT;
|
||||
fpi_ssm_jump_to_state(ssm, DEV_START);
|
||||
break;
|
||||
|
||||
case DEV_START:
|
||||
if (self->stop)
|
||||
{
|
||||
fp_dbg("Deactivating device, marking completed.");
|
||||
fpi_ssm_mark_completed(ssm);
|
||||
return;
|
||||
}
|
||||
|
||||
self->seq_pkt_index = 0;
|
||||
fpi_ssm_jump_to_state(ssm, DEV_REQ);
|
||||
break;
|
||||
|
||||
case DEV_REQ:
|
||||
send_cmd(ssm, dev);
|
||||
break;
|
||||
|
||||
case DEV_RESP:
|
||||
recv_cmd(ssm, dev);
|
||||
break;
|
||||
|
||||
case DEV_FULFILLED:
|
||||
fpi_ssm_jump_to_state(ssm, DEV_START);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ========================
|
||||
* SETUP
|
||||
* ========================
|
||||
*/
|
||||
|
||||
static void sm_cb(FpiSsm *ssm, FpDevice *dev, GError *error)
|
||||
{
|
||||
FpImageDevice *img_dev = FP_IMAGE_DEVICE(dev);
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
|
||||
self->running = FALSE;
|
||||
|
||||
if (error && !self->stop)
|
||||
fpi_image_device_session_error(img_dev, error);
|
||||
else if (error)
|
||||
g_error_free(error);
|
||||
|
||||
if (self->stop)
|
||||
fpi_image_device_deactivate_complete(img_dev, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Device activate
|
||||
*/
|
||||
static void dev_activate(FpImageDevice *dev)
|
||||
{
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
FpiSsm *ssm = fpi_ssm_new(FP_DEVICE(dev), ssm_run_state, NUM_STATES);
|
||||
|
||||
self->stop = FALSE;
|
||||
|
||||
fpi_ssm_start(ssm, sm_cb);
|
||||
|
||||
self->running = TRUE;
|
||||
|
||||
fpi_image_device_activate_complete(dev, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Img open
|
||||
*/
|
||||
static void dev_init(FpImageDevice *dev)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
g_usb_device_claim_interface(fpi_device_get_usb_device(FP_DEVICE(dev)), EGIS0576_INTF, 0, &error);
|
||||
|
||||
fpi_image_device_open_complete(dev, error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Img close
|
||||
*/
|
||||
static void dev_deinit(FpImageDevice *dev)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
g_usb_device_release_interface(fpi_device_get_usb_device(FP_DEVICE(dev)), EGIS0576_INTF, 0,
|
||||
&error);
|
||||
|
||||
fpi_image_device_close_complete(dev, error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Device deactivate
|
||||
*/
|
||||
static void dev_deactivate(FpImageDevice *dev)
|
||||
{
|
||||
FpDeviceEgis0576 *self = FPI_DEVICE_EGIS0576(dev);
|
||||
|
||||
if (self->running)
|
||||
self->stop = TRUE;
|
||||
else
|
||||
fpi_image_device_deactivate_complete(dev, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Driver ID
|
||||
*/
|
||||
static const FpIdEntry id_table[] = {
|
||||
{
|
||||
.vid = 0x1c7a,
|
||||
.pid = 0x0576,
|
||||
},
|
||||
{
|
||||
.vid = 0,
|
||||
.pid = 0,
|
||||
},
|
||||
};
|
||||
|
||||
static void fpi_device_egis0576_init(FpDeviceEgis0576 *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void fpi_device_egis0576_class_init(FpDeviceEgis0576Class *klass)
|
||||
{
|
||||
FpDeviceClass *dev_class = FP_DEVICE_CLASS(klass);
|
||||
FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS(klass);
|
||||
|
||||
dev_class->id = "egis0576";
|
||||
dev_class->full_name = "Egis Technology Inc. (aka. LighTuning) 0576";
|
||||
dev_class->type = FP_DEVICE_TYPE_USB;
|
||||
dev_class->id_table = id_table;
|
||||
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
|
||||
dev_class->nr_enroll_stages = 20;
|
||||
dev_class->temp_hot_seconds = 0;
|
||||
|
||||
img_class->img_open = dev_init;
|
||||
img_class->img_close = dev_deinit;
|
||||
img_class->activate = dev_activate;
|
||||
img_class->deactivate = dev_deactivate;
|
||||
|
||||
img_class->img_width = EGIS0576_CANVAS_WIDTH;
|
||||
img_class->img_height = EGIS0576_CANVAS_HEIGHT;
|
||||
|
||||
img_class->bz3_threshold = 10; /* security issue, can score more but not reliably */
|
||||
}
|
||||
205
libfprint/drivers/egis0576.h
Normal file
205
libfprint/drivers/egis0576.h
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Egis Technology Inc. (aka. LighTuning) 0576 driver for libfprint
|
||||
* Copyright (C) 2026 Marcel (Sprayxe) <sprayxe.marcel@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/* Device config */
|
||||
#define EGIS0576_INTF 0
|
||||
|
||||
/* Device endpoints */
|
||||
#define EGIS0576_EPOUT 0x01
|
||||
#define EGIS0576_EPIN 0x82
|
||||
|
||||
/* Device transfers */
|
||||
#define EGIS0576_TIMEOUT 10000
|
||||
#define EGIS0576_POLL_COUNT 3000
|
||||
|
||||
/* Sensor image */
|
||||
#define EGIS0576_IMG_WIDTH 70
|
||||
#define EGIS0576_IMG_HEIGHT 57
|
||||
#define EGIS0576_IMG_SIZE ((EGIS0576_IMG_WIDTH) * (EGIS0576_IMG_HEIGHT))
|
||||
|
||||
/* Upscaled sensor image */
|
||||
#define EGIS0576_IMG_UPSCALE 2
|
||||
#define EGIS0576_IMG_WIDTH_UPSCALE ((EGIS0576_IMG_WIDTH) * (EGIS0576_IMG_UPSCALE))
|
||||
#define EGIS0576_IMG_HEIGHT_UPSCALE ((EGIS0576_IMG_HEIGHT) * (EGIS0576_IMG_UPSCALE))
|
||||
#define EGIS0576_IMG_SIZE_UPSCALE ((EGIS0576_IMG_WIDTH_UPSCALE) * (EGIS0576_IMG_HEIGHT_UPSCALE))
|
||||
|
||||
/* Canvas image */
|
||||
#define EGIS0576_CANVAS_WIDTH 256
|
||||
#define EGIS0576_CANVAS_HEIGHT 256
|
||||
#define EGIS0576_CANVAS_SIZE ((EGIS0576_CANVAS_WIDTH) * (EGIS0576_CANVAS_HEIGHT))
|
||||
|
||||
/*
|
||||
* These values were acquired by testing with finger present and without finger
|
||||
* present.
|
||||
*/
|
||||
#define EGIS0576_CONTRAST 4
|
||||
#define EGIS0576_VARIANCE (3.2 * 3.2)
|
||||
#define EGIS0576_DARK_PORTION 0.05
|
||||
#define EGIS0576_BG_VARIANCE (2.5 * 2.5)
|
||||
|
||||
/*
|
||||
*
|
||||
* All packets work like this:
|
||||
* E G I S
|
||||
* 0x45, 0x47, 0x49, 0x53, [CMD], [REG], [VALUE(s)]
|
||||
*
|
||||
* [CMD] = Command
|
||||
* [REG] = Register
|
||||
* [VALUE(s)] = Values to write or read
|
||||
* [CMD] seem to be:
|
||||
* 0x60: Read [REG]
|
||||
* - [VALUE] is set to 0x00? Not sure, 0575 pkts sometimes had a value but 0x00
|
||||
* seems to work
|
||||
*
|
||||
* 0x61: Write [VALUE] into [REG]
|
||||
* - [VALUE] is the single byte to write
|
||||
*
|
||||
* 0x62: Read starting from [REG] (burst read)
|
||||
* - [VALUE] is the amount of bytes to read
|
||||
*
|
||||
* 0x63: Write starting from [REG] (burst read)
|
||||
* - [VALUE] consists of: [AMOUNT OF BYTES TO WRITE], [BYTES TO WRITE]
|
||||
* - example: "0x63, 0x01, 0x02, 0x0f, 0x03"
|
||||
* -> 0x63 [CMD]; 0x01 [REG]; 0x02 [two bytes]; 0x0f, 0x03 [bytes]
|
||||
*
|
||||
* 0x64: Seems to be the command that fetches image data
|
||||
* - no separate [REG] or [VALUE]
|
||||
* - it is just the image size (width * size), in our case 70 * 57 = 3990
|
||||
* (0x0f96)
|
||||
* -> So the command is "0x64, 0x0f, 0x96"
|
||||
*
|
||||
* Responses always (except the image response) echo back:
|
||||
* S I G E
|
||||
* 0x53, 0x49, 0x47, 0x45, ...[CMD], [REG], [VALUE(s)] ?
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int len;
|
||||
unsigned char *cmd;
|
||||
int res_len;
|
||||
} Egis0576Pkt;
|
||||
|
||||
/*
|
||||
* Huge credit goes to Animeshz's efforts on github
|
||||
* (https://github.com/Animeshz/EgisTec-EH575)
|
||||
* and on libfprint
|
||||
* (https://gitlab.freedesktop.org/libfprint/libfprint/-/merge_requests/317) as
|
||||
* those laid the base for this effort.
|
||||
*
|
||||
* Initial tests proved that the EH0575 and the EH0576 seem to work basically
|
||||
* the same as by just providing the EH0575 packets I was able to occasionally
|
||||
* get little data out of mine.
|
||||
*
|
||||
* I was unable to get the fingerprint reader to work in a VM (both Win10 and
|
||||
* Win11) and Wireshark USBcap does not allow to be run on Windows-To-Go so I
|
||||
* could not use any USB captures from Windows.
|
||||
*
|
||||
* Therefore, the rest of the reverse engineering was solely done with static
|
||||
* analysis in Ghidra of the EgisTouchFP0576.dll UMDF driver provided
|
||||
* by Lenovo
|
||||
* (https://pcsupport.lenovo.com/de/en/products/laptops-and-netbooks/flex-series/flex-5-14alc7).
|
||||
* I am no professional "hacker" by any means so I cannot guarantee that these
|
||||
* are 100% correct but they work :D
|
||||
*/
|
||||
|
||||
/*
|
||||
* According to static analysis the driver polls using this packet a max. amount
|
||||
* of EGIS0576_POLL_COUNT before requesting the image.
|
||||
* Translated to this driver it checks the following condition in order to
|
||||
* proceed to requesting the image:
|
||||
*
|
||||
* (response_buffer[6] & 0x01) == 0x01
|
||||
*
|
||||
* Based on observation this has been always true for already the first request
|
||||
* so it essentially runs once.
|
||||
*/
|
||||
static const Egis0576Pkt EGIS0576_POLL_PACKET
|
||||
= { .len = 7,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x00, 0x00 },
|
||||
.res_len = 9 };
|
||||
|
||||
/* Fetches the image data. */
|
||||
static const Egis0576Pkt EGIS0576_IMAGE_PACKET
|
||||
= { .len = 7,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x64, 0x0f, 0x96 },
|
||||
.res_len = EGIS0576_IMG_SIZE };
|
||||
|
||||
/* Initialization packets. */
|
||||
#define EGIS0576_INIT_PACKETS_LENGTH 30
|
||||
static const Egis0576Pkt EGIS0576_INIT_PACKETS[] = {
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x00, 0x00 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x01, 0x00 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x10, 0xfd }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x35, 0x02 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x80, 0x00 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x80, 0x00 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x10, 0xfc }, .res_len = 7 },
|
||||
{ .len = 9,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x63, 0x01, 0x02, 0x0f, 0x03 },
|
||||
.res_len = 9 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x0c, 0x22 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x09, 0x83 }, .res_len = 7 },
|
||||
{ .len = 13,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x63, 0x26, 0x06, 0x06, 0x60, 0x06, 0x05,
|
||||
0x2f, 0x06 },
|
||||
.res_len = 13 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x10, 0xf4 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x0c, 0x44 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x50, 0x03 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x50, 0x00 }, .res_len = 7 },
|
||||
EGIS0576_IMAGE_PACKET,
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x40, 0x00 }, .res_len = 7 },
|
||||
{ .len = 18,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x63, 0x09, 0x0b, 0x83, 0x24, 0x00, 0x44,
|
||||
0x0f, 0x08, 0x20, 0x20, 0x00, 0x00, 0x52 },
|
||||
.res_len = 18 },
|
||||
{ .len = 13,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x63, 0x26, 0x06, 0x06, 0x60, 0x06, 0x05,
|
||||
0x2f, 0x06 },
|
||||
.res_len = 13 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x23, 0x00 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x24, 0x38 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x20, 0x00 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x61, 0x21, 0x45 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x00, 0x00 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x01, 0x00 }, .res_len = 7 },
|
||||
{ .len = 9,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x63, 0x2c, 0x02, 0x00, 0x57 },
|
||||
.res_len = 9 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x2d, 0x00 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x62, 0x67, 0x03 }, .res_len = 10 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x0f, 0x00 }, .res_len = 7 },
|
||||
{ .len = 9,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x63, 0x2c, 0x02, 0x00, 0x13 },
|
||||
.res_len = 9 }
|
||||
};
|
||||
|
||||
/* Repeat packets. */
|
||||
#define EGIS0576_REPEAT_PACKETS_LENGTH 5
|
||||
static const Egis0576Pkt EGIS0576_REPEAT_PACKETS[] = {
|
||||
{ .len = 9,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x63, 0x2c, 0x02, 0x00, 0x57 },
|
||||
.res_len = 9 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x2d, 0x00 }, .res_len = 7 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x62, 0x67, 0x03 }, .res_len = 10 },
|
||||
{ .len = 7, .cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x60, 0x0f, 0x00 }, .res_len = 7 },
|
||||
{ .len = 9,
|
||||
.cmd = (unsigned char[]){ 0x45, 0x47, 0x49, 0x53, 0x63, 0x2c, 0x02, 0x00, 0x13 },
|
||||
.res_len = 9 }
|
||||
};
|
||||
|
|
@ -122,7 +122,6 @@ static const FpIdEntry allowlist_id_table[] = {
|
|||
{ .vid = 0x16d1, .pid = 0x1027 },
|
||||
{ .vid = 0x1c7a, .pid = 0x0300 },
|
||||
{ .vid = 0x1c7a, .pid = 0x0575 },
|
||||
{ .vid = 0x1c7a, .pid = 0x0576 },
|
||||
{ .vid = 0x1c7a, .pid = 0x0577 },
|
||||
{ .vid = 0x1c7a, .pid = 0x057e },
|
||||
{ .vid = 0x2541, .pid = 0x0236 },
|
||||
|
|
|
|||
|
|
@ -125,6 +125,8 @@ driver_sources = {
|
|||
[ 'drivers/etes603.c' ],
|
||||
'egis0570' :
|
||||
[ 'drivers/egis0570.c' ],
|
||||
'egis0576' :
|
||||
[ 'drivers/egis0576.c' ],
|
||||
'egismoc' :
|
||||
[ 'drivers/egismoc/egismoc.c' ],
|
||||
'vfs0050' :
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ default_drivers = [
|
|||
'vfs0050',
|
||||
'etes603',
|
||||
'egis0570',
|
||||
'egis0576',
|
||||
'egismoc',
|
||||
'vcom5s',
|
||||
'synaptics',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue