Initial GXFP5130 userspace prototype
This commit is contained in:
26
libfprint-driver/README.md
Normal file
26
libfprint-driver/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# libfprint GXFP5130 integration skeleton
|
||||
|
||||
This driver connects libfprint's `FpImageDevice` state machine to the
|
||||
hardware-tested `gxfp_session` API. Blocking sensor operations run in a GLib
|
||||
worker thread; all libfprint callbacks run in the main context.
|
||||
|
||||
Current discovery is intentionally explicit because the sensor is an ACPI
|
||||
platform/character device, while upstream libfprint has no generic ACPI char
|
||||
device discovery type. Build the driver as an optional virtual-type driver and
|
||||
start libfprint/fprintd with:
|
||||
|
||||
```sh
|
||||
FP_GXFP5130=/dev/gxfp
|
||||
```
|
||||
|
||||
The integration must add `gxfp5130` to `drivers_info` and
|
||||
`libfprint_drivers_sources` in upstream Meson, include this file, add the GXFP
|
||||
headers, and link the PIC-built `libgxfp.a` plus mbedTLS libraries.
|
||||
|
||||
The first image path uses the verified 64x80 sample order and the deterministic
|
||||
`gxfp_image12_to_u8()` conversion. No transpose, flip, inversion, percentile
|
||||
clipping, or unverified `0x21` command is applied.
|
||||
|
||||
Before upstreaming, replace the environment-backed discovery with a dedicated
|
||||
udev/ACPI character-device discovery mechanism and install permissions for
|
||||
`/dev/gxfp`.
|
||||
404
libfprint-driver/gxfp5130.c
Normal file
404
libfprint-driver/gxfp5130.c
Normal file
@@ -0,0 +1,404 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#define FP_COMPONENT "gxfp5130"
|
||||
|
||||
#include "drivers_api.h"
|
||||
#include "gxfp/session.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define GXFP_DEVICE_PATH_DEFAULT "/dev/gxfp"
|
||||
#define GXFP_ACTIVATE_TIMEOUT_MS 10000
|
||||
#define GXFP_FINGER_TIMEOUT_MS INT_MAX
|
||||
#define GXFP_CAPTURE_TIMEOUT_MS 20000
|
||||
#define GXFP_IMAGE_WIDTH 64
|
||||
#define GXFP_IMAGE_HEIGHT 80
|
||||
#define GXFP_IMAGE_PPMM (500.0 / 25.4)
|
||||
|
||||
typedef enum {
|
||||
GXFP_JOB_OPEN,
|
||||
GXFP_JOB_ACTIVATE,
|
||||
GXFP_JOB_WAIT_FINGER_DOWN,
|
||||
GXFP_JOB_CAPTURE,
|
||||
GXFP_JOB_WAIT_FINGER_UP,
|
||||
GXFP_JOB_DEACTIVATE,
|
||||
GXFP_JOB_CLOSE,
|
||||
} GxfpJob;
|
||||
|
||||
typedef struct {
|
||||
GxfpJob job;
|
||||
gint result;
|
||||
guint8 *image_data;
|
||||
gsize image_size;
|
||||
} GxfpJobResult;
|
||||
|
||||
struct _FpiDeviceGxfp5130
|
||||
{
|
||||
FpImageDevice parent;
|
||||
struct gxfp_session *session;
|
||||
gchar *device_path;
|
||||
gchar *debug_image_dir;
|
||||
guint64 capture_sequence;
|
||||
gboolean worker_running;
|
||||
gboolean deactivating;
|
||||
};
|
||||
|
||||
G_DECLARE_FINAL_TYPE (FpiDeviceGxfp5130, fpi_device_gxfp5130,
|
||||
FPI, DEVICE_GXFP5130, FpImageDevice)
|
||||
G_DEFINE_TYPE (FpiDeviceGxfp5130, fpi_device_gxfp5130, FP_TYPE_IMAGE_DEVICE)
|
||||
static void
|
||||
gxfp_save_debug_image (FpiDeviceGxfp5130 *self,
|
||||
const guint8 *data,
|
||||
gsize size)
|
||||
{
|
||||
g_autofree gchar *filename = NULL;
|
||||
g_autofree gchar *contents = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
gsize header_size;
|
||||
|
||||
if (!self->debug_image_dir || !self->debug_image_dir[0])
|
||||
return;
|
||||
|
||||
if (size != GXFP_IMAGE_WIDTH * GXFP_IMAGE_HEIGHT)
|
||||
{
|
||||
fp_warn ("Refusing to save debug image with invalid size %" G_GSIZE_FORMAT,
|
||||
size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_mkdir_with_parents (self->debug_image_dir, 0700) < 0)
|
||||
{
|
||||
fp_warn ("Failed to create debug image directory %s: %s",
|
||||
self->debug_image_dir, g_strerror (errno));
|
||||
return;
|
||||
}
|
||||
|
||||
self->capture_sequence++;
|
||||
filename = g_strdup_printf ("%s/capture-%u-%06" G_GUINT64_FORMAT ".pgm",
|
||||
self->debug_image_dir,
|
||||
(guint) getpid (),
|
||||
self->capture_sequence);
|
||||
contents = g_malloc (32 + size);
|
||||
header_size = g_snprintf (contents, 32, "P5\n%d %d\n255\n",
|
||||
GXFP_IMAGE_WIDTH, GXFP_IMAGE_HEIGHT);
|
||||
memcpy (contents + header_size, data, size);
|
||||
|
||||
if (!g_file_set_contents (filename, contents, header_size + size, &error))
|
||||
fp_warn ("Failed to save debug image %s: %s", filename, error->message);
|
||||
else
|
||||
fp_dbg ("Saved raw debug image to %s", filename);
|
||||
}
|
||||
|
||||
static void gxfp_start_job (FpiDeviceGxfp5130 *self, GxfpJob job);
|
||||
|
||||
static GError *
|
||||
gxfp_error_new (gint result)
|
||||
{
|
||||
gint error_number = result < 0 ? -result : EIO;
|
||||
|
||||
return g_error_new (G_IO_ERROR, g_io_error_from_errno (error_number),
|
||||
"GXFP session failed: %s (%d)",
|
||||
g_strerror (error_number), result);
|
||||
}
|
||||
|
||||
static void
|
||||
gxfp_job_result_free (GxfpJobResult *result)
|
||||
{
|
||||
if (!result)
|
||||
return;
|
||||
g_free (result->image_data);
|
||||
g_free (result);
|
||||
}
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GxfpJobResult, gxfp_job_result_free)
|
||||
|
||||
static void
|
||||
gxfp_job_thread (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (source_object);
|
||||
GxfpJob job = GPOINTER_TO_INT (task_data);
|
||||
GxfpJobResult *result = g_new0 (GxfpJobResult, 1);
|
||||
|
||||
(void) cancellable;
|
||||
result->job = job;
|
||||
|
||||
switch (job)
|
||||
{
|
||||
case GXFP_JOB_OPEN:
|
||||
result->result = gxfp_session_open (&self->session, self->device_path);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_ACTIVATE:
|
||||
result->result = gxfp_session_activate (self->session,
|
||||
GXFP_ACTIVATE_TIMEOUT_MS);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_WAIT_FINGER_DOWN:
|
||||
result->result = gxfp_session_arm_finger_down (self->session);
|
||||
if (!result->result)
|
||||
result->result = gxfp_session_wait_finger_down (
|
||||
self->session, GXFP_FINGER_TIMEOUT_MS);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_CAPTURE:
|
||||
{
|
||||
struct gxfp_image12 image = { 0 };
|
||||
|
||||
result->result = gxfp_session_capture (self->session, &image,
|
||||
GXFP_CAPTURE_TIMEOUT_MS);
|
||||
if (!result->result)
|
||||
{
|
||||
result->image_size = image.width * image.height;
|
||||
result->image_data = g_malloc (result->image_size);
|
||||
result->result = gxfp_image12_to_u8 (&image,
|
||||
result->image_data,
|
||||
result->image_size);
|
||||
}
|
||||
gxfp_image12_clear (&image);
|
||||
}
|
||||
break;
|
||||
|
||||
case GXFP_JOB_WAIT_FINGER_UP:
|
||||
result->result = gxfp_session_arm_finger_up (self->session);
|
||||
if (!result->result)
|
||||
result->result = gxfp_session_wait_finger_up (
|
||||
self->session, GXFP_FINGER_TIMEOUT_MS);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_DEACTIVATE:
|
||||
result->result = gxfp_session_deactivate (self->session);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_CLOSE:
|
||||
gxfp_session_close (self->session);
|
||||
self->session = NULL;
|
||||
result->result = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
result->result = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
g_task_return_pointer (task, result, (GDestroyNotify) gxfp_job_result_free);
|
||||
}
|
||||
|
||||
static void
|
||||
gxfp_finish_deactivate (FpiDeviceGxfp5130 *self, gint result)
|
||||
{
|
||||
self->deactivating = FALSE;
|
||||
fpi_image_device_deactivate_complete (
|
||||
FP_IMAGE_DEVICE (self), result ? gxfp_error_new (result) : NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
gxfp_job_done (GObject *source_object, GAsyncResult *res, gpointer user_data)
|
||||
{
|
||||
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (source_object);
|
||||
g_autoptr(GxfpJobResult) result = g_task_propagate_pointer (G_TASK (res), NULL);
|
||||
FpImageDevice *image_device = FP_IMAGE_DEVICE (self);
|
||||
|
||||
(void) user_data;
|
||||
self->worker_running = FALSE;
|
||||
|
||||
if (self->deactivating && result->job != GXFP_JOB_DEACTIVATE)
|
||||
{
|
||||
gxfp_start_job (self, GXFP_JOB_DEACTIVATE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result->result)
|
||||
{
|
||||
GError *error = gxfp_error_new (result->result);
|
||||
|
||||
switch (result->job)
|
||||
{
|
||||
case GXFP_JOB_OPEN:
|
||||
fpi_image_device_open_complete (image_device, error);
|
||||
break;
|
||||
case GXFP_JOB_ACTIVATE:
|
||||
fpi_image_device_activate_complete (image_device, error);
|
||||
break;
|
||||
case GXFP_JOB_DEACTIVATE:
|
||||
gxfp_finish_deactivate (self, result->result);
|
||||
g_error_free (error);
|
||||
break;
|
||||
case GXFP_JOB_CLOSE:
|
||||
fpi_image_device_close_complete (image_device, error);
|
||||
break;
|
||||
case GXFP_JOB_WAIT_FINGER_DOWN:
|
||||
case GXFP_JOB_CAPTURE:
|
||||
case GXFP_JOB_WAIT_FINGER_UP:
|
||||
default:
|
||||
fpi_image_device_session_error (image_device, error);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (result->job)
|
||||
{
|
||||
case GXFP_JOB_OPEN:
|
||||
fpi_image_device_open_complete (image_device, NULL);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_ACTIVATE:
|
||||
fpi_image_device_activate_complete (image_device, NULL);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_WAIT_FINGER_DOWN:
|
||||
fpi_image_device_report_finger_status (image_device, TRUE);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_CAPTURE:
|
||||
{
|
||||
FpImage *image;
|
||||
|
||||
g_assert (result->image_size == GXFP_IMAGE_WIDTH * GXFP_IMAGE_HEIGHT);
|
||||
image = fp_image_new (GXFP_IMAGE_WIDTH, GXFP_IMAGE_HEIGHT);
|
||||
memcpy (image->data, result->image_data, result->image_size);
|
||||
image->ppmm = GXFP_IMAGE_PPMM;
|
||||
image->flags = FPI_IMAGE_NONE;
|
||||
gxfp_save_debug_image (self, result->image_data, result->image_size);
|
||||
fpi_image_device_image_captured (image_device, image);
|
||||
}
|
||||
break;
|
||||
|
||||
case GXFP_JOB_WAIT_FINGER_UP:
|
||||
fpi_image_device_report_finger_status (image_device, FALSE);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_DEACTIVATE:
|
||||
gxfp_finish_deactivate (self, 0);
|
||||
break;
|
||||
|
||||
case GXFP_JOB_CLOSE:
|
||||
g_clear_pointer (&self->device_path, g_free);
|
||||
g_clear_pointer (&self->debug_image_dir, g_free);
|
||||
fpi_image_device_close_complete (image_device, NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gxfp_start_job (FpiDeviceGxfp5130 *self, GxfpJob job)
|
||||
{
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
g_assert (!self->worker_running);
|
||||
self->worker_running = TRUE;
|
||||
task = g_task_new (self, NULL, gxfp_job_done, NULL);
|
||||
g_task_set_task_data (task, GINT_TO_POINTER (job), NULL);
|
||||
g_task_run_in_thread (task, gxfp_job_thread);
|
||||
}
|
||||
|
||||
static void
|
||||
gxfp_dev_open (FpImageDevice *device)
|
||||
{
|
||||
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
|
||||
const gchar *path = fpi_device_get_virtual_env (FP_DEVICE (device));
|
||||
|
||||
self->device_path = g_strdup (path && path[0] ? path
|
||||
: GXFP_DEVICE_PATH_DEFAULT);
|
||||
self->debug_image_dir = g_strdup (g_getenv ("GXFP_DEBUG_IMAGE_DIR"));
|
||||
self->capture_sequence = 0;
|
||||
gxfp_start_job (self, GXFP_JOB_OPEN);
|
||||
}
|
||||
|
||||
static void
|
||||
gxfp_dev_close (FpImageDevice *device)
|
||||
{
|
||||
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
|
||||
|
||||
g_assert (!self->worker_running);
|
||||
gxfp_start_job (self, GXFP_JOB_CLOSE);
|
||||
}
|
||||
|
||||
static void
|
||||
gxfp_dev_activate (FpImageDevice *device)
|
||||
{
|
||||
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
|
||||
|
||||
self->deactivating = FALSE;
|
||||
gxfp_start_job (self, GXFP_JOB_ACTIVATE);
|
||||
}
|
||||
|
||||
static void
|
||||
gxfp_dev_deactivate (FpImageDevice *device)
|
||||
{
|
||||
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
|
||||
|
||||
self->deactivating = TRUE;
|
||||
if (self->worker_running)
|
||||
{
|
||||
gxfp_session_cancel (self->session);
|
||||
return;
|
||||
}
|
||||
gxfp_start_job (self, GXFP_JOB_DEACTIVATE);
|
||||
}
|
||||
|
||||
static void
|
||||
gxfp_dev_change_state (FpImageDevice *device, FpiImageDeviceState state)
|
||||
{
|
||||
FpiDeviceGxfp5130 *self = FPI_DEVICE_GXFP5130 (device);
|
||||
|
||||
if (self->deactivating || self->worker_running)
|
||||
return;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON:
|
||||
gxfp_start_job (self, GXFP_JOB_WAIT_FINGER_DOWN);
|
||||
break;
|
||||
case FPI_IMAGE_DEVICE_STATE_CAPTURE:
|
||||
gxfp_start_job (self, GXFP_JOB_CAPTURE);
|
||||
break;
|
||||
case FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF:
|
||||
gxfp_start_job (self, GXFP_JOB_WAIT_FINGER_UP);
|
||||
break;
|
||||
case FPI_IMAGE_DEVICE_STATE_INACTIVE:
|
||||
case FPI_IMAGE_DEVICE_STATE_ACTIVATING:
|
||||
case FPI_IMAGE_DEVICE_STATE_DEACTIVATING:
|
||||
case FPI_IMAGE_DEVICE_STATE_IDLE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const FpIdEntry id_table[] = {
|
||||
{ .virtual_envvar = "FP_GXFP5130" },
|
||||
{ .virtual_envvar = NULL },
|
||||
};
|
||||
|
||||
static void
|
||||
fpi_device_gxfp5130_init (FpiDeviceGxfp5130 *self)
|
||||
{
|
||||
self->session = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
fpi_device_gxfp5130_class_init (FpiDeviceGxfp5130Class *klass)
|
||||
{
|
||||
FpDeviceClass *device_class = FP_DEVICE_CLASS (klass);
|
||||
FpImageDeviceClass *image_class = FP_IMAGE_DEVICE_CLASS (klass);
|
||||
|
||||
device_class->id = FP_COMPONENT;
|
||||
device_class->full_name = "Goodix GXFP5130 (ChicagoHU 0x2504)";
|
||||
device_class->type = FP_DEVICE_TYPE_VIRTUAL;
|
||||
device_class->id_table = id_table;
|
||||
device_class->scan_type = FP_SCAN_TYPE_PRESS;
|
||||
|
||||
image_class->img_open = gxfp_dev_open;
|
||||
image_class->img_close = gxfp_dev_close;
|
||||
image_class->activate = gxfp_dev_activate;
|
||||
image_class->deactivate = gxfp_dev_deactivate;
|
||||
image_class->change_state = gxfp_dev_change_state;
|
||||
image_class->img_width = GXFP_IMAGE_WIDTH;
|
||||
image_class->img_height = GXFP_IMAGE_HEIGHT;
|
||||
}
|
||||
Reference in New Issue
Block a user